8076188: Optimize arraycopy out for non escaping destination
Summary: if the destination of an arraycopy is non escaping, the arraycopy may be optimized out
Reviewed-by: kvn, vlivanov
--- a/hotspot/src/share/vm/opto/arraycopynode.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/arraycopynode.cpp Tue May 12 10:27:50 2015 +0200
@@ -30,7 +30,9 @@
: 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 @@
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);
+}
--- a/hotspot/src/share/vm/opto/arraycopynode.hpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/arraycopynode.hpp Tue May 12 10:27:50 2015 +0200
@@ -124,6 +124,10 @@
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 @@
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
--- a/hotspot/src/share/vm/opto/callnode.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/callnode.cpp Tue May 12 10:27:50 2015 +0200
@@ -797,11 +797,12 @@
}
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 @@
//=============================================================================
+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 @@
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);
+}
--- a/hotspot/src/share/vm/opto/callnode.hpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/callnode.hpp Tue May 12 10:27:50 2015 +0200
@@ -556,6 +556,10 @@
// 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 @@
#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 @@
JVMState* dbg_jvms() const { return NULL; }
#endif
};
-
#endif // SHARE_VM_OPTO_CALLNODE_HPP
--- a/hotspot/src/share/vm/opto/escape.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/escape.cpp Tue May 12 10:27:50 2015 +0200
@@ -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 @@
GrowableArray<Node*> alloc_worklist;
GrowableArray<Node*> ptr_cmp_worklist;
GrowableArray<Node*> storestore_worklist;
+ GrowableArray<ArrayCopyNode*> arraycopy_worklist;
GrowableArray<PointsToNode*> ptnodes_worklist;
GrowableArray<JavaObjectNode*> java_objects_worklist;
GrowableArray<JavaObjectNode*> non_escaped_worklist;
@@ -173,6 +175,10 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
!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 @@
// 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 @@
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 @@
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 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 @@
//
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 @@
}
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 @@
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 @@
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 @@
//
// 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 @@
// 90 LoadP _ 120 30 ... alias_index=6
// 100 LoadP _ 80 20 ... alias_index=4
//
-void ConnectionGraph::split_unique_types(GrowableArray<Node *> &alloc_worklist) {
+void ConnectionGraph::split_unique_types(GrowableArray<Node *> &alloc_worklist, GrowableArray<ArrayCopyNode*> &arraycopy_worklist) {
GrowableArray<Node *> memnode_worklist;
GrowableArray<PhiNode *> orig_phis;
PhaseIterGVN *igvn = _igvn;
@@ -2912,9 +2943,12 @@
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 @@
}
}
+
+ // 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");
--- a/hotspot/src/share/vm/opto/escape.hpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/escape.hpp Tue May 12 10:27:50 2015 +0200
@@ -536,7 +536,7 @@
// Propagate unique types created for unescaped allocated objects
// through the graph
- void split_unique_types(GrowableArray<Node *> &alloc_worklist);
+ void split_unique_types(GrowableArray<Node *> &alloc_worklist, GrowableArray<ArrayCopyNode*> &arraycopy_worklist);
// Helper methods for unique types split.
bool split_AddP(Node *addp, Node *base);
--- a/hotspot/src/share/vm/opto/gcm.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/gcm.cpp Tue May 12 10:27:50 2015 +0200
@@ -483,7 +483,7 @@
// 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 ||
--- a/hotspot/src/share/vm/opto/ifnode.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/ifnode.cpp Tue May 12 10:27:50 2015 +0200
@@ -973,21 +973,25 @@
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() ||
--- a/hotspot/src/share/vm/opto/macro.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/macro.cpp Tue May 12 10:27:50 2015 +0200
@@ -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 @@
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 @@
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 @@
}
#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);
}
--- a/hotspot/src/share/vm/opto/macroArrayCopy.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/macroArrayCopy.cpp Tue May 12 10:27:50 2015 +0200
@@ -1097,8 +1097,15 @@
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 @@
}
// (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 @@
}
// 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,
--- a/hotspot/src/share/vm/opto/memnode.cpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/memnode.cpp Tue May 12 10:27:50 2015 +0200
@@ -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 @@
#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 @@
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 @@
} 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 @@
}
}
} 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 @@
}
+// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ // 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 @@
// 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 @@
//=============================================================================
//-------------------------------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
--- a/hotspot/src/share/vm/opto/memnode.hpp Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/src/share/vm/opto/memnode.hpp Tue May 12 10:27:50 2015 +0200
@@ -72,6 +72,8 @@
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 @@
// 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 @@
// 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)
--- a/hotspot/test/compiler/arraycopy/TestArrayCopyAsLoadsStores.java Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/test/compiler/arraycopy/TestArrayCopyAsLoadsStores.java Tue May 12 10:27:50 2015 +0200
@@ -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 @@
return false;
}
- final HashMap<String,Method> 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");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestArrayCopyUtils.java Tue May 12 10:27:50 2015 +0200
@@ -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<String,Method> 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();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestEliminateArrayCopy.java Tue May 12 10:27:50 2015 +0200
@@ -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");
+ }
+ }
+}
--- a/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java Mon May 11 09:44:07 2015 +0200
+++ b/hotspot/test/compiler/arraycopy/TestInstanceCloneAsLoadsStores.java Tue May 12 10:27:50 2015 +0200
@@ -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 @@
return (J)src.myclone();
}
- final HashMap<String,Method> tests = new HashMap<>();
- {
- for (Method m : this.getClass().getDeclaredMethods()) {
- if (m.getName().matches("m[0-9]+")) {
- assert(Modifier.isStatic(m.getModifiers())) : m;
- tests.put(m.getName(), m);
- }
- }
- }
-
- boolean success = true;
-
- void doTest(Base src, String name) throws Exception {
- Method m = tests.get(name);
-
- for (int i = 0; i < 20000; i++) {
- boolean failure = false;
- Base res = null;
- int s = 0;
- if (m.getReturnType().isPrimitive()) {
- s = (int)m.invoke(null, src);
- failure = (s != src.sum());
- } else {
- res = (Base)m.invoke(null, src);
- failure = !res.equals(src);
- }
- if (failure) {
- System.out.println("Test " + name + " failed");
- System.out.println("source: ");
- System.out.println(src);
- System.out.println("result: ");
- if (m.getReturnType().isPrimitive()) {
- System.out.println(s);
- } else {
- System.out.println(res);
- }
- success = false;
- break;
- }
- }
- }
-
public static void main(String[] args) throws Exception {
TestInstanceCloneAsLoadsStores test = new TestInstanceCloneAsLoadsStores();
- A a = new A();
- B b = new B();
- D d = new D();
- E e = new E();
- F f = new F();
- G g = new G();
- H h = new H();
- J j = new J();
- K k = new K();
-
test.doTest(d, "m1");
test.doTest(d, "m2");
test.doTest(e, "m3");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestInstanceCloneUtils.java Tue May 12 10:27:50 2015 +0200
@@ -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<String,Method> tests = new HashMap<>();
+ {
+ for (Method m : this.getClass().getDeclaredMethods()) {
+ if (m.getName().matches("m[0-9]+")) {
+ assert(Modifier.isStatic(m.getModifiers())) : m;
+ tests.put(m.getName(), m);
+ }
+ }
+ }
+
+ boolean success = true;
+
+ void doTest(Base src, String name) throws Exception {
+ Method m = tests.get(name);
+
+ for (int i = 0; i < 20000; i++) {
+ boolean failure = false;
+ Base res = null;
+ int s = 0;
+ 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;
+ }
+ }
+ }
+
+}