6395208: Elide autoboxing for calls to HashMap.get(int) and HashMap.get(long)

Reviewed-by: kvn, rasbold
This commit is contained in:
Tom Rodriguez 2007-12-05 09:01:00 -08:00
parent 5fa349cc42
commit 10c473e425
16 changed files with 540 additions and 67 deletions

View file

@ -634,6 +634,46 @@ uint LoadNode::hash() const {
Node* MemNode::can_see_stored_value(Node* st, PhaseTransform* phase) const {
Node* ld_adr = in(MemNode::Address);
const TypeInstPtr* tp = phase->type(ld_adr)->isa_instptr();
Compile::AliasType* atp = tp != NULL ? phase->C->alias_type(tp) : NULL;
if (EliminateAutoBox && atp != NULL && atp->index() >= Compile::AliasIdxRaw &&
atp->field() != NULL && !atp->field()->is_volatile()) {
uint alias_idx = atp->index();
bool final = atp->field()->is_final();
Node* result = NULL;
Node* current = st;
// Skip through chains of MemBarNodes checking the MergeMems for
// new states for the slice of this load. Stop once any other
// kind of node is encountered. Loads from final memory can skip
// through any kind of MemBar but normal loads shouldn't skip
// through MemBarAcquire since the could allow them to move out of
// a synchronized region.
while (current->is_Proj()) {
int opc = current->in(0)->Opcode();
if ((final && opc == Op_MemBarAcquire) ||
opc == Op_MemBarRelease || opc == Op_MemBarCPUOrder) {
Node* mem = current->in(0)->in(TypeFunc::Memory);
if (mem->is_MergeMem()) {
MergeMemNode* merge = mem->as_MergeMem();
Node* new_st = merge->memory_at(alias_idx);
if (new_st == merge->base_memory()) {
// Keep searching
current = merge->base_memory();
continue;
}
// Save the new memory state for the slice and fall through
// to exit.
result = new_st;
}
}
break;
}
if (result != NULL) {
st = result;
}
}
// Loop around twice in the case Load -> Initialize -> Store.
// (See PhaseIterGVN::add_users_to_worklist, which knows about this case.)
for (int trip = 0; trip <= 1; trip++) {
@ -723,6 +763,168 @@ Node *LoadNode::Identity( PhaseTransform *phase ) {
return this;
}
// Returns true if the AliasType refers to the field that holds the
// cached box array. Currently only handles the IntegerCache case.
static bool is_autobox_cache(Compile::AliasType* atp) {
if (atp != NULL && atp->field() != NULL) {
ciField* field = atp->field();
ciSymbol* klass = field->holder()->name();
if (field->name() == ciSymbol::cache_field_name() &&
field->holder()->uses_default_loader() &&
klass == ciSymbol::java_lang_Integer_IntegerCache()) {
return true;
}
}
return false;
}
// Fetch the base value in the autobox array
static bool fetch_autobox_base(Compile::AliasType* atp, int& cache_offset) {
if (atp != NULL && atp->field() != NULL) {
ciField* field = atp->field();
ciSymbol* klass = field->holder()->name();
if (field->name() == ciSymbol::cache_field_name() &&
field->holder()->uses_default_loader() &&
klass == ciSymbol::java_lang_Integer_IntegerCache()) {
assert(field->is_constant(), "what?");
ciObjArray* array = field->constant_value().as_object()->as_obj_array();
// Fetch the box object at the base of the array and get its value
ciInstance* box = array->obj_at(0)->as_instance();
ciInstanceKlass* ik = box->klass()->as_instance_klass();
if (ik->nof_nonstatic_fields() == 1) {
// This should be true nonstatic_field_at requires calling
// nof_nonstatic_fields so check it anyway
ciConstant c = box->field_value(ik->nonstatic_field_at(0));
cache_offset = c.as_int();
}
return true;
}
}
return false;
}
// Returns true if the AliasType refers to the value field of an
// autobox object. Currently only handles Integer.
static bool is_autobox_object(Compile::AliasType* atp) {
if (atp != NULL && atp->field() != NULL) {
ciField* field = atp->field();
ciSymbol* klass = field->holder()->name();
if (field->name() == ciSymbol::value_name() &&
field->holder()->uses_default_loader() &&
klass == ciSymbol::java_lang_Integer()) {
return true;
}
}
return false;
}
// We're loading from an object which has autobox behaviour.
// If this object is result of a valueOf call we'll have a phi
// merging a newly allocated object and a load from the cache.
// We want to replace this load with the original incoming
// argument to the valueOf call.
Node* LoadNode::eliminate_autobox(PhaseGVN* phase) {
Node* base = in(Address)->in(AddPNode::Base);
if (base->is_Phi() && base->req() == 3) {
AllocateNode* allocation = NULL;
int allocation_index = -1;
int load_index = -1;
for (uint i = 1; i < base->req(); i++) {
allocation = AllocateNode::Ideal_allocation(base->in(i), phase);
if (allocation != NULL) {
allocation_index = i;
load_index = 3 - allocation_index;
break;
}
}
LoadNode* load = NULL;
if (allocation != NULL && base->in(load_index)->is_Load()) {
load = base->in(load_index)->as_Load();
}
if (load != NULL && in(Memory)->is_Phi() && in(Memory)->in(0) == base->in(0)) {
// Push the loads from the phi that comes from valueOf up
// through it to allow elimination of the loads and the recovery
// of the original value.
Node* mem_phi = in(Memory);
Node* offset = in(Address)->in(AddPNode::Offset);
Node* in1 = clone();
Node* in1_addr = in1->in(Address)->clone();
in1_addr->set_req(AddPNode::Base, base->in(allocation_index));
in1_addr->set_req(AddPNode::Address, base->in(allocation_index));
in1_addr->set_req(AddPNode::Offset, offset);
in1->set_req(0, base->in(allocation_index));
in1->set_req(Address, in1_addr);
in1->set_req(Memory, mem_phi->in(allocation_index));
Node* in2 = clone();
Node* in2_addr = in2->in(Address)->clone();
in2_addr->set_req(AddPNode::Base, base->in(load_index));
in2_addr->set_req(AddPNode::Address, base->in(load_index));
in2_addr->set_req(AddPNode::Offset, offset);
in2->set_req(0, base->in(load_index));
in2->set_req(Address, in2_addr);
in2->set_req(Memory, mem_phi->in(load_index));
in1_addr = phase->transform(in1_addr);
in1 = phase->transform(in1);
in2_addr = phase->transform(in2_addr);
in2 = phase->transform(in2);
PhiNode* result = PhiNode::make_blank(base->in(0), this);
result->set_req(allocation_index, in1);
result->set_req(load_index, in2);
return result;
}
} else if (base->is_Load()) {
// Eliminate the load of Integer.value for integers from the cache
// array by deriving the value from the index into the array.
// Capture the offset of the load and then reverse the computation.
Node* load_base = base->in(Address)->in(AddPNode::Base);
if (load_base != NULL) {
Compile::AliasType* atp = phase->C->alias_type(load_base->adr_type());
intptr_t cache_offset;
int shift = -1;
Node* cache = NULL;
if (is_autobox_cache(atp)) {
shift = exact_log2(type2aelembytes[T_OBJECT]);
cache = AddPNode::Ideal_base_and_offset(load_base->in(Address), phase, cache_offset);
}
if (cache != NULL && base->in(Address)->is_AddP()) {
Node* elements[4];
int count = base->in(Address)->as_AddP()->unpack_offsets(elements, ARRAY_SIZE(elements));
int cache_low;
if (count > 0 && fetch_autobox_base(atp, cache_low)) {
int offset = arrayOopDesc::base_offset_in_bytes(memory_type()) - (cache_low << shift);
// Add up all the offsets making of the address of the load
Node* result = elements[0];
for (int i = 1; i < count; i++) {
result = phase->transform(new (phase->C, 3) AddXNode(result, elements[i]));
}
// Remove the constant offset from the address and then
// remove the scaling of the offset to recover the original index.
result = phase->transform(new (phase->C, 3) AddXNode(result, phase->MakeConX(-offset)));
if (result->Opcode() == Op_LShiftX && result->in(2) == phase->intcon(shift)) {
// Peel the shift off directly but wrap it in a dummy node
// since Ideal can't return existing nodes
result = new (phase->C, 3) RShiftXNode(result->in(1), phase->intcon(0));
} else {
result = new (phase->C, 3) RShiftXNode(result, phase->intcon(shift));
}
#ifdef _LP64
result = new (phase->C, 2) ConvL2INode(phase->transform(result));
#endif
return result;
}
}
}
}
return NULL;
}
//------------------------------Ideal------------------------------------------
// If the load is from Field memory and the pointer is non-null, we can
// zero out the control input.
@ -755,6 +957,17 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
}
if (EliminateAutoBox && can_reshape && in(Address)->is_AddP()) {
Node* base = in(Address)->in(AddPNode::Base);
if (base != NULL) {
Compile::AliasType* atp = phase->C->alias_type(adr_type());
if (is_autobox_object(atp)) {
Node* result = eliminate_autobox(phase);
if (result != NULL) return result;
}
}
}
// 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
@ -858,6 +1071,17 @@ const Type *LoadNode::Value( PhaseTransform *phase ) const {
// This can happen if a interface-typed array narrows to a class type.
jt = _type;
}
if (EliminateAutoBox) {
// The pointers in the autobox arrays are always non-null
Node* base = in(Address)->in(AddPNode::Base);
if (base != NULL) {
Compile::AliasType* atp = phase->C->alias_type(base->adr_type());
if (is_autobox_cache(atp)) {
return jt->join(TypePtr::NOTNULL)->is_ptr();
}
}
}
return jt;
}
}