8130847: Cloned object's fields observed as null after C2 escape analysis
Summary: Eliminated instance/array written to by an array copy variant must be correctly initialized when reallocated at a deopt
Reviewed-by: kvn, vlivanov
--- a/hotspot/src/share/vm/opto/arraycopynode.cpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/arraycopynode.cpp Sat Aug 15 02:54:18 2015 +0200
@@ -626,3 +626,75 @@
return CallNode::may_modify_arraycopy_helper(dest_t, t_oop, phase);
}
+
+bool ArrayCopyNode::may_modify_helper(const TypeOopPtr *t_oop, Node* n, PhaseTransform *phase) {
+ if (n->is_Proj()) {
+ n = n->in(0);
+ if (n->is_Call() && n->as_Call()->may_modify(t_oop, phase)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ArrayCopyNode::may_modify(const TypeOopPtr *t_oop, MemBarNode* mb, PhaseTransform *phase) {
+ Node* mem = mb->in(TypeFunc::Memory);
+
+ if (mem->is_MergeMem()) {
+ Node* n = mem->as_MergeMem()->memory_at(Compile::AliasIdxRaw);
+ if (may_modify_helper(t_oop, n, phase)) {
+ return true;
+ } else if (n->is_Phi()) {
+ for (uint i = 1; i < n->req(); i++) {
+ if (n->in(i) != NULL) {
+ if (may_modify_helper(t_oop, n->in(i), phase)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+// Does this array copy modify offsets between offset_lo and offset_hi
+// in the destination array
+// if must_modify is false, return true if the copy could write
+// between offset_lo and offset_hi
+// if must_modify is true, return true if the copy is guaranteed to
+// write between offset_lo and offset_hi
+bool ArrayCopyNode::modifies(intptr_t offset_lo, intptr_t offset_hi, PhaseTransform* phase, bool must_modify) {
+ assert(_kind == ArrayCopy || _kind == CopyOf || _kind == CopyOfRange, "only for real array copies");
+
+ Node* dest = in(ArrayCopyNode::Dest);
+ Node* src_pos = in(ArrayCopyNode::SrcPos);
+ Node* dest_pos = in(ArrayCopyNode::DestPos);
+ Node* len = in(ArrayCopyNode::Length);
+
+ const TypeInt *dest_pos_t = phase->type(dest_pos)->isa_int();
+ const TypeInt *len_t = phase->type(len)->isa_int();
+ const TypeAryPtr* ary_t = phase->type(dest)->isa_aryptr();
+
+ if (dest_pos_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 (must_modify) {
+ if (offset_lo >= dest_pos_hi && offset_hi < dest_pos_plus_len_lo) {
+ return true;
+ }
+ } else {
+ if (offset_hi >= dest_pos_lo && offset_lo < dest_pos_plus_len_hi) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
--- a/hotspot/src/share/vm/opto/arraycopynode.hpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/arraycopynode.hpp Sat Aug 15 02:54:18 2015 +0200
@@ -108,6 +108,7 @@
BasicType copy_type, const Type* value_type, int count);
bool finish_transform(PhaseGVN *phase, bool can_reshape,
Node* ctl, Node *mem);
+ static bool may_modify_helper(const TypeOopPtr *t_oop, Node* n, PhaseTransform *phase);
public:
@@ -162,6 +163,9 @@
bool is_alloc_tightly_coupled() const { return _alloc_tightly_coupled; }
+ static bool may_modify(const TypeOopPtr *t_oop, MemBarNode* mb, PhaseTransform *phase);
+ bool modifies(intptr_t offset_lo, intptr_t offset_hi, PhaseTransform* phase, bool must_modify);
+
#ifndef PRODUCT
virtual void dump_spec(outputStream *st) const;
virtual void dump_compact_spec(outputStream* st) const;
--- a/hotspot/src/share/vm/opto/callnode.cpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/callnode.cpp Sat Aug 15 02:54:18 2015 +0200
@@ -742,7 +742,7 @@
//
bool CallNode::may_modify(const TypeOopPtr *t_oop, PhaseTransform *phase) {
assert((t_oop != NULL), "sanity");
- if (is_call_to_arraycopystub()) {
+ if (is_call_to_arraycopystub() && strcmp(_name, "unsafe_arraycopy") != 0) {
const TypeTuple* args = _tf->domain();
Node* dest = NULL;
// Stubs that can be called once an ArrayCopyNode is expanded have
--- a/hotspot/src/share/vm/opto/macro.cpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/macro.cpp Sat Aug 15 02:54:18 2015 +0200
@@ -324,18 +324,28 @@
return in;
} else if (in->is_Call()) {
CallNode *call = in->as_Call();
- if (!call->may_modify(tinst, phase)) {
- mem = call->in(TypeFunc::Memory);
+ if (call->may_modify(tinst, phase)) {
+ assert(call->is_ArrayCopy(), "ArrayCopy is the only call node that doesn't make allocation escape");
+
+ if (call->as_ArrayCopy()->modifies(offset, offset, phase, false)) {
+ return in;
+ }
}
mem = in->in(TypeFunc::Memory);
} else if (in->is_MemBar()) {
+ if (ArrayCopyNode::may_modify(tinst, in->as_MemBar(), phase)) {
+ assert(in->in(0)->is_Proj() && in->in(0)->in(0)->is_ArrayCopy(), "should be arraycopy");
+ ArrayCopyNode* ac = in->in(0)->in(0)->as_ArrayCopy();
+ assert(ac->is_clonebasic(), "Only basic clone is a non escaping clone");
+ return ac;
+ }
mem = in->in(TypeFunc::Memory);
} else {
assert(false, "unexpected projection");
}
} else if (mem->is_Store()) {
const TypePtr* atype = mem->as_Store()->adr_type();
- int adr_idx = Compile::current()->get_alias_index(atype);
+ int adr_idx = phase->C->get_alias_index(atype);
if (adr_idx == alias_idx) {
assert(atype->isa_oopptr(), "address type must be oopptr");
int adr_offset = atype->offset();
@@ -373,7 +383,7 @@
adr = mem->in(3); // Destination array
}
const TypePtr* atype = adr->bottom_type()->is_ptr();
- int adr_idx = Compile::current()->get_alias_index(atype);
+ int adr_idx = phase->C->get_alias_index(atype);
if (adr_idx == alias_idx) {
assert(false, "Object is not scalar replaceable if a LoadStore node access its field");
return NULL;
@@ -386,12 +396,63 @@
}
}
+// Generate loads from source of the arraycopy for fields of
+// destination needed at a deoptimization point
+Node* PhaseMacroExpand::make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, Node* ctl, BasicType ft, const Type *ftype, AllocateNode *alloc) {
+ BasicType bt = ft;
+ const Type *type = ftype;
+ if (ft == T_NARROWOOP) {
+ bt = T_OBJECT;
+ type = ftype->make_oopptr();
+ }
+ Node* res = NULL;
+ if (ac->is_clonebasic()) {
+ Node* base = ac->in(ArrayCopyNode::Src)->in(AddPNode::Base);
+ Node* adr = _igvn.transform(new AddPNode(base, base, MakeConX(offset)));
+ const TypePtr* adr_type = _igvn.type(base)->is_ptr()->add_offset(offset);
+ Node* m = ac->in(TypeFunc::Memory);
+ while (m->is_MergeMem()) {
+ m = m->as_MergeMem()->memory_at(C->get_alias_index(adr_type));
+ if (m->is_Proj() && m->in(0)->is_MemBar()) {
+ m = m->in(0)->in(TypeFunc::Memory);
+ }
+ }
+ res = LoadNode::make(_igvn, ctl, m, adr, adr_type, type, bt, MemNode::unordered, LoadNode::Pinned);
+ } else {
+ if (ac->modifies(offset, offset, &_igvn, true)) {
+ assert(ac->in(ArrayCopyNode::Dest) == alloc->result_cast(), "arraycopy destination should be allocation's result");
+ uint shift = exact_log2(type2aelembytes(bt));
+ Node* diff = _igvn.transform(new SubINode(ac->in(ArrayCopyNode::SrcPos), ac->in(ArrayCopyNode::DestPos)));
+#ifdef _LP64
+ diff = _igvn.transform(new ConvI2LNode(diff));
+#endif
+ diff = _igvn.transform(new LShiftXNode(diff, intcon(shift)));
+
+ Node* off = _igvn.transform(new AddXNode(MakeConX(offset), diff));
+ Node* base = ac->in(ArrayCopyNode::Src);
+ Node* adr = _igvn.transform(new AddPNode(base, base, off));
+ const TypePtr* adr_type = _igvn.type(base)->is_ptr()->add_offset(offset);
+ Node* m = ac->in(TypeFunc::Memory);
+ res = LoadNode::make(_igvn, ctl, m, adr, adr_type, type, bt, MemNode::unordered, LoadNode::Pinned);
+ }
+ }
+ if (res != NULL) {
+ res = _igvn.transform(res);
+ if (ftype->isa_narrowoop()) {
+ // PhaseMacroExpand::scalar_replacement adds DecodeN nodes
+ res = _igvn.transform(new EncodePNode(res, ftype));
+ }
+ return res;
+ }
+ return NULL;
+}
+
//
// Given a Memory Phi, compute a value Phi containing the values from stores
// on the input paths.
-// Note: this function is recursive, its depth is limied by the "level" argument
+// Note: this function is recursive, its depth is limited by the "level" argument
// Returns the computed Phi, or NULL if it cannot compute it.
-Node *PhaseMacroExpand::value_from_mem_phi(Node *mem, BasicType ft, const Type *phi_type, const TypeOopPtr *adr_t, Node *alloc, Node_Stack *value_phis, int level) {
+Node *PhaseMacroExpand::value_from_mem_phi(Node *mem, BasicType ft, const Type *phi_type, const TypeOopPtr *adr_t, AllocateNode *alloc, Node_Stack *value_phis, int level) {
assert(mem->is_Phi(), "sanity");
int alias_idx = C->get_alias_index(adr_t);
int offset = adr_t->offset();
@@ -458,6 +519,9 @@
assert(val->in(0)->is_LoadStore() || val->in(0)->Opcode() == Op_EncodeISOArray, "sanity");
assert(false, "Object is not scalar replaceable if a LoadStore node access its field");
return NULL;
+ } else if (val->is_ArrayCopy()) {
+ Node* res = make_arraycopy_load(val->as_ArrayCopy(), offset, val->in(0), ft, phi_type, alloc);
+ values.at_put(j, res);
} else {
#ifdef ASSERT
val->dump();
@@ -479,7 +543,7 @@
}
// Search the last value stored into the object's field.
-Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, Node *alloc) {
+Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, Node *sfpt_ctl, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, AllocateNode *alloc) {
assert(adr_t->is_known_instance_field(), "instance required");
int instance_id = adr_t->instance_id();
assert((uint)instance_id == alloc->_idx, "wrong allocation");
@@ -538,6 +602,8 @@
} else {
done = true;
}
+ } else if (mem->is_ArrayCopy()) {
+ done = true;
} else {
assert(false, "unexpected node");
}
@@ -562,6 +628,13 @@
value_phis.pop();
}
}
+ } else if (mem->is_ArrayCopy()) {
+ Node* ctl = mem->in(0);
+ if (sfpt_ctl->is_Proj() && sfpt_ctl->as_Proj()->is_uncommon_trap_proj(Deoptimization::Reason_none)) {
+ // pin the loads in the uncommon trap path
+ ctl = sfpt_ctl;
+ }
+ return make_arraycopy_load(mem->as_ArrayCopy(), offset, ctl, ft, ftype, alloc);
}
}
// Something go wrong.
@@ -738,6 +811,7 @@
while (safepoints.length() > 0) {
SafePointNode* sfpt = safepoints.pop();
Node* mem = sfpt->memory();
+ Node* ctl = sfpt->control();
assert(sfpt->jvms() != NULL, "missed JVMS");
// Fields of scalar objs are referenced only at the end
// of regular debuginfo at the last (youngest) JVMS.
@@ -789,7 +863,7 @@
const TypeOopPtr *field_addr_type = res_type->add_offset(offset)->isa_oopptr();
- Node *field_val = value_from_mem(mem, basic_elem_type, field_type, field_addr_type, alloc);
+ Node *field_val = value_from_mem(mem, ctl, basic_elem_type, field_type, field_addr_type, alloc);
if (field_val == NULL) {
// We weren't able to find a value for this field,
// give up on eliminating this allocation.
--- a/hotspot/src/share/vm/opto/macro.hpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/macro.hpp Sat Aug 15 02:54:18 2015 +0200
@@ -85,8 +85,8 @@
Node* length,
const TypeFunc* slow_call_type,
address slow_call_address);
- Node *value_from_mem(Node *mem, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, Node *alloc);
- Node *value_from_mem_phi(Node *mem, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, Node *alloc, Node_Stack *value_phis, int level);
+ Node *value_from_mem(Node *mem, Node *ctl, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, AllocateNode *alloc);
+ Node *value_from_mem_phi(Node *mem, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, AllocateNode *alloc, Node_Stack *value_phis, int level);
bool eliminate_boxing_node(CallStaticJavaNode *boxing);
bool eliminate_allocate_node(AllocateNode *alloc);
@@ -200,6 +200,8 @@
Node* old_eden_top, Node* new_eden_top,
Node* length);
+ Node* make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, Node* ctl, BasicType ft, const Type *ftype, AllocateNode *alloc);
+
public:
PhaseMacroExpand(PhaseIterGVN &igvn) : Phase(Macro_Expand), _igvn(igvn), _has_locks(false) {
_igvn.set_delay_transform(true);
--- a/hotspot/src/share/vm/opto/memnode.cpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/memnode.cpp Sat Aug 15 02:54:18 2015 +0200
@@ -108,37 +108,6 @@
#endif
-static bool membar_for_arraycopy_helper(const TypeOopPtr *t_oop, Node* n, PhaseTransform *phase) {
- if (n->is_Proj()) {
- n = n->in(0);
- if (n->is_Call() && n->as_Call()->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()) {
- Node* n = mem->as_MergeMem()->memory_at(Compile::AliasIdxRaw);
- if (membar_for_arraycopy_helper(t_oop, n, phase)) {
- return true;
- } else if (n->is_Phi()) {
- for (uint i = 1; i < n->req(); i++) {
- if (n->in(i) != NULL) {
- if (membar_for_arraycopy_helper(t_oop, n->in(i), 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();
@@ -183,7 +152,7 @@
}
}
} else if (proj_in->is_MemBar()) {
- if (membar_for_arraycopy(t_oop, proj_in->as_MemBar(), phase)) {
+ if (ArrayCopyNode::may_modify(t_oop, proj_in->as_MemBar(), phase)) {
break;
}
result = proj_in->in(TypeFunc::Memory);
@@ -545,35 +514,12 @@
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;
- }
+ if (ac->modifies(ld_offs_t->_lo, ld_offs_t->_hi, phase, can_see_stored_value)) {
+ return ac;
+ }
+ if (!can_see_stored_value) {
+ mem = ac->in(TypeFunc::Memory);
}
}
}
@@ -703,7 +649,7 @@
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)) {
+ if (ArrayCopyNode::may_modify(addr_t, mem->in(0)->as_MemBar(), phase)) {
break;
}
mem = mem->in(0)->in(TypeFunc::Memory);
@@ -883,18 +829,17 @@
// 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* LoadNode::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");
-
+
+ Node* ld = clone();
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");
@@ -906,9 +851,7 @@
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));
@@ -933,8 +876,10 @@
assert(ac->in(0) != NULL, "alloc must have control");
ld->set_req(0, ac->in(0));
}
- return ld;
}
+ // load depends on the tests that validate the arraycopy
+ ld->as_Load()->_depends_only_on_test = Pinned;
+ return ld;
}
return NULL;
}
--- a/hotspot/src/share/vm/opto/memnode.hpp Wed Aug 12 09:58:39 2015 +0300
+++ b/hotspot/src/share/vm/opto/memnode.hpp Sat Aug 15 02:54:18 2015 +0200
@@ -126,7 +126,6 @@
// 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);
@@ -252,6 +251,9 @@
protected:
const Type* load_array_final_field(const TypeKlassPtr *tkls,
ciKlass* klass) const;
+
+ Node* can_see_arraycopy_value(Node* st, PhaseTransform* phase) const;
+
// depends_only_on_test is almost always true, and needs to be almost always
// true to enable key hoisting & commoning optimizations. However, for the
// special case of RawPtr loads from TLS top & end, and other loads performed by
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/arraycopy/TestEliminatedArrayCopyDeopt.java Sat Aug 15 02:54:18 2015 +0200
@@ -0,0 +1,204 @@
+/*
+ * 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 8130847
+ * @summary Eliminated instance/array written to by an array copy variant must be correctly initialized when reallocated at a deopt
+ * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement TestEliminatedArrayCopyDeopt
+ *
+ */
+
+// Test that if an ArrayCopy node is eliminated because it doesn't
+// escape, then the correct field/array element values are captured so
+// on a deoptimization, when the object/array is reallocated, it is
+// correctly initialized
+
+public class TestEliminatedArrayCopyDeopt {
+
+ static class A implements Cloneable {
+ int f0;
+ int f1;
+ int f2;
+ int f3;
+ int f4;
+ int f5;
+ int f6;
+ int f7;
+ int f8;
+ int f9;
+ int f10;
+ int f11;
+ int f12;
+ int f13;
+ int f14;
+ int f15;
+
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+ }
+
+ // Clone
+ static boolean m1(A a, boolean flag) throws CloneNotSupportedException {
+ A c = (A)a.clone();
+ if (flag) {
+ // never taken branch that causes the deoptimization
+ if (c.f0 != 0x42) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Array clone
+ static int[] m2_src = null;
+ static boolean m2(boolean flag) throws CloneNotSupportedException {
+ int[] src = new int[10];
+ m2_src = src;
+ for (int i = 0; i < src.length; i++) {
+ src[i] = 0x42+i;
+ }
+ int[] c = (int[])src.clone();
+ if (flag) {
+ for (int i = 0; i < c.length; i++) {
+ if (c[i] != src[i]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // Array copy
+ static boolean m3(int[] src, boolean flag) {
+ int[] dst = new int[10];
+ System.arraycopy(src, 0, dst, 0, 10);
+ if (flag) {
+ for (int i = 0; i < dst.length; i++) {
+ if (dst[i] != src[i]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // Array copy of subrange
+ static boolean m4(int[] src, boolean flag) {
+ int[] dst = new int[10];
+ dst[0] = 0x42;
+ dst[1] = 0x42 - 1;
+ dst[2] = 0x42 - 2;
+ dst[8] = 0x42 - 8;
+ dst[9] = 0x42 - 9;
+ int src_off = 2;
+ int dst_off = 3;
+ int len = 5;
+ System.arraycopy(src, src_off, dst, dst_off, len);
+ if (flag) {
+ for (int i = 0; i < dst.length; i++) {
+ if (i >= dst_off && i < dst_off + len) {
+ if (dst[i] != src[i - dst_off + src_off]) {
+ return false;
+ }
+ } else {
+ if (dst[i] != 0x42-i) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ // Array copy with Phi
+ static boolean m5(int[] src, boolean flag1, boolean flag2) {
+ int[] dst = new int[10];
+ if (flag1) {
+ System.arraycopy(src, 0, dst, 0, 10);
+ }
+ if (flag2) {
+ for (int i = 0; i < dst.length; i++) {
+ if (dst[i] != src[i]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ static public void main(String[] args) throws Exception {
+ boolean success = true;
+ A a = new A();
+ a.f0 = 0x42;
+ for (int i = 0; i < 20000; i++) {
+ m1(a, false);
+ }
+ if (!m1(a, true)) {
+ System.out.println("m1 failed");
+ success = false;
+ }
+
+ for (int i = 0; i < 20000; i++) {
+ m2(false);
+ }
+ if (!m2(true)) {
+ System.out.println("m2 failed");
+ success = false;
+ }
+
+ int[] src = new int[10];
+ for (int i = 0; i < src.length; i++) {
+ src[i] = 0x42+i;
+ }
+
+ for (int i = 0; i < 20000; i++) {
+ m3(src, false);
+ }
+ if (!m3(src, true)) {
+ System.out.println("m3 failed");
+ success = false;
+ }
+
+ for (int i = 0; i < 20000; i++) {
+ m4(src, false);
+ }
+ if (!m4(src, true)) {
+ System.out.println("m4 failed");
+ success = false;
+ }
+
+ for (int i = 0; i < 20000; i++) {
+ m5(src, i%2 == 0, false);
+ }
+ if (!m5(src, true, true)) {
+ System.out.println("m4 failed");
+ success = false;
+ }
+
+ if (!success) {
+ throw new RuntimeException("Test failed");
+ }
+ }
+}