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