diff --git a/hotspot/src/share/vm/opto/arraycopynode.cpp b/hotspot/src/share/vm/opto/arraycopynode.cpp index 5542d7fc822e2..a9e673cff584d 100644 --- a/hotspot/src/share/vm/opto/arraycopynode.cpp +++ b/hotspot/src/share/vm/opto/arraycopynode.cpp @@ -30,7 +30,9 @@ ArrayCopyNode::ArrayCopyNode(Compile* C, bool alloc_tightly_coupled) : CallNode(arraycopy_type(), NULL, TypeRawPtr::BOTTOM), _alloc_tightly_coupled(alloc_tightly_coupled), _kind(None), - _arguments_validated(false) { + _arguments_validated(false), + _src_type(TypeOopPtr::BOTTOM), + _dest_type(TypeOopPtr::BOTTOM) { init_class_id(Class_ArrayCopy); init_flags(Flag_is_macro); C->add_macro_node(this); @@ -595,3 +597,17 @@ Node *ArrayCopyNode::Ideal(PhaseGVN *phase, bool can_reshape) { return mem; } + +bool ArrayCopyNode::may_modify(const TypeOopPtr *t_oop, PhaseTransform *phase) { + const TypeOopPtr* dest_t = phase->type(in(ArrayCopyNode::Dest))->is_oopptr(); + assert(!dest_t->is_known_instance() || _dest_type->is_known_instance(), "result of EA not recorded"); + const TypeOopPtr* src_t = phase->type(in(ArrayCopyNode::Src))->is_oopptr(); + assert(!src_t->is_known_instance() || _src_type->is_known_instance(), "result of EA not recorded"); + + if (_dest_type != TypeOopPtr::BOTTOM || t_oop->is_known_instance()) { + assert(_dest_type == TypeOopPtr::BOTTOM || _dest_type->is_known_instance(), "result of EA is known instance"); + return t_oop->instance_id() == _dest_type->instance_id(); + } + + return CallNode::may_modify_arraycopy_helper(dest_t, t_oop, phase); +} diff --git a/hotspot/src/share/vm/opto/arraycopynode.hpp b/hotspot/src/share/vm/opto/arraycopynode.hpp index e61f1ba2c4b60..967ae556d4ea0 100644 --- a/hotspot/src/share/vm/opto/arraycopynode.hpp +++ b/hotspot/src/share/vm/opto/arraycopynode.hpp @@ -124,6 +124,10 @@ class ArrayCopyNode : public CallNode { ParmLimit }; + // Results from escape analysis for non escaping inputs + const TypeOopPtr* _src_type; + const TypeOopPtr* _dest_type; + static ArrayCopyNode* make(GraphKit* kit, bool may_throw, Node* src, Node* src_offset, Node* dest, Node* dest_offset, @@ -154,11 +158,12 @@ class ArrayCopyNode : public CallNode { virtual bool guaranteed_safepoint() { return false; } virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); + virtual bool may_modify(const TypeOopPtr *t_oop, PhaseTransform *phase); + bool is_alloc_tightly_coupled() const { return _alloc_tightly_coupled; } #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif }; - #endif // SHARE_VM_OPTO_ARRAYCOPYNODE_HPP diff --git a/hotspot/src/share/vm/opto/callnode.cpp b/hotspot/src/share/vm/opto/callnode.cpp index 46975b51e10a8..36927498fffc9 100644 --- a/hotspot/src/share/vm/opto/callnode.cpp +++ b/hotspot/src/share/vm/opto/callnode.cpp @@ -797,11 +797,12 @@ Node *CallNode::result_cast() { } cast = use; } else if (!use->is_Initialize() && - !use->is_AddP()) { + !use->is_AddP() && + use->Opcode() != Op_MemBarStoreStore) { // Expected uses are restricted to a CheckCastPP, an Initialize - // node, and AddP nodes. If we encounter any other use (a Phi - // node can be seen in rare cases) return this to prevent - // incorrect optimizations. + // node, a MemBarStoreStore (clone) and AddP nodes. If we + // encounter any other use (a Phi node can be seen in rare + // cases) return this to prevent incorrect optimizations. return this; } } @@ -1006,6 +1007,14 @@ void CallRuntimeNode::calling_convention( BasicType* sig_bt, VMRegPair *parm_reg //============================================================================= +bool CallLeafNode::is_call_to_arraycopystub() const { + if (_name != NULL && strstr(_name, "arraycopy") != 0) { + return true; + } + return false; +} + + #ifndef PRODUCT void CallLeafNode::dump_spec(outputStream *st) const { st->print("# "); @@ -1875,3 +1884,72 @@ void AbstractLockNode::log_lock_optimization(Compile *C, const char * tag) cons log->tail(tag); } } + +bool CallNode::may_modify_arraycopy_helper(const TypeOopPtr* dest_t, const TypeOopPtr *t_oop, PhaseTransform *phase) { + if (dest_t->is_known_instance() && t_oop->is_known_instance()) { + return dest_t->instance_id() == t_oop->instance_id(); + } + + if (dest_t->isa_instptr() && !dest_t->klass()->equals(phase->C->env()->Object_klass())) { + // clone + if (t_oop->isa_aryptr()) { + return false; + } + if (!t_oop->isa_instptr()) { + return true; + } + if (dest_t->klass()->is_subtype_of(t_oop->klass()) || t_oop->klass()->is_subtype_of(dest_t->klass())) { + return true; + } + // unrelated + return false; + } + + if (dest_t->isa_aryptr()) { + // arraycopy or array clone + if (t_oop->isa_instptr()) { + return false; + } + if (!t_oop->isa_aryptr()) { + return true; + } + + const Type* elem = dest_t->is_aryptr()->elem(); + if (elem == Type::BOTTOM) { + // An array but we don't know what elements are + return true; + } + + dest_t = dest_t->add_offset(Type::OffsetBot)->is_oopptr(); + uint dest_alias = phase->C->get_alias_index(dest_t); + uint t_oop_alias = phase->C->get_alias_index(t_oop); + + return dest_alias == t_oop_alias; + } + + return true; +} + +bool CallLeafNode::may_modify(const TypeOopPtr *t_oop, PhaseTransform *phase) { + if (is_call_to_arraycopystub()) { + const TypeTuple* args = _tf->domain(); + Node* dest = NULL; + // Stubs that can be called once an ArrayCopyNode is expanded have + // different signatures. Look for the second pointer argument, + // that is the destination of the copy. + for (uint i = TypeFunc::Parms, j = 0; i < args->cnt(); i++) { + if (args->field_at(i)->isa_ptr()) { + j++; + if (j == 2) { + dest = in(i); + break; + } + } + } + if (may_modify_arraycopy_helper(phase->type(dest)->is_oopptr(), t_oop, phase)) { + return true; + } + return false; + } + return CallNode::may_modify(t_oop, phase); +} diff --git a/hotspot/src/share/vm/opto/callnode.hpp b/hotspot/src/share/vm/opto/callnode.hpp index 26f5c6ba705f6..4eaaaebe0a6e5 100644 --- a/hotspot/src/share/vm/opto/callnode.hpp +++ b/hotspot/src/share/vm/opto/callnode.hpp @@ -556,6 +556,10 @@ class CallGenerator; // contain the functionality of a full scope chain of debug nodes. class CallNode : public SafePointNode { friend class VMStructs; + +protected: + bool may_modify_arraycopy_helper(const TypeOopPtr* dest_t, const TypeOopPtr *t_oop, PhaseTransform *phase); + public: const TypeFunc *_tf; // Function type address _entry_point; // Address of method being called @@ -781,6 +785,8 @@ class CallLeafNode : public CallRuntimeNode { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + bool is_call_to_arraycopystub() const; + virtual bool may_modify(const TypeOopPtr *t_oop, PhaseTransform *phase); }; //------------------------------CallLeafNoFPNode------------------------------- @@ -1082,5 +1088,4 @@ class UnlockNode : public AbstractLockNode { JVMState* dbg_jvms() const { return NULL; } #endif }; - #endif // SHARE_VM_OPTO_CALLNODE_HPP diff --git a/hotspot/src/share/vm/opto/escape.cpp b/hotspot/src/share/vm/opto/escape.cpp index eec4536a86481..67b49117aafc1 100644 --- a/hotspot/src/share/vm/opto/escape.cpp +++ b/hotspot/src/share/vm/opto/escape.cpp @@ -28,6 +28,7 @@ #include "libadt/vectset.hpp" #include "memory/allocation.hpp" #include "opto/c2compiler.hpp" +#include "opto/arraycopynode.hpp" #include "opto/callnode.hpp" #include "opto/cfgnode.hpp" #include "opto/compile.hpp" @@ -113,6 +114,7 @@ bool ConnectionGraph::compute_escape() { GrowableArray alloc_worklist; GrowableArray ptr_cmp_worklist; GrowableArray storestore_worklist; + GrowableArray arraycopy_worklist; GrowableArray ptnodes_worklist; GrowableArray java_objects_worklist; GrowableArray non_escaped_worklist; @@ -173,6 +175,10 @@ bool ConnectionGraph::compute_escape() { // Collect address nodes for graph verification. addp_worklist.append(n); #endif + } else if (n->is_ArrayCopy()) { + // Keep a list of ArrayCopy nodes so if one of its input is non + // escaping, we can record a unique type + arraycopy_worklist.append(n->as_ArrayCopy()); } for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { Node* m = n->fast_out(i); // Get user @@ -289,7 +295,7 @@ bool ConnectionGraph::compute_escape() { C->AliasLevel() >= 3 && EliminateAllocations) { // Now use the escape information to create unique types for // scalar replaceable objects. - split_unique_types(alloc_worklist); + split_unique_types(alloc_worklist, arraycopy_worklist); if (C->failing()) return false; C->print_method(PHASE_AFTER_EA, 2); @@ -333,7 +339,7 @@ void ConnectionGraph::add_objload_to_connection_graph(Node *n, Unique_Node_List // Populate Connection Graph with PointsTo nodes and create simple // connection graph edges. void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *delayed_worklist) { - assert(!_verify, "this method sould not be called for verification"); + assert(!_verify, "this method should not be called for verification"); PhaseGVN* igvn = _igvn; uint n_idx = n->_idx; PointsToNode* n_ptn = ptnode_adr(n_idx); @@ -901,8 +907,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { // are still a few direct calls to the copy subroutines (See // PhaseStringOpts::copy_string()) is_arraycopy = (call->Opcode() == Op_ArrayCopy) || - (call->as_CallLeaf()->_name != NULL && - strstr(call->as_CallLeaf()->_name, "arraycopy") != 0); + call->as_CallLeaf()->is_call_to_arraycopystub(); // fall through case Op_CallLeaf: { // Stub calls, objects do not escape but they are not scale replaceable. @@ -980,7 +985,17 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { !arg_is_arraycopy_dest) { continue; } - set_escape_state(arg_ptn, PointsToNode::ArgEscape); + PointsToNode::EscapeState es = PointsToNode::ArgEscape; + if (call->is_ArrayCopy()) { + ArrayCopyNode* ac = call->as_ArrayCopy(); + if (ac->is_clonebasic() || + ac->is_arraycopy_validated() || + ac->is_copyof_validated() || + ac->is_copyofrange_validated()) { + es = PointsToNode::NoEscape; + } + } + set_escape_state(arg_ptn, es); if (arg_is_arraycopy_dest) { Node* src = call->in(TypeFunc::Parms); if (src->is_AddP()) { @@ -994,7 +1009,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { // as base since objects escape states are not related. // Only escape state of destination object's fields affects // escape state of fields in source object. - add_arraycopy(call, PointsToNode::ArgEscape, src_ptn, arg_ptn); + add_arraycopy(call, es, src_ptn, arg_ptn); } } } @@ -1272,12 +1287,12 @@ bool ConnectionGraph::find_non_escaped_objects(GrowableArray& ptn if ((e->escape_state() < field_es) && e->is_Field() && ptn->is_JavaObject() && e->as_Field()->is_oop()) { - // Change escape state of referenced fileds. + // Change escape state of referenced fields. set_escape_state(e, field_es); - es_changed = true;; + es_changed = true; } else if (e->escape_state() < es) { set_escape_state(e, es); - es_changed = true;; + es_changed = true; } if (es_changed) { escape_worklist.push(e); @@ -1389,7 +1404,7 @@ void ConnectionGraph::add_field_uses_to_worklist(FieldNode* field) { for (UseIterator k(arycp); k.has_next(); k.next()) { PointsToNode* abase = k.get(); if (abase->arraycopy_dst() && abase != base) { - // Look for the same arracopy reference. + // Look for the same arraycopy reference. add_fields_to_worklist(field, abase); } } @@ -1469,12 +1484,13 @@ int ConnectionGraph::find_init_values(JavaObjectNode* pta, PointsToNode* init_va int new_edges = 0; Node* alloc = pta->ideal_node(); if (init_val == phantom_obj) { - // Do nothing for Allocate nodes since its fields values are "known". - if (alloc->is_Allocate()) + // Do nothing for Allocate nodes since its fields values are + // "known" unless they are initialized by arraycopy/clone. + if (alloc->is_Allocate() && !pta->arraycopy_dst()) return 0; - assert(alloc->as_CallStaticJava(), "sanity"); + assert(pta->arraycopy_dst() || alloc->as_CallStaticJava(), "sanity"); #ifdef ASSERT - if (alloc->as_CallStaticJava()->method() == NULL) { + if (!pta->arraycopy_dst() && alloc->as_CallStaticJava()->method() == NULL) { const char* name = alloc->as_CallStaticJava()->_name; assert(strncmp(name, "_multianewarray", 15) == 0, "sanity"); } @@ -1623,11 +1639,12 @@ void ConnectionGraph::adjust_scalar_replaceable_state(JavaObjectNode* jobj) { // for (UseIterator i(jobj); i.has_next(); i.next()) { PointsToNode* use = i.get(); - assert(!use->is_Arraycopy(), "sanity"); + if (use->is_Arraycopy()) { + continue; + } if (use->is_Field()) { FieldNode* field = use->as_Field(); - assert(field->is_oop() && field->scalar_replaceable() && - field->fields_escape_state() == PointsToNode::NoEscape, "sanity"); + assert(field->is_oop() && field->scalar_replaceable(), "sanity"); if (field->offset() == Type::OffsetBot) { jobj->set_scalar_replaceable(false); return; @@ -1660,6 +1677,10 @@ void ConnectionGraph::adjust_scalar_replaceable_state(JavaObjectNode* jobj) { } for (EdgeIterator j(jobj); j.has_next(); j.next()) { + if (j.get()->is_Arraycopy()) { + continue; + } + // Non-escaping object node should point only to field nodes. FieldNode* field = j.get()->as_Field(); int offset = field->as_Field()->offset(); @@ -2636,6 +2657,7 @@ Node* ConnectionGraph::find_inst_mem(Node *orig_mem, int alias_idx, GrowableArra if (proj_in->is_Allocate() && proj_in->_idx == (uint)toop->instance_id()) { break; // hit one of our sentinels } else if (proj_in->is_Call()) { + // ArrayCopy node processed here as well CallNode *call = proj_in->as_Call(); if (!call->may_modify(toop, igvn)) { result = call->in(TypeFunc::Memory); @@ -2648,6 +2670,15 @@ Node* ConnectionGraph::find_inst_mem(Node *orig_mem, int alias_idx, GrowableArra result = proj_in->in(TypeFunc::Memory); } } else if (proj_in->is_MemBar()) { + if (proj_in->in(TypeFunc::Memory)->is_MergeMem() && + proj_in->in(TypeFunc::Memory)->as_MergeMem()->in(Compile::AliasIdxRaw)->is_Proj() && + proj_in->in(TypeFunc::Memory)->as_MergeMem()->in(Compile::AliasIdxRaw)->in(0)->is_ArrayCopy()) { + // clone + ArrayCopyNode* ac = proj_in->in(TypeFunc::Memory)->as_MergeMem()->in(Compile::AliasIdxRaw)->in(0)->as_ArrayCopy(); + if (ac->may_modify(toop, igvn)) { + break; + } + } result = proj_in->in(TypeFunc::Memory); } } else if (result->is_MergeMem()) { @@ -2724,7 +2755,7 @@ Node* ConnectionGraph::find_inst_mem(Node *orig_mem, int alias_idx, GrowableArra // // Phase 1: Process possible allocations from alloc_worklist. Create instance // types for the CheckCastPP for allocations where possible. -// Propagate the the new types through users as follows: +// Propagate the new types through users as follows: // casts and Phi: push users on alloc_worklist // AddP: cast Base and Address inputs to the instance type // push any AddP users on alloc_worklist and push any memnode @@ -2803,7 +2834,7 @@ Node* ConnectionGraph::find_inst_mem(Node *orig_mem, int alias_idx, GrowableArra // 90 LoadP _ 120 30 ... alias_index=6 // 100 LoadP _ 80 20 ... alias_index=4 // -void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist) { +void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, GrowableArray &arraycopy_worklist) { GrowableArray memnode_worklist; GrowableArray orig_phis; PhaseIterGVN *igvn = _igvn; @@ -2912,9 +2943,12 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist) if (alloc->is_Allocate() && (t->isa_instptr() || t->isa_aryptr())) { // First, put on the worklist all Field edges from Connection Graph - // which is more accurate then putting immediate users from Ideal Graph. + // which is more accurate than putting immediate users from Ideal Graph. for (EdgeIterator e(ptn); e.has_next(); e.next()) { PointsToNode* tgt = e.get(); + if (tgt->is_Arraycopy()) { + continue; + } Node* use = tgt->ideal_node(); assert(tgt->is_Field() && use->is_AddP(), "only AddP nodes are Field edges in CG"); @@ -3068,6 +3102,38 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist) } } + + // Go over all ArrayCopy nodes and if one of the inputs has a unique + // type, record it in the ArrayCopy node so we know what memory this + // node uses/modified. + for (int next = 0; next < arraycopy_worklist.length(); next++) { + ArrayCopyNode* ac = arraycopy_worklist.at(next); + Node* dest = ac->in(ArrayCopyNode::Dest); + if (dest->is_AddP()) { + dest = get_addp_base(dest); + } + JavaObjectNode* jobj = unique_java_object(dest); + if (jobj != NULL) { + Node *base = get_map(jobj->idx()); + if (base != NULL) { + const TypeOopPtr *base_t = _igvn->type(base)->isa_oopptr(); + ac->_dest_type = base_t; + } + } + Node* src = ac->in(ArrayCopyNode::Src); + if (src->is_AddP()) { + src = get_addp_base(src); + } + jobj = unique_java_object(src); + if (jobj != NULL) { + Node* base = get_map(jobj->idx()); + if (base != NULL) { + const TypeOopPtr *base_t = _igvn->type(base)->isa_oopptr(); + ac->_src_type = base_t; + } + } + } + // New alias types were created in split_AddP(). uint new_index_end = (uint) _compile->num_alias_types(); assert(unique_old == _compile->unique(), "there should be no new ideal nodes after Phase 1"); diff --git a/hotspot/src/share/vm/opto/escape.hpp b/hotspot/src/share/vm/opto/escape.hpp index 89a0d44a35895..655ad526a7fd0 100644 --- a/hotspot/src/share/vm/opto/escape.hpp +++ b/hotspot/src/share/vm/opto/escape.hpp @@ -536,7 +536,7 @@ class ConnectionGraph: public ResourceObj { // Propagate unique types created for unescaped allocated objects // through the graph - void split_unique_types(GrowableArray &alloc_worklist); + void split_unique_types(GrowableArray &alloc_worklist, GrowableArray &arraycopy_worklist); // Helper methods for unique types split. bool split_AddP(Node *addp, Node *base); diff --git a/hotspot/src/share/vm/opto/gcm.cpp b/hotspot/src/share/vm/opto/gcm.cpp index 4dab736ac919a..d3a579a1064b1 100644 --- a/hotspot/src/share/vm/opto/gcm.cpp +++ b/hotspot/src/share/vm/opto/gcm.cpp @@ -483,7 +483,7 @@ Block* PhaseCFG::insert_anti_dependences(Block* LCA, Node* load, bool verify) { // Compute the alias index. Loads and stores with different alias indices // do not need anti-dependence edges. - uint load_alias_idx = C->get_alias_index(load->adr_type()); + int load_alias_idx = C->get_alias_index(load->adr_type()); #ifdef ASSERT if (load_alias_idx == Compile::AliasIdxBot && C->AliasLevel() > 0 && (PrintOpto || VerifyAliases || diff --git a/hotspot/src/share/vm/opto/ifnode.cpp b/hotspot/src/share/vm/opto/ifnode.cpp index 126b2f48dc7e0..8132fd7daa9d2 100644 --- a/hotspot/src/share/vm/opto/ifnode.cpp +++ b/hotspot/src/share/vm/opto/ifnode.cpp @@ -973,21 +973,25 @@ void IfNode::improve_address_types(Node* l, Node* r, ProjNode* fail, PhaseIterGV assert(init_n->Opcode() == Op_ConvI2L, "unexpected first node"); Node* new_n = igvn->C->conv_I2X_index(igvn, l, array_size); - for (uint j = 2; j < stack.size(); j++) { - Node* n = stack.node_at(j); - Node* clone = n->clone(); - int rep = clone->replace_edge(init_n, new_n); + // The type of the ConvI2L may be widen and so the new + // ConvI2L may not be better than an existing ConvI2L + if (new_n != init_n) { + for (uint j = 2; j < stack.size(); j++) { + Node* n = stack.node_at(j); + Node* clone = n->clone(); + int rep = clone->replace_edge(init_n, new_n); + assert(rep > 0, "can't find expected node?"); + clone = igvn->transform(clone); + init_n = n; + new_n = clone; + } + igvn->hash_delete(use); + int rep = use->replace_edge(init_n, new_n); assert(rep > 0, "can't find expected node?"); - clone = igvn->transform(clone); - init_n = n; - new_n = clone; - } - igvn->hash_delete(use); - int rep = use->replace_edge(init_n, new_n); - assert(rep > 0, "can't find expected node?"); - igvn->transform(use); - if (init_n->outcnt() == 0) { - igvn->_worklist.push(init_n); + igvn->transform(use); + if (init_n->outcnt() == 0) { + igvn->_worklist.push(init_n); + } } } } else if (use->in(0) == NULL && (igvn->type(use)->isa_long() || diff --git a/hotspot/src/share/vm/opto/macro.cpp b/hotspot/src/share/vm/opto/macro.cpp index 828fc9ffcc4d2..7d80fc41c4fd0 100644 --- a/hotspot/src/share/vm/opto/macro.cpp +++ b/hotspot/src/share/vm/opto/macro.cpp @@ -26,6 +26,7 @@ #include "compiler/compileLog.hpp" #include "libadt/vectset.hpp" #include "opto/addnode.hpp" +#include "opto/arraycopynode.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" @@ -613,7 +614,10 @@ bool PhaseMacroExpand::can_eliminate_allocation(AllocateNode *alloc, GrowableArr for (DUIterator_Fast kmax, k = use->fast_outs(kmax); k < kmax && can_eliminate; k++) { Node* n = use->fast_out(k); - if (!n->is_Store() && n->Opcode() != Op_CastP2X) { + if (!n->is_Store() && n->Opcode() != Op_CastP2X && + !(n->is_ArrayCopy() && + n->as_ArrayCopy()->is_clonebasic() && + n->in(ArrayCopyNode::Dest) == use)) { DEBUG_ONLY(disq_node = n;) if (n->is_Load() || n->is_LoadStore()) { NOT_PRODUCT(fail_eliminate = "Field load";) @@ -623,6 +627,12 @@ bool PhaseMacroExpand::can_eliminate_allocation(AllocateNode *alloc, GrowableArr can_eliminate = false; } } + } else if (use->is_ArrayCopy() && + (use->as_ArrayCopy()->is_arraycopy_validated() || + use->as_ArrayCopy()->is_copyof_validated() || + use->as_ArrayCopy()->is_copyofrange_validated()) && + use->in(ArrayCopyNode::Dest) == res) { + // ok to eliminate } else if (use->is_SafePoint()) { SafePointNode* sfpt = use->as_SafePoint(); if (sfpt->is_Call() && sfpt->as_Call()->has_non_debug_use(res)) { @@ -887,11 +897,49 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { } #endif _igvn.replace_node(n, n->in(MemNode::Memory)); + } else if (n->is_ArrayCopy()) { + // Disconnect ArrayCopy node + ArrayCopyNode* ac = n->as_ArrayCopy(); + assert(ac->is_clonebasic(), "unexpected array copy kind"); + Node* ctl_proj = ac->proj_out(TypeFunc::Control); + Node* mem_proj = ac->proj_out(TypeFunc::Memory); + if (ctl_proj != NULL) { + _igvn.replace_node(ctl_proj, n->in(0)); + } + if (mem_proj != NULL) { + _igvn.replace_node(mem_proj, n->in(TypeFunc::Memory)); + } } else { eliminate_card_mark(n); } k -= (oc2 - use->outcnt()); } + } else if (use->is_ArrayCopy()) { + // Disconnect ArrayCopy node + ArrayCopyNode* ac = use->as_ArrayCopy(); + assert(ac->is_arraycopy_validated() || + ac->is_copyof_validated() || + ac->is_copyofrange_validated(), "unsupported"); + CallProjections callprojs; + ac->extract_projections(&callprojs, true); + + _igvn.replace_node(callprojs.fallthrough_ioproj, ac->in(TypeFunc::I_O)); + _igvn.replace_node(callprojs.fallthrough_memproj, ac->in(TypeFunc::Memory)); + _igvn.replace_node(callprojs.fallthrough_catchproj, ac->in(TypeFunc::Control)); + + // Set control to top. IGVN will remove the remaining projections + ac->set_req(0, top()); + ac->replace_edge(res, top()); + + // Disconnect src right away: it can help find new + // opportunities for allocation elimination + Node* src = ac->in(ArrayCopyNode::Src); + ac->replace_edge(src, top()); + if (src->outcnt() == 0) { + _igvn.remove_dead_node(src); + } + + _igvn._worklist.push(ac); } else { eliminate_card_mark(use); } diff --git a/hotspot/src/share/vm/opto/macroArrayCopy.cpp b/hotspot/src/share/vm/opto/macroArrayCopy.cpp index d6f0caeb9553d..7e3279c68aaa1 100644 --- a/hotspot/src/share/vm/opto/macroArrayCopy.cpp +++ b/hotspot/src/share/vm/opto/macroArrayCopy.cpp @@ -1097,8 +1097,15 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { assert(alloc != NULL, "expect alloc"); } + const TypePtr* adr_type = _igvn.type(dest)->is_oopptr()->add_offset(Type::OffsetBot); + if (ac->_dest_type != TypeOopPtr::BOTTOM) { + adr_type = ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr(); + } + if (ac->_src_type != ac->_dest_type) { + adr_type = TypeRawPtr::BOTTOM; + } generate_arraycopy(ac, alloc, &ctrl, merge_mem, &io, - TypeAryPtr::OOPS, T_OBJECT, + adr_type, T_OBJECT, src, src_offset, dest, dest_offset, length, true, !ac->is_copyofrange()); @@ -1152,7 +1159,7 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { } // (2) src and dest arrays must have elements of the same BasicType // Figure out the size and type of the elements we will be copying. - BasicType src_elem = top_src->klass()->as_array_klass()->element_type()->basic_type(); + BasicType src_elem = top_src->klass()->as_array_klass()->element_type()->basic_type(); BasicType dest_elem = top_dest->klass()->as_array_klass()->element_type()->basic_type(); if (src_elem == T_ARRAY) src_elem = T_OBJECT; if (dest_elem == T_ARRAY) dest_elem = T_OBJECT; @@ -1232,6 +1239,13 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { } // This is where the memory effects are placed: const TypePtr* adr_type = TypeAryPtr::get_array_body_type(dest_elem); + if (ac->_dest_type != TypeOopPtr::BOTTOM) { + adr_type = ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr(); + } + if (ac->_src_type != ac->_dest_type) { + adr_type = TypeRawPtr::BOTTOM; + } + generate_arraycopy(ac, alloc, &ctrl, merge_mem, &io, adr_type, dest_elem, src, src_offset, dest, dest_offset, length, diff --git a/hotspot/src/share/vm/opto/memnode.cpp b/hotspot/src/share/vm/opto/memnode.cpp index 81bb5ab81e7a0..c2a03c96b3150 100644 --- a/hotspot/src/share/vm/opto/memnode.cpp +++ b/hotspot/src/share/vm/opto/memnode.cpp @@ -28,6 +28,7 @@ #include "memory/allocation.inline.hpp" #include "oops/objArrayKlass.hpp" #include "opto/addnode.hpp" +#include "opto/arraycopynode.hpp" #include "opto/cfgnode.hpp" #include "opto/compile.hpp" #include "opto/connode.hpp" @@ -107,6 +108,32 @@ extern void print_alias_types(); #endif +static bool membar_for_arraycopy_helper(const TypeOopPtr *t_oop, MergeMemNode* mm, PhaseTransform *phase) { + if (mm->memory_at(Compile::AliasIdxRaw)->is_Proj()) { + Node* n = mm->memory_at(Compile::AliasIdxRaw)->in(0); + if ((n->is_ArrayCopy() && n->as_ArrayCopy()->may_modify(t_oop, phase)) || + (n->is_CallLeaf() && n->as_CallLeaf()->may_modify(t_oop, phase))) { + return true; + } + } + return false; +} + +static bool membar_for_arraycopy(const TypeOopPtr *t_oop, MemBarNode* mb, PhaseTransform *phase) { + Node* mem = mb->in(TypeFunc::Memory); + if (mem->is_MergeMem()) { + return membar_for_arraycopy_helper(t_oop, mem->as_MergeMem(), phase); + } else if (mem->is_Phi()) { + // after macro expansion of an ArrayCopyNode we may have a Phi + for (uint i = 1; i < mem->req(); i++) { + if (mem->in(i) != NULL && mem->in(i)->is_MergeMem() && membar_for_arraycopy_helper(t_oop, mem->in(i)->as_MergeMem(), phase)) { + return true; + } + } + } + return false; +} + Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oop, Node *load, PhaseGVN *phase) { assert((t_oop != NULL), "sanity"); bool is_instance = t_oop->is_known_instance_field(); @@ -129,6 +156,7 @@ Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oo if (proj_in->is_Allocate() && proj_in->_idx == instance_id) { break; // hit one of our sentinels } else if (proj_in->is_Call()) { + // ArrayCopyNodes processed here as well CallNode *call = proj_in->as_Call(); if (!call->may_modify(t_oop, phase)) { // returns false for instances result = call->in(TypeFunc::Memory); @@ -136,7 +164,7 @@ Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oo } else if (proj_in->is_Initialize()) { AllocateNode* alloc = proj_in->as_Initialize()->allocation(); // Stop if this is the initialization for the object instance which - // which contains this memory slice, otherwise skip over it. + // contains this memory slice, otherwise skip over it. if ((alloc == NULL) || (alloc->_idx == instance_id)) { break; } @@ -150,6 +178,9 @@ Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oo } } } else if (proj_in->is_MemBar()) { + if (membar_for_arraycopy(t_oop, proj_in->as_MemBar(), phase)) { + break; + } result = proj_in->in(TypeFunc::Memory); } else { assert(false, "unexpected projection"); @@ -477,6 +508,75 @@ bool MemNode::detect_ptr_independence(Node* p1, AllocateNode* a1, } +// Find an arraycopy that must have set (can_see_stored_value=true) or +// could have set (can_see_stored_value=false) the value for this load +Node* LoadNode::find_previous_arraycopy(PhaseTransform* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const { + if (mem->is_Proj() && mem->in(0) != NULL && (mem->in(0)->Opcode() == Op_MemBarStoreStore || + mem->in(0)->Opcode() == Op_MemBarCPUOrder)) { + Node* mb = mem->in(0); + if (mb->in(0) != NULL && mb->in(0)->is_Proj() && + mb->in(0)->in(0) != NULL && mb->in(0)->in(0)->is_ArrayCopy()) { + ArrayCopyNode* ac = mb->in(0)->in(0)->as_ArrayCopy(); + if (ac->is_clonebasic()) { + intptr_t offset; + AllocateNode* alloc = AllocateNode::Ideal_allocation(ac->in(ArrayCopyNode::Dest), phase, offset); + assert(alloc != NULL && alloc->initialization()->is_complete_with_arraycopy(), "broken allocation"); + if (alloc == ld_alloc) { + return ac; + } + } + } + } else if (mem->is_Proj() && mem->in(0) != NULL && mem->in(0)->is_ArrayCopy()) { + ArrayCopyNode* ac = mem->in(0)->as_ArrayCopy(); + + if (ac->is_arraycopy_validated() || + ac->is_copyof_validated() || + ac->is_copyofrange_validated()) { + Node* ld_addp = in(MemNode::Address); + if (ld_addp->is_AddP()) { + Node* ld_base = ld_addp->in(AddPNode::Address); + Node* ld_offs = ld_addp->in(AddPNode::Offset); + + Node* dest = ac->in(ArrayCopyNode::Dest); + + if (dest == ld_base) { + Node* src_pos = ac->in(ArrayCopyNode::SrcPos); + Node* dest_pos = ac->in(ArrayCopyNode::DestPos); + Node* len = ac->in(ArrayCopyNode::Length); + + const TypeInt *dest_pos_t = phase->type(dest_pos)->isa_int(); + const TypeX *ld_offs_t = phase->type(ld_offs)->isa_intptr_t(); + const TypeInt *len_t = phase->type(len)->isa_int(); + const TypeAryPtr* ary_t = phase->type(dest)->isa_aryptr(); + + if (dest_pos_t != NULL && ld_offs_t != NULL && len_t != NULL && ary_t != NULL) { + BasicType ary_elem = ary_t->klass()->as_array_klass()->element_type()->basic_type(); + uint header = arrayOopDesc::base_offset_in_bytes(ary_elem); + uint elemsize = type2aelembytes(ary_elem); + + intptr_t dest_pos_plus_len_lo = (((intptr_t)dest_pos_t->_lo) + len_t->_lo) * elemsize + header; + intptr_t dest_pos_plus_len_hi = (((intptr_t)dest_pos_t->_hi) + len_t->_hi) * elemsize + header; + intptr_t dest_pos_lo = ((intptr_t)dest_pos_t->_lo) * elemsize + header; + intptr_t dest_pos_hi = ((intptr_t)dest_pos_t->_hi) * elemsize + header; + + if (can_see_stored_value) { + if (ld_offs_t->_lo >= dest_pos_hi && ld_offs_t->_hi < dest_pos_plus_len_lo) { + return ac; + } + } else { + if (ld_offs_t->_hi < dest_pos_lo || ld_offs_t->_lo >= dest_pos_plus_len_hi) { + mem = ac->in(TypeFunc::Memory); + } + return ac; + } + } + } + } + } + } + return NULL; +} + // The logic for reordering loads and stores uses four steps: // (a) Walk carefully past stores and initializations which we // can prove are independent of this load. @@ -510,6 +610,7 @@ Node* MemNode::find_previous_store(PhaseTransform* phase) { for (;;) { // While we can dance past unrelated stores... if (--cnt < 0) break; // Caught in cycle or a complicated dance? + Node* prev = mem; if (mem->is_Store()) { Node* st_adr = mem->in(MemNode::Address); intptr_t st_offset = 0; @@ -580,15 +681,26 @@ Node* MemNode::find_previous_store(PhaseTransform* phase) { return mem; // let caller handle steps (c), (d) } + } else if (find_previous_arraycopy(phase, alloc, mem, false) != NULL) { + if (prev != mem) { + // Found an arraycopy but it doesn't affect that load + continue; + } + // Found an arraycopy that may affect that load + return mem; } else if (addr_t != NULL && addr_t->is_known_instance_field()) { // Can't use optimize_simple_memory_chain() since it needs PhaseGVN. if (mem->is_Proj() && mem->in(0)->is_Call()) { + // ArrayCopyNodes processed here as well. CallNode *call = mem->in(0)->as_Call(); if (!call->may_modify(addr_t, phase)) { mem = call->in(TypeFunc::Memory); continue; // (a) advance through independent call memory } } else if (mem->is_Proj() && mem->in(0)->is_MemBar()) { + if (membar_for_arraycopy(addr_t, mem->in(0)->as_MemBar(), phase)) { + break; + } mem = mem->in(0)->in(TypeFunc::Memory); continue; // (a) advance through independent MemBar memory } else if (mem->is_ClearArray()) { @@ -760,6 +872,66 @@ static bool skip_through_membars(Compile::AliasType* atp, const TypeInstPtr* tp, return false; } +// Is the value loaded previously stored by an arraycopy? If so return +// a load node that reads from the source array so we may be able to +// optimize out the ArrayCopy node later. +Node* MemNode::can_see_arraycopy_value(Node* st, PhaseTransform* phase) const { + Node* ld_adr = in(MemNode::Address); + intptr_t ld_off = 0; + AllocateNode* ld_alloc = AllocateNode::Ideal_allocation(ld_adr, phase, ld_off); + Node* ac = find_previous_arraycopy(phase, ld_alloc, st, true); + if (ac != NULL) { + assert(ac->is_ArrayCopy(), "what kind of node can this be?"); + assert(is_Load(), "only for loads"); + + if (ac->as_ArrayCopy()->is_clonebasic()) { + assert(ld_alloc != NULL, "need an alloc"); + Node* ld = clone(); + Node* addp = in(MemNode::Address)->clone(); + assert(addp->is_AddP(), "address must be addp"); + assert(addp->in(AddPNode::Base) == ac->in(ArrayCopyNode::Dest)->in(AddPNode::Base), "strange pattern"); + assert(addp->in(AddPNode::Address) == ac->in(ArrayCopyNode::Dest)->in(AddPNode::Address), "strange pattern"); + addp->set_req(AddPNode::Base, ac->in(ArrayCopyNode::Src)->in(AddPNode::Base)); + addp->set_req(AddPNode::Address, ac->in(ArrayCopyNode::Src)->in(AddPNode::Address)); + ld->set_req(MemNode::Address, phase->transform(addp)); + if (in(0) != NULL) { + assert(ld_alloc->in(0) != NULL, "alloc must have control"); + ld->set_req(0, ld_alloc->in(0)); + } + return ld; + } else { + Node* ld = clone(); + Node* addp = in(MemNode::Address)->clone(); + assert(addp->in(AddPNode::Base) == addp->in(AddPNode::Address), "should be"); + addp->set_req(AddPNode::Base, ac->in(ArrayCopyNode::Src)); + addp->set_req(AddPNode::Address, ac->in(ArrayCopyNode::Src)); + + const TypeAryPtr* ary_t = phase->type(in(MemNode::Address))->isa_aryptr(); + BasicType ary_elem = ary_t->klass()->as_array_klass()->element_type()->basic_type(); + uint header = arrayOopDesc::base_offset_in_bytes(ary_elem); + uint shift = exact_log2(type2aelembytes(ary_elem)); + + Node* diff = phase->transform(new SubINode(ac->in(ArrayCopyNode::SrcPos), ac->in(ArrayCopyNode::DestPos))); +#ifdef _LP64 + diff = phase->transform(new ConvI2LNode(diff)); +#endif + diff = phase->transform(new LShiftXNode(diff, phase->intcon(shift))); + + Node* offset = phase->transform(new AddXNode(addp->in(AddPNode::Offset), diff)); + addp->set_req(AddPNode::Offset, offset); + ld->set_req(MemNode::Address, phase->transform(addp)); + + if (in(0) != NULL) { + assert(ac->in(0) != NULL, "alloc must have control"); + ld->set_req(0, ac->in(0)); + } + return ld; + } + } + return NULL; +} + + //---------------------------can_see_stored_value------------------------------ // This routine exists to make sure this set of tests is done the same // everywhere. We need to make a coordinated change: first LoadNode::Ideal @@ -793,6 +965,7 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseTransform* phase) const { opc == Op_MemBarRelease || opc == Op_StoreFence || opc == Op_MemBarReleaseLock || + opc == Op_MemBarStoreStore || opc == Op_MemBarCPUOrder) { Node* mem = current->in(0)->in(TypeFunc::Memory); if (mem->is_MergeMem()) { @@ -863,8 +1036,9 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseTransform* phase) const { if ((alloc != NULL) && (alloc == ld_alloc)) { // examine a captured store value st = init->find_captured_store(ld_off, memory_size(), phase); - if (st != NULL) + if (st != NULL) { continue; // take one more trip around + } } } @@ -1335,6 +1509,29 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { } } + // Is there a dominating load that loads the same value? Leave + // anything that is not a load of a field/array element (like + // barriers etc.) alone + if (in(0) != NULL && adr_type() != TypeRawPtr::BOTTOM && can_reshape) { + for (DUIterator_Fast imax, i = mem->fast_outs(imax); i < imax; i++) { + Node *use = mem->fast_out(i); + if (use != this && + use->Opcode() == Opcode() && + use->in(0) != NULL && + use->in(0) != in(0) && + use->in(Address) == in(Address)) { + Node* ctl = in(0); + for (int i = 0; i < 10 && ctl != NULL; i++) { + ctl = IfNode::up_one_dom(ctl); + if (ctl == use->in(0)) { + set_req(0, use->in(0)); + return this; + } + } + } + } + } + // Check for prior store with a different base or offset; make Load // independent. Skip through any number of them. Bail out if the stores // are in an endless dead cycle and report no progress. This is a key @@ -1348,6 +1545,12 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { // the alias index stuff. So instead, peek through Stores and IFF we can // fold up, do so. Node* prev_mem = find_previous_store(phase); + if (prev_mem != NULL) { + Node* value = can_see_arraycopy_value(prev_mem, phase); + if (value != NULL) { + return value; + } + } // Steps (a), (b): Walk past independent stores to find an exact match. if (prev_mem != NULL && prev_mem != in(MemNode::Memory)) { // (c) See if we can fold up on the spot, but don't fold up here. @@ -2529,7 +2732,6 @@ LoadStoreConditionalNode::LoadStoreConditionalNode( Node *c, Node *mem, Node *ad //============================================================================= //-------------------------------adr_type-------------------------------------- -// Do we Match on this edge index or not? Do not match memory const TypePtr* ClearArrayNode::adr_type() const { Node *adr = in(3); if (adr == NULL) return NULL; // node is dead diff --git a/hotspot/src/share/vm/opto/memnode.hpp b/hotspot/src/share/vm/opto/memnode.hpp index a15e1ca27598a..814d6d410a64b 100644 --- a/hotspot/src/share/vm/opto/memnode.hpp +++ b/hotspot/src/share/vm/opto/memnode.hpp @@ -72,6 +72,8 @@ class MemNode : public Node { debug_only(_adr_type=at; adr_type();) } + virtual Node* find_previous_arraycopy(PhaseTransform* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const { return NULL; } + public: // Helpers for the optimizer. Documented in memnode.cpp. static bool detect_ptr_independence(Node* p1, AllocateNode* a1, @@ -124,6 +126,7 @@ class MemNode : public Node { // Can this node (load or store) accurately see a stored value in // the given memory state? (The state may or may not be in(Memory).) Node* can_see_stored_value(Node* st, PhaseTransform* phase) const; + Node* can_see_arraycopy_value(Node* st, PhaseTransform* phase) const; #ifndef PRODUCT static void dump_adr_type(const Node* mem, const TypePtr* adr_type, outputStream *st); @@ -147,6 +150,8 @@ class LoadNode : public MemNode { // Should LoadNode::Ideal() attempt to remove control edges? virtual bool can_remove_control() const; const Type* const _type; // What kind of value is loaded? + + virtual Node* find_previous_arraycopy(PhaseTransform* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const; public: LoadNode(Node *c, Node *mem, Node *adr, const TypePtr* at, const Type *rt, MemOrd mo) diff --git a/hotspot/test/compiler/arraycopy/TestArrayCopyAsLoadsStores.java b/hotspot/test/compiler/arraycopy/TestArrayCopyAsLoadsStores.java index 05549106f0cd8..6a1b4e6aeba56 100644 --- a/hotspot/test/compiler/arraycopy/TestArrayCopyAsLoadsStores.java +++ b/hotspot/test/compiler/arraycopy/TestArrayCopyAsLoadsStores.java @@ -25,50 +25,15 @@ * @test * @bug 6912521 * @summary small array copy as loads/stores + * @compile TestArrayCopyAsLoadsStores.java TestArrayCopyUtils.java * @run main/othervm -ea -XX:-BackgroundCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=dontinline,TestArrayCopyAsLoadsStores::m* -XX:TypeProfileLevel=200 TestArrayCopyAsLoadsStores * @run main/othervm -ea -XX:-BackgroundCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=dontinline,TestArrayCopyAsLoadsStores::m* -XX:+IgnoreUnrecognizedVMOptions -XX:+StressArrayCopyMacroNode -XX:TypeProfileLevel=200 TestArrayCopyAsLoadsStores * */ -import java.lang.annotation.*; -import java.lang.reflect.*; import java.util.*; -public class TestArrayCopyAsLoadsStores { - - public enum ArraySrc { - SMALL, - LARGE, - ZERO - } - - public enum ArrayDst { - NONE, - NEW, - SRC - } - - static class A { - } - - static class B extends A { - } - - static final A[] small_a_src = new A[5]; - static final A[] large_a_src = new A[10]; - static final A[] zero_a_src = new A[0]; - static final int[] small_int_src = new int[5]; - static final int[] large_int_src = new int[10]; - static final int[] zero_int_src = new int[0]; - static final Object[] small_object_src = new Object[5]; - static Object src; - - @Retention(RetentionPolicy.RUNTIME) - @interface Args { - ArraySrc src(); - ArrayDst dst() default ArrayDst.NONE; - int[] extra_args() default {}; - } +public class TestArrayCopyAsLoadsStores extends TestArrayCopyUtils { // array clone should be compiled as loads/stores @Args(src=ArraySrc.SMALL) @@ -349,166 +314,7 @@ static boolean m25_check(int[] src, int[] dest) { return false; } - final HashMap tests = new HashMap<>(); - { - for (Method m : this.getClass().getDeclaredMethods()) { - if (m.getName().matches("m[0-9]+(_check)?")) { - assert(Modifier.isStatic(m.getModifiers())) : m; - tests.put(m.getName(), m); - } - } - } - - boolean success = true; - - void doTest(String name) throws Exception { - Method m = tests.get(name); - Method m_check = tests.get(name + "_check"); - Class[] paramTypes = m.getParameterTypes(); - Object[] params = new Object[paramTypes.length]; - Class retType = m.getReturnType(); - boolean isIntArray = (retType.isPrimitive() && !retType.equals(Void.TYPE)) || - (retType.equals(Void.TYPE) && paramTypes[0].getComponentType().isPrimitive()) || - (retType.isArray() && retType.getComponentType().isPrimitive()); - - Args args = m.getAnnotation(Args.class); - - Object src = null; - switch(args.src()) { - case SMALL: { - if (isIntArray) { - src = small_int_src; - } else { - src = small_a_src; - } - break; - } - case LARGE: { - if (isIntArray) { - src = large_int_src; - } else { - src = large_a_src; - } - break; - } - case ZERO: { - if (isIntArray) { - src = zero_int_src; - } else { - src = zero_a_src; - } - break; - } - } - - for (int i = 0; i < 20000; i++) { - boolean failure = false; - - int p = 0; - - if (params.length > 0) { - if (isIntArray) { - params[0] = ((int[])src).clone(); - } else { - params[0] = ((A[])src).clone(); - } - p++; - } - - if (params.length > 1) { - switch(args.dst()) { - case NEW: { - if (isIntArray) { - params[1] = new int[((int[])params[0]).length]; - } else { - params[1] = new A[((A[])params[0]).length]; - } - p++; - break; - } - case SRC: { - params[1] = params[0]; - p++; - break; - } - case NONE: break; - } - } - - for (int j = 0; j < args.extra_args().length; j++) { - params[p+j] = args.extra_args()[j]; - } - - Object res = m.invoke(null, params); - - if (retType.isPrimitive() && !retType.equals(Void.TYPE)) { - int s = (int)res; - int sum = 0; - int[] int_res = (int[])src; - for (int j = 0; j < int_res.length; j++) { - sum += int_res[j]; - } - failure = (s != sum); - if (failure) { - System.out.println("Test " + name + " failed: result = " + s + " != " + sum); - } - } else { - Object dest = null; - if (!retType.equals(Void.TYPE)) { - dest = res; - } else { - dest = params[1]; - } - - if (m_check != null) { - failure = (boolean)m_check.invoke(null, new Object[] { src, dest }); - } else { - if (isIntArray) { - int[] int_res = (int[])src; - int[] int_dest = (int[])dest; - for (int j = 0; j < int_res.length; j++) { - if (int_res[j] != int_dest[j]) { - System.out.println("Test " + name + " failed for " + j + " src[" + j +"]=" + int_res[j] + ", dest[" + j + "]=" + int_dest[j]); - failure = true; - } - } - } else { - Object[] object_res = (Object[])src; - Object[] object_dest = (Object[])dest; - for (int j = 0; j < object_res.length; j++) { - if (object_res[j] != object_dest[j]) { - System.out.println("Test " + name + " failed for " + j + " src[" + j +"]=" + object_res[j] + ", dest[" + j + "]=" + object_dest[j]); - failure = true; - } - } - } - } - } - - if (failure) { - success = false; - break; - } - } - } - public static void main(String[] args) throws Exception { - for (int i = 0; i < small_a_src.length; i++) { - small_a_src[i] = new A(); - } - - for (int i = 0; i < small_int_src.length; i++) { - small_int_src[i] = i; - } - - for (int i = 0; i < large_int_src.length; i++) { - large_int_src[i] = i; - } - - for (int i = 0; i < 5; i++) { - small_object_src[i] = new Object(); - } - TestArrayCopyAsLoadsStores test = new TestArrayCopyAsLoadsStores(); test.doTest("m1"); diff --git a/hotspot/test/compiler/arraycopy/TestArrayCopyUtils.java b/hotspot/test/compiler/arraycopy/TestArrayCopyUtils.java new file mode 100644 index 0000000000000..de6af41ede462 --- /dev/null +++ b/hotspot/test/compiler/arraycopy/TestArrayCopyUtils.java @@ -0,0 +1,223 @@ +/* + * 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. + */ + +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.util.*; + +abstract class TestArrayCopyUtils { + public enum ArraySrc { + SMALL, + LARGE, + ZERO + } + + public enum ArrayDst { + NONE, + NEW, + SRC + } + + static class A { + } + + static class B extends A { + } + + static final A[] small_a_src = new A[5]; + static final A[] large_a_src = new A[10]; + static final A[] zero_a_src = new A[0]; + static final int[] small_int_src = new int[5]; + static final int[] large_int_src = new int[10]; + static final int[] zero_int_src = new int[0]; + static final Object[] small_object_src = new Object[5]; + static Object src; + + @Retention(RetentionPolicy.RUNTIME) + @interface Args { + ArraySrc src(); + ArrayDst dst() default ArrayDst.NONE; + int[] extra_args() default {}; + } + + final HashMap tests = new HashMap<>(); + { + for (Method m : this.getClass().getDeclaredMethods()) { + if (m.getName().matches("m[0-9]+(_check)?")) { + assert(Modifier.isStatic(m.getModifiers())) : m; + tests.put(m.getName(), m); + } + } + } + + boolean success = true; + + void doTest(String name) throws Exception { + Method m = tests.get(name); + Method m_check = tests.get(name + "_check"); + Class[] paramTypes = m.getParameterTypes(); + Object[] params = new Object[paramTypes.length]; + Class retType = m.getReturnType(); + boolean isIntArray = (retType.isPrimitive() && !retType.equals(Void.TYPE)) || + (retType.equals(Void.TYPE) && paramTypes[0].getComponentType().isPrimitive()) || + (retType.isArray() && retType.getComponentType().isPrimitive()); + + Args args = m.getAnnotation(Args.class); + + Object src = null; + switch(args.src()) { + case SMALL: { + if (isIntArray) { + src = small_int_src; + } else { + src = small_a_src; + } + break; + } + case LARGE: { + if (isIntArray) { + src = large_int_src; + } else { + src = large_a_src; + } + break; + } + case ZERO: { + if (isIntArray) { + src = zero_int_src; + } else { + src = zero_a_src; + } + break; + } + } + + for (int i = 0; i < 20000; i++) { + boolean failure = false; + + int p = 0; + + if (params.length > 0) { + if (isIntArray) { + params[0] = ((int[])src).clone(); + } else { + params[0] = ((A[])src).clone(); + } + p++; + } + + if (params.length > 1) { + switch(args.dst()) { + case NEW: { + if (isIntArray) { + params[1] = new int[((int[])params[0]).length]; + } else { + params[1] = new A[((A[])params[0]).length]; + } + p++; + break; + } + case SRC: { + params[1] = params[0]; + p++; + break; + } + case NONE: break; + } + } + + for (int j = 0; j < args.extra_args().length; j++) { + params[p+j] = args.extra_args()[j]; + } + + Object res = m.invoke(null, params); + + if (retType.isPrimitive() && !retType.equals(Void.TYPE)) { + int s = (int)res; + int sum = 0; + int[] int_res = (int[])src; + for (int j = 0; j < int_res.length; j++) { + sum += int_res[j]; + } + failure = (s != sum); + if (failure) { + System.out.println("Test " + name + " failed: result = " + s + " != " + sum); + } + } else { + Object dest = null; + if (!retType.equals(Void.TYPE)) { + dest = res; + } else { + dest = params[1]; + } + + if (m_check != null) { + failure = (boolean)m_check.invoke(null, new Object[] { src, dest }); + } else { + if (isIntArray) { + int[] int_res = (int[])src; + int[] int_dest = (int[])dest; + for (int j = 0; j < int_res.length; j++) { + if (int_res[j] != int_dest[j]) { + System.out.println("Test " + name + " failed for " + j + " src[" + j +"]=" + int_res[j] + ", dest[" + j + "]=" + int_dest[j]); + failure = true; + } + } + } else { + Object[] object_res = (Object[])src; + Object[] object_dest = (Object[])dest; + for (int j = 0; j < object_res.length; j++) { + if (object_res[j] != object_dest[j]) { + System.out.println("Test " + name + " failed for " + j + " src[" + j +"]=" + object_res[j] + ", dest[" + j + "]=" + object_dest[j]); + failure = true; + } + } + } + } + } + + if (failure) { + success = false; + break; + } + } + } + + TestArrayCopyUtils() { + for (int i = 0; i < small_a_src.length; i++) { + small_a_src[i] = new A(); + } + + for (int i = 0; i < small_int_src.length; i++) { + small_int_src[i] = i; + } + + for (int i = 0; i < large_int_src.length; i++) { + large_int_src[i] = i; + } + + for (int i = 0; i < 5; i++) { + small_object_src[i] = new Object(); + } + } +} diff --git a/hotspot/test/compiler/arraycopy/TestEliminateArrayCopy.java b/hotspot/test/compiler/arraycopy/TestEliminateArrayCopy.java new file mode 100644 index 0000000000000..624a65c42b4c2 --- /dev/null +++ b/hotspot/test/compiler/arraycopy/TestEliminateArrayCopy.java @@ -0,0 +1,195 @@ +/* + * 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 8076188 + * @summary arraycopy to non escaping destination may be eliminated + * @compile TestEliminateArrayCopy.java TestArrayCopyUtils.java + * @run main/othervm -ea -XX:-BackgroundCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=dontinline,TestEliminateArrayCopy*::m* TestEliminateArrayCopy + * + */ + +public class TestEliminateArrayCopy { + + static class CloneTests extends TestInstanceCloneUtils { + // object allocation and ArrayCopyNode should be eliminated + static void m1(E src) throws CloneNotSupportedException { + src.clone(); + } + + // both object allocations and ArrayCopyNode should be eliminated + static void m2(Object dummy) throws CloneNotSupportedException { + E src = new E(false); + src.clone(); + } + + // object allocation and ArrayCopyNode should be eliminated. Fields should be loaded from src. + 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; + } + } + + static class ArrayCopyTests extends TestArrayCopyUtils { + + // object allocation and ArrayCopyNode should be eliminated. + @Args(src=ArraySrc.LARGE) + static int m1() throws CloneNotSupportedException { + int[] array_clone = (int[])large_int_src.clone(); + return array_clone[0] + array_clone[1] + array_clone[2] + + array_clone[3] + array_clone[4] + array_clone[5] + + array_clone[6] + array_clone[7] + array_clone[8] + + array_clone[9]; + } + + // object allocation and ArrayCopyNode should be eliminated. + @Args(src=ArraySrc.LARGE) + static int m2() { + int[] dest = new int[10]; + System.arraycopy(large_int_src, 0, dest, 0, 10); + return dest[0] + dest[1] + dest[2] + dest[3] + dest[4] + + dest[5] + dest[6] + dest[7] + dest[8] + dest[9]; + } + + // object allocations and ArrayCopyNodes should be eliminated. + @Args(src=ArraySrc.LARGE) + static int m3() { + int[] dest1 = new int[10]; + System.arraycopy(large_int_src, 0, dest1, 0, 10); + + int[] dest2 = new int[10]; + System.arraycopy(dest1, 0, dest2, 0, 10); + + return dest2[0] + dest2[1] + dest2[2] + dest2[3] + dest2[4] + + dest2[5] + dest2[6] + dest2[7] + dest2[8] + dest2[9]; + } + + static class m4_class { + Object f; + } + + static void m4_helper() {} + + // allocations eliminated and arraycopy optimized out + @Args(src=ArraySrc.LARGE) + static int m4() { + int[] dest = new int[10]; + m4_class o = new m4_class(); + o.f = dest; + m4_helper(); + System.arraycopy(large_int_src, 0, o.f, 0, 10); + return dest[0] + dest[1] + dest[2] + dest[3] + dest[4] + + dest[5] + dest[6] + dest[7] + dest[8] + dest[9]; + } + + static void m5_helper() {} + + // Small copy cannot be converted to loads/stores because + // allocation is not close enough to arraycopy but arraycopy + // itself can be eliminated + @Args(src=ArraySrc.SMALL, dst=ArrayDst.NEW) + static void m5(A[] src, A[] dest) { + A[] temp = new A[5]; + m5_helper(); + System.arraycopy(src, 0, temp, 0, 5); + dest[0] = temp[0]; + dest[1] = temp[1]; + dest[2] = temp[2]; + dest[3] = temp[3]; + dest[4] = temp[4]; + } + + // object allocation and ArrayCopyNode should be eliminated. + @Args(src=ArraySrc.LARGE) + static int m6(int [] src) { + int res = src[0] + src[1] + src[2] + src[3] + src[4] + + src[5] + src[6] + src[7] + src[8] + src[9]; + + int[] dest = new int[10]; + + System.arraycopy(src, 0, dest, 0, 10); + + res += dest[0] + dest[1] + dest[2] + dest[3] + dest[4] + + dest[5] + dest[6] + dest[7] + dest[8] + dest[9]; + return res/2; + } + + @Args(src=ArraySrc.LARGE) + static int m7() { + int[] dest = new int[10]; + dest[0] = large_int_src[8]; + dest[1] = large_int_src[9]; + System.arraycopy(large_int_src, 0, dest, 2, 8); + return dest[0] + dest[1] + dest[2] + dest[3] + dest[4] + + dest[5] + dest[6] + dest[7] + dest[8] + dest[9]; + } + } + + // test that OptimizePtrCompare still works + static final Object[] m1_array = new Object[10]; + static boolean m1_array_null_element = false; + static void m1(int i) { + Object[] array_clone = (Object[])m1_array.clone(); + if (array_clone[i] == null) { + m1_array_null_element = true; + } + } + + static public void main(String[] args) throws Exception { + CloneTests clone_tests = new CloneTests(); + + clone_tests.doTest(clone_tests.e, "m1"); + clone_tests.doTest(null, "m2"); + clone_tests.doTest(clone_tests.e, "m3"); + + ArrayCopyTests ac_tests = new ArrayCopyTests(); + + ac_tests.doTest("m1"); + ac_tests.doTest("m2"); + ac_tests.doTest("m3"); + ac_tests.doTest("m4"); + ac_tests.doTest("m5"); + ac_tests.doTest("m6"); + ac_tests.doTest("m7"); + + if (!clone_tests.success || !ac_tests.success) { + throw new RuntimeException("some tests failed"); + } + + // Make sure both branches of the if in m1() appear taken + for (int i = 0; i < 7000; i++) { + m1(0); + } + m1_array[0] = new Object(); + for (int i = 0; i < 20000; i++) { + m1(0); + } + m1_array_null_element = false; + m1(0); + if (m1_array_null_element) { + throw new RuntimeException("OptimizePtrCompare test failed"); + } + } +} diff --git a/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java b/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java index d327735547232..8e95036f55043 100644 --- a/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java +++ b/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java @@ -25,200 +25,13 @@ * @test * @bug 6700100 * @summary small instance clone as loads/stores + * @compile TestInstanceCloneAsLoadsStores.java TestInstanceCloneUtils.java * @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; - } +public class TestInstanceCloneAsLoadsStores extends TestInstanceCloneUtils { // Should be compiled as loads/stores static Object m1(D src) throws CloneNotSupportedException { @@ -269,62 +82,10 @@ 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"); diff --git a/hotspot/test/compiler/arraycopy/TestInstanceCloneUtils.java b/hotspot/test/compiler/arraycopy/TestInstanceCloneUtils.java new file mode 100644 index 0000000000000..af1e6fc703964 --- /dev/null +++ b/hotspot/test/compiler/arraycopy/TestInstanceCloneUtils.java @@ -0,0 +1,310 @@ +/* + * 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. + */ + +import java.lang.reflect.*; +import java.util.*; + +abstract class TestInstanceCloneUtils { + 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(boolean initialize) { + if (initialize) { + 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; + + A(boolean initialize) { + super(initialize); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + static class B extends A { + int i6; + + B(boolean initialize) { + super(initialize); + } + } + + static final class D extends Base { + byte i1; + short i2; + long i3; + int i4; + int i5; + + D(boolean initialize) { + super(initialize); + } + + 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; + + E(boolean initialize) { + super(initialize); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + static final class F extends Base { + F(boolean initialize) { + super(initialize); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + static class G extends Base { + int i1; + int i2; + int i3; + + G(boolean initialize) { + super(initialize); + } + + public Object myclone() throws CloneNotSupportedException { + return clone(); + } + } + + static class H extends G { + int i4; + int i5; + + H(boolean initialize) { + super(initialize); + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + static class J extends Base { + int i1; + int i2; + int i3; + + J(boolean initialize) { + super(initialize); + } + + public Object myclone() throws CloneNotSupportedException { + return clone(); + } + } + + static class K extends J { + int i4; + int i5; + + K(boolean initialize) { + super(initialize); + } + + } + + static final A a = new A(true); + static final B b = new B(true); + static final D d = new D(true); + static final E e = new E(true); + static final F f = new F(true); + static final G g = new G(true); + static final H h = new H(true); + static final J j = new J(true); + static final K k = new K(true); + + 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; + Class retType = m.getReturnType(); + if (retType.isPrimitive()) { + if (!retType.equals(Void.TYPE)) { + s = (int)m.invoke(null, src); + failure = (s != src.sum()); + } else { + m.invoke(null, src); + } + } 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; + } + } + } + +}