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