/* * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @summary tests on constant folding of unsafe get operations * @library /testlibrary * * @requires vm.flavor != "client" * * @modules java.base/jdk.internal.org.objectweb.asm * java.base/jdk.internal.vm.annotation * java.base/jdk.internal.misc * * @run main/bootclasspath/othervm -XX:+UnlockDiagnosticVMOptions * -Xbatch -XX:-TieredCompilation * -XX:+FoldStableValues * -XX:CompileCommand=dontinline,UnsafeGetConstantField.checkGetAddress() * -XX:CompileCommand=dontinline,*.test* * -XX:+UseUnalignedAccesses * --add-reads=java.base=ALL-UNNAMED * compiler.unsafe.UnsafeGetConstantField * * @run main/bootclasspath/othervm -XX:+UnlockDiagnosticVMOptions * -Xbatch -XX:-TieredCompilation * -XX:+FoldStableValues * -XX:CompileCommand=dontinline,UnsafeGetConstantField.checkGetAddress() * -XX:CompileCommand=dontinline,*.test* * -XX:CompileCommand=inline,*Unsafe.get* * -XX:-UseUnalignedAccesses * --add-reads=java.base=ALL-UNNAMED * compiler.unsafe.UnsafeGetConstantField */ package compiler.unsafe; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.FieldVisitor; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import jdk.internal.org.objectweb.asm.Type; import jdk.internal.vm.annotation.Stable; import jdk.test.lib.Asserts; import jdk.test.lib.Platform; import jdk.internal.misc.Unsafe; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import static jdk.internal.org.objectweb.asm.Opcodes.*; public class UnsafeGetConstantField { static final Class THIS_CLASS = UnsafeGetConstantField.class; static final Unsafe U = Unsafe.getUnsafe(); public static void main(String[] args) { if (Platform.isServer()) { testUnsafeGetAddress(); testUnsafeGetField(); testUnsafeGetFieldUnaligned(); } System.out.println("TEST PASSED"); } static final long nativeAddr = U.allocateMemory(16); static void testUnsafeGetAddress() { long cookie = 0x12345678L; U.putAddress(nativeAddr, cookie); for (int i = 0; i < 20_000; i++) { Asserts.assertEquals(checkGetAddress(), cookie); } } static long checkGetAddress() { return U.getAddress(nativeAddr); } static void testUnsafeGetField() { int[] testedFlags = new int[] { 0, ACC_STATIC, ACC_FINAL, (ACC_STATIC | ACC_FINAL) }; boolean[] boolValues = new boolean[] { false, true }; String[] modes = new String[] { "", "Volatile" }; for (JavaType t : JavaType.values()) { for (int flags : testedFlags) { for (boolean stable : boolValues) { for (boolean hasDefaultValue : boolValues) { for (String suffix : modes) { runTest(t, flags, stable, hasDefaultValue, suffix); } } } } } } static void testUnsafeGetFieldUnaligned() { JavaType[] types = new JavaType[] { JavaType.S, JavaType.C, JavaType.I, JavaType.J }; int[] testedFlags = new int[] { 0, ACC_STATIC, ACC_FINAL, (ACC_STATIC | ACC_FINAL) }; boolean[] boolValues = new boolean[] { false, true }; for (JavaType t : types) { for (int flags : testedFlags) { for (boolean stable : boolValues) { for (boolean hasDefaultValue : boolValues) { runTest(t, flags, stable, hasDefaultValue, "Unaligned"); } } } } } static void runTest(JavaType t, int flags, boolean stable, boolean hasDefaultValue, String postfix) { Generator g = new Generator(t, flags, stable, hasDefaultValue, postfix); Test test = g.generate(); System.err.printf("type=%s flags=%d stable=%b default=%b post=%s\n", t.typeName, flags, stable, hasDefaultValue, postfix); try { Object expected = hasDefaultValue ? t.defaultValue : t.value; // Trigger compilation for (int i = 0; i < 20_000; i++) { Asserts.assertEQ(expected, test.testDirect(), "i = "+ i +" direct read returns wrong value"); Asserts.assertEQ(expected, test.testUnsafe(), "i = "+ i +" unsafe read returns wrong value"); } test.changeToDefault(); if (!hasDefaultValue && (stable || g.isFinal())) { Asserts.assertEQ(t.value, test.testDirect(), "direct read doesn't return prev value"); Asserts.assertEQ(test.testDirect(), test.testUnsafe()); } else { Asserts.assertEQ(t.defaultValue, test.testDirect(), "direct read doesn't return default value"); Asserts.assertEQ(test.testDirect(), test.testUnsafe(), "direct and unsafe reads return different values"); } } catch (Throwable e) { try { g.dump(); } catch (IOException io) { io.printStackTrace(); } throw e; } } public interface Test { Object testDirect(); Object testUnsafe(); void changeToDefault(); } enum JavaType { Z("Boolean", true, false), B("Byte", new Byte((byte) -1), new Byte((byte) 0)), S("Short", new Short((short) -1), new Short((short) 0)), C("Char", Character.MAX_VALUE, '\0'), I("Int", -1, 0), J("Long", -1L, 0L), F("Float", -1F, 0F), D("Double", -1D, 0D), L("Object", "", null); String typeName; Object value; Object defaultValue; String wrapper; JavaType(String name, Object value, Object defaultValue) { this.typeName = name; this.value = value; this.defaultValue = defaultValue; this.wrapper = internalName(value.getClass()); } String desc() { if (this == JavaType.L) { return "Ljava/lang/Object;"; } else { return name(); } } } static String internalName(Class cls) { return cls.getName().replace('.', '/'); } static String descriptor(Class cls) { return String.format("L%s;", internalName(cls)); } /** * Sample generated class: * static class T1 implements Test { * final int f = -1; * static final long FIELD_OFFSET; * static final T1 t = new T1(); * static { * FIELD_OFFSET = U.objectFieldOffset(T1.class.getDeclaredField("f")); * } * public Object testDirect() { return t.f; } * public Object testUnsafe() { return U.getInt(t, FIELD_OFFSET); } * public void changeToDefault() { U.putInt(t, 0, FIELD_OFFSET); } * } */ static class Generator { static final String FIELD_NAME = "f"; static final String UNSAFE_NAME = internalName(Unsafe.class); static final String UNSAFE_DESC = descriptor(Unsafe.class); final JavaType type; final int flags; final boolean stable; final boolean hasDefaultValue; final String nameSuffix; final String name; final String className; final String classDesc; final String fieldDesc; final byte[] classFile; Generator(JavaType t, int flags, boolean stable, boolean hasDefaultValue, String suffix) { this.type = t; this.flags = flags; this.stable = stable; this.hasDefaultValue = hasDefaultValue; this.nameSuffix = suffix; fieldDesc = type.desc(); name = String.format("Test%s%s__f=%d__s=%b__d=%b", type.typeName, suffix, flags, stable, hasDefaultValue); className = "java/lang/invoke/" + name; classDesc = String.format("L%s;", className); classFile = generateClassFile(); } byte[] generateClassFile() { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null, "java/lang/Object", new String[]{ internalName(Test.class) }); // Declare fields cw.visitField(ACC_FINAL | ACC_STATIC, "t", classDesc, null, null).visitEnd(); cw.visitField(ACC_FINAL | ACC_STATIC, "FIELD_OFFSET", "J", null, null).visitEnd(); cw.visitField(ACC_FINAL | ACC_STATIC, "U", UNSAFE_DESC, null, null).visitEnd(); if (isStatic()) { cw.visitField(ACC_FINAL | ACC_STATIC, "STATIC_BASE", "Ljava/lang/Object;", null, null).visitEnd(); } FieldVisitor fv = cw.visitField(flags, FIELD_NAME, fieldDesc, null, null); if (stable) { fv.visitAnnotation(descriptor(Stable.class), true); } fv.visitEnd(); // Methods { // MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); if (!isStatic()) { initField(mv); } mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { // public Object testDirect() { return t.f; } MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "testDirect", "()Ljava/lang/Object;", null, null); mv.visitCode(); getFieldValue(mv); wrapResult(mv); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { // public Object testUnsafe() { return U.getInt(t, FIELD_OFFSET); } MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "testUnsafe", "()Ljava/lang/Object;", null, null); mv.visitCode(); getFieldValueUnsafe(mv); wrapResult(mv); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { // public void changeToDefault() { U.putInt(t, FIELD_OFFSET, 0); } MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "changeToDefault", "()V", null, null); mv.visitCode(); getUnsafe(mv); if (isStatic()) { mv.visitFieldInsn(GETSTATIC, className, "STATIC_BASE", "Ljava/lang/Object;"); } else { mv.visitFieldInsn(GETSTATIC, className, "t", classDesc); } mv.visitFieldInsn(GETSTATIC, className, "FIELD_OFFSET", "J"); if (type.defaultValue != null) { mv.visitLdcInsn(type.defaultValue); } else { mv.visitInsn(ACONST_NULL); } String name = "put" + type.typeName + nameSuffix; mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, name, "(Ljava/lang/Object;J" + type.desc()+ ")V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { // MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); mv.visitCode(); // Cache Unsafe instance mv.visitMethodInsn(INVOKESTATIC, UNSAFE_NAME, "getUnsafe", "()"+UNSAFE_DESC, false); mv.visitFieldInsn(PUTSTATIC, className, "U", UNSAFE_DESC); // Create test object instance mv.visitTypeInsn(NEW, className); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, className, "", "()V", false); mv.visitFieldInsn(PUTSTATIC, className, "t", classDesc); // Compute field offset getUnsafe(mv); getField(mv); mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, (isStatic() ? "staticFieldOffset" : "objectFieldOffset"), "(Ljava/lang/reflect/Field;)J", false); mv.visitFieldInsn(PUTSTATIC, className, "FIELD_OFFSET", "J"); // Compute base offset for static field if (isStatic()) { getUnsafe(mv); getField(mv); mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, "staticFieldBase", "(Ljava/lang/reflect/Field;)Ljava/lang/Object;", false); mv.visitFieldInsn(PUTSTATIC, className, "STATIC_BASE", "Ljava/lang/Object;"); initField(mv); } mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } return cw.toByteArray(); } Test generate() { Class c = U.defineClass(className, classFile, 0, classFile.length, THIS_CLASS.getClassLoader(), null); try { return (Test) c.newInstance(); } catch(Exception e) { throw new Error(e); } } boolean isStatic() { return (flags & ACC_STATIC) > 0; } boolean isFinal() { return (flags & ACC_FINAL) > 0; } void getUnsafe(MethodVisitor mv) { mv.visitFieldInsn(GETSTATIC, className, "U", UNSAFE_DESC); } void getField(MethodVisitor mv) { mv.visitLdcInsn(Type.getType(classDesc)); mv.visitLdcInsn(FIELD_NAME); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", false); } void getFieldValue(MethodVisitor mv) { if (isStatic()) { mv.visitFieldInsn(GETSTATIC, className, FIELD_NAME, fieldDesc); } else { mv.visitFieldInsn(GETSTATIC, className, "t", classDesc); mv.visitFieldInsn(GETFIELD, className, FIELD_NAME, fieldDesc); } } void getFieldValueUnsafe(MethodVisitor mv) { getUnsafe(mv); if (isStatic()) { mv.visitFieldInsn(GETSTATIC, className, "STATIC_BASE", "Ljava/lang/Object;"); } else { mv.visitFieldInsn(GETSTATIC, className, "t", classDesc); } mv.visitFieldInsn(GETSTATIC, className, "FIELD_OFFSET", "J"); String name = "get" + type.typeName + nameSuffix; mv.visitMethodInsn(INVOKEVIRTUAL, UNSAFE_NAME, name, "(Ljava/lang/Object;J)" + type.desc(), false); } void wrapResult(MethodVisitor mv) { if (type != JavaType.L) { String desc = String.format("(%s)L%s;", type.desc(), type.wrapper); mv.visitMethodInsn(INVOKESTATIC, type.wrapper, "valueOf", desc, false); } } void initField(MethodVisitor mv) { if (hasDefaultValue) { return; // Nothing to do } if (!isStatic()) { mv.visitVarInsn(ALOAD, 0); } mv.visitLdcInsn(type.value); mv.visitFieldInsn((isStatic() ? PUTSTATIC : PUTFIELD), className, FIELD_NAME, fieldDesc); } public void dump() throws IOException { Path path = Paths.get(".", name + ".class").toAbsolutePath(); System.err.println("dumping test class to " + path); Files.write(path, classFile); } } }