--- /dev/null 2014-10-17 05:25:30.601014041 +0200 +++ new/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java 2014-12-16 17:45:47.073231158 +0100 @@ -0,0 +1,342 @@ +/* + * 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 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 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"); + } + + } +} --- /dev/null 2014-10-17 05:25:30.601014041 +0200 +++ new/test/compiler/arraycopy/TestArrayCopyMacro.java 2014-12-16 17:45:47.050081620 +0100 @@ -0,0 +1,79 @@ +/* + * 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 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]); + } + } + } + } +} --- old/src/share/vm/opto/macroArrayCopy.cpp 2014-12-16 17:45:47.330552221 +0100 +++ new/src/share/vm/opto/macroArrayCopy.cpp 2014-12-16 17:45:47.131163116 +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 --- old/test/compiler/arraycopy/TestArrayOfNoTypeCheck.java 2014-12-16 17:45:47.355336792 +0100 +++ /dev/null 2014-10-17 05:25:30.601014041 +0200 @@ -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 2014-10-17 05:25:30.601014041 +0200 +++ new/test/compiler/arraycopy/TestArraysCopyOfNoTypeCheck.java 2014-12-16 17:45:47.031490435 +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"); + } + } +} --- old/src/share/vm/ci/ciInstanceKlass.cpp 2014-12-16 17:45:47.356766346 +0100 +++ new/src/share/vm/ci/ciInstanceKlass.cpp 2014-12-16 17:45:47.097804497 +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 // --- old/src/share/vm/ci/ciInstanceKlass.hpp 2014-12-16 17:45:47.418303572 +0100 +++ new/src/share/vm/ci/ciInstanceKlass.hpp 2014-12-16 17:45:47.153306229 +0100 @@ -64,13 +64,17 @@ ciConstantPoolCache* _field_cache; // cached map index->field GrowableArray* _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. // A ciInstanceKlass that's not itself: one implementor. // 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, ""); --- old/src/share/vm/oops/fieldStreams.hpp 2014-12-16 17:45:47.441139901 +0100 +++ new/src/share/vm/oops/fieldStreams.hpp 2014-12-16 17:45:47.197094545 +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; --- old/src/share/vm/opto/callnode.cpp 2014-12-16 17:45:47.461258375 +0100 +++ new/src/share/vm/opto/callnode.cpp 2014-12-16 17:45:47.173777704 +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; +} --- old/src/share/vm/opto/library_call.cpp 2014-12-16 17:45:47.504103456 +0100 +++ new/src/share/vm/opto/library_call.cpp 2014-12-16 17:45:47.218649225 +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; } --- old/src/share/vm/opto/callnode.hpp 2014-12-16 17:45:47.531274581 +0100 +++ new/src/share/vm/opto/callnode.hpp 2014-12-16 17:45:47.271887603 +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; } - - 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; } + 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(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; } --- old/src/share/vm/opto/c2_globals.hpp 2014-12-16 17:45:47.581409707 +0100 +++ new/src/share/vm/opto/c2_globals.hpp 2014-12-16 17:45:47.241642151 +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)