--- old/src/share/vm/oops/fieldStreams.hpp 2014-10-08 14:26:18.910215462 +0200 +++ new/src/share/vm/oops/fieldStreams.hpp 2014-10-08 14:26:17.595533063 +0200 @@ -64,6 +64,7 @@ length --; skipped_generic_signature_slots ++; } + num_fields ++; } /* Scan from the current _index. */ for (int i = _index; i*FieldInfo::field_slots < length; i++) { --- old/src/share/vm/ci/ciInstanceKlass.cpp 2014-10-08 14:26:18.937132379 +0200 +++ new/src/share/vm/ci/ciInstanceKlass.cpp 2014-10-08 14:26:17.725947480 +0200 @@ -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/opto/callnode.cpp 2014-10-08 14:26:19.322326125 +0200 +++ new/src/share/vm/opto/callnode.cpp 2014-10-08 14:26:17.750628587 +0200 @@ -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" @@ -1817,7 +1818,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); @@ -1869,3 +1873,133 @@ 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(); + if (ik->has_injected_fields() || (!inst_src->klass_is_exact() && (ik->is_interface() || ik->has_subklass()))) { + 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/callnode.hpp 2014-10-08 14:26:19.321933901 +0200 +++ new/src/share/vm/opto/callnode.hpp 2014-10-08 14:26:17.627855428 +0200 @@ -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/ci/ciInstanceKlass.hpp 2014-10-08 14:26:19.437908320 +0200 +++ new/src/share/vm/ci/ciInstanceKlass.hpp 2014-10-08 14:26:17.667187589 +0200 @@ -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/opto/c2_globals.hpp 2014-10-08 14:26:19.485720779 +0200 +++ new/src/share/vm/opto/c2_globals.hpp 2014-10-08 14:26:17.774752730 +0200 @@ -660,6 +660,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) --- old/src/share/vm/opto/macroArrayCopy.cpp 2014-10-08 14:26:19.555766694 +0200 +++ new/src/share/vm/opto/macroArrayCopy.cpp 2014-10-08 14:26:17.569381753 +0200 @@ -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-10-08 14:26:19.612511267 +0200 +++ /dev/null 2014-06-17 14:40:17.837204769 +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-06-17 14:40:17.837204769 +0200 +++ new/test/compiler/arraycopy/TestArraysCopyOfNoTypeCheck.java 2014-10-08 14:26:17.527042249 +0200 @@ -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/opto/library_call.cpp 2014-10-08 14:26:19.749862744 +0200 +++ new/src/share/vm/opto/library_call.cpp 2014-10-08 14:26:17.692073747 +0200 @@ -4389,8 +4389,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) { @@ -4455,6 +4458,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) @@ -4611,6 +4634,10 @@ Node* dest_offset = argument(3); // type: int Node* length = argument(4); // type: int + // Check for allocation before we add nodes that would confuse + // tightly_coupled_allocation() + AllocateArrayNode* alloc = tightly_coupled_allocation(dest, NULL); + // The following tests must be performed // (1) src and dest are arrays. // (2) src and dest arrays must have elements of the same BasicType @@ -4627,7 +4654,7 @@ src = null_check(src, T_ARRAY); dest = null_check(dest, T_ARRAY); - bool notest = false; + bool validated = false; const Type* src_type = _gvn.type(src); const Type* dest_type = _gvn.type(dest); @@ -4731,7 +4758,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); @@ -4784,7 +4811,6 @@ return true; } - AllocateArrayNode* alloc = tightly_coupled_allocation(dest, NULL); ArrayCopyNode* ac = ArrayCopyNode::make(this, true, src, src_offset, dest, dest_offset, length, alloc != NULL, // Create LoadRange and LoadKlass nodes for use during macro expansion here // so the compiler has a chance to eliminate them: during macro expansion, @@ -4792,13 +4818,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; }