blob: f3d0a07c4779d5a233212a27905c48fdf721035f [file] [log] [blame]
/*
* Copyright (c) 2015, 2018, 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.
*
* 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);
}
}
}