| /* |
| * 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 /test/lib |
| * |
| * @requires vm.flavor == "server" & !vm.emulatedClient |
| * |
| * @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,compiler.unsafe.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,compiler.unsafe.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.misc.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 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.ACC_FINAL; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP; |
| import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; |
| import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; |
| import static jdk.internal.org.objectweb.asm.Opcodes.NEW; |
| import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; |
| |
| 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() || Platform.isEmulatedClient()) { |
| throw new Error("TESTBUG: Not server mode"); |
| } |
| 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 |
| { // <init> |
| MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); |
| mv.visitCode(); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()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(); |
| } |
| |
| { // <clinit> |
| MethodVisitor mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()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, "<init>", "()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); |
| } |
| } |
| } |