6700100: optimize inline_native_clone() for small objects with exact klass
authorroland
Tue, 16 Dec 2014 13:49:36 +0100
changeset 28396 7fe4347e6792
parent 28395 fbe08d791778
child 28397 6b74798bc83d
child 28486 b0df113b962e
child 28487 34406bbb4f3a
6700100: optimize inline_native_clone() for small objects with exact klass Summary: optimize small instance clones as loads/stores Reviewed-by: kvn, iveresov
hotspot/src/share/vm/ci/ciInstanceKlass.cpp
hotspot/src/share/vm/ci/ciInstanceKlass.hpp
hotspot/src/share/vm/oops/fieldStreams.hpp
hotspot/src/share/vm/opto/c2_globals.hpp
hotspot/src/share/vm/opto/callnode.cpp
hotspot/src/share/vm/opto/callnode.hpp
hotspot/src/share/vm/opto/library_call.cpp
hotspot/src/share/vm/opto/macroArrayCopy.cpp
hotspot/test/compiler/arraycopy/TestArrayCopyMacro.java
hotspot/test/compiler/arraycopy/TestArrayOfNoTypeCheck.java
hotspot/test/compiler/arraycopy/TestArraysCopyOfNoTypeCheck.java
hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java
--- a/hotspot/src/share/vm/ci/ciInstanceKlass.cpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/ci/ciInstanceKlass.cpp	Tue Dec 16 13:49:36 2014 +0100
@@ -59,7 +59,7 @@
   _has_nonstatic_fields = ik->has_nonstatic_fields();
   _has_default_methods = ik->has_default_methods();
   _nonstatic_fields = NULL; // initialized lazily by compute_nonstatic_fields:
-
+  _has_injected_fields = -1;
   _implementor = NULL; // we will fill these lazily
 
   Thread *thread = Thread::current();
@@ -100,6 +100,7 @@
   _nonstatic_field_size = -1;
   _has_nonstatic_fields = false;
   _nonstatic_fields = NULL;
+  _has_injected_fields = -1;
   _loader = loader;
   _protection_domain = protection_domain;
   _is_shared = false;
@@ -500,6 +501,34 @@
   return fields;
 }
 
+void ciInstanceKlass::compute_injected_fields_helper() {
+  ASSERT_IN_VM;
+  InstanceKlass* k = get_instanceKlass();
+
+  for (InternalFieldStream fs(k); !fs.done(); fs.next()) {
+    if (fs.access_flags().is_static())  continue;
+    _has_injected_fields++;
+    break;
+  }
+}
+
+bool ciInstanceKlass::compute_injected_fields() {
+  assert(_has_injected_fields == -1, "shouldn't be initialized yet");
+  assert(is_loaded(), "must be loaded");
+
+  if (super() != NULL && super()->has_injected_fields()) {
+    _has_injected_fields = 1;
+    return true;
+  }
+
+  _has_injected_fields = 0;
+  GUARDED_VM_ENTRY({
+      compute_injected_fields_helper();
+    });
+
+  return _has_injected_fields > 0 ? true : false;
+}
+
 // ------------------------------------------------------------------
 // ciInstanceKlass::find_method
 //
--- a/hotspot/src/share/vm/ci/ciInstanceKlass.hpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/ci/ciInstanceKlass.hpp	Tue Dec 16 13:49:36 2014 +0100
@@ -64,6 +64,7 @@
 
   ciConstantPoolCache*   _field_cache;  // cached map index->field
   GrowableArray<ciField*>* _nonstatic_fields;
+  int                    _has_injected_fields; // any non static injected fields? lazily initialized.
 
   // The possible values of the _implementor fall into following three cases:
   //   NULL: no implementor.
@@ -71,6 +72,9 @@
   //   Itsef: more than one implementors.
   ciInstanceKlass*       _implementor;
 
+  bool compute_injected_fields();
+  void compute_injected_fields_helper();
+
 protected:
   ciInstanceKlass(KlassHandle h_k);
   ciInstanceKlass(ciSymbol* name, jobject loader, jobject protection_domain);
@@ -186,6 +190,14 @@
     else
       return _nonstatic_fields->length();
   }
+
+  bool has_injected_fields() {
+    if (_has_injected_fields == -1) {
+      return compute_injected_fields();
+    }
+    return _has_injected_fields > 0 ? true : false;
+  }
+
   // nth nonstatic field (presented by ascending address)
   ciField* nonstatic_field_at(int i) {
     assert(_nonstatic_fields != NULL, "");
--- a/hotspot/src/share/vm/oops/fieldStreams.hpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/oops/fieldStreams.hpp	Tue Dec 16 13:49:36 2014 +0100
@@ -51,7 +51,7 @@
 
   int init_generic_signature_start_slot() {
     int length = _fields->length();
-    int num_fields = 0;
+    int num_fields = _index;
     int skipped_generic_signature_slots = 0;
     FieldInfo* fi;
     AccessFlags flags;
--- a/hotspot/src/share/vm/opto/c2_globals.hpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/opto/c2_globals.hpp	Tue Dec 16 13:49:36 2014 +0100
@@ -669,6 +669,13 @@
   product_pd(bool, TrapBasedRangeChecks,                                    \
           "Generate code for range checks that uses a cmp and trap "        \
           "instruction raising SIGTRAP. Used on PPC64.")                    \
+                                                                            \
+  product(intx, ArrayCopyLoadStoreMaxElem, 8,                               \
+          "Maximum number of arraycopy elements inlined as a sequence of"   \
+          "loads/stores")                                                   \
+                                                                            \
+  develop(bool, StressArrayCopyMacroNode, false,                            \
+          "Perform ArrayCopy load/store replacement during IGVN only")
 
 C2_FLAGS(DECLARE_DEVELOPER_FLAG, DECLARE_PD_DEVELOPER_FLAG, DECLARE_PRODUCT_FLAG, DECLARE_PD_PRODUCT_FLAG, DECLARE_DIAGNOSTIC_FLAG, DECLARE_EXPERIMENTAL_FLAG, DECLARE_NOTPRODUCT_FLAG)
 
--- a/hotspot/src/share/vm/opto/callnode.cpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/opto/callnode.cpp	Tue Dec 16 13:49:36 2014 +0100
@@ -28,6 +28,7 @@
 #include "opto/callGenerator.hpp"
 #include "opto/callnode.hpp"
 #include "opto/castnode.hpp"
+#include "opto/convertnode.hpp"
 #include "opto/escape.hpp"
 #include "opto/locknode.hpp"
 #include "opto/machnode.hpp"
@@ -1818,7 +1819,10 @@
 }
 
 ArrayCopyNode::ArrayCopyNode(Compile* C, bool alloc_tightly_coupled)
-  : CallNode(arraycopy_type(), NULL, TypeRawPtr::BOTTOM), _alloc_tightly_coupled(alloc_tightly_coupled), _kind(ArrayCopy) {
+  : CallNode(arraycopy_type(), NULL, TypeRawPtr::BOTTOM),
+    _alloc_tightly_coupled(alloc_tightly_coupled),
+    _kind(None),
+    _arguments_validated(false) {
   init_class_id(Class_ArrayCopy);
   init_flags(Flag_is_macro);
   C->add_macro_node(this);
@@ -1870,3 +1874,136 @@
   st->print(" (%s%s)", _kind_names[_kind], _alloc_tightly_coupled ? ", tightly coupled allocation" : "");
 }
 #endif
+
+int ArrayCopyNode::get_count(PhaseGVN *phase) const {
+  Node* src = in(ArrayCopyNode::Src);
+  const Type* src_type = phase->type(src);
+
+  assert(is_clonebasic(), "unexpected arraycopy type");
+  if (src_type->isa_instptr()) {
+    const TypeInstPtr* inst_src = src_type->is_instptr();
+    ciInstanceKlass* ik = inst_src->klass()->as_instance_klass();
+    // ciInstanceKlass::nof_nonstatic_fields() doesn't take injected
+    // fields into account. They are rare anyway so easier to simply
+    // skip instances with injected fields.
+    if ((!inst_src->klass_is_exact() && (ik->is_interface() || ik->has_subklass())) || ik->has_injected_fields()) {
+      return -1;
+    }
+    int nb_fields = ik->nof_nonstatic_fields();
+    return nb_fields;
+  }
+  return -1;
+}
+
+Node* ArrayCopyNode::try_clone_instance(PhaseGVN *phase, bool can_reshape, int count) {
+  assert(is_clonebasic(), "unexpected arraycopy type");
+
+  Node* src = in(ArrayCopyNode::Src);
+  Node* dest = in(ArrayCopyNode::Dest);
+  Node* ctl = in(TypeFunc::Control);
+  Node* in_mem = in(TypeFunc::Memory);
+
+  const Type* src_type = phase->type(src);
+  const Type* dest_type = phase->type(dest);
+
+  assert(src->is_AddP(), "should be base + off");
+  assert(dest->is_AddP(), "should be base + off");
+  Node* base_src = src->in(AddPNode::Base);
+  Node* base_dest = dest->in(AddPNode::Base);
+
+  MergeMemNode* mem = MergeMemNode::make(in_mem);
+
+  const TypeInstPtr* inst_src = src_type->is_instptr();
+
+  if (!inst_src->klass_is_exact()) {
+    ciInstanceKlass* ik = inst_src->klass()->as_instance_klass();
+    assert(!ik->is_interface() && !ik->has_subklass(), "inconsistent klass hierarchy");
+    phase->C->dependencies()->assert_leaf_type(ik);
+  }
+
+  ciInstanceKlass* ik = inst_src->klass()->as_instance_klass();
+  assert(ik->nof_nonstatic_fields() <= ArrayCopyLoadStoreMaxElem, "too many fields");
+
+  for (int i = 0; i < count; i++) {
+    ciField* field = ik->nonstatic_field_at(i);
+    int fieldidx = phase->C->alias_type(field)->index();
+    const TypePtr* adr_type = phase->C->alias_type(field)->adr_type();
+    Node* off = phase->MakeConX(field->offset());
+    Node* next_src = phase->transform(new AddPNode(base_src,base_src,off));
+    Node* next_dest = phase->transform(new AddPNode(base_dest,base_dest,off));
+    BasicType bt = field->layout_type();
+
+    const Type *type;
+    if (bt == T_OBJECT) {
+      if (!field->type()->is_loaded()) {
+        type = TypeInstPtr::BOTTOM;
+      } else {
+        ciType* field_klass = field->type();
+        type = TypeOopPtr::make_from_klass(field_klass->as_klass());
+      }
+    } else {
+      type = Type::get_const_basic_type(bt);
+    }
+
+    Node* v = LoadNode::make(*phase, ctl, mem->memory_at(fieldidx), next_src, adr_type, type, bt, MemNode::unordered);
+    v = phase->transform(v);
+    Node* s = StoreNode::make(*phase, ctl, mem->memory_at(fieldidx), next_dest, adr_type, v, bt, MemNode::unordered);
+    s = phase->transform(s);
+    mem->set_memory_at(fieldidx, s);
+  }
+
+  if (!finish_transform(phase, can_reshape, ctl, mem)) {
+    return NULL;
+  }
+
+  return mem;
+}
+
+bool ArrayCopyNode::finish_transform(PhaseGVN *phase, bool can_reshape,
+                                     Node* ctl, Node *mem) {
+  if (can_reshape) {
+    PhaseIterGVN* igvn = phase->is_IterGVN();
+    assert(is_clonebasic(), "unexpected arraycopy type");
+    Node* out_mem = proj_out(TypeFunc::Memory);
+
+    if (out_mem->outcnt() != 1 || !out_mem->raw_out(0)->is_MergeMem() ||
+        out_mem->raw_out(0)->outcnt() != 1 || !out_mem->raw_out(0)->raw_out(0)->is_MemBar()) {
+      assert(!GraphKit::use_ReduceInitialCardMarks(), "can only happen with card marking");
+      return false;
+    }
+
+    igvn->replace_node(out_mem->raw_out(0), mem);
+
+    Node* out_ctl = proj_out(TypeFunc::Control);
+    igvn->replace_node(out_ctl, ctl);
+  }
+  return true;
+}
+
+
+Node *ArrayCopyNode::Ideal(PhaseGVN *phase, bool can_reshape) {
+
+  if (StressArrayCopyMacroNode && !can_reshape) return NULL;
+
+  // See if it's a small array copy and we can inline it as
+  // loads/stores
+  // Here we can only do:
+  // - clone for which we don't need to do card marking
+
+  if (!is_clonebasic()) {
+    return NULL;
+  }
+
+  if (in(TypeFunc::Control)->is_top() || in(TypeFunc::Memory)->is_top()) {
+    return NULL;
+  }
+
+  int count = get_count(phase);
+
+  if (count < 0 || count > ArrayCopyLoadStoreMaxElem) {
+    return NULL;
+  }
+
+  Node* mem = try_clone_instance(phase, can_reshape, count);
+  return mem;
+}
--- a/hotspot/src/share/vm/opto/callnode.hpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/opto/callnode.hpp	Tue Dec 16 13:49:36 2014 +0100
@@ -1070,8 +1070,8 @@
 
   // What kind of arraycopy variant is this?
   enum {
+    None,            // not set yet
     ArrayCopy,       // System.arraycopy()
-    ArrayCopyNoTest, // System.arraycopy(), all arguments validated
     CloneBasic,      // A clone that can be copied by 64 bit chunks
     CloneOop,        // An oop array clone
     CopyOf,          // Arrays.copyOf()
@@ -1095,6 +1095,8 @@
   // LibraryCallKit::tightly_coupled_allocation() is called.
   bool _alloc_tightly_coupled;
 
+  bool _arguments_validated;
+
   static const TypeFunc* arraycopy_type() {
     const Type** fields = TypeTuple::fields(ParmLimit - TypeFunc::Parms);
     fields[Src]       = TypeInstPtr::BOTTOM;
@@ -1118,6 +1120,13 @@
 
   ArrayCopyNode(Compile* C, bool alloc_tightly_coupled);
 
+  int get_count(PhaseGVN *phase) const;
+  static const TypePtr* get_address_type(PhaseGVN *phase, Node* n);
+
+  Node* try_clone_instance(PhaseGVN *phase, bool can_reshape, int count);
+  bool finish_transform(PhaseGVN *phase, bool can_reshape,
+                        Node* ctl, Node *mem);
+
 public:
 
   enum {
@@ -1143,23 +1152,23 @@
 
   void connect_outputs(GraphKit* kit);
 
-  bool is_arraycopy()         const { return _kind == ArrayCopy; }
-  bool is_arraycopy_notest()  const { return _kind == ArrayCopyNoTest; }
-  bool is_clonebasic()        const { return _kind == CloneBasic; }
-  bool is_cloneoop()          const { return _kind == CloneOop; }
-  bool is_copyof()            const { return _kind == CopyOf; }
-  bool is_copyofrange()       const { return _kind == CopyOfRange; }
+  bool is_arraycopy()             const  { assert(_kind != None, "should bet set"); return _kind == ArrayCopy; }
+  bool is_arraycopy_validated()   const  { assert(_kind != None, "should bet set"); return _kind == ArrayCopy && _arguments_validated; }
+  bool is_clonebasic()            const  { assert(_kind != None, "should bet set"); return _kind == CloneBasic; }
+  bool is_cloneoop()              const  { assert(_kind != None, "should bet set"); return _kind == CloneOop; }
+  bool is_copyof()                const  { assert(_kind != None, "should bet set"); return _kind == CopyOf; }
+  bool is_copyofrange()           const  { assert(_kind != None, "should bet set"); return _kind == CopyOfRange; }
 
-  void set_arraycopy()         { _kind = ArrayCopy; }
-  void set_arraycopy_notest()  { _kind = ArrayCopyNoTest; }
-  void set_clonebasic()        { _kind = CloneBasic; }
-  void set_cloneoop()          { _kind = CloneOop; }
-  void set_copyof()            { _kind = CopyOf; }
-  void set_copyofrange()       { _kind = CopyOfRange; }
+  void set_arraycopy(bool validated)   { assert(_kind == None, "shouldn't bet set yet"); _kind = ArrayCopy; _arguments_validated = validated; }
+  void set_clonebasic()                { assert(_kind == None, "shouldn't bet set yet"); _kind = CloneBasic; }
+  void set_cloneoop()                  { assert(_kind == None, "shouldn't bet set yet"); _kind = CloneOop; }
+  void set_copyof()                    { assert(_kind == None, "shouldn't bet set yet"); _kind = CopyOf; _arguments_validated = false; }
+  void set_copyofrange()               { assert(_kind == None, "shouldn't bet set yet"); _kind = CopyOfRange; _arguments_validated = false; }
 
   virtual int Opcode() const;
   virtual uint size_of() const; // Size is bigger
   virtual bool guaranteed_safepoint()  { return false; }
+  virtual Node *Ideal(PhaseGVN *phase, bool can_reshape);
 
   bool is_alloc_tightly_coupled() const { return _alloc_tightly_coupled; }
 
--- a/hotspot/src/share/vm/opto/library_call.cpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/opto/library_call.cpp	Tue Dec 16 13:49:36 2014 +0100
@@ -4475,8 +4475,11 @@
   ArrayCopyNode* ac = ArrayCopyNode::make(this, false, src, NULL, dest, NULL, countx, false);
   ac->set_clonebasic();
   Node* n = _gvn.transform(ac);
-  assert(n == ac, "cannot disappear");
-  set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), raw_adr_type);
+  if (n == ac) {
+    set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), raw_adr_type);
+  } else {
+    set_all_memory(n);
+  }
 
   // If necessary, emit some card marks afterwards.  (Non-arrays only.)
   if (card_mark) {
@@ -4541,6 +4544,26 @@
     Node* obj = null_check_receiver();
     if (stopped())  return true;
 
+    const TypeOopPtr* obj_type = _gvn.type(obj)->is_oopptr();
+
+    // If we are going to clone an instance, we need its exact type to
+    // know the number and types of fields to convert the clone to
+    // loads/stores. Maybe a speculative type can help us.
+    if (!obj_type->klass_is_exact() &&
+        obj_type->speculative_type() != NULL &&
+        obj_type->speculative_type()->is_instance_klass()) {
+      ciInstanceKlass* spec_ik = obj_type->speculative_type()->as_instance_klass();
+      if (spec_ik->nof_nonstatic_fields() <= ArrayCopyLoadStoreMaxElem &&
+          !spec_ik->has_injected_fields()) {
+        ciKlass* k = obj_type->klass();
+        if (!k->is_instance_klass() ||
+            k->as_instance_klass()->is_interface() ||
+            k->as_instance_klass()->has_subklass()) {
+          obj = maybe_cast_profiled_obj(obj, obj_type->speculative_type(), false);
+        }
+      }
+    }
+
     Node* obj_klass = load_object_klass(obj);
     const TypeKlassPtr* tklass = _gvn.type(obj_klass)->isa_klassptr();
     const TypeOopPtr*   toop   = ((tklass != NULL)
@@ -4743,7 +4766,7 @@
     sfpt->set_memory(map()->memory());
   }
 
-  bool notest = false;
+  bool validated = false;
 
   const Type* src_type  = _gvn.type(src);
   const Type* dest_type = _gvn.type(dest);
@@ -4847,7 +4870,7 @@
 
   if (!too_many_traps(Deoptimization::Reason_intrinsic) && !src->is_top() && !dest->is_top()) {
     // validate arguments: enables transformation the ArrayCopyNode
-    notest = true;
+    validated = true;
 
     RegionNode* slow_region = new RegionNode(1);
     record_for_igvn(slow_region);
@@ -4922,13 +4945,15 @@
                                           load_object_klass(src), load_object_klass(dest),
                                           load_array_length(src), load_array_length(dest));
 
-  if (notest) {
-    ac->set_arraycopy_notest();
-  }
+  ac->set_arraycopy(validated);
 
   Node* n = _gvn.transform(ac);
-  assert(n == ac, "cannot disappear");
-  ac->connect_outputs(this);
+  if (n == ac) {
+    ac->connect_outputs(this);
+  } else {
+    assert(validated, "shouldn't transform if all arguments not validated");
+    set_all_memory(n);
+  }
 
   return true;
 }
--- a/hotspot/src/share/vm/opto/macroArrayCopy.cpp	Mon Dec 22 11:21:20 2014 +0100
+++ b/hotspot/src/share/vm/opto/macroArrayCopy.cpp	Tue Dec 16 13:49:36 2014 +0100
@@ -519,7 +519,8 @@
     // Test S[] against D[], not S against D, because (probably)
     // the secondary supertype cache is less busy for S[] than S.
     // This usually only matters when D is an interface.
-    Node* not_subtype_ctrl = ac->is_arraycopy_notest() ? top() : Phase::gen_subtype_check(src_klass, dest_klass, ctrl, mem, &_igvn);
+    Node* not_subtype_ctrl = ac->is_arraycopy_validated() ? top() :
+      Phase::gen_subtype_check(src_klass, dest_klass, ctrl, mem, &_igvn);
     // Plug failing path into checked_oop_disjoint_arraycopy
     if (not_subtype_ctrl != top()) {
       Node* local_ctrl = not_subtype_ctrl;
@@ -1109,7 +1110,7 @@
     assert(alloc != NULL, "expect alloc");
   }
 
-  assert(ac->is_arraycopy() || ac->is_arraycopy_notest(), "should be an arraycopy");
+  assert(ac->is_arraycopy() || ac->is_arraycopy_validated(), "should be an arraycopy");
 
   // Compile time checks.  If any of these checks cannot be verified at compile time,
   // we do not make a fast path for this call.  Instead, we let the call remain as it
@@ -1191,7 +1192,7 @@
   RegionNode* slow_region = new RegionNode(1);
   transform_later(slow_region);
 
-  if (!ac->is_arraycopy_notest()) {
+  if (!ac->is_arraycopy_validated()) {
     // (3) operands must not be null
     // We currently perform our null checks with the null_check routine.
     // This means that the null exceptions will be reported in the caller
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestArrayCopyMacro.java	Tue Dec 16 13:49:36 2014 +0100
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ *
+ * 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
+ * @bug 7173584
+ * @summary arraycopy as macro node
+ * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement TestArrayCopyMacro
+ *
+ */
+
+public class TestArrayCopyMacro {
+    static class A {
+    }
+
+    // In its own method so profiling reports both branches taken
+    static Object m2(Object o1, Object o2, int i) {
+        if (i == 4) {
+            return o1;
+        }
+        return o2;
+    }
+
+    static Object m1(A[] src, Object dest) {
+        int i = 1;
+
+        // won't be optimized out until after parsing
+        for (; i < 3; i *= 4) {
+        }
+        dest = m2(new A[10], dest, i);
+
+        // dest is new array here but C2 picks the "disjoint" stub
+        // only if stub to call is decided after parsing
+        System.arraycopy(src, 0, dest, 0, 10);
+        return dest;
+    }
+
+    public static void main(String[] args) {
+        A[] array_src = new A[10];
+
+        for (int i = 0; i < array_src.length; i++) {
+            array_src[i] = new A();
+        }
+
+        for (int i = 0; i < 20000; i++) {
+            m2(null, null, 0);
+        }
+
+        for (int i = 0; i < 20000; i++) {
+            Object[] array_dest = (Object[])m1(array_src, null);
+
+            for (int j = 0; j < array_src.length; j++) {
+                if (array_dest[j] != array_src[j]) {
+                    throw new RuntimeException("copy failed at index " + j + " src = " + array_src[j] + " dest = " + array_dest[j]);
+                }
+            }
+        }
+    }
+}
--- a/hotspot/test/compiler/arraycopy/TestArrayOfNoTypeCheck.java	Mon Dec 22 11:21:20 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * Copyright (c) 2014, 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
- * @bug 8055910
- * @summary Arrays.copyOf doesn't perform subtype check
- * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement TestArrayOfNoTypeCheck
- *
- */
-
-import java.util.Arrays;
-
-public class TestArrayOfNoTypeCheck {
-
-    static class A {
-    }
-
-    static class B extends A {
-    }
-
-    static B[] test(A[] arr) {
-        return Arrays.copyOf(arr, 10, B[].class);
-    }
-
-    static public void main(String[] args) {
-        A[] arr = new A[20];
-        for (int i = 0; i < 20000; i++) {
-            test(arr);
-        }
-        A[] arr2 = new A[20];
-        arr2[0] = new A();
-        boolean exception = false;
-        try {
-            test(arr2);
-        } catch (ArrayStoreException ase) {
-            exception = true;
-        }
-        if (!exception) {
-            throw new RuntimeException("TEST FAILED: ArrayStoreException not thrown");
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestArraysCopyOfNoTypeCheck.java	Tue Dec 16 13:49:36 2014 +0100
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014, 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
+ * @bug 8055910
+ * @summary Arrays.copyOf doesn't perform subtype check
+ * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement TestArraysCopyOfNoTypeCheck
+ *
+ */
+
+import java.util.Arrays;
+
+public class TestArraysCopyOfNoTypeCheck {
+
+    static class A {
+    }
+
+    static class B extends A {
+    }
+
+    static B[] test(A[] arr) {
+        return Arrays.copyOf(arr, 10, B[].class);
+    }
+
+    static public void main(String[] args) {
+        A[] arr = new A[20];
+        for (int i = 0; i < 20000; i++) {
+            test(arr);
+        }
+        A[] arr2 = new A[20];
+        arr2[0] = new A();
+        boolean exception = false;
+        try {
+            test(arr2);
+        } catch (ArrayStoreException ase) {
+            exception = true;
+        }
+        if (!exception) {
+            throw new RuntimeException("TEST FAILED: ArrayStoreException not thrown");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java	Tue Dec 16 13:49:36 2014 +0100
@@ -0,0 +1,342 @@
+/*
+ * 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.
+ *
+ * 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
+ * @bug 6700100
+ * @summary small instance clone as loads/stores
+ * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=dontinline,TestInstanceCloneAsLoadsStores::m* TestInstanceCloneAsLoadsStores
+ * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=dontinline,TestInstanceCloneAsLoadsStores::m* -XX:+IgnoreUnrecognizedVMOptions -XX:+StressArrayCopyMacroNode TestInstanceCloneAsLoadsStores
+ *
+ */
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class TestInstanceCloneAsLoadsStores {
+    static class Base implements Cloneable {
+        void initialize(Class c, int i) {
+            for (Field f : c.getDeclaredFields()) {
+                setVal(f, i);
+                i++;
+            }
+            if (c != Base.class) {
+                initialize(c.getSuperclass(), i);
+            }
+        }
+
+        Base() {
+            initialize(getClass(), 0);
+        }
+
+        void setVal(Field f, int i) {
+            try {
+                if (f.getType() == int.class) {
+                    f.setInt(this, i);
+                    return;
+                } else if (f.getType() == short.class) {
+                    f.setShort(this, (short)i);
+                    return;
+                } else if (f.getType() == byte.class) {
+                    f.setByte(this, (byte)i);
+                    return;
+                } else if (f.getType() == long.class) {
+                    f.setLong(this, i);
+                    return;
+                }
+            } catch(IllegalAccessException iae) {
+                throw new RuntimeException("Getting fields failed");
+            }
+            throw new RuntimeException("unexpected field type");
+        }
+
+        int getVal(Field f) {
+            try {
+                if (f.getType() == int.class) {
+                    return f.getInt(this);
+                } else if (f.getType() == short.class) {
+                    return (int)f.getShort(this);
+                } else if (f.getType() == byte.class) {
+                    return (int)f.getByte(this);
+                } else if (f.getType() == long.class) {
+                    return (int)f.getLong(this);
+                }
+            } catch(IllegalAccessException iae) {
+                throw new RuntimeException("Setting fields failed");
+            }
+            throw new RuntimeException("unexpected field type");
+        }
+
+        boolean fields_equal(Class c, Base o) {
+            for (Field f : c.getDeclaredFields()) {
+                if (getVal(f) != o.getVal(f)) {
+                    return false;
+                }
+            }
+            if (c != Base.class) {
+                return fields_equal(c.getSuperclass(), o);
+            }
+            return true;
+        }
+
+        public boolean equals(Object obj) {
+            return fields_equal(getClass(), (Base)obj);
+        }
+
+        String print_fields(Class c, String s) {
+            for (Field f : c.getDeclaredFields()) {
+                if (s != "") {
+                    s += "\n";
+                }
+                s = s + f + " = " + getVal(f);
+            }
+            if (c != Base.class) {
+                return print_fields(c.getSuperclass(), s);
+            }
+            return s;
+        }
+
+        public String toString() {
+            return print_fields(getClass(), "");
+        }
+
+        int fields_sum(Class c, int s) {
+            for (Field f : c.getDeclaredFields()) {
+                s += getVal(f);
+            }
+            if (c != Base.class) {
+                return fields_sum(c.getSuperclass(), s);
+            }
+            return s;
+        }
+
+        public int sum() {
+            return fields_sum(getClass(), 0);
+        }
+
+    }
+
+    static class A extends Base {
+        int i1;
+        int i2;
+        int i3;
+        int i4;
+        int i5;
+
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    static class B extends A {
+        int i6;
+    }
+
+    static final class D extends Base {
+        byte  i1;
+        short i2;
+        long  i3;
+        int   i4;
+        int   i5;
+
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    static final class E extends Base {
+        int i1;
+        int i2;
+        int i3;
+        int i4;
+        int i5;
+        int i6;
+        int i7;
+        int i8;
+        int i9;
+
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    static final class F extends Base {
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    static class G extends Base {
+        int i1;
+        int i2;
+        int i3;
+
+        public Object myclone() throws CloneNotSupportedException {
+            return clone();
+        }
+    }
+
+    static class H extends G {
+        int i4;
+        int i5;
+
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    static class J extends Base  {
+        int i1;
+        int i2;
+        int i3;
+
+        public Object myclone() throws CloneNotSupportedException {
+            return clone();
+        }
+    }
+
+    static class K extends J {
+        int i4;
+        int i5;
+    }
+
+    // Should be compiled as loads/stores
+    static Object m1(D src) throws CloneNotSupportedException {
+        return src.clone();
+    }
+
+    // Should be compiled as adds of src (dest allocation eliminated)
+    static int m2(D src) throws CloneNotSupportedException {
+        D dest = (D)src.clone();
+        return dest.i1 + dest.i2 + ((int)dest.i3) + dest.i4 + dest.i5;
+    }
+
+    // Should be compiled as arraycopy stub call (object too large)
+    static int m3(E src) throws CloneNotSupportedException {
+        E dest = (E)src.clone();
+        return dest.i1 + dest.i2 + dest.i3 + dest.i4 + dest.i5 +
+            dest.i6 + dest.i7 + dest.i8 + dest.i9;
+    }
+
+    // Need profiling on src's type to be able to know number of
+    // fields. Cannot clone as loads/stores if compile doesn't use it.
+    static Object m4(A src) throws CloneNotSupportedException {
+        return src.clone();
+    }
+
+    // Same as above but should optimize out dest allocation
+    static int m5(A src) throws CloneNotSupportedException {
+        A dest = (A)src.clone();
+        return dest.i1 + dest.i2 + dest.i3 + dest.i4 + dest.i5;
+    }
+
+    // Check that if we have no fields to clone we do fine
+    static Object m6(F src) throws CloneNotSupportedException {
+        return src.clone();
+    }
+
+    // With virtual call to clone: clone inlined from profling which
+    // gives us exact type of src so we can clone it with
+    // loads/stores.
+    static G m7(G src) throws CloneNotSupportedException {
+        return (G)src.myclone();
+    }
+
+    // Virtual call to clone but single target: exact type unknown,
+    // clone intrinsic uses profiling to determine exact type and
+    // clone with loads/stores.
+    static J m8(J src) throws CloneNotSupportedException {
+        return (J)src.myclone();
+    }
+
+    final HashMap<String,Method> tests = new HashMap<>();
+    {
+        for (Method m : this.getClass().getDeclaredMethods()) {
+            if (m.getName().matches("m[0-9]+")) {
+                assert(Modifier.isStatic(m.getModifiers())) : m;
+                tests.put(m.getName(), m);
+            }
+        }
+    }
+
+    boolean success = true;
+
+    void doTest(Base src, String name) throws Exception {
+        Method m = tests.get(name);
+
+        for (int i = 0; i < 20000; i++) {
+            boolean failure = false;
+            Base res = null;
+            int s = 0;
+            if (m.getReturnType().isPrimitive()) {
+                s = (int)m.invoke(null, src);
+                failure = (s != src.sum());
+            } else {
+                res = (Base)m.invoke(null, src);
+                failure = !res.equals(src);
+            }
+            if (failure) {
+                System.out.println("Test " + name + " failed");
+                System.out.println("source: ");
+                System.out.println(src);
+                System.out.println("result: ");
+                if (m.getReturnType().isPrimitive()) {
+                    System.out.println(s);
+                } else {
+                    System.out.println(res);
+                }
+                success = false;
+                break;
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        TestInstanceCloneAsLoadsStores test = new TestInstanceCloneAsLoadsStores();
+
+        A a = new A();
+        B b = new B();
+        D d = new D();
+        E e = new E();
+        F f = new F();
+        G g = new G();
+        H h = new H();
+        J j = new J();
+        K k = new K();
+
+        test.doTest(d, "m1");
+        test.doTest(d, "m2");
+        test.doTest(e, "m3");
+        test.doTest(a, "m4");
+        test.doTest(a, "m5");
+        test.doTest(f, "m6");
+        test.doTest(g, "m7");
+        test.doTest(k, "m8");
+
+        if (!test.success) {
+            throw new RuntimeException("some tests failed");
+        }
+
+    }
+}