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