1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * @test
  28  * @summary tests on constant folding of unsafe get operations
  29  * @library /testlibrary
  30  *
  31  * @requires vm.flavor != "client"
  32  *
  33  * @modules java.base/jdk.internal.org.objectweb.asm
  34  *          java.base/jdk.internal.vm.annotation
  35  *          java.base/jdk.internal.misc
  36  *
  37  * @run main/bootclasspath/othervm -XX:+UnlockDiagnosticVMOptions
  38  *                                 -Xbatch -XX:-TieredCompilation
  39  *                                 -XX:+FoldStableValues
  40  *                                 -XX:CompileCommand=dontinline,compiler.unsafe.UnsafeGetConstantField::checkGetAddress
  41  *                                 -XX:CompileCommand=dontinline,*::test*
  42  *                                 -XX:+UseUnalignedAccesses
  43  *                                 -XaddReads:java.base=ALL-UNNAMED
  44  *                                 compiler.unsafe.UnsafeGetConstantField
  45  *
  46  * @run main/bootclasspath/othervm -XX:+UnlockDiagnosticVMOptions
  47  *                                 -Xbatch -XX:-TieredCompilation
  48  *                                 -XX:+FoldStableValues
  49  *                                 -XX:CompileCommand=dontinline,compiler.unsafe.UnsafeGetConstantField::checkGetAddress
  50  *                                 -XX:CompileCommand=dontinline,*::test*
  51  *                                 -XX:CompileCommand=inline,*Unsafe::get*
  52  *                                 -XX:-UseUnalignedAccesses
  53  *                                 -XaddReads:java.base=ALL-UNNAMED
  54  *                                 compiler.unsafe.UnsafeGetConstantField
  55  */
  56 
  57 package compiler.unsafe;
  58 
  59 import jdk.internal.misc.Unsafe;
  60 import jdk.internal.org.objectweb.asm.ClassWriter;
  61 import jdk.internal.org.objectweb.asm.FieldVisitor;
  62 import jdk.internal.org.objectweb.asm.MethodVisitor;
  63 import jdk.internal.org.objectweb.asm.Opcodes;
  64 import jdk.internal.org.objectweb.asm.Type;
  65 import jdk.internal.vm.annotation.Stable;
  66 import jdk.test.lib.Asserts;
  67 import jdk.test.lib.Platform;
  68 
  69 import java.io.IOException;
  70 import java.nio.file.Files;
  71 import java.nio.file.Path;
  72 import java.nio.file.Paths;
  73 
  74 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
  75 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  76 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  77 import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL;
  78 import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
  79 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN;
  80 import static jdk.internal.org.objectweb.asm.Opcodes.DUP;
  81 import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD;
  82 import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC;
  83 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
  84 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
  85 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
  86 import static jdk.internal.org.objectweb.asm.Opcodes.NEW;
  87 import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD;
  88 import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
  89 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
  90 
  91 public class UnsafeGetConstantField {
  92     static final Class<?> THIS_CLASS = UnsafeGetConstantField.class;
  93     static final Unsafe U = Unsafe.getUnsafe();
  94 
  95     public static void main(String[] args) {
  96         if (Platform.isServer()) {
  97             testUnsafeGetAddress();
  98             testUnsafeGetField();
  99             testUnsafeGetFieldUnaligned();
 100         }
 101         System.out.println("TEST PASSED");
 102     }
 103 
 104     static final long nativeAddr = U.allocateMemory(16);
 105     static void testUnsafeGetAddress() {
 106         long cookie = 0x12345678L;
 107         U.putAddress(nativeAddr, cookie);
 108         for (int i = 0; i < 20_000; i++) {
 109             Asserts.assertEquals(checkGetAddress(), cookie);
 110         }
 111     }
 112 
 113     static long checkGetAddress() {
 114         return U.getAddress(nativeAddr);
 115     }
 116 
 117     static void testUnsafeGetField() {
 118         int[] testedFlags = new int[] { 0, ACC_STATIC, ACC_FINAL, (ACC_STATIC | ACC_FINAL) };
 119         boolean[] boolValues = new boolean[] { false, true };
 120         String[] modes = new String[] { "", "Volatile" };
 121 
 122         for (JavaType t : JavaType.values()) {
 123             for (int flags : testedFlags) {
 124                 for (boolean stable : boolValues) {
 125                     for (boolean hasDefaultValue : boolValues) {
 126                         for (String suffix : modes) {
 127                             runTest(t, flags, stable, hasDefaultValue, suffix);
 128                         }
 129                     }
 130                 }
 131             }
 132         }
 133     }
 134 
 135     static void testUnsafeGetFieldUnaligned() {
 136         JavaType[] types = new JavaType[] { JavaType.S, JavaType.C, JavaType.I, JavaType.J };
 137         int[] testedFlags = new int[] { 0, ACC_STATIC, ACC_FINAL, (ACC_STATIC | ACC_FINAL) };
 138         boolean[] boolValues = new boolean[] { false, true };
 139 
 140         for (JavaType t : types) {
 141             for (int flags : testedFlags) {
 142                 for (boolean stable : boolValues) {
 143                     for (boolean hasDefaultValue : boolValues) {
 144                         runTest(t, flags, stable, hasDefaultValue, "Unaligned");
 145                     }
 146                 }
 147             }
 148         }
 149     }
 150 
 151     static void runTest(JavaType t, int flags, boolean stable, boolean hasDefaultValue, String postfix) {
 152         Generator g = new Generator(t, flags, stable, hasDefaultValue, postfix);
 153         Test test = g.generate();
 154         System.err.printf("type=%s flags=%d stable=%b default=%b post=%s\n",
 155                           t.typeName, flags, stable, hasDefaultValue, postfix);
 156         try {
 157             Object expected = hasDefaultValue ? t.defaultValue : t.value;
 158             // Trigger compilation
 159             for (int i = 0; i < 20_000; i++) {
 160                 Asserts.assertEQ(expected, test.testDirect(), "i = "+ i +" direct read returns wrong value");
 161                 Asserts.assertEQ(expected, test.testUnsafe(), "i = "+ i +" unsafe read returns wrong value");
 162             }
 163 
 164             test.changeToDefault();
 165             if (!hasDefaultValue && (stable || g.isFinal())) {
 166                 Asserts.assertEQ(t.value, test.testDirect(),
 167                         "direct read doesn't return prev value");
 168                 Asserts.assertEQ(test.testDirect(), test.testUnsafe());
 169             } else {
 170                 Asserts.assertEQ(t.defaultValue, test.testDirect(),
 171                         "direct read doesn't return default value");
 172                 Asserts.assertEQ(test.testDirect(), test.testUnsafe(),
 173                         "direct and unsafe reads return different values");
 174             }
 175         } catch (Throwable e) {
 176             try {
 177                 g.dump();
 178             } catch (IOException io) {
 179                 io.printStackTrace();
 180             }
 181             throw e;
 182         }
 183     }
 184 
 185     public interface Test {
 186         Object testDirect();
 187         Object testUnsafe();
 188         void changeToDefault();
 189     }
 190 
 191     enum JavaType {
 192         Z("Boolean", true, false),
 193         B("Byte", new Byte((byte) -1), new Byte((byte) 0)),
 194         S("Short", new Short((short) -1), new Short((short) 0)),
 195         C("Char", Character.MAX_VALUE, '\0'),
 196         I("Int", -1, 0),
 197         J("Long", -1L, 0L),
 198         F("Float", -1F, 0F),
 199         D("Double", -1D, 0D),
 200         L("Object", "", null);
 201 
 202         String typeName;
 203         Object value;
 204         Object defaultValue;
 205         String wrapper;
 206         JavaType(String name, Object value, Object defaultValue) {
 207             this.typeName = name;
 208             this.value = value;
 209             this.defaultValue = defaultValue;
 210             this.wrapper = internalName(value.getClass());
 211         }
 212 
 213         String desc() {
 214             if (this == JavaType.L) {
 215                 return "Ljava/lang/Object;";
 216             } else {
 217                 return name();
 218             }
 219         }
 220     }
 221 
 222     static String internalName(Class cls) {
 223         return cls.getName().replace('.', '/');
 224     }
 225     static String descriptor(Class cls) {
 226         return String.format("L%s;", internalName(cls));
 227     }
 228 
 229     /**
 230      * Sample generated class:
 231      * static class T1 implements Test {
 232      *   final int f = -1;
 233      *   static final long FIELD_OFFSET;
 234      *   static final T1 t = new T1();
 235      *   static {
 236      *     FIELD_OFFSET = U.objectFieldOffset(T1.class.getDeclaredField("f"));
 237      *   }
 238      *   public Object testDirect()  { return t.f; }
 239      *   public Object testUnsafe()  { return U.getInt(t, FIELD_OFFSET); }
 240      *   public void changeToDefault() { U.putInt(t, 0, FIELD_OFFSET); }
 241      * }
 242      */
 243     static class Generator {
 244         static final String FIELD_NAME = "f";
 245         static final String UNSAFE_NAME = internalName(Unsafe.class);
 246         static final String UNSAFE_DESC = descriptor(Unsafe.class);
 247 
 248         final JavaType type;
 249         final int flags;
 250         final boolean stable;
 251         final boolean hasDefaultValue;
 252         final String nameSuffix;
 253 
 254         final String name;
 255         final String className;
 256         final String classDesc;
 257         final String fieldDesc;
 258         final byte[] classFile;
 259 
 260         Generator(JavaType t, int flags, boolean stable, boolean hasDefaultValue, String suffix) {
 261             this.type = t;
 262             this.flags = flags;
 263             this.stable = stable;
 264             this.hasDefaultValue = hasDefaultValue;
 265             this.nameSuffix = suffix;
 266 
 267             fieldDesc = type.desc();
 268             name = String.format("Test%s%s__f=%d__s=%b__d=%b",
 269                     type.typeName, suffix, flags, stable, hasDefaultValue);
 270             className = "java/lang/invoke/" + name;
 271             classDesc = String.format("L%s;", className);
 272             classFile = generateClassFile();
 273         }
 274 
 275         byte[] generateClassFile() {
 276             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
 277             cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null, "java/lang/Object",
 278                     new String[]{ internalName(Test.class) });
 279 
 280             // Declare fields
 281             cw.visitField(ACC_FINAL | ACC_STATIC, "t", classDesc, null, null).visitEnd();
 282             cw.visitField(ACC_FINAL | ACC_STATIC, "FIELD_OFFSET", "J", null, null).visitEnd();
 283             cw.visitField(ACC_FINAL | ACC_STATIC, "U", UNSAFE_DESC, null, null).visitEnd();
 284             if (isStatic()) {
 285                 cw.visitField(ACC_FINAL | ACC_STATIC, "STATIC_BASE", "Ljava/lang/Object;", null, null).visitEnd();
 286             }
 287 
 288             FieldVisitor fv = cw.visitField(flags, FIELD_NAME, fieldDesc, null, null);
 289             if (stable) {
 290                 fv.visitAnnotation(descriptor(Stable.class), true);
 291             }
 292             fv.visitEnd();
 293 
 294             // Methods
 295             {   // <init>
 296                 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 297                 mv.visitCode();
 298 
 299                 mv.visitVarInsn(ALOAD, 0);
 300                 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
 301                 if (!isStatic()) {
 302                     initField(mv);
 303                 }
 304                 mv.visitInsn(RETURN);
 305 
 306                 mv.visitMaxs(0, 0);
 307                 mv.visitEnd();
 308             }
 309 
 310             {   // public Object testDirect() { return t.f; }
 311                 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "testDirect", "()Ljava/lang/Object;", null, null);
 312                 mv.visitCode();
 313 
 314                 getFieldValue(mv);
 315                 wrapResult(mv);
 316                 mv.visitInsn(ARETURN);
 317 
 318                 mv.visitMaxs(0, 0);
 319                 mv.visitEnd();
 320             }
 321 
 322             {   // public Object testUnsafe() { return U.getInt(t, FIELD_OFFSET); }
 323                 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "testUnsafe", "()Ljava/lang/Object;", null, null);
 324                 mv.visitCode();
 325 
 326                 getFieldValueUnsafe(mv);
 327                 wrapResult(mv);
 328                 mv.visitInsn(ARETURN);
 329 
 330                 mv.visitMaxs(0, 0);
 331                 mv.visitEnd();
 332             }
 333 
 334             {   // public void changeToDefault() { U.putInt(t, FIELD_OFFSET, 0); }
 335                 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "changeToDefault", "()V", null, null);
 336                 mv.visitCode();
 337                 getUnsafe(mv);
 338                 if (isStatic()) {
 339                     mv.visitFieldInsn(GETSTATIC, className, "STATIC_BASE", "Ljava/lang/Object;");
 340                 } else {
 341                     mv.visitFieldInsn(GETSTATIC, className, "t", classDesc);
 342                 }
 343                 mv.visitFieldInsn(GETSTATIC, className, "FIELD_OFFSET", "J");
 344 
 345                 if (type.defaultValue != null) {
 346                     mv.visitLdcInsn(type.defaultValue);
 347                 } else {
 348                     mv.visitInsn(ACONST_NULL);
 349                 }
 350                 String name = "put" + type.typeName + nameSuffix;
 351                 mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, name, "(Ljava/lang/Object;J" + type.desc()+ ")V", false);
 352                 mv.visitInsn(RETURN);
 353 
 354                 mv.visitMaxs(0, 0);
 355                 mv.visitEnd();
 356             }
 357 
 358             {   // <clinit>
 359                 MethodVisitor mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
 360                 mv.visitCode();
 361 
 362                 // Cache Unsafe instance
 363                 mv.visitMethodInsn(INVOKESTATIC, UNSAFE_NAME, "getUnsafe", "()"+UNSAFE_DESC, false);
 364                 mv.visitFieldInsn(PUTSTATIC, className, "U", UNSAFE_DESC);
 365 
 366                 // Create test object instance
 367                 mv.visitTypeInsn(NEW, className);
 368                 mv.visitInsn(DUP);
 369                 mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
 370                 mv.visitFieldInsn(PUTSTATIC, className, "t", classDesc);
 371 
 372                 // Compute field offset
 373                 getUnsafe(mv);
 374                 getField(mv);
 375                 mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, (isStatic() ? "staticFieldOffset" : "objectFieldOffset"),
 376                         "(Ljava/lang/reflect/Field;)J", false);
 377                 mv.visitFieldInsn(PUTSTATIC, className, "FIELD_OFFSET", "J");
 378 
 379                 // Compute base offset for static field
 380                 if (isStatic()) {
 381                     getUnsafe(mv);
 382                     getField(mv);
 383                     mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, "staticFieldBase", "(Ljava/lang/reflect/Field;)Ljava/lang/Object;", false);
 384                     mv.visitFieldInsn(PUTSTATIC, className, "STATIC_BASE", "Ljava/lang/Object;");
 385                     initField(mv);
 386                 }
 387 
 388                 mv.visitInsn(RETURN);
 389                 mv.visitMaxs(0, 0);
 390                 mv.visitEnd();
 391             }
 392 
 393             return cw.toByteArray();
 394         }
 395 
 396         Test generate() {
 397             Class<?> c = U.defineClass(className, classFile, 0, classFile.length, THIS_CLASS.getClassLoader(), null);
 398             try {
 399                 return (Test) c.newInstance();
 400             } catch(Exception e) {
 401                 throw new Error(e);
 402             }
 403         }
 404 
 405         boolean isStatic() {
 406             return (flags & ACC_STATIC) > 0;
 407         }
 408         boolean isFinal() {
 409             return (flags & ACC_FINAL) > 0;
 410         }
 411         void getUnsafe(MethodVisitor mv) {
 412             mv.visitFieldInsn(GETSTATIC, className, "U", UNSAFE_DESC);
 413         }
 414         void getField(MethodVisitor mv) {
 415             mv.visitLdcInsn(Type.getType(classDesc));
 416             mv.visitLdcInsn(FIELD_NAME);
 417             mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", false);
 418         }
 419         void getFieldValue(MethodVisitor mv) {
 420             if (isStatic()) {
 421                 mv.visitFieldInsn(GETSTATIC, className, FIELD_NAME, fieldDesc);
 422             } else {
 423                 mv.visitFieldInsn(GETSTATIC, className, "t", classDesc);
 424                 mv.visitFieldInsn(GETFIELD, className, FIELD_NAME, fieldDesc);
 425             }
 426         }
 427         void getFieldValueUnsafe(MethodVisitor mv) {
 428             getUnsafe(mv);
 429             if (isStatic()) {
 430                 mv.visitFieldInsn(GETSTATIC, className, "STATIC_BASE", "Ljava/lang/Object;");
 431             } else {
 432                 mv.visitFieldInsn(GETSTATIC, className, "t", classDesc);
 433             }
 434             mv.visitFieldInsn(GETSTATIC, className, "FIELD_OFFSET", "J");
 435             String name = "get" + type.typeName + nameSuffix;
 436             mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, name, "(Ljava/lang/Object;J)" + type.desc(), false);
 437         }
 438         void wrapResult(MethodVisitor mv) {
 439             if (type != JavaType.L) {
 440                 String desc = String.format("(%s)L%s;", type.desc(), type.wrapper);
 441                 mv.visitMethodInsn(INVOKESTATIC, type.wrapper, "valueOf", desc, false);
 442             }
 443         }
 444         void initField(MethodVisitor mv) {
 445             if (hasDefaultValue) {
 446                 return; // Nothing to do
 447             }
 448             if (!isStatic()) {
 449                 mv.visitVarInsn(ALOAD, 0);
 450             }
 451             mv.visitLdcInsn(type.value);
 452             mv.visitFieldInsn((isStatic() ? PUTSTATIC : PUTFIELD), className, FIELD_NAME, fieldDesc);
 453         }
 454 
 455         public void dump() throws IOException {
 456             Path path = Paths.get(".", name + ".class").toAbsolutePath();
 457             System.err.println("dumping test class to " + path);
 458             Files.write(path, classFile);
 459         }
 460     }
 461 }