8076188: Optimize arraycopy out for non escaping destination

If the destination of an arraycopy is non escaping, the arraycopy may be optimized out

Reviewed-by: kvn, vlivanov
This commit is contained in:
Roland Westrelin 2015-05-12 10:27:50 +02:00
parent 3dc9461bc8
commit a9cdbd0407
17 changed files with 1224 additions and 486 deletions

View file

@ -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);
}

View file

@ -124,6 +124,10 @@ public:
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 @@ public:
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

View file

@ -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);
}

View file

@ -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 @@ public:
#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 @@ public:
JVMState* dbg_jvms() const { return NULL; }
#endif
};
#endif // SHARE_VM_OPTO_CALLNODE_HPP

View file

@ -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<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 @@ 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<PointsToNode*>& 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<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 @@ void ConnectionGraph::split_unique_types(GrowableArray<Node *> &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<Node *> &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");

View file

@ -536,7 +536,7 @@ private:
// 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);

View file

@ -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 ||

View file

@ -973,6 +973,9 @@ 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);
// 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();
@ -990,6 +993,7 @@ void IfNode::improve_address_types(Node* l, Node* r, ProjNode* fail, PhaseIterGV
igvn->_worklist.push(init_n);
}
}
}
} else if (use->in(0) == NULL && (igvn->type(use)->isa_long() ||
igvn->type(use)->isa_ptr())) {
stack.set_index(i+1);

View file

@ -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);
}

View file

@ -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());
@ -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,

View file

@ -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,10 +1036,11 @@ 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
}
}
}
// Load boxed value from result of valueOf() call is input parameter.
if (this->is_Load() && ld_adr->is_AddP() &&
@ -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

View file

@ -72,6 +72,8 @@ protected:
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 @@ public:
// 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 @@ protected:
// 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)

View file

@ -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 @@ public class TestArrayCopyAsLoadsStores {
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");

View file

@ -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();
}
}
}

View file

@ -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");
}
}
}

View file

@ -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 @@ public class TestInstanceCloneAsLoadsStores {
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");

View file

@ -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;
}
}
}
}