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 
main(String[] args)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);
testUnsafeGetAddress()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 
checkGetAddress()112     static long checkGetAddress() {
113         return U.getAddress(nativeAddr);
114     }
115 
testUnsafeGetField()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 
testUnsafeGetFieldUnaligned()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 
runTest(JavaType t, int flags, boolean stable, boolean hasDefaultValue, String postfix)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 {
testDirect()185         Object testDirect();
testUnsafe()186         Object testUnsafe();
changeToDefault()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;
JavaType(String name, Object value, Object defaultValue)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 
desc()212         String desc() {
213             if (this == JavaType.L) {
214                 return "Ljava/lang/Object;";
215             } else {
216                 return name();
217             }
218         }
219     }
220 
internalName(Class cls)221     static String internalName(Class cls) {
222         return cls.getName().replace('.', '/');
223     }
descriptor(Class cls)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 
Generator(JavaType t, int flags, boolean stable, boolean hasDefaultValue, String suffix)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 
generateClassFile()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 
generate()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 
isStatic()404         boolean isStatic() {
405             return (flags & ACC_STATIC) > 0;
406         }
isFinal()407         boolean isFinal() {
408             return (flags & ACC_FINAL) > 0;
409         }
getUnsafe(MethodVisitor mv)410         void getUnsafe(MethodVisitor mv) {
411             mv.visitFieldInsn(GETSTATIC, className, "U", UNSAFE_DESC);
412         }
getField(MethodVisitor mv)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         }
getFieldValue(MethodVisitor mv)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         }
getFieldValueUnsafe(MethodVisitor mv)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         }
wrapResult(MethodVisitor mv)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         }
initField(MethodVisitor mv)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 
dump()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 }
461