8078629: VM should constant fold Unsafe.get*() loads from final fields
Reviewed-by: kvn, jrose, psandoz
--- a/hotspot/src/share/vm/ci/ciField.hpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/ci/ciField.hpp Tue Jul 14 06:44:50 2015 -0700
@@ -181,6 +181,17 @@
return (holder()->is_subclass_of(callsite_klass) && (name() == ciSymbol::target_name()));
}
+ bool is_autobox_cache() {
+ ciSymbol* klass_name = holder()->name();
+ return (name() == ciSymbol::cache_field_name() &&
+ holder()->uses_default_loader() &&
+ (klass_name == ciSymbol::java_lang_Character_CharacterCache() ||
+ klass_name == ciSymbol::java_lang_Byte_ByteCache() ||
+ klass_name == ciSymbol::java_lang_Short_ShortCache() ||
+ klass_name == ciSymbol::java_lang_Integer_IntegerCache() ||
+ klass_name == ciSymbol::java_lang_Long_LongCache()));
+ }
+
// Debugging output
void print();
void print_name_on(outputStream* st);
--- a/hotspot/src/share/vm/opto/library_call.cpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/opto/library_call.cpp Tue Jul 14 06:44:50 2015 -0700
@@ -2687,35 +2687,48 @@
// of safe & unsafe memory.
if (need_mem_bar) insert_mem_bar(Op_MemBarCPUOrder);
- if (!is_store) {
- MemNode::MemOrd mo = is_volatile ? MemNode::acquire : MemNode::unordered;
- // To be valid, unsafe loads may depend on other conditions than
- // the one that guards them: pin the Load node
- Node* p = make_load(control(), adr, value_type, type, adr_type, mo, LoadNode::Pinned, is_volatile);
- // load value
- switch (type) {
- case T_BOOLEAN:
- case T_CHAR:
- case T_BYTE:
- case T_SHORT:
- case T_INT:
- case T_LONG:
- case T_FLOAT:
- case T_DOUBLE:
- break;
- case T_OBJECT:
- if (need_read_barrier) {
- insert_pre_barrier(heap_base_oop, offset, p, !(is_volatile || need_mem_bar));
+ if (!is_store) {
+ Node* p = NULL;
+ // Try to constant fold a load from a constant field
+ ciField* field = alias_type->field();
+ if (heap_base_oop != top() &&
+ field != NULL && field->is_constant() && field->layout_type() == type) {
+ // final or stable field
+ const Type* con_type = Type::make_constant(alias_type->field(), heap_base_oop);
+ if (con_type != NULL) {
+ p = makecon(con_type);
}
- break;
- case T_ADDRESS:
- // Cast to an int type.
- p = _gvn.transform(new CastP2XNode(NULL, p));
- p = ConvX2UL(p);
- break;
- default:
- fatal(err_msg_res("unexpected type %d: %s", type, type2name(type)));
- break;
+ }
+ if (p == NULL) {
+ MemNode::MemOrd mo = is_volatile ? MemNode::acquire : MemNode::unordered;
+ // To be valid, unsafe loads may depend on other conditions than
+ // the one that guards them: pin the Load node
+ p = make_load(control(), adr, value_type, type, adr_type, mo, LoadNode::Pinned, is_volatile);
+ // load value
+ switch (type) {
+ case T_BOOLEAN:
+ case T_CHAR:
+ case T_BYTE:
+ case T_SHORT:
+ case T_INT:
+ case T_LONG:
+ case T_FLOAT:
+ case T_DOUBLE:
+ break;
+ case T_OBJECT:
+ if (need_read_barrier) {
+ insert_pre_barrier(heap_base_oop, offset, p, !(is_volatile || need_mem_bar));
+ }
+ break;
+ case T_ADDRESS:
+ // Cast to an int type.
+ p = _gvn.transform(new CastP2XNode(NULL, p));
+ p = ConvX2UL(p);
+ break;
+ default:
+ fatal(err_msg_res("unexpected type %d: %s", type, type2name(type)));
+ break;
+ }
}
// The load node has the control of the preceding MemBarCPUOrder. All
// following nodes will have the control of the MemBarCPUOrder inserted at
--- a/hotspot/src/share/vm/opto/parse.hpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/opto/parse.hpp Tue Jul 14 06:44:50 2015 -0700
@@ -539,10 +539,6 @@
void do_get_xxx(Node* obj, ciField* field, bool is_field);
void do_put_xxx(Node* obj, ciField* field, bool is_field);
- // loading from a constant field or the constant pool
- // returns false if push failed (non-perm field constants only, not ldcs)
- bool push_constant(ciConstant con, bool require_constant = false, bool is_autobox_cache = false, const Type* basic_type = NULL);
-
// implementation of object creation bytecodes
void emit_guard_for_new(ciInstanceKlass* klass);
void do_new();
--- a/hotspot/src/share/vm/opto/parse2.cpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/opto/parse2.cpp Tue Jul 14 06:44:50 2015 -0700
@@ -1478,8 +1478,10 @@
}
assert(constant.basic_type() != T_OBJECT || constant.as_object()->is_instance(),
"must be java_mirror of klass");
- bool pushed = push_constant(constant, true);
- guarantee(pushed, "must be possible to push this constant");
+ const Type* con_type = Type::make_from_constant(constant);
+ if (con_type != NULL) {
+ push_node(con_type->basic_type(), makecon(con_type));
+ }
}
break;
--- a/hotspot/src/share/vm/opto/parse3.cpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/opto/parse3.cpp Tue Jul 14 06:44:50 2015 -0700
@@ -149,51 +149,10 @@
// Does this field have a constant value? If so, just push the value.
if (field->is_constant()) {
// final or stable field
- const Type* stable_type = NULL;
- if (FoldStableValues && field->is_stable()) {
- stable_type = Type::get_const_type(field->type());
- if (field->type()->is_array_klass()) {
- int stable_dimension = field->type()->as_array_klass()->dimension();
- stable_type = stable_type->is_aryptr()->cast_to_stable(true, stable_dimension);
- }
- }
- if (field->is_static()) {
- // final static field
- if (C->eliminate_boxing()) {
- // The pointers in the autobox arrays are always non-null.
- ciSymbol* klass_name = field->holder()->name();
- if (field->name() == ciSymbol::cache_field_name() &&
- field->holder()->uses_default_loader() &&
- (klass_name == ciSymbol::java_lang_Character_CharacterCache() ||
- klass_name == ciSymbol::java_lang_Byte_ByteCache() ||
- klass_name == ciSymbol::java_lang_Short_ShortCache() ||
- klass_name == ciSymbol::java_lang_Integer_IntegerCache() ||
- klass_name == ciSymbol::java_lang_Long_LongCache())) {
- bool require_const = true;
- bool autobox_cache = true;
- if (push_constant(field->constant_value(), require_const, autobox_cache)) {
- return;
- }
- }
- }
- if (push_constant(field->constant_value(), false, false, stable_type))
- return;
- } else {
- // final or stable non-static field
- // Treat final non-static fields of trusted classes (classes in
- // java.lang.invoke and sun.invoke packages and subpackages) as
- // compile time constants.
- if (obj->is_Con()) {
- const TypeOopPtr* oop_ptr = obj->bottom_type()->isa_oopptr();
- ciObject* constant_oop = oop_ptr->const_oop();
- ciConstant constant = field->constant_value_of(constant_oop);
- if (FoldStableValues && field->is_stable() && constant.is_null_or_zero()) {
- // fall through to field load; the field is not yet initialized
- } else {
- if (push_constant(constant, true, false, stable_type))
- return;
- }
- }
+ const Type* con_type = Type::make_constant(field, obj);
+ if (con_type != NULL) {
+ push_node(con_type->basic_type(), makecon(con_type));
+ return;
}
}
@@ -362,39 +321,6 @@
}
}
-
-
-bool Parse::push_constant(ciConstant constant, bool require_constant, bool is_autobox_cache, const Type* stable_type) {
- const Type* con_type = Type::make_from_constant(constant, require_constant, is_autobox_cache);
- switch (constant.basic_type()) {
- case T_ARRAY:
- case T_OBJECT:
- // cases:
- // can_be_constant = (oop not scavengable || ScavengeRootsInCode != 0)
- // should_be_constant = (oop not scavengable || ScavengeRootsInCode >= 2)
- // An oop is not scavengable if it is in the perm gen.
- if (stable_type != NULL && con_type != NULL && con_type->isa_oopptr())
- con_type = con_type->join_speculative(stable_type);
- break;
-
- case T_ILLEGAL:
- // Invalid ciConstant returned due to OutOfMemoryError in the CI
- assert(C->env()->failing(), "otherwise should not see this");
- // These always occur because of object types; we are going to
- // bail out anyway, so make the stack depths match up
- push( zerocon(T_OBJECT) );
- return false;
- }
-
- if (con_type == NULL)
- // we cannot inline the oop, but we can use it later to narrow a type
- return false;
-
- push_node(constant.basic_type(), makecon(con_type));
- return true;
-}
-
-
//=============================================================================
void Parse::do_anewarray() {
bool will_link;
--- a/hotspot/src/share/vm/opto/type.cpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/opto/type.cpp Tue Jul 14 06:44:50 2015 -0700
@@ -200,8 +200,7 @@
//-----------------------make_from_constant------------------------------------
-const Type* Type::make_from_constant(ciConstant constant,
- bool require_constant, bool is_autobox_cache) {
+const Type* Type::make_from_constant(ciConstant constant, bool require_constant) {
switch (constant.basic_type()) {
case T_BOOLEAN: return TypeInt::make(constant.as_boolean());
case T_CHAR: return TypeInt::make(constant.as_char());
@@ -222,14 +221,57 @@
if (oop_constant->is_null_object()) {
return Type::get_zero_type(T_OBJECT);
} else if (require_constant || oop_constant->should_be_constant()) {
- return TypeOopPtr::make_from_constant(oop_constant, require_constant, is_autobox_cache);
+ return TypeOopPtr::make_from_constant(oop_constant, require_constant);
+ }
+ }
+ case T_ILLEGAL:
+ // Invalid ciConstant returned due to OutOfMemoryError in the CI
+ assert(Compile::current()->env()->failing(), "otherwise should not see this");
+ return NULL;
+ }
+ // Fall through to failure
+ return NULL;
+}
+
+
+const Type* Type::make_constant(ciField* field, Node* obj) {
+ if (!field->is_constant()) return NULL;
+
+ const Type* con_type = NULL;
+ if (field->is_static()) {
+ // final static field
+ con_type = Type::make_from_constant(field->constant_value(), /*require_const=*/true);
+ if (Compile::current()->eliminate_boxing() && field->is_autobox_cache() && con_type != NULL) {
+ con_type = con_type->is_aryptr()->cast_to_autobox_cache(true);
+ }
+ } else {
+ // final or stable non-static field
+ // Treat final non-static fields of trusted classes (classes in
+ // java.lang.invoke and sun.invoke packages and subpackages) as
+ // compile time constants.
+ if (obj->is_Con()) {
+ const TypeOopPtr* oop_ptr = obj->bottom_type()->isa_oopptr();
+ ciObject* constant_oop = oop_ptr->const_oop();
+ ciConstant constant = field->constant_value_of(constant_oop);
+ con_type = Type::make_from_constant(constant, /*require_const=*/true);
+ }
+ }
+ if (FoldStableValues && field->is_stable() && con_type != NULL) {
+ if (con_type->is_zero_type()) {
+ return NULL; // the field hasn't been initialized yet
+ } else if (con_type->isa_oopptr()) {
+ const Type* stable_type = Type::get_const_type(field->type());
+ if (field->type()->is_array_klass()) {
+ int stable_dimension = field->type()->as_array_klass()->dimension();
+ stable_type = stable_type->is_aryptr()->cast_to_stable(true, stable_dimension);
+ }
+ if (stable_type != NULL) {
+ con_type = con_type->join_speculative(stable_type);
}
}
}
- // Fall through to failure
- return NULL;
-}
-
+ return con_type;
+}
//------------------------------make-------------------------------------------
// Create a simple Type, with default empty symbol sets. Then hashcons it
@@ -3009,9 +3051,7 @@
//------------------------------make_from_constant-----------------------------
// Make a java pointer from an oop constant
-const TypeOopPtr* TypeOopPtr::make_from_constant(ciObject* o,
- bool require_constant,
- bool is_autobox_cache) {
+const TypeOopPtr* TypeOopPtr::make_from_constant(ciObject* o, bool require_constant) {
assert(!o->is_null_object(), "null object not yet handled here.");
ciKlass* klass = o->klass();
if (klass->is_instance_klass()) {
@@ -3026,10 +3066,6 @@
// Element is an object array. Recursively call ourself.
const TypeOopPtr *etype =
TypeOopPtr::make_from_klass_raw(klass->as_obj_array_klass()->element_klass());
- if (is_autobox_cache) {
- // The pointers in the autobox arrays are always non-null.
- etype = etype->cast_to_ptr_type(TypePtr::NotNull)->is_oopptr();
- }
const TypeAry* arr0 = TypeAry::make(etype, TypeInt::make(o->as_array()->length()));
// We used to pass NotNull in here, asserting that the sub-arrays
// are all not-null. This is not true in generally, as code can
@@ -3039,7 +3075,7 @@
} else if (!o->should_be_constant()) {
return TypeAryPtr::make(TypePtr::NotNull, arr0, klass, true, 0);
}
- const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, 0, InstanceBot, NULL, InlineDepthBottom, is_autobox_cache);
+ const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, 0);
return arr;
} else if (klass->is_type_array_klass()) {
// Element is an typeArray
@@ -3940,7 +3976,6 @@
return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _instance_id, _speculative, _inline_depth);
}
-
//------------------------------cast_to_stable---------------------------------
const TypeAryPtr* TypeAryPtr::cast_to_stable(bool stable, int stable_dimension) const {
if (stable_dimension <= 0 || (stable_dimension == 1 && stable == this->is_stable()))
@@ -3969,6 +4004,18 @@
return dim;
}
+//----------------------cast_to_autobox_cache-----------------------------------
+const TypeAryPtr* TypeAryPtr::cast_to_autobox_cache(bool cache) const {
+ if (is_autobox_cache() == cache) return this;
+ const TypeOopPtr* etype = elem()->make_oopptr();
+ if (etype == NULL) return this;
+ // The pointers in the autobox arrays are always non-null.
+ TypePtr::PTR ptr_type = cache ? TypePtr::NotNull : TypePtr::AnyNull;
+ etype = etype->cast_to_ptr_type(TypePtr::NotNull)->is_oopptr();
+ const TypeAry* new_ary = TypeAry::make(etype, size(), is_stable());
+ return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _instance_id, _speculative, _inline_depth, cache);
+}
+
//------------------------------eq---------------------------------------------
// Structural equality check for Type representations
bool TypeAryPtr::eq( const Type *t ) const {
@@ -4455,7 +4502,7 @@
// TRUE if Type is a singleton type, FALSE otherwise. Singletons are simple
// constants
bool TypeMetadataPtr::singleton(void) const {
- // detune optimizer to not generate constant metadta + constant offset as a constant!
+ // detune optimizer to not generate constant metadata + constant offset as a constant!
// TopPTR, Null, AnyNull, Constant are all singletons
return (_offset == 0) && !below_centerline(_ptr);
}
--- a/hotspot/src/share/vm/opto/type.hpp Tue Mar 31 21:46:44 2015 +0200
+++ b/hotspot/src/share/vm/opto/type.hpp Tue Jul 14 06:44:50 2015 -0700
@@ -412,8 +412,9 @@
static const Type* get_typeflow_type(ciType* type);
static const Type* make_from_constant(ciConstant constant,
- bool require_constant = false,
- bool is_autobox_cache = false);
+ bool require_constant = false);
+
+ static const Type* make_constant(ciField* field, Node* obj);
// Speculative type helper methods. See TypePtr.
virtual const TypePtr* speculative() const { return NULL; }
@@ -973,8 +974,7 @@
// may return a non-singleton type.
// If require_constant, produce a NULL if a singleton is not possible.
static const TypeOopPtr* make_from_constant(ciObject* o,
- bool require_constant = false,
- bool not_null_elements = false);
+ bool require_constant = false);
// Make a generic (unclassed) pointer to an oop.
static const TypeOopPtr* make(PTR ptr, int offset, int instance_id,
@@ -1184,6 +1184,8 @@
const TypeAryPtr* cast_to_stable(bool stable, int stable_dimension = 1) const;
int stable_dimension() const;
+ const TypeAryPtr* cast_to_autobox_cache(bool cache) const;
+
// Convenience common pre-built types.
static const TypeAryPtr *RANGE;
static const TypeAryPtr *OOPS;
@@ -1674,12 +1676,12 @@
inline const TypePtr* Type::make_ptr() const {
return (_base == NarrowOop) ? is_narrowoop()->get_ptrtype() :
- ((_base == NarrowKlass) ? is_narrowklass()->get_ptrtype() :
- (isa_ptr() ? is_ptr() : NULL));
+ ((_base == NarrowKlass) ? is_narrowklass()->get_ptrtype() :
+ isa_ptr());
}
inline const TypeOopPtr* Type::make_oopptr() const {
- return (_base == NarrowOop) ? is_narrowoop()->get_ptrtype()->is_oopptr() : is_oopptr();
+ return (_base == NarrowOop) ? is_narrowoop()->get_ptrtype()->isa_oopptr() : isa_oopptr();
}
inline const TypeNarrowOop* Type::make_narrowoop() const {
@@ -1689,7 +1691,7 @@
inline const TypeNarrowKlass* Type::make_narrowklass() const {
return (_base == NarrowKlass) ? is_narrowklass() :
- (isa_ptr() ? TypeNarrowKlass::make(is_ptr()) : NULL);
+ (isa_ptr() ? TypeNarrowKlass::make(is_ptr()) : NULL);
}
inline bool Type::is_floatingpoint() const {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/unsafe/UnsafeGetConstantField.java Tue Jul 14 06:44:50 2015 -0700
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2015, 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 /../../test/lib
+ * @run main/bootclasspath -XX:+UnlockDiagnosticVMOptions
+ * -Xbatch -XX:-TieredCompilation
+ * -XX:+FoldStableValues
+ * -XX:+UseUnalignedAccesses
+ * java.lang.invoke.UnsafeGetConstantField
+ * @run main/bootclasspath -XX:+UnlockDiagnosticVMOptions
+ * -Xbatch -XX:-TieredCompilation
+ * -XX:+FoldStableValues
+ * -XX:-UseUnalignedAccesses
+ * java.lang.invoke.UnsafeGetConstantField
+ */
+package java.lang.invoke;
+
+import jdk.internal.org.objectweb.asm.*;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.Utils;
+import sun.misc.Unsafe;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+public class UnsafeGetConstantField {
+ static final Class<?> THIS_CLASS = UnsafeGetConstantField.class;
+
+ static final Unsafe U = Utils.getUnsafe();
+
+ public static void main(String[] args) {
+ 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);
+ }
+ }
+ @DontInline
+ 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.out.printf("type=%s flags=%d stable=%b default=%b post=%s\n",
+ t.typeName, flags, stable, hasDefaultValue, postfix);
+ // Trigger compilation
+ for (int i = 0; i < 20_000; i++) {
+ Asserts.assertEQ(test.testDirect(), test.testUnsafe());
+ }
+ }
+
+ interface Test {
+ Object testDirect();
+ Object testUnsafe();
+ }
+
+ enum JavaType {
+ Z("Boolean", true),
+ B("Byte", new Byte((byte)-1)),
+ S("Short", new Short((short)-1)),
+ C("Char", Character.MAX_VALUE),
+ I("Int", -1),
+ J("Long", -1L),
+ F("Float", -1F),
+ D("Double", -1D),
+ L("Object", new Object());
+
+ String typeName;
+ Object value;
+ String wrapper;
+ JavaType(String name, Object value) {
+ this.typeName = name;
+ this.value = value;
+ this.wrapper = internalName(value.getClass());
+ }
+
+ String desc() {
+ if (this == JavaType.L) {
+ return "Ljava/lang/Object;";
+ } else {
+ return toString();
+ }
+ }
+ }
+
+ 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); }
+ * }
+ */
+ 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 className;
+ final String classDesc;
+ final String fieldDesc;
+
+ 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();
+ className = String.format("%s$Test%s%s__f=%d__s=%b__d=%b", internalName(THIS_CLASS), type.typeName,
+ suffix, flags, stable, hasDefaultValue);
+ classDesc = String.format("L%s;", className);
+ }
+
+ 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(0, "<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();
+ }
+
+ { // <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() {
+ byte[] classFile = generateClassFile();
+ 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);
+ }
+ switch (type) {
+ case L: {
+ mv.visitTypeInsn(NEW, "java/lang/Object");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+
+ break;
+ }
+ default: {
+ mv.visitLdcInsn(type.value);
+ break;
+ }
+ }
+ mv.visitFieldInsn((isStatic() ? PUTSTATIC : PUTFIELD), className, FIELD_NAME, fieldDesc);
+ }
+ }
+}