6695810: null oop passed to encode_heap_oop_not_null
Summary: fix several problems in C2 related to Escape Analysis and Compressed Oops.
Reviewed-by: never, jrose
--- a/hotspot/src/cpu/sparc/vm/sparc.ad Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/cpu/sparc/vm/sparc.ad Wed May 21 10:45:07 2008 -0700
@@ -5471,7 +5471,7 @@
// Load Klass Pointer
instruct loadKlass(iRegP dst, memory mem) %{
match(Set dst (LoadKlass mem));
- predicate(!n->in(MemNode::Address)->bottom_type()->is_narrow());
+ predicate(!n->in(MemNode::Address)->bottom_type()->is_ptr_to_narrowoop());
ins_cost(MEMORY_REF_COST);
size(4);
@@ -5489,7 +5489,7 @@
// Load Klass Pointer
instruct loadKlassComp(iRegP dst, memory mem) %{
match(Set dst (LoadKlass mem));
- predicate(n->in(MemNode::Address)->bottom_type()->is_narrow());
+ predicate(n->in(MemNode::Address)->bottom_type()->is_ptr_to_narrowoop());
ins_cost(MEMORY_REF_COST);
format %{ "LDUW $mem,$dst\t! compressed klass ptr" %}
--- a/hotspot/src/cpu/x86/vm/x86_64.ad Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/cpu/x86/vm/x86_64.ad Wed May 21 10:45:07 2008 -0700
@@ -6044,10 +6044,9 @@
%}
// Load Compressed Pointer
-instruct loadN(rRegN dst, memory mem, rFlagsReg cr)
+instruct loadN(rRegN dst, memory mem)
%{
match(Set dst (LoadN mem));
- effect(KILL cr);
ins_cost(125); // XXX
format %{ "movl $dst, $mem\t# compressed ptr" %}
@@ -6064,7 +6063,7 @@
instruct loadKlass(rRegP dst, memory mem)
%{
match(Set dst (LoadKlass mem));
- predicate(!n->in(MemNode::Address)->bottom_type()->is_narrow());
+ predicate(!n->in(MemNode::Address)->bottom_type()->is_ptr_to_narrowoop());
ins_cost(125); // XXX
format %{ "movq $dst, $mem\t# class" %}
@@ -6074,10 +6073,11 @@
%}
// Load Klass Pointer
-instruct loadKlassComp(rRegP dst, memory mem)
+instruct loadKlassComp(rRegP dst, memory mem, rFlagsReg cr)
%{
match(Set dst (LoadKlass mem));
- predicate(n->in(MemNode::Address)->bottom_type()->is_narrow());
+ predicate(n->in(MemNode::Address)->bottom_type()->is_ptr_to_narrowoop());
+ effect(KILL cr);
ins_cost(125); // XXX
format %{ "movl $dst, $mem\t# compressed class\n\t"
@@ -6358,8 +6358,9 @@
ins_pipe(ialu_reg);
%}
-instruct loadConN(rRegN dst, immN src) %{
+instruct loadConN(rRegN dst, immN src, rFlagsReg cr) %{
match(Set dst src);
+ effect(KILL cr);
ins_cost(125);
format %{ "movq $dst, $src\t# compressed ptr\n\t"
@@ -6633,10 +6634,9 @@
%}
// Store Compressed Pointer
-instruct storeN(memory mem, rRegN src, rFlagsReg cr)
+instruct storeN(memory mem, rRegN src)
%{
match(Set mem (StoreN mem src));
- effect(KILL cr);
ins_cost(125); // XXX
format %{ "movl $mem, $src\t# ptr" %}
--- a/hotspot/src/share/vm/opto/callnode.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/callnode.cpp Wed May 21 10:45:07 2008 -0700
@@ -637,7 +637,7 @@
}
Compile *C = phase->C;
int offset = adrInst_t->offset();
- assert(offset >= 0, "should be valid offset");
+ assert(adrInst_t->klass_is_exact() && offset >= 0, "should be valid offset");
ciKlass* adr_k = adrInst_t->klass();
assert(adr_k->is_loaded() &&
adr_k->is_java_klass() &&
@@ -674,12 +674,11 @@
ciKlass* at_k = at_ptr->klass();
if ((adrInst_t->base() == at_ptr->base()) &&
at_k->is_loaded() &&
- at_k->is_java_klass() &&
- !at_k->is_interface()) {
+ at_k->is_java_klass()) {
// If we have found an argument matching addr_t, check if the field
// at the specified offset is modified.
- int at_idx = C->get_alias_index(at_ptr->add_offset(offset)->isa_oopptr());
- if (base_idx == at_idx &&
+ if ((at_k->is_interface() || adr_k == at_k ||
+ adr_k->is_subclass_of(at_k) && !at_ptr->klass_is_exact()) &&
(bcea == NULL ||
bcea->is_arg_modified(i - TypeFunc::Parms, offset, size))) {
return true;
--- a/hotspot/src/share/vm/opto/cfgnode.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/cfgnode.cpp Wed May 21 10:45:07 2008 -0700
@@ -707,8 +707,14 @@
//------------------------split_out_instance-----------------------------------
// Split out an instance type from a bottom phi.
PhiNode* PhiNode::split_out_instance(const TypePtr* at, PhaseIterGVN *igvn) const {
- assert(type() == Type::MEMORY && (adr_type() == TypePtr::BOTTOM ||
- adr_type() == TypeRawPtr::BOTTOM) , "bottom or raw memory required");
+ const TypeOopPtr *t_oop = at->isa_oopptr();
+ assert(t_oop != NULL && t_oop->is_instance(), "expecting instance oopptr");
+ const TypePtr *t = adr_type();
+ assert(type() == Type::MEMORY &&
+ (t == TypePtr::BOTTOM || t == TypeRawPtr::BOTTOM ||
+ t->isa_oopptr() && !t->is_oopptr()->is_instance() &&
+ t->is_oopptr()->cast_to_instance(t_oop->instance_id()) == t_oop),
+ "bottom or raw memory required");
// Check if an appropriate node already exists.
Node *region = in(0);
@@ -1342,7 +1348,7 @@
Node *n = phi->in(i);
if( !n ) return NULL;
if( phase->type(n) == Type::TOP ) return NULL;
- if( n->Opcode() == Op_ConP )
+ if( n->Opcode() == Op_ConP || n->Opcode() == Op_ConN )
break;
}
if( i >= phi->req() ) // Only split for constants
--- a/hotspot/src/share/vm/opto/compile.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/compile.cpp Wed May 21 10:45:07 2008 -0700
@@ -368,7 +368,12 @@
BufferBlob* blob = BufferBlob::create("Compile::scratch_buffer", size);
// Record the buffer blob for next time.
set_scratch_buffer_blob(blob);
- guarantee(scratch_buffer_blob() != NULL, "Need BufferBlob for code generation");
+ // Have we run out of code space?
+ if (scratch_buffer_blob() == NULL) {
+ // Let CompilerBroker disable further compilations.
+ record_failure("Not enough space for scratch buffer in CodeCache");
+ return;
+ }
// Initialize the relocation buffers
relocInfo* locs_buf = (relocInfo*) blob->instructions_end() - MAX_locs_size;
@@ -1065,6 +1070,8 @@
// No constant oop pointers (such as Strings); they alias with
// unknown strings.
tj = to = TypeInstPtr::make(TypePtr::BotPTR,to->klass(),false,0,offset);
+ } else if( to->is_instance_field() ) {
+ tj = to; // Keep NotNull and klass_is_exact for instance type
} else if( ptr == TypePtr::NotNull || to->klass_is_exact() ) {
// During the 2nd round of IterGVN, NotNull castings are removed.
// Make sure the Bottom and NotNull variants alias the same.
@@ -1084,7 +1091,7 @@
} else {
ciInstanceKlass *canonical_holder = k->get_canonical_holder(offset);
if (!k->equals(canonical_holder) || tj->offset() != offset) {
- tj = to = TypeInstPtr::make(TypePtr::BotPTR, canonical_holder, false, NULL, offset, to->instance_id());
+ tj = to = TypeInstPtr::make(to->ptr(), canonical_holder, false, NULL, offset, to->instance_id());
}
}
}
--- a/hotspot/src/share/vm/opto/connode.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/connode.cpp Wed May 21 10:45:07 2008 -0700
@@ -578,8 +578,11 @@
const Type* newtype = value->bottom_type();
if (newtype == TypeNarrowOop::NULL_PTR) {
return phase->transform(new (phase->C, 1) ConPNode(TypePtr::NULL_PTR));
+ } else if (newtype->isa_narrowoop()) {
+ return phase->transform(new (phase->C, 2) DecodeNNode(value, newtype->is_narrowoop()->make_oopptr()));
} else {
- return phase->transform(new (phase->C, 2) DecodeNNode(value, newtype->is_narrowoop()->make_oopptr()));
+ ShouldNotReachHere();
+ return NULL; // to make C++ compiler happy.
}
}
@@ -617,6 +620,9 @@
}
}
+Node *EncodePNode::Ideal_DU_postCCP( PhaseCCP *ccp ) {
+ return MemNode::Ideal_common_DU_postCCP(ccp, this, in(1));
+}
//=============================================================================
//------------------------------Identity---------------------------------------
--- a/hotspot/src/share/vm/opto/connode.hpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/connode.hpp Wed May 21 10:45:07 2008 -0700
@@ -283,6 +283,7 @@
virtual uint ideal_reg() const { return Op_RegN; }
static Node* encode(PhaseGVN* phase, Node* value);
+ virtual Node *Ideal_DU_postCCP( PhaseCCP *ccp );
};
//------------------------------DecodeN--------------------------------
--- a/hotspot/src/share/vm/opto/escape.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/escape.cpp Wed May 21 10:45:07 2008 -0700
@@ -888,6 +888,23 @@
record_for_optimizer(n);
if (alloc->is_Allocate() && ptn->_scalar_replaceable &&
(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.
+ for (uint e = 0; e < ptn->edge_count(); e++) {
+ Node *use = _nodes->adr_at(ptn->edge_target(e))->_node;
+ assert(ptn->edge_type(e) == PointsToNode::FieldEdge && use->is_AddP(),
+ "only AddP nodes are Field edges in CG");
+ if (use->outcnt() > 0) { // Don't process dead nodes
+ Node* addp2 = find_second_addp(use, use->in(AddPNode::Base));
+ if (addp2 != NULL) {
+ assert(alloc->is_AllocateArray(),"array allocation was expected");
+ alloc_worklist.append_if_missing(addp2);
+ }
+ alloc_worklist.append_if_missing(use);
+ }
+ }
+
// An allocation may have an Initialize which has raw stores. Scan
// the users of the raw allocation result and push AddP users
// on alloc_worklist.
@@ -919,6 +936,8 @@
tinst = igvn->type(base)->isa_oopptr();
} else if (n->is_Phi() ||
n->is_CheckCastPP() ||
+ n->Opcode() == Op_EncodeP ||
+ n->Opcode() == Op_DecodeN ||
(n->is_ConstraintCast() && n->Opcode() == Op_CastPP)) {
if (visited.test_set(n->_idx)) {
assert(n->is_Phi(), "loops only through Phi's");
@@ -935,13 +954,25 @@
tinst = igvn->type(val)->isa_oopptr();
assert(tinst != NULL && tinst->is_instance() &&
tinst->instance_id() == elem , "instance type expected.");
- const TypeOopPtr *tn_t = igvn->type(tn)->isa_oopptr();
+
+ const TypeOopPtr *tn_t = NULL;
+ const Type *tn_type = igvn->type(tn);
+ if (tn_type->isa_narrowoop()) {
+ tn_t = tn_type->is_narrowoop()->make_oopptr()->isa_oopptr();
+ } else {
+ tn_t = tn_type->isa_oopptr();
+ }
if (tn_t != NULL &&
tinst->cast_to_instance(TypeOopPtr::UNKNOWN_INSTANCE)->higher_equal(tn_t)) {
+ if (tn_type->isa_narrowoop()) {
+ tn_type = tinst->make_narrowoop();
+ } else {
+ tn_type = tinst;
+ }
igvn->hash_delete(tn);
- igvn->set_type(tn, tinst);
- tn->set_type(tinst);
+ igvn->set_type(tn, tn_type);
+ tn->set_type(tn_type);
igvn->hash_insert(tn);
record_for_optimizer(n);
}
@@ -978,6 +1009,8 @@
alloc_worklist.append_if_missing(use);
} else if (use->is_Phi() ||
use->is_CheckCastPP() ||
+ use->Opcode() == Op_EncodeP ||
+ use->Opcode() == Op_DecodeN ||
(use->is_ConstraintCast() && use->Opcode() == Op_CastPP)) {
alloc_worklist.append_if_missing(use);
}
@@ -1199,7 +1232,7 @@
void ConnectionGraph::compute_escape() {
- // 1. Populate Connection Graph with Ideal nodes.
+ // 1. Populate Connection Graph (CG) with Ideal nodes.
Unique_Node_List worklist_init;
worklist_init.map(_compile->unique(), NULL); // preallocate space
@@ -1281,11 +1314,13 @@
remove_deferred(ni, &deferred_edges, &visited);
if (n->is_AddP()) {
// If this AddP computes an address which may point to more that one
- // object, nothing the address points to can be scalar replaceable.
+ // object or more then one field (array's element), nothing the address
+ // points to can be scalar replaceable.
Node *base = get_addp_base(n);
ptset.Clear();
PointsTo(ptset, base, igvn);
- if (ptset.Size() > 1) {
+ if (ptset.Size() > 1 ||
+ (ptset.Size() != 0 && ptn->offset() == Type::OffsetBot)) {
for( VectorSetI j(&ptset); j.test(); ++j ) {
uint pt = j.elem;
ptnode_adr(pt)->_scalar_replaceable = false;
@@ -1979,6 +2014,11 @@
assert(false, "Op_ConP");
break;
}
+ case Op_ConN:
+ {
+ assert(false, "Op_ConN");
+ break;
+ }
case Op_CreateEx:
{
assert(false, "Op_CreateEx");
--- a/hotspot/src/share/vm/opto/library_call.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/library_call.cpp Wed May 21 10:45:07 2008 -0700
@@ -2168,7 +2168,7 @@
// (They don't if CAS fails, but it isn't worth checking.)
pre_barrier(control(), base, adr, alias_idx, newval, value_type, T_OBJECT);
#ifdef _LP64
- if (adr->bottom_type()->is_narrow()) {
+ if (adr->bottom_type()->is_ptr_to_narrowoop()) {
cas = _gvn.transform(new (C, 5) CompareAndSwapNNode(control(), mem, adr,
EncodePNode::encode(&_gvn, newval),
EncodePNode::encode(&_gvn, oldval)));
@@ -2838,6 +2838,8 @@
_sp += nargs; // set original stack for use by uncommon_trap
mirror = do_null_check(mirror, T_OBJECT);
_sp -= nargs;
+ // If mirror or obj is dead, only null-path is taken.
+ if (stopped()) return true;
enum { _normal_path = 1, _slow_path = 2, PATH_LIMIT };
RegionNode* result_reg = new(C, PATH_LIMIT) RegionNode(PATH_LIMIT);
@@ -3827,24 +3829,22 @@
if (!stopped()) {
// Copy the fastest available way.
// (No need for PreserveJVMState, since we're using it all up now.)
+ // TODO: generate fields/elements copies for small objects instead.
Node* src = obj;
Node* dest = raw_obj;
- Node* end = dest;
Node* size = _gvn.transform(alloc_siz);
// Exclude the header.
int base_off = instanceOopDesc::base_offset_in_bytes();
if (UseCompressedOops) {
- // copy the header gap though.
- Node* sptr = basic_plus_adr(src, base_off);
- Node* dptr = basic_plus_adr(dest, base_off);
- Node* sval = make_load(control(), sptr, TypeInt::INT, T_INT, raw_adr_type);
- store_to_memory(control(), dptr, sval, T_INT, raw_adr_type);
- base_off += sizeof(int);
+ assert(base_off % BytesPerLong != 0, "base with compressed oops");
+ // With compressed oops base_offset_in_bytes is 12 which creates
+ // the gap since countx is rounded by 8 bytes below.
+ // Copy klass and the gap.
+ base_off = instanceOopDesc::klass_offset_in_bytes();
}
src = basic_plus_adr(src, base_off);
dest = basic_plus_adr(dest, base_off);
- end = basic_plus_adr(end, size);
// Compute the length also, if needed:
Node* countx = size;
--- a/hotspot/src/share/vm/opto/macro.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/macro.cpp Wed May 21 10:45:07 2008 -0700
@@ -1282,12 +1282,6 @@
}
rawmem = make_store(control, rawmem, object, oopDesc::mark_offset_in_bytes(), mark_node, T_ADDRESS);
- if (UseCompressedOops) {
- Node *zeronode = makecon(TypeInt::ZERO);
- // store uncompressed 0 into klass ptr to zero out gap. The gap is
- // used for primitive fields and has to be zeroed.
- rawmem = make_store(control, rawmem, object, oopDesc::klass_gap_offset_in_bytes(), zeronode, T_INT);
- }
rawmem = make_store(control, rawmem, object, oopDesc::klass_offset_in_bytes(), klass_node, T_OBJECT);
int header_size = alloc->minimum_header_size(); // conservatively small
--- a/hotspot/src/share/vm/opto/matcher.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/matcher.cpp Wed May 21 10:45:07 2008 -0700
@@ -880,7 +880,7 @@
Node *m = n->in(i); // Get input
int op = m->Opcode();
assert((op == Op_BoxLock) == jvms->is_monitor_use(i), "boxes only at monitor sites");
- if( op == Op_ConI || op == Op_ConP ||
+ if( op == Op_ConI || op == Op_ConP || op == Op_ConN ||
op == Op_ConF || op == Op_ConD || op == Op_ConL
// || op == Op_BoxLock // %%%% enable this and remove (+++) in chaitin.cpp
) {
@@ -1726,6 +1726,14 @@
}
break;
}
+ case Op_ConN: { // Convert narrow pointers above the centerline to NUL
+ TypeNode *tn = n->as_Type(); // Constants derive from type nodes
+ const TypePtr* tp = tn->type()->is_narrowoop()->make_oopptr();
+ if (tp->_ptr == TypePtr::AnyNull) {
+ tn->set_type(TypeNarrowOop::NULL_PTR);
+ }
+ break;
+ }
case Op_Binary: // These are introduced in the Post_Visit state.
ShouldNotReachHere();
break;
--- a/hotspot/src/share/vm/opto/memnode.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/memnode.cpp Wed May 21 10:45:07 2008 -0700
@@ -133,7 +133,9 @@
PhiNode *mphi = result->as_Phi();
assert(mphi->bottom_type() == Type::MEMORY, "memory phi required");
const TypePtr *t = mphi->adr_type();
- if (t == TypePtr::BOTTOM || t == TypeRawPtr::BOTTOM) {
+ if (t == TypePtr::BOTTOM || t == TypeRawPtr::BOTTOM ||
+ t->isa_oopptr() && !t->is_oopptr()->is_instance() &&
+ t->is_oopptr()->cast_to_instance(t_oop->instance_id()) == t_oop) {
// clone the Phi with our address type
result = mphi->split_out_instance(t_adr, igvn);
} else {
@@ -263,7 +265,10 @@
// of all its inputs dominate or equal to sub's control edge.
// Currently 'sub' is either Allocate, Initialize or Start nodes.
- assert(sub->is_Allocate() || sub->is_Initialize() || sub->is_Start(), "expecting only these nodes");
+ // Or Region for the check in LoadNode::Ideal();
+ // 'sub' should have sub->in(0) != NULL.
+ assert(sub->is_Allocate() || sub->is_Initialize() || sub->is_Start() ||
+ sub->is_Region(), "expecting only these nodes");
// Get control edge of 'sub'.
sub = sub->find_exact_control(sub->in(0));
@@ -576,6 +581,9 @@
// Find any cast-away of null-ness and keep its control. Null cast-aways are
// going away in this pass and we need to make this memory op depend on the
// gating null check.
+Node *MemNode::Ideal_DU_postCCP( PhaseCCP *ccp ) {
+ return Ideal_common_DU_postCCP(ccp, this, in(MemNode::Address));
+}
// I tried to leave the CastPP's in. This makes the graph more accurate in
// some sense; we get to keep around the knowledge that an oop is not-null
@@ -585,15 +593,14 @@
// some of the more trivial cases in the optimizer. Removing more useless
// Phi's started allowing Loads to illegally float above null checks. I gave
// up on this approach. CNC 10/20/2000
-Node *MemNode::Ideal_DU_postCCP( PhaseCCP *ccp ) {
- Node *ctr = in(MemNode::Control);
- Node *mem = in(MemNode::Memory);
- Node *adr = in(MemNode::Address);
+// This static method may be called not from MemNode (EncodePNode calls it).
+// Only the control edge of the node 'n' might be updated.
+Node *MemNode::Ideal_common_DU_postCCP( PhaseCCP *ccp, Node* n, Node* adr ) {
Node *skipped_cast = NULL;
// Need a null check? Regular static accesses do not because they are
// from constant addresses. Array ops are gated by the range check (which
// always includes a NULL check). Just check field ops.
- if( !ctr ) {
+ if( n->in(MemNode::Control) == NULL ) {
// Scan upwards for the highest location we can place this memory op.
while( true ) {
switch( adr->Opcode() ) {
@@ -618,10 +625,10 @@
}
// CastPP is going away in this pass! We need this memory op to be
// control-dependent on the test that is guarding the CastPP.
- ccp->hash_delete(this);
- set_req(MemNode::Control, adr->in(0));
- ccp->hash_insert(this);
- return this;
+ ccp->hash_delete(n);
+ n->set_req(MemNode::Control, adr->in(0));
+ ccp->hash_insert(n);
+ return n;
case Op_Phi:
// Attempt to float above a Phi to some dominating point.
@@ -652,10 +659,10 @@
adr = adr->in(1);
continue;
}
- ccp->hash_delete(this);
- set_req(MemNode::Control, adr->in(0));
- ccp->hash_insert(this);
- return this;
+ ccp->hash_delete(n);
+ n->set_req(MemNode::Control, adr->in(0));
+ ccp->hash_insert(n);
+ return n;
// List of "safe" opcodes; those that implicitly block the memory
// op below any null check.
@@ -665,6 +672,7 @@
case Op_LoadN: // Loading from within a klass
case Op_LoadKlass: // Loading from within a klass
case Op_ConP: // Loading from a klass
+ case Op_ConN: // Loading from a klass
case Op_CreateEx: // Sucking up the guts of an exception oop
case Op_Con: // Reading from TLS
case Op_CMoveP: // CMoveP is pinned
@@ -676,8 +684,8 @@
{
assert(adr->as_Proj()->_con == TypeFunc::Parms, "must be return value");
const Node* call = adr->in(0);
- if (call->is_CallStaticJava()) {
- const CallStaticJavaNode* call_java = call->as_CallStaticJava();
+ if (call->is_CallJava()) {
+ const CallJavaNode* call_java = call->as_CallJava();
const TypeTuple *r = call_java->tf()->range();
assert(r->cnt() > TypeFunc::Parms, "must return value");
const Type* ret_type = r->field_at(TypeFunc::Parms);
@@ -749,7 +757,7 @@
case T_ADDRESS: return new (C, 3) LoadPNode(ctl, mem, adr, adr_type, rt->is_ptr() );
case T_OBJECT:
#ifdef _LP64
- if (adr->bottom_type()->is_narrow()) {
+ if (adr->bottom_type()->is_ptr_to_narrowoop()) {
const TypeNarrowOop* narrowtype;
if (rt->isa_narrowoop()) {
narrowtype = rt->is_narrowoop();
@@ -761,10 +769,10 @@
return DecodeNNode::decode(&gvn, load);
} else
#endif
- {
- assert(!adr->bottom_type()->is_narrow(), "should have got back a narrow oop");
- return new (C, 3) LoadPNode(ctl, mem, adr, adr_type, rt->is_oopptr());
- }
+ {
+ assert(!adr->bottom_type()->is_ptr_to_narrowoop(), "should have got back a narrow oop");
+ return new (C, 3) LoadPNode(ctl, mem, adr, adr_type, rt->is_oopptr());
+ }
}
ShouldNotReachHere();
return (LoadNode*)NULL;
@@ -1118,6 +1126,127 @@
return NULL;
}
+//------------------------------split_through_phi------------------------------
+// Split instance field load through Phi.
+Node *LoadNode::split_through_phi(PhaseGVN *phase) {
+ Node* mem = in(MemNode::Memory);
+ Node* address = in(MemNode::Address);
+ const TypePtr *addr_t = phase->type(address)->isa_ptr();
+ const TypeOopPtr *t_oop = addr_t->isa_oopptr();
+
+ assert(mem->is_Phi() && (t_oop != NULL) &&
+ t_oop->is_instance_field(), "invalide conditions");
+
+ Node *region = mem->in(0);
+ if (region == NULL) {
+ return NULL; // Wait stable graph
+ }
+ uint cnt = mem->req();
+ for( uint i = 1; i < cnt; i++ ) {
+ Node *in = mem->in(i);
+ if( in == NULL ) {
+ return NULL; // Wait stable graph
+ }
+ }
+ // Check for loop invariant.
+ if (cnt == 3) {
+ for( uint i = 1; i < cnt; i++ ) {
+ Node *in = mem->in(i);
+ Node* m = MemNode::optimize_memory_chain(in, addr_t, phase);
+ if (m == mem) {
+ set_req(MemNode::Memory, mem->in(cnt - i)); // Skip this phi.
+ return this;
+ }
+ }
+ }
+ // Split through Phi (see original code in loopopts.cpp).
+ assert(phase->C->have_alias_type(addr_t), "instance should have alias type");
+
+ // Do nothing here if Identity will find a value
+ // (to avoid infinite chain of value phis generation).
+ if ( !phase->eqv(this, this->Identity(phase)) )
+ return NULL;
+
+ // Skip the split if the region dominates some control edge of the address.
+ if (cnt == 3 && !MemNode::all_controls_dominate(address, region))
+ return NULL;
+
+ const Type* this_type = this->bottom_type();
+ int this_index = phase->C->get_alias_index(addr_t);
+ int this_offset = addr_t->offset();
+ int this_iid = addr_t->is_oopptr()->instance_id();
+ int wins = 0;
+ PhaseIterGVN *igvn = phase->is_IterGVN();
+ Node *phi = new (igvn->C, region->req()) PhiNode(region, this_type, NULL, this_iid, this_index, this_offset);
+ for( uint i = 1; i < region->req(); i++ ) {
+ Node *x;
+ Node* the_clone = NULL;
+ if( region->in(i) == phase->C->top() ) {
+ x = phase->C->top(); // Dead path? Use a dead data op
+ } else {
+ x = this->clone(); // Else clone up the data op
+ the_clone = x; // Remember for possible deletion.
+ // Alter data node to use pre-phi inputs
+ if( this->in(0) == region ) {
+ x->set_req( 0, region->in(i) );
+ } else {
+ x->set_req( 0, NULL );
+ }
+ for( uint j = 1; j < this->req(); j++ ) {
+ Node *in = this->in(j);
+ if( in->is_Phi() && in->in(0) == region )
+ x->set_req( j, in->in(i) ); // Use pre-Phi input for the clone
+ }
+ }
+ // Check for a 'win' on some paths
+ const Type *t = x->Value(igvn);
+
+ bool singleton = t->singleton();
+
+ // See comments in PhaseIdealLoop::split_thru_phi().
+ if( singleton && t == Type::TOP ) {
+ singleton &= region->is_Loop() && (i != LoopNode::EntryControl);
+ }
+
+ if( singleton ) {
+ wins++;
+ x = igvn->makecon(t);
+ } else {
+ // We now call Identity to try to simplify the cloned node.
+ // Note that some Identity methods call phase->type(this).
+ // Make sure that the type array is big enough for
+ // our new node, even though we may throw the node away.
+ // (This tweaking with igvn only works because x is a new node.)
+ igvn->set_type(x, t);
+ Node *y = x->Identity(igvn);
+ if( y != x ) {
+ wins++;
+ x = y;
+ } else {
+ y = igvn->hash_find(x);
+ if( y ) {
+ wins++;
+ x = y;
+ } else {
+ // Else x is a new node we are keeping
+ // We do not need register_new_node_with_optimizer
+ // because set_type has already been called.
+ igvn->_worklist.push(x);
+ }
+ }
+ }
+ if (x != the_clone && the_clone != NULL)
+ igvn->remove_dead_node(the_clone);
+ phi->set_req(i, x);
+ }
+ if( wins > 0 ) {
+ // Record Phi
+ igvn->register_new_node_with_optimizer(phi);
+ return phi;
+ }
+ igvn->remove_dead_node(phi);
+ return NULL;
+}
//------------------------------Ideal------------------------------------------
// If the load is from Field memory and the pointer is non-null, we can
@@ -1175,112 +1304,9 @@
const TypeOopPtr *t_oop = addr_t->isa_oopptr();
if (can_reshape && opt_mem->is_Phi() &&
(t_oop != NULL) && t_oop->is_instance_field()) {
- assert(t_oop->offset() != Type::OffsetBot && t_oop->offset() != Type::OffsetTop, "");
- Node *region = opt_mem->in(0);
- uint cnt = opt_mem->req();
- for( uint i = 1; i < cnt; i++ ) {
- Node *in = opt_mem->in(i);
- if( in == NULL ) {
- region = NULL; // Wait stable graph
- break;
- }
- }
- if (region != NULL) {
- // Check for loop invariant.
- if (cnt == 3) {
- for( uint i = 1; i < cnt; i++ ) {
- Node *in = opt_mem->in(i);
- Node* m = MemNode::optimize_memory_chain(in, addr_t, phase);
- if (m == opt_mem) {
- set_req(MemNode::Memory, opt_mem->in(cnt - i)); // Skip this phi.
- return this;
- }
- }
- }
- // Split through Phi (see original code in loopopts.cpp).
- assert(phase->C->have_alias_type(addr_t), "instance should have alias type");
-
- // Do nothing here if Identity will find a value
- // (to avoid infinite chain of value phis generation).
- if ( !phase->eqv(this, this->Identity(phase)) )
- return NULL;
-
- const Type* this_type = this->bottom_type();
- int this_index = phase->C->get_alias_index(addr_t);
- int this_offset = addr_t->offset();
- int this_iid = addr_t->is_oopptr()->instance_id();
- int wins = 0;
- PhaseIterGVN *igvn = phase->is_IterGVN();
- Node *phi = new (igvn->C, region->req()) PhiNode(region, this_type, NULL, this_iid, this_index, this_offset);
- for( uint i = 1; i < region->req(); i++ ) {
- Node *x;
- Node* the_clone = NULL;
- if( region->in(i) == phase->C->top() ) {
- x = phase->C->top(); // Dead path? Use a dead data op
- } else {
- x = this->clone(); // Else clone up the data op
- the_clone = x; // Remember for possible deletion.
- // Alter data node to use pre-phi inputs
- if( this->in(0) == region ) {
- x->set_req( 0, region->in(i) );
- } else {
- x->set_req( 0, NULL );
- }
- for( uint j = 1; j < this->req(); j++ ) {
- Node *in = this->in(j);
- if( in->is_Phi() && in->in(0) == region )
- x->set_req( j, in->in(i) ); // Use pre-Phi input for the clone
- }
- }
- // Check for a 'win' on some paths
- const Type *t = x->Value(igvn);
-
- bool singleton = t->singleton();
-
- // See comments in PhaseIdealLoop::split_thru_phi().
- if( singleton && t == Type::TOP ) {
- singleton &= region->is_Loop() && (i != LoopNode::EntryControl);
- }
-
- if( singleton ) {
- wins++;
- x = igvn->makecon(t);
- } else {
- // We now call Identity to try to simplify the cloned node.
- // Note that some Identity methods call phase->type(this).
- // Make sure that the type array is big enough for
- // our new node, even though we may throw the node away.
- // (This tweaking with igvn only works because x is a new node.)
- igvn->set_type(x, t);
- Node *y = x->Identity(igvn);
- if( y != x ) {
- wins++;
- x = y;
- } else {
- y = igvn->hash_find(x);
- if( y ) {
- wins++;
- x = y;
- } else {
- // Else x is a new node we are keeping
- // We do not need register_new_node_with_optimizer
- // because set_type has already been called.
- igvn->_worklist.push(x);
- }
- }
- }
- if (x != the_clone && the_clone != NULL)
- igvn->remove_dead_node(the_clone);
- phi->set_req(i, x);
- }
- if( wins > 0 ) {
- // Record Phi
- igvn->register_new_node_with_optimizer(phi);
- return phi;
- } else {
- igvn->remove_dead_node(phi);
- }
- }
+ // Split instance field load through Phi.
+ Node* result = split_through_phi(phase);
+ if (result != NULL) return result;
}
}
@@ -1835,7 +1861,7 @@
case T_ADDRESS:
case T_OBJECT:
#ifdef _LP64
- if (adr->bottom_type()->is_narrow() ||
+ if (adr->bottom_type()->is_ptr_to_narrowoop() ||
(UseCompressedOops && val->bottom_type()->isa_klassptr() &&
adr->bottom_type()->isa_rawptr())) {
const TypePtr* type = val->bottom_type()->is_ptr();
--- a/hotspot/src/share/vm/opto/memnode.hpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/memnode.hpp Wed May 21 10:45:07 2008 -0700
@@ -72,7 +72,8 @@
// This one should probably be a phase-specific function:
static bool all_controls_dominate(Node* dom, Node* sub);
- // Is this Node a MemNode or some descendent? Default is YES.
+ // Find any cast-away of null-ness and keep its control.
+ static Node *Ideal_common_DU_postCCP( PhaseCCP *ccp, Node* n, Node* adr );
virtual Node *Ideal_DU_postCCP( PhaseCCP *ccp );
virtual const class TypePtr *adr_type() const; // returns bottom_type of address
@@ -150,6 +151,9 @@
// zero out the control input.
virtual Node *Ideal(PhaseGVN *phase, bool can_reshape);
+ // Split instance field load through Phi.
+ Node* split_through_phi(PhaseGVN *phase);
+
// Recover original value from boxed values
Node *eliminate_autobox(PhaseGVN *phase);
--- a/hotspot/src/share/vm/opto/node.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/node.cpp Wed May 21 10:45:07 2008 -0700
@@ -1049,51 +1049,80 @@
Node* orig_sub = sub;
nlist.clear();
bool this_dominates = false;
- uint region_input = 0;
+ bool result = false; // Conservative answer
+
while (sub != NULL) { // walk 'sub' up the chain to 'this'
if (sub == this) {
if (nlist.size() == 0) {
// No Region nodes except loops were visited before and the EntryControl
// path was taken for loops: it did not walk in a cycle.
- return true;
- } else if (!this_dominates) {
+ result = true;
+ break;
+ } else if (this_dominates) {
+ result = false; // already met before: walk in a cycle
+ break;
+ } else {
// Region nodes were visited. Continue walk up to Start or Root
// to make sure that it did not walk in a cycle.
this_dominates = true; // first time meet
iterations_without_region_limit = DominatorSearchLimit; // Reset
- } else {
- return false; // already met before: walk in a cycle
- }
+ }
}
- if (sub->is_Start() || sub->is_Root())
- return this_dominates;
-
+ if (sub->is_Start() || sub->is_Root()) {
+ result = this_dominates;
+ break;
+ }
Node* up = sub->find_exact_control(sub->in(0));
- if (up == NULL || up->is_top())
- return false; // Conservative answer for dead code
+ if (up == NULL || up->is_top()) {
+ result = false; // Conservative answer for dead code
+ break;
+ }
+ if (sub == up && (sub->is_Loop() || sub->is_Region() && sub->req() != 3)) {
+ // Take first valid path on the way up to 'this'.
+ up = sub->in(1); // in(LoopNode::EntryControl);
+ } else if (sub == up && sub->is_Region()) {
+ assert(sub->req() == 3, "sanity");
+ iterations_without_region_limit = DominatorSearchLimit; // Reset
- if (sub == up && sub->is_Loop()) {
- up = sub->in(1); // in(LoopNode::EntryControl);
- } else if (sub == up && sub->is_Region() && sub->req() == 3) {
- iterations_without_region_limit = DominatorSearchLimit; // Reset
+ // Try both paths for such Regions.
+ // It is not accurate without regions dominating information.
+ // With such information the other path should be checked for
+ // the most dominating Region which was visited before.
+ bool region_was_visited_before = false;
uint i = 1;
uint size = nlist.size();
if (size == 0) {
- // No Region nodes (except Loops) were visited before.
+ // No such Region nodes were visited before.
// Take first valid path on the way up to 'this'.
- } else if (nlist.at(size - 1) == sub) {
- // This Region node was just visited. Take other path.
- i = region_input + 1;
- nlist.pop();
} else {
// Was this Region node visited before?
- for (uint j = 0; j < size; j++) {
- if (nlist.at(j) == sub) {
- return false; // The Region node was visited before. Give up.
+ intptr_t ni;
+ int j = size - 1;
+ for (; j >= 0; j--) {
+ ni = (intptr_t)nlist.at(j);
+ if ((Node*)(ni & ~1) == sub) {
+ if ((ni & 1) != 0) {
+ break; // Visited 2 paths. Give up.
+ } else {
+ // The Region node was visited before only once.
+ nlist.remove(j);
+ region_was_visited_before = true;
+ for (; i < sub->req(); i++) {
+ Node* in = sub->in(i);
+ if (in != NULL && !in->is_top() && in != sub) {
+ break;
+ }
+ }
+ i++; // Take other path.
+ break;
+ }
}
}
+ if (j >= 0 && (ni & 1) != 0) {
+ result = false; // Visited 2 paths. Give up.
+ break;
+ }
// The Region node was not visited before.
- // Take first valid path on the way up to 'this'.
}
for (; i < sub->req(); i++) {
Node* in = sub->in(i);
@@ -1102,20 +1131,26 @@
}
}
if (i < sub->req()) {
- nlist.push(sub);
up = sub->in(i);
- region_input = i;
+ if (region_was_visited_before && sub != up) {
+ // Set 0 bit to indicate that both paths were taken.
+ nlist.push((Node*)((intptr_t)sub + 1));
+ } else {
+ nlist.push(sub);
+ }
}
}
- if (sub == up)
- return false; // some kind of tight cycle
-
- if (--iterations_without_region_limit < 0)
- return false; // dead cycle
-
+ if (sub == up) {
+ result = false; // some kind of tight cycle
+ break;
+ }
+ if (--iterations_without_region_limit < 0) {
+ result = false; // dead cycle
+ break;
+ }
sub = up;
}
- return false;
+ return result;
}
//------------------------------remove_dead_region-----------------------------
--- a/hotspot/src/share/vm/opto/output.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/output.cpp Wed May 21 10:45:07 2008 -0700
@@ -48,6 +48,7 @@
// Initialize the space for the BufferBlob used to find and verify
// instruction size in MachNode::emit_size()
init_scratch_buffer_blob();
+ if (failing()) return; // Out of memory
// Make sure I can find the Start Node
Block_Array& bbs = _cfg->_bbs;
--- a/hotspot/src/share/vm/opto/type.cpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/type.cpp Wed May 21 10:45:07 2008 -0700
@@ -311,8 +311,18 @@
mreg2type[Op_RegFlags] = TypeInt::CC;
TypeAryPtr::RANGE = TypeAryPtr::make( TypePtr::BotPTR, TypeAry::make(Type::BOTTOM,TypeInt::POS), current->env()->Object_klass(), false, arrayOopDesc::length_offset_in_bytes());
- // There is no shared klass for Object[]. See note in TypeAryPtr::klass().
- TypeAryPtr::OOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM,TypeInt::POS), NULL /*ciArrayKlass::make(o)*/, false, Type::OffsetBot);
+
+ TypeAryPtr::NARROWOOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeNarrowOop::BOTTOM, TypeInt::POS), NULL /*ciArrayKlass::make(o)*/, false, Type::OffsetBot);
+
+#ifdef _LP64
+ if (UseCompressedOops) {
+ TypeAryPtr::OOPS = TypeAryPtr::NARROWOOPS;
+ } else
+#endif
+ {
+ // There is no shared klass for Object[]. See note in TypeAryPtr::klass().
+ TypeAryPtr::OOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM,TypeInt::POS), NULL /*ciArrayKlass::make(o)*/, false, Type::OffsetBot);
+ }
TypeAryPtr::BYTES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::BYTE ,TypeInt::POS), ciTypeArrayKlass::make(T_BYTE), true, Type::OffsetBot);
TypeAryPtr::SHORTS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::SHORT ,TypeInt::POS), ciTypeArrayKlass::make(T_SHORT), true, Type::OffsetBot);
TypeAryPtr::CHARS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::CHAR ,TypeInt::POS), ciTypeArrayKlass::make(T_CHAR), true, Type::OffsetBot);
@@ -321,9 +331,10 @@
TypeAryPtr::FLOATS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::FLOAT ,TypeInt::POS), ciTypeArrayKlass::make(T_FLOAT), true, Type::OffsetBot);
TypeAryPtr::DOUBLES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::DOUBLE ,TypeInt::POS), ciTypeArrayKlass::make(T_DOUBLE), true, Type::OffsetBot);
- TypeAryPtr::_array_body_type[T_NARROWOOP] = NULL; // what should this be?
+ // Nobody should ask _array_body_type[T_NARROWOOP]. Use NULL as assert.
+ TypeAryPtr::_array_body_type[T_NARROWOOP] = NULL;
TypeAryPtr::_array_body_type[T_OBJECT] = TypeAryPtr::OOPS;
- TypeAryPtr::_array_body_type[T_ARRAY] = TypeAryPtr::OOPS; // arrays are stored in oop arrays
+ TypeAryPtr::_array_body_type[T_ARRAY] = TypeAryPtr::OOPS; // arrays are stored in oop arrays
TypeAryPtr::_array_body_type[T_BYTE] = TypeAryPtr::BYTES;
TypeAryPtr::_array_body_type[T_BOOLEAN] = TypeAryPtr::BYTES; // boolean[] is a byte array
TypeAryPtr::_array_body_type[T_SHORT] = TypeAryPtr::SHORTS;
@@ -696,7 +707,7 @@
ResourceMark rm;
Dict d(cmpkey,hashkey); // Stop recursive type dumping
dump2(d,1, st);
- if (isa_ptr() && is_ptr()->is_narrow()) {
+ if (is_ptr_to_narrowoop()) {
st->print(" [narrow]");
}
}
@@ -2146,6 +2157,67 @@
// Convenience common pre-built type.
const TypeOopPtr *TypeOopPtr::BOTTOM;
+//------------------------------TypeOopPtr-------------------------------------
+TypeOopPtr::TypeOopPtr( TYPES t, PTR ptr, ciKlass* k, bool xk, ciObject* o, int offset, int instance_id )
+ : TypePtr(t, ptr, offset),
+ _const_oop(o), _klass(k),
+ _klass_is_exact(xk),
+ _is_ptr_to_narrowoop(false),
+ _instance_id(instance_id) {
+#ifdef _LP64
+ if (UseCompressedOops && _offset != 0) {
+ if (klass() == NULL) {
+ assert(this->isa_aryptr(), "only arrays without klass");
+ _is_ptr_to_narrowoop = true;
+ } else if (_offset == oopDesc::klass_offset_in_bytes()) {
+ _is_ptr_to_narrowoop = true;
+ } else if (this->isa_aryptr()) {
+ _is_ptr_to_narrowoop = (klass()->is_obj_array_klass() &&
+ _offset != arrayOopDesc::length_offset_in_bytes());
+ } else if (klass() == ciEnv::current()->Class_klass() &&
+ (_offset == java_lang_Class::klass_offset_in_bytes() ||
+ _offset == java_lang_Class::array_klass_offset_in_bytes())) {
+ // Special hidden fields from the Class.
+ assert(this->isa_instptr(), "must be an instance ptr.");
+ _is_ptr_to_narrowoop = true;
+ } else if (klass()->is_instance_klass()) {
+ ciInstanceKlass* ik = klass()->as_instance_klass();
+ ciField* field = NULL;
+ if (this->isa_klassptr()) {
+ // Perm objects don't use compressed references, except for
+ // static fields which are currently compressed.
+ field = ik->get_field_by_offset(_offset, true);
+ if (field != NULL) {
+ BasicType basic_elem_type = field->layout_type();
+ _is_ptr_to_narrowoop = (basic_elem_type == T_OBJECT ||
+ basic_elem_type == T_ARRAY);
+ }
+ } else if (_offset == OffsetBot || _offset == OffsetTop) {
+ // unsafe access
+ _is_ptr_to_narrowoop = true;
+ } else { // exclude unsafe ops
+ assert(this->isa_instptr(), "must be an instance ptr.");
+ // Field which contains a compressed oop references.
+ field = ik->get_field_by_offset(_offset, false);
+ if (field != NULL) {
+ BasicType basic_elem_type = field->layout_type();
+ _is_ptr_to_narrowoop = (basic_elem_type == T_OBJECT ||
+ basic_elem_type == T_ARRAY);
+ } else if (klass()->equals(ciEnv::current()->Object_klass())) {
+ // Compile::find_alias_type() cast exactness on all types to verify
+ // that it does not affect alias type.
+ _is_ptr_to_narrowoop = true;
+ } else {
+ // Type for the copy start in LibraryCallKit::inline_native_clone().
+ assert(!klass_is_exact(), "only non-exact klass");
+ _is_ptr_to_narrowoop = true;
+ }
+ }
+ }
+ }
+#endif
+}
+
//------------------------------make-------------------------------------------
const TypeOopPtr *TypeOopPtr::make(PTR ptr,
int offset) {
@@ -2593,9 +2665,13 @@
//-----------------------------cast_to_instance-------------------------------
const TypeOopPtr *TypeInstPtr::cast_to_instance(int instance_id) const {
if( instance_id == _instance_id) return this;
- bool exact = (instance_id == UNKNOWN_INSTANCE) ? _klass_is_exact : true;
-
- return make(ptr(), klass(), exact, const_oop(), _offset, instance_id);
+ bool exact = true;
+ PTR ptr_t = NotNull;
+ if (instance_id == UNKNOWN_INSTANCE) {
+ exact = _klass_is_exact;
+ ptr_t = _ptr;
+ }
+ return make(ptr_t, klass(), exact, const_oop(), _offset, instance_id);
}
//------------------------------xmeet_unloaded---------------------------------
@@ -3014,6 +3090,7 @@
// Convenience common pre-built types.
const TypeAryPtr *TypeAryPtr::RANGE;
const TypeAryPtr *TypeAryPtr::OOPS;
+const TypeAryPtr *TypeAryPtr::NARROWOOPS;
const TypeAryPtr *TypeAryPtr::BYTES;
const TypeAryPtr *TypeAryPtr::SHORTS;
const TypeAryPtr *TypeAryPtr::CHARS;
@@ -3063,8 +3140,13 @@
//-----------------------------cast_to_instance-------------------------------
const TypeOopPtr *TypeAryPtr::cast_to_instance(int instance_id) const {
if( instance_id == _instance_id) return this;
- bool exact = (instance_id == UNKNOWN_INSTANCE) ? _klass_is_exact : true;
- return make(ptr(), const_oop(), _ary, klass(), exact, _offset, instance_id);
+ bool exact = true;
+ PTR ptr_t = NotNull;
+ if (instance_id == UNKNOWN_INSTANCE) {
+ exact = _klass_is_exact;
+ ptr_t = _ptr;
+ }
+ return make(ptr_t, const_oop(), _ary, klass(), exact, _offset, instance_id);
}
//-----------------------------narrow_size_type-------------------------------
@@ -3547,7 +3629,7 @@
k_ary = ciTypeArrayKlass::make(el->basic_type());
}
- if( this != TypeAryPtr::OOPS )
+ if( this != TypeAryPtr::OOPS ) {
// The _klass field acts as a cache of the underlying
// ciKlass for this array type. In order to set the field,
// we need to cast away const-ness.
@@ -3562,6 +3644,11 @@
// a bit less efficient than caching, but calls to
// TypeAryPtr::OOPS->klass() are not common enough to matter.
((TypeAryPtr*)this)->_klass = k_ary;
+ if (UseCompressedOops && k_ary != NULL && k_ary->is_obj_array_klass() &&
+ _offset != 0 && _offset != arrayOopDesc::length_offset_in_bytes()) {
+ ((TypeAryPtr*)this)->_is_ptr_to_narrowoop = true;
+ }
+ }
return k_ary;
}
--- a/hotspot/src/share/vm/opto/type.hpp Tue May 20 06:32:58 2008 -0700
+++ b/hotspot/src/share/vm/opto/type.hpp Wed May 21 10:45:07 2008 -0700
@@ -191,9 +191,8 @@
virtual const Type *filter( const Type *kills ) const;
// Returns true if this pointer points at memory which contains a
- // compressed oop references. In 32-bit builds it's non-virtual
- // since we don't support compressed oops at all in the mode.
- LP64_ONLY(virtual) bool is_narrow() const { return false; }
+ // compressed oop references.
+ bool is_ptr_to_narrowoop() const;
// Convenience access
float getf() const;
@@ -213,8 +212,8 @@
const TypePtr *isa_ptr() const; // Returns NULL if not ptr type
const TypeRawPtr *isa_rawptr() const; // NOT Java oop
const TypeRawPtr *is_rawptr() const; // Asserts is rawptr
- const TypeNarrowOop *is_narrowoop() const; // Java-style GC'd pointer
- const TypeNarrowOop *isa_narrowoop() const; // Returns NULL if not oop ptr type
+ const TypeNarrowOop *is_narrowoop() const; // Java-style GC'd pointer
+ const TypeNarrowOop *isa_narrowoop() const; // Returns NULL if not oop ptr type
const TypeOopPtr *isa_oopptr() const; // Returns NULL if not oop ptr type
const TypeOopPtr *is_oopptr() const; // Java-style GC'd pointer
const TypeKlassPtr *isa_klassptr() const; // Returns NULL if not KlassPtr
@@ -643,7 +642,7 @@
// Some kind of oop (Java pointer), either klass or instance or array.
class TypeOopPtr : public TypePtr {
protected:
- TypeOopPtr( TYPES t, PTR ptr, ciKlass* k, bool xk, ciObject* o, int offset, int instance_id ) : TypePtr(t, ptr, offset), _const_oop(o), _klass(k), _klass_is_exact(xk), _instance_id(instance_id) { }
+ TypeOopPtr( TYPES t, PTR ptr, ciKlass* k, bool xk, ciObject* o, int offset, int instance_id );
public:
virtual bool eq( const Type *t ) const;
virtual int hash() const; // Type specific hashing
@@ -660,8 +659,9 @@
ciKlass* _klass; // Klass object
// Does the type exclude subclasses of the klass? (Inexact == polymorphic.)
bool _klass_is_exact;
+ bool _is_ptr_to_narrowoop;
- int _instance_id; // if not UNKNOWN_INSTANCE, indicates that this is a particular instance
+ int _instance_id; // if not UNKNOWN_INSTANCE, indicates that this is a particular instance
// of this type which is distinct. This is the the node index of the
// node creating this instance
@@ -696,6 +696,11 @@
ciObject* const_oop() const { return _const_oop; }
virtual ciKlass* klass() const { return _klass; }
bool klass_is_exact() const { return _klass_is_exact; }
+
+ // Returns true if this pointer points at memory which contains a
+ // compressed oop references.
+ bool is_ptr_to_narrowoop_nv() const { return _is_ptr_to_narrowoop; }
+
bool is_instance() const { return _instance_id != UNKNOWN_INSTANCE; }
uint instance_id() const { return _instance_id; }
bool is_instance_field() const { return _instance_id != UNKNOWN_INSTANCE && _offset >= 0; }
@@ -716,12 +721,6 @@
// returns the equivalent compressed version of this pointer type
virtual const TypeNarrowOop* make_narrowoop() const;
-#ifdef _LP64
- virtual bool is_narrow() const {
- return (UseCompressedOops && _offset != 0);
- }
-#endif
-
virtual const Type *xmeet( const Type *t ) const;
virtual const Type *xdual() const; // Compute dual right now.
@@ -843,15 +842,10 @@
virtual const Type *xmeet( const Type *t ) const;
virtual const Type *xdual() const; // Compute dual right now.
-#ifdef _LP64
- virtual bool is_narrow() const {
- return (UseCompressedOops && klass() != NULL && _offset != 0);
- }
-#endif
-
// Convenience common pre-built types.
static const TypeAryPtr *RANGE;
static const TypeAryPtr *OOPS;
+ static const TypeAryPtr *NARROWOOPS;
static const TypeAryPtr *BYTES;
static const TypeAryPtr *SHORTS;
static const TypeAryPtr *CHARS;
@@ -901,18 +895,6 @@
virtual const Type *xmeet( const Type *t ) const;
virtual const Type *xdual() const; // Compute dual right now.
-#ifdef _LP64
- // Perm objects don't use compressed references, except for static fields
- // which are currently compressed
- virtual bool is_narrow() const {
- if (UseCompressedOops && _offset != 0 && _klass->is_instance_klass()) {
- ciInstanceKlass* ik = _klass->as_instance_klass();
- return ik != NULL && ik->get_field_by_offset(_offset, true) != NULL;
- }
- return false;
- }
-#endif
-
// Convenience common pre-built types.
static const TypeKlassPtr* OBJECT; // Not-null object klass or below
static const TypeKlassPtr* OBJECT_OR_NULL; // Maybe-null version of same
@@ -921,7 +903,7 @@
#endif
};
-//------------------------------TypeNarrowOop----------------------------------------
+//------------------------------TypeNarrowOop----------------------------------
// A compressed reference to some kind of Oop. This type wraps around
// a preexisting TypeOopPtr and forwards most of it's operations to
// the underlying type. It's only real purpose is to track the
@@ -1013,6 +995,14 @@
};
//------------------------------accessors--------------------------------------
+inline bool Type::is_ptr_to_narrowoop() const {
+#ifdef _LP64
+ return (isa_oopptr() != NULL && is_oopptr()->is_ptr_to_narrowoop_nv());
+#else
+ return false;
+#endif
+}
+
inline float Type::getf() const {
assert( _base == FloatCon, "Not a FloatCon" );
return ((TypeF*)this)->_f;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/6689060/Test.java Wed May 21 10:45:07 2008 -0700
@@ -0,0 +1,577 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+/*
+ * @test
+ * @bug 6689060
+ * @summary Escape Analysis does not work with Compressed Oops
+ * @run main/othervm -Xbatch -XX:CompileCommand=exclude,Test.dummy -XX:+AggressiveOpts Test
+ */
+
+import java.lang.reflect.Array;
+
+class Point {
+ int x;
+ int y;
+ Point next;
+ int ax[];
+ int ay[];
+ Point pax[];
+ Point pay[];
+ public Point getNext() {
+ return next;
+ }
+}
+
+public class Test {
+
+ void dummy() {
+ // Empty method to verify correctness of DebugInfo.
+ // Use -XX:CompileCommand=exclude,Test.dummy
+ }
+
+ int ival(int i) {
+ return i*2;
+ }
+
+ int test80(int y, int l, int i) {
+ Point p = new Point();
+ p.ax = new int[2];
+ p.ay = new int[2];
+ int x = 3;
+ p.ax[0] = x;
+ p.ay[1] = 3 * x + y;
+ dummy();
+ return p.ax[0] * p.ay[1];
+ }
+
+ int test81(int y, int l, int i) {
+ Point p = new Point();
+ p.ax = new int[2];
+ p.ay = new int[2];
+ int x = 3;
+ p.ax[0] = x;
+ p.ay[1] = 3 * x + y;
+ dummy();
+ return p.ax[0] * p.ay[1];
+ }
+
+
+ int test44(int y) {
+ Point p1 = new Point();
+ p1.x = ival(3);
+ dummy();
+ p1.y = 3 * p1.x + y;
+ return p1.y;
+ }
+
+ int test43(int y) {
+ Point p1 = new Point();
+ if ( (y & 1) == 1 ) {
+ p1.x = ival(3);
+ } else {
+ p1.x = ival(5);
+ }
+ dummy();
+ p1.y = 3 * p1.x + y;
+ return p1.y;
+ }
+
+ int test42(int y) {
+ Point p1 = new Point();
+ p1.x = 3;
+ for (int i = 0; i < y; i++) {
+ if ( (i & 1) == 1 ) {
+ p1.x += 4;
+ }
+ }
+ p1.y = 3 * y + p1.x;
+ return p1.y;
+ }
+
+ int test40(int y) {
+ Point p1 = new Point();
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ } else {
+ p1.x = 5;
+ }
+ p1.y = 3 * p1.x + y;
+ return p1.y;
+ }
+
+ int test41(int y) {
+ Point p1 = new Point();
+ if ( (y & 1) == 1 ) {
+ p1.x += 4;
+ } else {
+ p1.x += 5;
+ }
+ p1.y = 3 * p1.x + y;
+ return p1.y;
+ }
+
+ Point test00(int y) {
+ int x = 3;
+ Point p = new Point();
+ p.x = x;
+ p.y = 3 * x + y;
+ return p;
+ }
+
+ Point test01(int y) {
+ int x = 3;
+ Point p = new Point();
+ p.x = x;
+ p.y = 3 * x + y;
+ dummy();
+ return p;
+ }
+
+ Point test02(int y) {
+ int x = 3;
+ Point p1 = null;
+ for (int i = 0; i < y; i++) {
+ Point p2 = new Point();
+ p2.x = x;
+ p2.y = 3 * y + x;
+ p2.next = p1;
+ p1 = p2;
+ }
+ return p1;
+ }
+
+ Point test03(int y) {
+ int x = 3;
+ Point p1 = null;
+ for (int i = 0; i < y; i++) {
+ Point p2 = new Point();
+ p2.x = x;
+ p2.y = 3 * y + x;
+ p2.next = p1;
+ p1 = p2;
+ }
+ dummy();
+ return p1;
+ }
+
+ Point test04(int y) {
+ int x = 3;
+ Point p1 = null;
+ for (int i = 0; i < y; i++) {
+ Point p2 = new Point();
+ p2.x = x;
+ p2.y = 3 * y + x;
+ p2.next = p1;
+ dummy();
+ p1 = p2;
+ }
+ return p1;
+ }
+
+ int test05(int y) {
+ int x = 3;
+ Point p1 = new Point();
+ for (int i = 0; i < y; i++) {
+ Point p2 = new Point();
+ p2.x = x;
+ p2.y = 3 * y + x;
+ p1.next = p2;
+ p1 = p2;
+ }
+ return p1.y;
+ }
+
+ int test0(int y) {
+ int x = 3;
+ Point p = new Point();
+ p.x = x;
+ p.y = 3 * x + y;
+ dummy();
+ return p.x * p.y;
+ }
+
+ int test1(int y) {
+ Point p = new Point();
+ if ( (y & 1) == 1 ) {
+ p = new Point(); // Kill previous
+ }
+ int x = 3;
+ p.x = x;
+ p.y = 3 * x + y;
+ dummy();
+ return p.x * p.y;
+ }
+
+ int test2(int y) {
+ Point p1 = new Point();
+ Point p2 = new Point();
+ p1.x = 3;
+ p2.x = 4;
+ p1.y = 3 * p2.x + y;
+ p2.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p2.y;
+ }
+
+ int test3(int y, Point p1) {
+ Point p2 = new Point();
+ p1.x = 3;
+ p2.x = 4;
+ p1.y = 3 * p2.x + y;
+ p2.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p2.y;
+ }
+
+ int test4(int y) {
+ Point p1 = new Point();
+ Point p2 = new Point();
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ p2.x = 4;
+ } else {
+ p1.x = 5;
+ p2.x = 6;
+ }
+ p1.y = 3 * p2.x + y;
+ p2.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p2.y;
+ }
+
+ int test5(int y, Point p1) {
+ Point p2 = new Point();
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ p2.x = 4;
+ } else {
+ p1.x = 5;
+ p2.x = 6;
+ }
+ p1.y = 3 * p2.x + y;
+ p2.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p2.y;
+ }
+
+ int test6(int y) {
+ Point p1 = new Point();
+ Point p2 = new Point();
+ p1.next = p2;
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ p1.getNext().x = 4;
+ } else {
+ p1.x = 5;
+ p1.getNext().x = 6;
+ }
+ p1.y = 3 * p2.x + y;
+ p2.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p2.y;
+ }
+
+ int test7(int y, Point p1) {
+ Point p2 = new Point();
+ p1.next = p2;
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ p1.getNext().x = 4;
+ } else {
+ p1.x = 5;
+ p1.getNext().x = 6;
+ }
+ p1.y = 3 * p2.x + y;
+ p2.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p2.y;
+ }
+
+ int test8(int y, int l, int i) {
+ Point p = new Point();
+ p.ax = new int[l];
+ p.ay = new int[l];
+ int x = 3;
+ p.ax[i] = x;
+ p.ay[i] = 3 * x + y;
+ dummy();
+ return p.ax[i] * p.ay[i];
+ }
+
+ int test9(int y, int l, int i) {
+ Point p = new Point();
+ p.pax = new Point[l];
+ p.pay = new Point[l];
+ p.pax[i] = new Point();
+ p.pay[i] = new Point();
+ p.pax[i].x = 3;
+ p.pay[i].x = 4;
+ p.pax[i].y = 3 * p.pay[i].x + y;
+ p.pay[i].y = 3 * p.pax[i].x + y;
+ dummy();
+ return p.pax[i].y * p.pay[i].y;
+ }
+
+ int test10(int y, int l, int i, Class cls) {
+ Point p = new Point();
+ try {
+ p.pax = (Point[])Array.newInstance(cls, l);
+ p.pax[i] = (Point)cls.newInstance();
+ }
+ catch(java.lang.InstantiationException ex) {
+ return 0;
+ }
+ catch(java.lang.IllegalAccessException ex) {
+ return 0;
+ }
+ p.pax[i].x = 3;
+ p.pax[i].y = 3 * p.pax[i].x + y;
+ dummy();
+ return p.pax[i].x * p.pax[i].y;
+ }
+
+ int test11(int y) {
+ Point p1 = new Point();
+ Point p2 = new Point();
+ p1.next = p2;
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ p1.next.x = 4;
+ } else {
+ p1.x = 5;
+ p1.next.x = 6;
+ }
+ p1.y = 3 * p1.next.x + y;
+ p1.next.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p1.next.y;
+ }
+
+ int test12(int y) {
+ Point p1 = new Point();
+ p1.next = p1;
+ if ( (y & 1) == 1 ) {
+ p1.x = 3;
+ p1.next.x = 4;
+ } else {
+ p1.x = 5;
+ p1.next.x = 6;
+ }
+ p1.y = 3 * p1.next.x + y;
+ p1.next.y = 3 * p1.x + y;
+ dummy();
+ return p1.y * p1.next.y;
+ }
+
+
+ public static void main(String args[]) {
+ Test tsr = new Test();
+ Point p = new Point();
+ Point ptmp = p;
+ Class cls = Point.class;
+ int y = 0;
+ for (int i=0; i<10000; i++) {
+ ptmp.next = tsr.test00(1);
+ ptmp.next = tsr.test01(1);
+ ptmp.next = tsr.test02(1);
+ ptmp.next = tsr.test03(1);
+ ptmp.next = tsr.test04(1);
+
+ y = tsr.test05(1);
+
+ y = tsr.test80(y, 1, 0);
+ y = tsr.test81(y, 1, 0);
+
+ y = tsr.test44(y);
+ y = tsr.test43(y);
+ y = tsr.test42(y);
+ y = tsr.test40(y);
+ y = tsr.test41(y);
+
+ y = tsr.test0(y);
+ y = tsr.test1(y);
+ y = tsr.test2(y);
+ y = tsr.test3(y, p);
+ y = tsr.test4(y);
+ y = tsr.test5(y, p);
+ y = tsr.test6(y);
+ y = tsr.test7(y, p);
+ y = tsr.test8(y, 1, 0);
+ y = tsr.test9(y, 1, 0);
+ y = tsr.test10(y, 1, 0, cls);
+ y = tsr.test11(y);
+ y = tsr.test12(y);
+ }
+ for (int i=0; i<10000; i++) {
+ ptmp.next = tsr.test00(1);
+ ptmp.next = tsr.test01(1);
+ ptmp.next = tsr.test02(1);
+ ptmp.next = tsr.test03(1);
+ ptmp.next = tsr.test04(1);
+
+ y = tsr.test05(1);
+
+ y = tsr.test80(y, 1, 0);
+ y = tsr.test81(y, 1, 0);
+
+ y = tsr.test44(y);
+ y = tsr.test43(y);
+ y = tsr.test42(y);
+ y = tsr.test40(y);
+ y = tsr.test41(y);
+
+ y = tsr.test0(y);
+ y = tsr.test1(y);
+ y = tsr.test2(y);
+ y = tsr.test3(y, p);
+ y = tsr.test4(y);
+ y = tsr.test5(y, p);
+ y = tsr.test6(y);
+ y = tsr.test7(y, p);
+ y = tsr.test8(y, 1, 0);
+ y = tsr.test9(y, 1, 0);
+ y = tsr.test10(y, 1, 0, cls);
+ y = tsr.test11(y);
+ y = tsr.test12(y);
+ }
+ for (int i=0; i<10000; i++) {
+ ptmp.next = tsr.test00(1);
+ ptmp.next = tsr.test01(1);
+ ptmp.next = tsr.test02(1);
+ ptmp.next = tsr.test03(1);
+ ptmp.next = tsr.test04(1);
+
+ y = tsr.test05(1);
+
+ y = tsr.test80(y, 1, 0);
+ y = tsr.test81(y, 1, 0);
+
+ y = tsr.test44(y);
+ y = tsr.test43(y);
+ y = tsr.test42(y);
+ y = tsr.test40(y);
+ y = tsr.test41(y);
+
+ y = tsr.test0(y);
+ y = tsr.test1(y);
+ y = tsr.test2(y);
+ y = tsr.test3(y, p);
+ y = tsr.test4(y);
+ y = tsr.test5(y, p);
+ y = tsr.test6(y);
+ y = tsr.test7(y, p);
+ y = tsr.test8(y, 1, 0);
+ y = tsr.test9(y, 1, 0);
+ y = tsr.test10(y, 1, 0, cls);
+ y = tsr.test11(y);
+ y = tsr.test12(y);
+ }
+
+ int z = 0;
+ y = tsr.test80(0, 1, 0);
+ z += y;
+ System.out.println("After 'test80' y=" + y);
+ y = tsr.test81(0, 1, 0);
+ z += y;
+ System.out.println("After 'test81' y=" + y);
+
+ y = tsr.test44(0);
+ z += y;
+ System.out.println("After 'test44' y=" + y);
+ y = tsr.test43(0);
+ z += y;
+ System.out.println("After 'test43' y=" + y);
+ y = tsr.test42(0);
+ z += y;
+ System.out.println("After 'test42' y=" + y);
+ y = tsr.test40(0);
+ z += y;
+ System.out.println("After 'test40' y=" + y);
+ y = tsr.test41(0);
+ z += y;
+ System.out.println("After 'test41' y=" + y);
+
+ ptmp.next = tsr.test00(1);
+ z += y;
+ System.out.println("After 'test00' p.y=" + ptmp.next.y);
+ ptmp.next = tsr.test01(1);
+ z += y;
+ System.out.println("After 'test01' p.y=" + ptmp.next.y);
+ ptmp.next = tsr.test02(1);
+ z += y;
+ System.out.println("After 'test02' p.y=" + ptmp.next.y);
+ ptmp.next = tsr.test03(1);
+ z += y;
+ System.out.println("After 'test03' p.y=" + ptmp.next.y);
+ ptmp.next = tsr.test04(1);
+ z += y;
+ System.out.println("After 'test04' p.y=" + ptmp.next.y);
+
+ y = tsr.test05(1);
+ z += y;
+ System.out.println("After 'test05' y=" + y);
+
+ y = tsr.test0(0);
+ z += y;
+ System.out.println("After 'test0' y=" + y);
+ y = tsr.test1(0);
+ z += y;
+ System.out.println("After 'test1' y=" + y);
+ y = tsr.test2(0);
+ z += y;
+ System.out.println("After 'test2' y=" + y);
+ y = tsr.test3(0, new Point());
+ z += y;
+ System.out.println("After 'test3' y=" + y);
+ y = tsr.test4(0);
+ z += y;
+ System.out.println("After 'test4' y=" + y);
+ y = tsr.test5(0, new Point());
+ z += y;
+ System.out.println("After 'test5' y=" + y);
+ y = tsr.test6(0);
+ z += y;
+ System.out.println("After 'test6' y=" + y);
+ y = tsr.test7(0, new Point());
+ z += y;
+ System.out.println("After 'test7' y=" + y);
+ y = tsr.test8(0, 1, 0);
+ z += y;
+ System.out.println("After 'test8' y=" + y);
+ y = tsr.test9(0, 1, 0);
+ z += y;
+ System.out.println("After 'test9' y=" + y);
+ y = tsr.test10(0, 1, 0, cls);
+ z += y;
+ System.out.println("After 'test10' y=" + y);
+ y = tsr.test11(0);
+ z += y;
+ System.out.println("After 'test11' y=" + y);
+ y = tsr.test12(0);
+ z += y;
+ System.out.println("After 'test12' y=" + y);
+ System.out.println("Sum of y =" + z);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/6695810/Test.java Wed May 21 10:45:07 2008 -0700
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+/*
+ * @test
+ * @bug 6695810
+ * @summary null oop passed to encode_heap_oop_not_null
+ * @run main/othervm -Xbatch Test
+ */
+
+public class Test {
+ Test _t;
+
+ static void test(Test t1, Test t2) {
+ if (t2 != null)
+ t1._t = t2;
+
+ if (t2 != null)
+ t1._t = t2;
+ }
+
+ public static void main(String[] args) {
+ Test t = new Test();
+ for (int i = 0; i < 50; i++) {
+ for (int j = 0; j < 100; j++) {
+ test(t, t);
+ }
+ test(t, null);
+ }
+ for (int i = 0; i < 10000; i++) {
+ test(t, t);
+ }
+ test(t, null);
+ }
+}