changeset 8723:c8abbfeb4101

8078629: VM should constant fold Unsafe.get*() loads from final fields Reviewed-by: kvn, jrose, psandoz
author vlivanov
date Tue, 14 Jul 2015 06:44:50 -0700
parents 0fb7705845de
children 2545ed8e99c5
files src/share/vm/ci/ciField.hpp src/share/vm/opto/library_call.cpp src/share/vm/opto/parse.hpp src/share/vm/opto/parse2.cpp src/share/vm/opto/parse3.cpp src/share/vm/opto/type.cpp src/share/vm/opto/type.hpp test/compiler/unsafe/UnsafeGetConstantField.java
diffstat 8 files changed, 504 insertions(+), 137 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/vm/ci/ciField.hpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/src/share/vm/opto/library_call.cpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/src/share/vm/opto/parse.hpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/src/share/vm/opto/parse2.cpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/src/share/vm/opto/parse3.cpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/src/share/vm/opto/type.cpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/src/share/vm/opto/type.hpp	Tue Mar 31 21:46:44 2015 +0200
+++ b/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/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);
+        }
+    }
+}