--- old/src/hotspot/cpu/x86/x86_64.ad 2019-06-11 16:49:42.000000000 +0200 +++ new/src/hotspot/cpu/x86/x86_64.ad 2019-06-11 16:49:37.000000000 +0200 @@ -5369,14 +5369,9 @@ match(Set dst (LoadKlass mem)); ins_cost(125); // XXX - format %{ "movq $dst, $mem\t# class\n\t" - "shlq $dst, oopDesc::storage_props_nof_bits\n\t" - "shrq $dst, oopDesc::storage_props_nof_bits" %} - ins_encode %{ - __ movptr($dst$$Register, $mem$$Address); - __ shlq($dst$$Register, oopDesc::storage_props_nof_bits); - __ shrq($dst$$Register, oopDesc::storage_props_nof_bits); - %} + format %{ "movq $dst, $mem\t# class" %} + opcode(0x8B); + ins_encode(REX_reg_mem_wide(dst, mem), OpcP, reg_mem(dst, mem)); ins_pipe(ialu_reg_mem); // XXX %} @@ -5386,11 +5381,9 @@ match(Set dst (LoadNKlass mem)); ins_cost(125); // XXX - format %{ "movl $dst, $mem\t# compressed klass ptr\n\t" - "andl $dst, oopDesc::compressed_klass_mask()" %} + format %{ "movl $dst, $mem\t# compressed klass ptr" %} ins_encode %{ __ movl($dst$$Register, $mem$$Address); - __ andl($dst$$Register, oopDesc::compressed_klass_mask()); %} ins_pipe(ialu_reg_mem); // XXX %} @@ -6933,6 +6926,33 @@ ins_pipe(ialu_reg_reg); // XXX %} +instruct castN2I(rRegI dst, rRegN src) +%{ + match(Set dst (CastN2I src)); + + format %{ "movl $dst, $src\t# compressed ptr -> int" %} + ins_encode %{ + if ($dst$$reg != $src$$reg) { + __ movl($dst$$Register, $src$$Register); + } + %} + ins_pipe(ialu_reg_reg); // XXX +%} + +instruct castI2N(rRegN dst, rRegI src) +%{ + match(Set dst (CastI2N src)); + + format %{ "movl $dst, $src\t# int -> compressed ptr" %} + ins_encode %{ + if ($dst$$reg != $src$$reg) { + __ movl($dst$$Register, $src$$Register); + } + %} + ins_pipe(ialu_reg_reg); // XXX +%} + + // Convert oop into int for vectors alignment masking instruct convP2I(rRegI dst, rRegP src) %{ --- old/src/hotspot/share/ci/ciInstanceKlass.cpp 2019-06-11 16:49:48.000000000 +0200 +++ new/src/hotspot/share/ci/ciInstanceKlass.cpp 2019-06-11 16:49:43.000000000 +0200 @@ -753,6 +753,7 @@ } case T_ARRAY: // fall-through case T_OBJECT: { + _out->print("%s ", fd->signature()->as_quoted_ascii()); oop value = mirror->obj_field_acquire(fd->offset()); if (value == NULL) { _out->print_cr("null"); @@ -763,7 +764,7 @@ _out->print("\"%s\"", (ascii_value != NULL) ? ascii_value : ""); } else { const char* klass_name = value->klass()->name()->as_quoted_ascii(); - _out->print_cr("%s", klass_name); + _out->print("%s", klass_name); } } else if (value->is_array()) { typeArrayOop ta = (typeArrayOop)value; @@ -773,7 +774,6 @@ const char* klass_name = value->klass()->name()->as_quoted_ascii(); _out->print(" %s", klass_name); } - _out->cr(); } else { ShouldNotReachHere(); } --- old/src/hotspot/share/ci/ciReplay.cpp 2019-06-11 16:49:54.000000000 +0200 +++ new/src/hotspot/share/ci/ciReplay.cpp 2019-06-11 16:49:48.000000000 +0200 @@ -932,7 +932,8 @@ java_mirror->obj_field_put(fd->offset(), value()); return true; } else if (field_signature[0] == 'L') { - Klass* k = resolve_klass(field_signature, CHECK_(true)); + const char* instance = parse_escaped_string(); + Klass* k = resolve_klass(instance, CHECK_(true)); oop value = InstanceKlass::cast(k)->allocate_instance(CHECK_(true)); java_mirror->obj_field_put(fd->offset(), value); return true; --- old/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp 2019-06-11 16:50:00.000000000 +0200 +++ new/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp 2019-06-11 16:49:54.000000000 +0200 @@ -41,6 +41,10 @@ PhaseGVN& C2ParseAccess::gvn() const { return _kit->gvn(); } +Node* C2ParseAccess::control() const { + return _ctl == NULL ? _kit->control() : _ctl; +} + bool C2Access::needs_cpu_membar() const { bool mismatched = (_decorators & C2_MISMATCHED) != 0; bool is_unordered = (_decorators & MO_UNORDERED) != 0; @@ -144,7 +148,7 @@ if (access.is_parse_access()) { C2ParseAccess& parse_access = static_cast(access); GraphKit* kit = parse_access.kit(); - Node* control = control_dependent ? kit->control() : NULL; + Node* control = control_dependent ? parse_access.control() : NULL; if (in_native) { load = kit->make_load(control, adr, val_type, access.type(), mo); --- old/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp 2019-06-11 16:50:06.000000000 +0200 +++ new/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp 2019-06-11 16:50:00.000000000 +0200 @@ -139,18 +139,22 @@ class C2ParseAccess: public C2Access { protected: GraphKit* _kit; + Node* _ctl; void* barrier_set_state() const; public: C2ParseAccess(GraphKit* kit, DecoratorSet decorators, - BasicType type, Node* base, C2AccessValuePtr& addr) : + BasicType type, Node* base, C2AccessValuePtr& addr, + Node* ctl = NULL) : C2Access(decorators, type, base, addr), - _kit(kit) { + _kit(kit), + _ctl(ctl) { fixup_decorators(); } GraphKit* kit() const { return _kit; } + Node* control() const; template T barrier_set_state_as() const { --- old/src/hotspot/share/opto/castnode.hpp 2019-06-11 16:50:11.000000000 +0200 +++ new/src/hotspot/share/opto/castnode.hpp 2019-06-11 16:50:06.000000000 +0200 @@ -149,6 +149,23 @@ virtual bool depends_only_on_test() const { return false; } }; +// convert between a 32 bit integer and a compressed pointer +class CastI2NNode : public TypeNode { + public: + CastI2NNode(Node *n, const Type *t) : TypeNode(t, 2) { + init_req(1, n); + } + virtual int Opcode() const; + virtual uint ideal_reg() const { return Op_RegN; } +}; + +class CastN2INode : public Node { + public: + CastN2INode(Node *n) : Node(NULL, n) {} + virtual int Opcode() const; + virtual uint ideal_reg() const { return Op_RegI; } + virtual const Type *bottom_type() const { return TypeInt::INT; } +}; #endif // SHARE_OPTO_CASTNODE_HPP --- old/src/hotspot/share/opto/cfgnode.cpp 2019-06-11 16:50:17.000000000 +0200 +++ new/src/hotspot/share/opto/cfgnode.cpp 2019-06-11 16:50:11.000000000 +0200 @@ -903,7 +903,7 @@ // create a new phi with edges matching r and set (initially) to x PhiNode* PhiNode::make(Node* r, Node* x, const Type *t, const TypePtr* at) { uint preds = r->req(); // Number of predecessor paths - assert(t != Type::MEMORY || at == flatten_phi_adr_type(at), "flatten at"); + assert(t != Type::MEMORY || at == flatten_phi_adr_type(at) || (flatten_phi_adr_type(at) == TypeAryPtr::VALUES && Compile::current()->flattened_accesses_share_alias()), "flatten at"); PhiNode* p = new PhiNode(r, t, at); for (uint j = 1; j < preds; j++) { // Fill in all inputs, except those which the region does not yet have --- old/src/hotspot/share/opto/classes.hpp 2019-06-11 16:50:22.000000000 +0200 +++ new/src/hotspot/share/opto/classes.hpp 2019-06-11 16:50:17.000000000 +0200 @@ -60,6 +60,8 @@ macro(CastII) macro(CastX2P) macro(CastP2X) +macro(CastI2N) +macro(CastN2I) macro(CastPP) macro(Catch) macro(CatchProj) @@ -414,3 +416,4 @@ macro(LowerCase) macro(UpperCase) macro(Whitespace) +macro(GetNullFreeProperty) --- old/src/hotspot/share/opto/compile.cpp 2019-06-11 16:50:28.000000000 +0200 +++ new/src/hotspot/share/opto/compile.cpp 2019-06-11 16:50:22.000000000 +0200 @@ -1167,6 +1167,9 @@ set_do_freq_based_layout(_directive->BlockLayoutByFrequencyOption); _loop_opts_cnt = LoopOptsCount; + _has_flattened_accesses = false; + _flattened_accesses_share_alias = true; + set_do_inlining(Inline); set_max_inline_size(MaxInlineSize); set_freq_inline_size(FreqInlineSize); @@ -1534,6 +1537,11 @@ const TypeAry *tary = TypeAry::make(TypeInstPtr::BOTTOM, ta->size()); tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,NULL,false,Type::Offset(offset), ta->field_offset()); } + // Initially all flattened array accesses share a single slice + if (ta->elem()->isa_valuetype() && ta->elem() != TypeValueType::BOTTOM && _flattened_accesses_share_alias) { + const TypeAry *tary = TypeAry::make(TypeValueType::BOTTOM, ta->size()); + tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,NULL,false,Type::Offset(offset), Type::Offset(Type::OffsetBot)); + } // Arrays of bytes and of booleans both use 'bastore' and 'baload' so // cannot be distinguished by bytecode alone. if (ta->elem() == TypeInt::BOOL) { @@ -1775,13 +1783,16 @@ //--------------------------------find_alias_type------------------------------ -Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_create, ciField* original_field) { +Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_create, ciField* original_field, bool uncached) { if (_AliasLevel == 0) return alias_type(AliasIdxBot); - AliasCacheEntry* ace = probe_alias_cache(adr_type); - if (ace->_adr_type == adr_type) { - return alias_type(ace->_index); + AliasCacheEntry* ace = NULL; + if (!uncached) { + ace = probe_alias_cache(adr_type); + if (ace->_adr_type == adr_type) { + return alias_type(ace->_index); + } } // Handle special cases. @@ -1843,7 +1854,9 @@ alias_type(idx)->set_element(elemtype); } int field_offset = flat->is_aryptr()->field_offset().get(); - if (elemtype->isa_valuetype() && field_offset != Type::OffsetBot) { + if (elemtype->isa_valuetype() && + elemtype->value_klass() != NULL && + field_offset != Type::OffsetBot) { ciValueKlass* vk = elemtype->value_klass(); field_offset += vk->first_field_offset(); field = vk->get_field_by_offset(field_offset, false); @@ -1858,6 +1871,8 @@ alias_type(idx)->set_rewritable(false); if (flat->offset() == in_bytes(Klass::java_mirror_offset())) alias_type(idx)->set_rewritable(false); + if (flat->offset() == in_bytes(Klass::layout_helper_offset())) + alias_type(idx)->set_rewritable(false); } // %%% (We would like to finalize JavaThread::threadObj_offset(), // but the base pointer type is not distinctive enough to identify @@ -1891,16 +1906,18 @@ } // Fill the cache for next time. - ace->_adr_type = adr_type; - ace->_index = idx; - assert(alias_type(adr_type) == alias_type(idx), "type must be installed"); - - // Might as well try to fill the cache for the flattened version, too. - AliasCacheEntry* face = probe_alias_cache(flat); - if (face->_adr_type == NULL) { - face->_adr_type = flat; - face->_index = idx; - assert(alias_type(flat) == alias_type(idx), "flat type must work too"); + if (!uncached) { + ace->_adr_type = adr_type; + ace->_index = idx; + assert(alias_type(adr_type) == alias_type(idx), "type must be installed"); + + // Might as well try to fill the cache for the flattened version, too. + AliasCacheEntry* face = probe_alias_cache(flat); + if (face->_adr_type == NULL) { + face->_adr_type = flat; + face->_index = idx; + assert(alias_type(flat) == alias_type(idx), "flat type must work too"); + } } return alias_type(idx); @@ -2135,6 +2152,258 @@ igvn.optimize(); } +void Compile::adjust_flattened_array_access_aliases(PhaseIterGVN& igvn) { + if (!_has_flattened_accesses) { + return; + } + // Initially, all flattened array accesses share the same slice to + // keep dependencies with Object[] array accesses (that could be + // to a flattened array) correct. We're done with parsing so we + // now know all flattened array accesses in this compile + // unit. Let's move flattened array accesses to their own slice, + // one per element field. This should help memory access + // optimizations. + ResourceMark rm; + Unique_Node_List wq; + wq.push(root()); + + Node_List mergememnodes; + Node_List memnodes; + + // Alias index currently shared by all flattened memory accesses + int index = get_alias_index(TypeAryPtr::VALUES); + + // Find MergeMem nodes and flattened array accesses + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + if (n->is_Mem()) { + const TypePtr* adr_type = get_adr_type(get_alias_index(n->adr_type())); + if (adr_type == TypeAryPtr::VALUES) { + memnodes.push(n); + } + } else if (n->is_MergeMem()) { + MergeMemNode* mm = n->as_MergeMem(); + if (mm->memory_at(index) != mm->base_memory()) { + mergememnodes.push(n); + } + } + for (uint j = 0; j < n->req(); j++) { + Node* m = n->in(j); + if (m != NULL) { + wq.push(m); + } + } + } + + if (memnodes.size() > 0) { + _flattened_accesses_share_alias = false; + + // We are going to change the slice for the flattened array + // accesses so we need to clear the cache entries that refer to + // them. + for (uint i = 0; i < AliasCacheSize; i++) { + AliasCacheEntry* ace = &_alias_cache[i]; + if (ace->_adr_type != NULL && + ace->_adr_type->isa_aryptr() && + ace->_adr_type->is_aryptr()->elem()->isa_valuetype()) { + ace->_adr_type = NULL; + ace->_index = 0; + } + } + + // Find what aliases we are going to add + int start_alias = num_alias_types()-1; + int stop_alias = 0; + + for (uint i = 0; i < memnodes.size(); i++) { + Node* m = memnodes.at(i); + const TypePtr* adr_type = m->adr_type(); +#ifdef ASSERT + m->as_Mem()->set_adr_type(adr_type); +#endif + int idx = get_alias_index(adr_type); + start_alias = MIN2(start_alias, idx); + stop_alias = MAX2(stop_alias, idx); + } + + assert(stop_alias >= start_alias, "should have expanded aliases"); + + Node_Stack stack(0); +#ifdef ASSERT + VectorSet seen(Thread::current()->resource_area()); +#endif + // Now let's fix the memory graph so each flattened array access + // is moved to the right slice. Start from the MergeMem nodes. + uint last = unique(); + for (uint i = 0; i < mergememnodes.size(); i++) { + MergeMemNode* current = mergememnodes.at(i)->as_MergeMem(); + Node* n = current->memory_at(index); + MergeMemNode* mm = NULL; + do { + // Follow memory edges through memory accesses, phis and + // narrow membars and push nodes on the stack. Once we hit + // bottom memory, we pop element off the stack one at a + // time, in reverse order, and move them to the right slice + // by changing their memory edges. + if ((n->is_Phi() && n->adr_type() != TypePtr::BOTTOM) || n->is_Mem() || n->adr_type() == TypeAryPtr::VALUES) { + assert(!seen.test_set(n->_idx), ""); + // Uses (a load for instance) will need to be moved to the + // right slice as well and will get a new memory state + // that we don't know yet. The use could also be the + // backedge of a loop. We put a place holder node between + // the memory node and its uses. We replace that place + // holder with the correct memory state once we know it, + // i.e. when nodes are popped off the stack. Using the + // place holder make the logic work in the presence of + // loops. + if (n->outcnt() > 1) { + Node* place_holder = NULL; + assert(!n->has_out_with(Op_Node), ""); + for (DUIterator k = n->outs(); n->has_out(k); k++) { + Node* u = n->out(k); + if (u != current && u->_idx < last) { + bool success = false; + for (uint l = 0; l < u->req(); l++) { + if (!stack.is_empty() && u == stack.node() && l == stack.index()) { + continue; + } + Node* in = u->in(l); + if (in == n) { + if (place_holder == NULL) { + place_holder = new Node(1); + place_holder->init_req(0, n); + } + igvn.replace_input_of(u, l, place_holder); + success = true; + } + } + if (success) { + --k; + } + } + } + } + if (n->is_Phi()) { + stack.push(n, 1); + n = n->in(1); + } else if (n->is_Mem()) { + stack.push(n, n->req()); + n = n->in(MemNode::Memory); + } else { + assert(n->is_Proj() && n->in(0)->Opcode() == Op_MemBarCPUOrder, ""); + stack.push(n, n->req()); + n = n->in(0)->in(TypeFunc::Memory); + } + } else { + assert(n->adr_type() == TypePtr::BOTTOM || (n->Opcode() == Op_Node && n->_idx >= last) || (n->is_Proj() && n->in(0)->is_Initialize()), ""); + // Build a new MergeMem node to carry the new memory state + // as we build it. IGVN should fold extraneous MergeMem + // nodes. + mm = MergeMemNode::make(n); + igvn.register_new_node_with_optimizer(mm); + while (stack.size() > 0) { + Node* m = stack.node(); + uint idx = stack.index(); + if (m->is_Mem()) { + // Move memory node to its new slice + const TypePtr* adr_type = m->adr_type(); + int alias = get_alias_index(adr_type); + Node* prev = mm->memory_at(alias); + igvn.replace_input_of(m, MemNode::Memory, prev); + mm->set_memory_at(alias, m); + } else if (m->is_Phi()) { + // We need as many new phis as there are new aliases + igvn.replace_input_of(m, idx, mm); + if (idx == m->req()-1) { + Node* r = m->in(0); + for (uint j = (uint)start_alias; j <= (uint)stop_alias; j++) { + const Type* adr_type = get_adr_type(j); + if (!adr_type->isa_aryptr() || !adr_type->is_aryptr()->elem()->isa_valuetype()) { + continue; + } + Node* phi = new PhiNode(r, Type::MEMORY, get_adr_type(j)); + igvn.register_new_node_with_optimizer(phi); + for (uint k = 1; k < m->req(); k++) { + phi->init_req(k, m->in(k)->as_MergeMem()->memory_at(j)); + } + mm->set_memory_at(j, phi); + } + Node* base_phi = new PhiNode(r, Type::MEMORY, TypePtr::BOTTOM); + igvn.register_new_node_with_optimizer(base_phi); + for (uint k = 1; k < m->req(); k++) { + base_phi->init_req(k, m->in(k)->as_MergeMem()->base_memory()); + } + mm->set_base_memory(base_phi); + } + } else { + // This is a MemBarCPUOrder node from + // Parse::array_load()/Parse::array_store(), in the + // branch that handles flattened arrays hidden under + // an Object[] array. We also need one new membar per + // new alias to keep the unknown access that the + // membars protect properly ordered with accesses to + // known flattened array. + assert(m->is_Proj(), "projection expected"); + Node* ctrl = m->in(0)->in(TypeFunc::Control); + igvn.replace_input_of(m->in(0), TypeFunc::Control, top()); + for (uint j = (uint)start_alias; j <= (uint)stop_alias; j++) { + const Type* adr_type = get_adr_type(j); + if (!adr_type->isa_aryptr() || !adr_type->is_aryptr()->elem()->isa_valuetype()) { + continue; + } + MemBarNode* mb = new MemBarCPUOrderNode(this, j, NULL); + igvn.register_new_node_with_optimizer(mb); + Node* mem = mm->memory_at(j); + mb->init_req(TypeFunc::Control, ctrl); + mb->init_req(TypeFunc::Memory, mem); + ctrl = new ProjNode(mb, TypeFunc::Control); + igvn.register_new_node_with_optimizer(ctrl); + mem = new ProjNode(mb, TypeFunc::Memory); + igvn.register_new_node_with_optimizer(mem); + mm->set_memory_at(j, mem); + } + igvn.replace_node(m->in(0)->as_Multi()->proj_out(TypeFunc::Control), ctrl); + } + if (idx < m->req()-1) { + idx += 1; + stack.set_index(idx); + n = m->in(idx); + break; + } + // Take care of place holder nodes + if (m->has_out_with(Op_Node)) { + Node* place_holder = m->find_out_with(Op_Node); + if (place_holder != NULL) { + Node* mm_clone = mm->clone(); + igvn.register_new_node_with_optimizer(mm_clone); + Node* hook = new Node(1); + hook->init_req(0, mm); + igvn.replace_node(place_holder, mm_clone); + hook->destruct(); + } + assert(!m->has_out_with(Op_Node), "place holder should be gone now"); + } + stack.pop(); + } + } + } while(stack.size() > 0); + // Fix the memory state at the MergeMem we started from + igvn.rehash_node_delayed(current); + for (uint j = (uint)start_alias; j <= (uint)stop_alias; j++) { + const Type* adr_type = get_adr_type(j); + if (!adr_type->isa_aryptr() || !adr_type->is_aryptr()->elem()->isa_valuetype()) { + continue; + } + current->set_memory_at(j, mm); + } + current->set_memory_at(index, current->base_memory()); + } + igvn.optimize(); + } + print_method(PHASE_SPLIT_VALUES_ARRAY, 2); +} + + // StringOpts and late inlining of string methods void Compile::inline_string_calls(bool parse_time) { { @@ -2414,6 +2683,8 @@ process_value_types(igvn); } + adjust_flattened_array_access_aliases(igvn); + // Perform escape analysis if (_do_escape_analysis && ConnectionGraph::has_candidates(this)) { if (has_loops()) { @@ -3130,6 +3401,43 @@ assert( !tp || oop_offset_is_sane(tp), "" ); } #endif + if (nop == Op_LoadKlass || nop == Op_LoadNKlass) { + const TypeKlassPtr* tk = n->bottom_type()->make_ptr()->is_klassptr(); + assert(!tk->klass_is_exact(), "should have been folded"); + if (tk->klass()->is_obj_array_klass() || tk->klass()->is_java_lang_Object()) { + bool maybe_value_array = tk->klass()->is_java_lang_Object(); + if (!maybe_value_array) { + ciArrayKlass* ak = tk->klass()->as_array_klass(); + ciKlass* elem = ak->element_klass(); + maybe_value_array = elem->is_java_lang_Object() || elem->is_interface() || elem->is_valuetype(); + } + if (maybe_value_array) { + // Array load klass needs to filter out property bits (but not + // GetNullFreePropertyNode which needs to extract the null free + // bits) + uint last = unique(); + Node* pointer = NULL; + if (nop == Op_LoadKlass) { + Node* cast = new CastP2XNode(NULL, n); + Node* masked = new LShiftXNode(cast, new ConINode(TypeInt::make(oopDesc::storage_props_nof_bits))); + masked = new RShiftXNode(masked, new ConINode(TypeInt::make(oopDesc::storage_props_nof_bits))); + pointer = new CastX2PNode(masked); + pointer = new CheckCastPPNode(NULL, pointer, n->bottom_type()); + } else { + Node* cast = new CastN2INode(n); + Node* masked = new AndINode(cast, new ConINode(TypeInt::make(oopDesc::compressed_klass_mask()))); + pointer = new CastI2NNode(masked, n->bottom_type()); + } + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* u = n->fast_out(i); + if (u->_idx < last && u->Opcode() != Op_GetNullFreeProperty) { + int nb = u->replace_edge(n, pointer); + --i, imax -= nb; + } + } + } + } + } break; } @@ -3652,6 +3960,21 @@ break; } #endif + case Op_GetNullFreeProperty: { + // Extract the null free bits + uint last = unique(); + Node* null_free = NULL; + if (n->in(1)->Opcode() == Op_LoadKlass) { + Node* cast = new CastP2XNode(NULL, n->in(1)); + null_free = new AndLNode(cast, new ConLNode(TypeLong::make(((jlong)1)<<(oopDesc::wide_storage_props_shift + ArrayStorageProperties::null_free_bit)))); + } else { + assert(n->in(1)->Opcode() == Op_LoadNKlass, "not a compressed klass?"); + Node* cast = new CastN2INode(n->in(1)); + null_free = new AndINode(cast, new ConINode(TypeInt::make(1<<(oopDesc::narrow_storage_props_shift + ArrayStorageProperties::null_free_bit)))); + } + n->replace_by(null_free); + break; + } default: assert(!n->is_Call(), ""); assert(!n->is_Mem(), ""); --- old/src/hotspot/share/opto/compile.hpp 2019-06-11 16:50:33.000000000 +0200 +++ new/src/hotspot/share/opto/compile.hpp 2019-06-11 16:50:28.000000000 +0200 @@ -422,7 +422,9 @@ bool _has_method_handle_invokes; // True if this method has MethodHandle invokes. RTMState _rtm_state; // State of Restricted Transactional Memory usage int _loop_opts_cnt; // loop opts round - + bool _has_flattened_accesses; // Any known flattened array accesses? + bool _flattened_accesses_share_alias; // Initially all flattened array share a single slice + // Compilation environment. Arena _comp_arena; // Arena with lifetime equivalent to Compile void* _barrier_set_state; // Potential GC barrier state for Compile @@ -721,6 +723,9 @@ bool profile_rtm() const { return _rtm_state == ProfileRTM; } uint max_node_limit() const { return (uint)_max_node_limit; } void set_max_node_limit(uint n) { _max_node_limit = n; } + void set_flattened_accesses() { _has_flattened_accesses = true; } + bool flattened_accesses_share_alias() const { return _flattened_accesses_share_alias; } + void set_flattened_accesses_share_alias(bool z) { _flattened_accesses_share_alias = z; } // Support for scalarized value type calling convention bool has_scalarized_args() const { return _method != NULL && _method->has_scalarized_args(); } @@ -854,6 +859,8 @@ void process_value_types(PhaseIterGVN &igvn); bool can_add_value_type() const { return _value_type_nodes != NULL; } + void adjust_flattened_array_access_aliases(PhaseIterGVN& igvn); + // remove the opaque nodes that protect the predicates so that the unused checks and // uncommon traps will be eliminated from the graph. void cleanup_loop_predicates(PhaseIterGVN &igvn); @@ -997,11 +1004,11 @@ } AliasType* alias_type(int idx) { assert(idx < num_alias_types(), "oob"); return _alias_types[idx]; } - AliasType* alias_type(const TypePtr* adr_type, ciField* field = NULL) { return find_alias_type(adr_type, false, field); } + AliasType* alias_type(const TypePtr* adr_type, ciField* field = NULL, bool uncached = false) { return find_alias_type(adr_type, false, field, uncached); } bool have_alias_type(const TypePtr* adr_type); AliasType* alias_type(ciField* field); - int get_alias_index(const TypePtr* at) { return alias_type(at)->index(); } + int get_alias_index(const TypePtr* at, bool uncached = false) { return alias_type(at, NULL, uncached)->index(); } const TypePtr* get_adr_type(uint aidx) { return alias_type(aidx)->adr_type(); } int get_general_index(uint aidx) { return alias_type(aidx)->general_index(); } @@ -1318,7 +1325,7 @@ void grow_alias_types(); AliasCacheEntry* probe_alias_cache(const TypePtr* adr_type); const TypePtr *flatten_alias_type(const TypePtr* adr_type) const; - AliasType* find_alias_type(const TypePtr* adr_type, bool no_create, ciField* field); + AliasType* find_alias_type(const TypePtr* adr_type, bool no_create, ciField* field, bool uncached = false); void verify_top(Node*) const PRODUCT_RETURN; --- old/src/hotspot/share/opto/graphKit.cpp 2019-06-11 16:50:38.000000000 +0200 +++ new/src/hotspot/share/opto/graphKit.cpp 2019-06-11 16:50:33.000000000 +0200 @@ -1631,13 +1631,14 @@ const TypePtr* adr_type, const Type* val_type, BasicType bt, - DecoratorSet decorators) { + DecoratorSet decorators, + Node* ctl) { if (stopped()) { return top(); // Dead path ? } C2AccessValuePtr addr(adr, adr_type); - C2ParseAccess access(this, decorators | C2_READ_ACCESS, bt, obj, addr); + C2ParseAccess access(this, decorators | C2_READ_ACCESS, bt, obj, addr, ctl); if (access.is_raw()) { return _barrier_set->BarrierSetC2::load_at(access, val_type); } else { @@ -3404,15 +3405,26 @@ if (null_ctl != top()) { PreserveJVMState pjvms(this); set_control(null_ctl); - // Get array element mirror and corresponding value mirror - Node* array_type_mirror = load_mirror_from_klass(load_object_klass(ary)); - Node* elem_mirror_adr = basic_plus_adr(array_type_mirror, java_lang_Class::component_mirror_offset_in_bytes()); - Node* elem_mirror = access_load_at(array_type_mirror, elem_mirror_adr, _gvn.type(elem_mirror_adr)->is_ptr(), TypeInstPtr::MIRROR, T_OBJECT, IN_HEAP); - Node* inline_mirror_adr = basic_plus_adr(elem_mirror, java_lang_Class::inline_mirror_offset_in_bytes()); - Node* inline_mirror = access_load_at(elem_mirror, inline_mirror_adr, _gvn.type(inline_mirror_adr)->is_ptr(), TypeInstPtr::MIRROR->cast_to_ptr_type(TypePtr::BotPTR), T_OBJECT, IN_HEAP); - // Deoptimize if elem_mirror == inline_mirror => null-free array - Node* cmp = _gvn.transform(new CmpPNode(elem_mirror, inline_mirror)); - Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::ne)); + // Extract null free property from klass pointer + Node* k_adr = basic_plus_adr(ary, oopDesc::klass_offset_in_bytes()); + const TypePtr *k_adr_type = k_adr->bottom_type()->isa_ptr(); + Node* klass = NULL; + if (k_adr_type->is_ptr_to_narrowklass()) { + klass = _gvn.transform(new LoadNKlassNode(NULL, immutable_memory(), k_adr, TypeInstPtr::KLASS, TypeKlassPtr::OBJECT->make_narrowklass(), MemNode::unordered)); + } else { + klass = _gvn.transform(new LoadKlassNode(NULL, immutable_memory(), k_adr, TypeInstPtr::KLASS, TypeKlassPtr::OBJECT, MemNode::unordered)); + } + + Node* null_free = _gvn.transform(new GetNullFreePropertyNode(klass)); + // Deoptimize if null-free array + Node* cmp = NULL; + if (_gvn.type(klass)->isa_klassptr()) { + cmp = new CmpLNode(null_free, zerocon(T_LONG)); + } else { + cmp = new CmpINode(null_free, zerocon(T_INT)); + } + cmp = _gvn.transform(cmp); + Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq)); { BuildCutout unless(this, bol, PROB_MAX); inc_sp(nargs); uncommon_trap(Deoptimization::Reason_null_check, @@ -3427,7 +3439,8 @@ Node* GraphKit::load_lh_array_tag(Node* kls) { Node* lhp = basic_plus_adr(kls, in_bytes(Klass::layout_helper_offset())); - Node* layout_val = make_load(NULL, lhp, TypeInt::INT, T_INT, MemNode::unordered); + Node* layout_val = _gvn.transform(LoadNode::make(_gvn, NULL, immutable_memory(), lhp, lhp->bottom_type()->is_ptr(), TypeInt::INT, T_INT, MemNode::unordered)); + return _gvn.transform(new RShiftINode(layout_val, intcon(Klass::_lh_array_tag_shift))); } @@ -3705,6 +3718,12 @@ if (oop_type->isa_aryptr()) { const TypeAryPtr* arytype = oop_type->is_aryptr(); if (arytype->klass()->is_value_array_klass()) { + // Initially all flattened array accesses share a single slice + // but that changes after parsing. Prepare the memory graph so + // it can optimize flattened array accesses properly once they + // don't share a single slice. + assert(C->flattened_accesses_share_alias(), "should be set at parse time"); + C->set_flattened_accesses_share_alias(false); ciValueArrayKlass* vak = arytype->klass()->as_value_array_klass(); ciValueKlass* vk = vak->element_klass()->as_value_klass(); for (int i = 0, len = vk->nof_nonstatic_fields(); i < len; i++) { @@ -3713,9 +3732,11 @@ continue; // do not bother to track really large numbers of fields int off_in_vt = field->offset() - vk->first_field_offset(); const TypePtr* adr_type = arytype->with_field_offset(off_in_vt)->add_offset(Type::OffsetBot); - int fieldidx = C->get_alias_index(adr_type); + int fieldidx = C->get_alias_index(adr_type, true); hook_memory_on_init(*this, fieldidx, minit_in, minit_out); } + C->set_flattened_accesses_share_alias(true); + hook_memory_on_init(*this, C->get_alias_index(TypeAryPtr::VALUES), minit_in, minit_out); } else { const TypePtr* telemref = oop_type->add_offset(Type::OffsetBot); int elemidx = C->get_alias_index(telemref); --- old/src/hotspot/share/opto/graphKit.hpp 2019-06-11 16:50:44.000000000 +0200 +++ new/src/hotspot/share/opto/graphKit.hpp 2019-06-11 16:50:39.000000000 +0200 @@ -600,7 +600,8 @@ const TypePtr* adr_type, const Type* val_type, BasicType bt, - DecoratorSet decorators); + DecoratorSet decorators, + Node* ctl = NULL); Node* access_load(Node* adr, // actual adress to load val at const Type* val_type, --- old/src/hotspot/share/opto/loopUnswitch.cpp 2019-06-11 16:50:49.000000000 +0200 +++ new/src/hotspot/share/opto/loopUnswitch.cpp 2019-06-11 16:50:44.000000000 +0200 @@ -24,6 +24,8 @@ #include "precompiled.hpp" #include "memory/allocation.inline.hpp" +#include "opto/mulnode.hpp" +#include "opto/addnode.hpp" #include "opto/connode.hpp" #include "opto/convertnode.hpp" #include "opto/loopnode.hpp" @@ -51,6 +53,46 @@ // // Note: the "else" clause may be empty +static bool is_flattened_array_check(Node* iff, PhaseTransform* phase) { + if (iff->Opcode() != Op_If) { + return false; + } + Node* bol = iff->in(1); + if (!bol->is_Bool() || bol->as_Bool()->_test._test != BoolTest::ne) { + return false; + } + Node* cmp = bol->in(1); + if (cmp->Opcode() != Op_CmpI) { + return false; + } + Node* cmp_in1 = cmp->in(1); + Node* cmp_in2 = cmp->in(2); + if ((unsigned int)cmp_in2->find_int_con(0) != Klass::_lh_array_tag_vt_value) { + return false; + } + if (cmp_in1->Opcode() != Op_RShiftI) { + return false; + } + Node* shift_in1 = cmp_in1->in(1); + Node* shift_in2 = cmp_in1->in(2); + if ((unsigned int)shift_in2->find_int_con(0) != Klass::_lh_array_tag_shift) { + return false; + } + if (shift_in1->Opcode() != Op_LoadI) { + return false; + } + intptr_t offset; + Node* addr = AddPNode::Ideal_base_and_offset(shift_in1->in(MemNode::Address), phase, offset); + if (addr == NULL || offset != in_bytes(Klass::layout_helper_offset())) { + return false; + } + if (!phase->type(addr)->isa_klassptr()) { + return false; + } + + return true; +} + //------------------------------policy_unswitching----------------------------- // Return TRUE or FALSE if the loop should be unswitched // (ie. clone loop with an invariant test that does not exit the loop) @@ -74,7 +116,13 @@ if (head->unswitch_count() + 1 > head->unswitch_max()) { return false; } - if (phase->find_unswitching_candidate(this) == NULL) { + + if (head->is_flattened_arrays()) { + return false; + } + + Node_List flattened_checks; + if (phase->find_unswitching_candidate(this, flattened_checks) == NULL && flattened_checks.size() == 0) { return false; } @@ -84,7 +132,7 @@ //------------------------------find_unswitching_candidate----------------------------- // Find candidate "if" for unswitching -IfNode* PhaseIdealLoop::find_unswitching_candidate(const IdealLoopTree *loop) const { +IfNode* PhaseIdealLoop::find_unswitching_candidate(const IdealLoopTree *loop, Node_List& flattened_checks) const { // Find first invariant test that doesn't exit the loop LoopNode *head = loop->_head->as_Loop(); @@ -109,6 +157,20 @@ } n = n_dom; } + + if (unswitch_iff == NULL || is_flattened_array_check(unswitch_iff, &_igvn)) { + // collect all flattened array checks + for (uint i = 0; i < loop->_body.size(); i++) { + Node* n = loop->_body.at(i); + if (is_flattened_array_check(n, &_igvn) && + loop->is_invariant(n->in(1)) && + !loop->is_loop_exit(n)) { + flattened_checks.push(n); + } + } + unswitch_iff = NULL; + } + return unswitch_iff; } @@ -121,9 +183,13 @@ // Find first invariant test that doesn't exit the loop LoopNode *head = loop->_head->as_Loop(); - IfNode* unswitch_iff = find_unswitching_candidate((const IdealLoopTree *)loop); - assert(unswitch_iff != NULL, "should be at least one"); - + Node_List flattened_checks; + IfNode* unswitch_iff = find_unswitching_candidate((const IdealLoopTree *)loop, flattened_checks); + assert(unswitch_iff != NULL || flattened_checks.size() > 0, "should be at least one"); + if (unswitch_iff == NULL) { + unswitch_iff = flattened_checks.at(0)->as_If(); + } + #ifndef PRODUCT if (TraceLoopOpts) { tty->print("Unswitch %d ", head->unswitch_count()+1); @@ -168,13 +234,45 @@ int nct = head->unswitch_count() + 1; head->set_unswitch_count(nct); head_clone->set_unswitch_count(nct); + head_clone->mark_flattened_arrays(); // Add test to new "if" outside of loop IfNode* invar_iff = proj_true->in(0)->as_If(); Node* invar_iff_c = invar_iff->in(0); - BoolNode* bol = unswitch_iff->in(1)->as_Bool(); - invar_iff->set_req(1, bol); invar_iff->_prob = unswitch_iff->_prob; + if (flattened_checks.size() > 0) { + // Flattened array checks are used in + // Parse::array_store()/Parse::array_load() to switch between a + // legacy object array access and a flattened value array + // access. We want the performance impact on legacy accesses to be + // as small as possible so we make 2 copies of the loops: a fast + // one where all accesses are known to be legacy, a slow one where + // some accesses are to flattened arrays. Flattened array checks + // can be removed from the first one but not from the second one + // as it can have a mix of flattened/legacy accesses. + BoolNode* bol = unswitch_iff->in(1)->clone()->as_Bool(); + register_new_node(bol, invar_iff->in(0)); + Node* cmp = bol->in(1)->clone(); + register_new_node(cmp, invar_iff->in(0)); + bol->set_req(1, cmp); + Node* in1 = NULL; + for (uint i = 0; i < flattened_checks.size(); i++) { + Node* v = flattened_checks.at(i)->in(1)->in(1)->in(1); + v = new AndINode(v, _igvn.intcon(Klass::_lh_array_tag_vt_value)); + register_new_node(v, invar_iff->in(0)); + if (in1 == NULL) { + in1 = v; + } else { + in1 = new OrINode(in1, v); + register_new_node(in1, invar_iff->in(0)); + } + } + cmp->set_req(1, in1); + invar_iff->set_req(1, bol); + } else { + BoolNode* bol = unswitch_iff->in(1)->as_Bool(); + invar_iff->set_req(1, bol); + } ProjNode* proj_false = invar_iff->proj_out(0)->as_Proj(); @@ -205,13 +303,21 @@ } } - // Hardwire the control paths in the loops into if(true) and if(false) - _igvn.rehash_node_delayed(unswitch_iff); - short_circuit_if(unswitch_iff, proj_true); - IfNode* unswitch_iff_clone = old_new[unswitch_iff->_idx]->as_If(); - _igvn.rehash_node_delayed(unswitch_iff_clone); - short_circuit_if(unswitch_iff_clone, proj_false); + if (flattened_checks.size() > 0) { + for (uint i = 0; i < flattened_checks.size(); i++) { + IfNode* iff = flattened_checks.at(i)->as_If(); + _igvn.rehash_node_delayed(iff); + short_circuit_if(iff, proj_true); + } + } else { + // Hardwire the control paths in the loops into if(true) and if(false) + _igvn.rehash_node_delayed(unswitch_iff); + short_circuit_if(unswitch_iff, proj_true); + + _igvn.rehash_node_delayed(unswitch_iff_clone); + short_circuit_if(unswitch_iff_clone, proj_false); + } // Reoptimize loops loop->record_for_igvn(); --- old/src/hotspot/share/opto/loopnode.hpp 2019-06-11 16:50:54.000000000 +0200 +++ new/src/hotspot/share/opto/loopnode.hpp 2019-06-11 16:50:49.000000000 +0200 @@ -76,7 +76,8 @@ IsMultiversioned=16384, StripMined=32768, SubwordLoop=65536, - ProfileTripFailed=131072}; + ProfileTripFailed=131072, + FlattenedArrays=262144}; char _unswitch_count; enum { _unswitch_max=3 }; char _postloop_flags; @@ -101,6 +102,7 @@ bool is_strip_mined() const { return _loop_flags & StripMined; } bool is_profile_trip_failed() const { return _loop_flags & ProfileTripFailed; } bool is_subword_loop() const { return _loop_flags & SubwordLoop; } + bool is_flattened_arrays() const { return _loop_flags & FlattenedArrays; } void mark_partial_peel_failed() { _loop_flags |= PartialPeelFailed; } void mark_has_reductions() { _loop_flags |= HasReductions; } @@ -115,6 +117,7 @@ void clear_strip_mined() { _loop_flags &= ~StripMined; } void mark_profile_trip_failed() { _loop_flags |= ProfileTripFailed; } void mark_subword_loop() { _loop_flags |= SubwordLoop; } + void mark_flattened_arrays() { _loop_flags |= FlattenedArrays; } int unswitch_max() { return _unswitch_max; } int unswitch_count() { return _unswitch_count; } @@ -1219,7 +1222,7 @@ void do_unswitching (IdealLoopTree *loop, Node_List &old_new); // Find candidate "if" for unswitching - IfNode* find_unswitching_candidate(const IdealLoopTree *loop) const; + IfNode* find_unswitching_candidate(const IdealLoopTree *loop, Node_List& flattened_checks) const; // Range Check Elimination uses this function! // Constrain the main loop iterations so the affine function: --- old/src/hotspot/share/opto/memnode.cpp 2019-06-11 16:51:01.000000000 +0200 +++ new/src/hotspot/share/opto/memnode.cpp 2019-06-11 16:50:55.000000000 +0200 @@ -2333,6 +2333,46 @@ return klass_identity_common(phase); } +const Type* GetNullFreePropertyNode::Value(PhaseGVN* phase) const { + if (in(1) != NULL) { + const Type* in1_t = phase->type(in(1)); + if (in1_t == Type::TOP) { + return Type::TOP; + } + const TypeKlassPtr* tk = in1_t->make_ptr()->is_klassptr(); + ciArrayKlass* ak = tk->klass()->as_array_klass(); + ciKlass* elem = ak->element_klass(); + if (tk->klass_is_exact() || (!elem->is_java_lang_Object() && !elem->is_interface() && !elem->is_valuetype())) { + int props_shift = in1_t->isa_narrowklass() ? oopDesc::narrow_storage_props_shift : oopDesc::wide_storage_props_shift; + ArrayStorageProperties props = ak->storage_properties(); + intptr_t storage_properties = props.encode(props_shift); + if (in1_t->isa_narrowklass()) { + return TypeInt::make((int)storage_properties); + } + return TypeX::make(storage_properties); + } + } + return bottom_type(); +} + +Node* GetNullFreePropertyNode::Ideal(PhaseGVN *phase, bool can_reshape) { + if (!can_reshape) { + return NULL; + } + if (in(1) != NULL && in(1)->is_Phi()) { + Node* phi = in(1); + Node* r = phi->in(0); + Node* new_phi = new PhiNode(r, bottom_type()); + for (uint i = 1; i < r->req(); i++) { + Node* in = phi->in(i); + if (in == NULL) continue; + new_phi->init_req(i, phase->transform(new GetNullFreePropertyNode(in))); + } + return new_phi; + } + return NULL; +} + Node* LoadNode::klass_identity_common(PhaseGVN* phase) { Node* x = LoadNode::Identity(phase); if (x != this) return x; @@ -2580,7 +2620,7 @@ // unsafe if I have intervening uses... Also disallowed for StoreCM // since they must follow each StoreP operation. Redundant StoreCMs // are eliminated just before matching in final_graph_reshape. - { + if (phase->C->get_adr_type(phase->C->get_alias_index(adr_type())) != TypeAryPtr::VALUES) { Node* st = mem; // If Store 'st' has more than one use, we cannot fold 'st' away. // For example, 'st' might be the final state at a conditional --- old/src/hotspot/share/opto/memnode.hpp 2019-06-11 16:51:07.000000000 +0200 +++ new/src/hotspot/share/opto/memnode.hpp 2019-06-11 16:51:01.000000000 +0200 @@ -111,6 +111,10 @@ #endif } +#ifdef ASSERT + void set_adr_type(const TypePtr* adr_type) { _adr_type = adr_type; } +#endif + // Map a load or store opcode to its corresponding store opcode. // (Return -1 if unknown.) virtual int store_Opcode() const { return -1; } @@ -522,6 +526,33 @@ virtual bool depends_only_on_test() const { return true; } }; +// Retrieve the null free property from an array klass. This is +// treated a bit like a field that would be read from the klass +// structure at runtime except, the implementation encodes the +// property as a bit in the klass header field of the array. This +// implementation detail is hidden under this node so it doesn't make +// a difference for high level optimizations. At final graph reshaping +// time, this node is turned into the actual logical operations that +// extract the property from the klass pointer. For this to work +// correctly, GetNullFreePropertyNode must take a LoadKlass/LoadNKlass +// input. The Ideal transformation splits the GetNullFreePropertyNode +// through phis, Value returns a constant if the node's input is a +// constant. These 2 should guarantee GetNullFreePropertyNode does +// indeed have a LoadKlass/LoadNKlass input at final graph reshaping +// time. +class GetNullFreePropertyNode : public Node { +public: + GetNullFreePropertyNode(Node* klass) : Node(NULL, klass) {} + virtual int Opcode() const; + virtual const Type* Value(PhaseGVN* phase) const; + virtual Node* Ideal(PhaseGVN *phase, bool can_reshape); + virtual const Type* bottom_type() const { + if (in(1)->bottom_type()->isa_klassptr()) { + return TypeLong::LONG; + } + return TypeInt::INT; + } +}; //------------------------------StoreNode-------------------------------------- // Store value; requires Store, Address and Value --- old/src/hotspot/share/opto/parse2.cpp 2019-06-11 16:51:13.000000000 +0200 +++ new/src/hotspot/share/opto/parse2.cpp 2019-06-11 16:51:07.000000000 +0200 @@ -65,6 +65,7 @@ const TypeOopPtr* elemptr = elemtype->make_oopptr(); const TypeAryPtr* ary_t = _gvn.type(ary)->is_aryptr(); if (elemtype->isa_valuetype() != NULL) { + C->set_flattened_accesses(); // Load from flattened value type array Node* vt = ValueTypeNode::make_from_flattened(this, elemtype->value_klass(), ary, adr); push(vt); @@ -75,6 +76,7 @@ } else if (ValueArrayFlatten && elemptr != NULL && elemptr->can_be_value_type() && !ary_t->klass_is_exact() && (!elemptr->is_valuetypeptr() || elemptr->value_klass()->flatten_array())) { // Cannot statically determine if array is flattened, emit runtime check + Node* ctl = control(); IdealKit ideal(this); IdealVariable res(ideal); ideal.declarations_done(); @@ -85,7 +87,7 @@ sync_kit(ideal); const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); Node* ld = access_load_at(ary, adr, adr_type, elemptr, bt, - IN_HEAP | IS_ARRAY | C2_CONTROL_DEPENDENT_LOAD); + IN_HEAP | IS_ARRAY | C2_CONTROL_DEPENDENT_LOAD, ctl); ideal.sync_kit(this); ideal.set(res, ld); } ideal.else_(); { @@ -102,6 +104,7 @@ adr = array_element_address(cast, idx, T_VALUETYPE, ary_t->size(), control()); Node* vt = ValueTypeNode::make_from_flattened(this, vk, cast, adr)->allocate(this, false, false)->get_oop(); ideal.set(res, vt); + ideal.sync_kit(this); } else { // Element type is unknown, emit runtime call assert(!ary_t->klass_is_exact(), "should not have exact type here"); @@ -116,6 +119,12 @@ AllocateNode* alloc = AllocateNode::Ideal_allocation(alloc_obj, &_gvn); assert(alloc->maybe_set_complete(&_gvn), ""); alloc->initialization()->set_complete_with_arraycopy(); + + // This membar keeps this access to an unknown flattened array + // correctly ordered with other unknown and known flattened + // array accesses. + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::VALUES)); + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); // Unknown value type might contain reference fields if (!bs->array_copy_requires_gc_barriers(false, T_OBJECT, false, BarrierSetC2::Parsing)) { @@ -144,11 +153,24 @@ sync_kit(ideal); } - insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out_or_null(AllocateNode::RawAddress)); + // This makes sure no other thread sees a partially initialized buffered value + insert_mem_bar_volatile(Op_MemBarStoreStore, Compile::AliasIdxRaw, alloc->proj_out_or_null(AllocateNode::RawAddress)); + + // Same as MemBarCPUOrder above: keep this unknown flattened + // array access correctly ordered with other flattened array + // access + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::VALUES)); + + // Prevent any use of the newly allocated value before it is + // fully initialized + alloc_obj = new CastPPNode(alloc_obj, _gvn.type(alloc_obj), true); + alloc_obj->set_req(0, control()); + alloc_obj = _gvn.transform(alloc_obj); + + ideal.sync_kit(this); ideal.set(res, alloc_obj); } - ideal.sync_kit(this); } ideal.end_if(); sync_kit(ideal); push_node(bt, _gvn.transform(ideal.value(res))); @@ -195,6 +217,7 @@ const TypeOopPtr* elemptr = elemtype->make_oopptr(); const Type* val_t = _gvn.type(val); if (elemtype->isa_valuetype() != NULL) { + C->set_flattened_accesses(); // Store to flattened value type array if (!cast_val->is_ValueType()) { inc_sp(3); @@ -269,10 +292,26 @@ } else if (!ideal.ctrl()->is_top()) { // Element type is unknown, emit runtime call assert(!ary_t->klass_is_exact(), "should not have exact type here"); + sync_kit(ideal); + + // This membar keeps this access to an unknown flattened + // array correctly ordered with other unknown and known + // flattened array accesses. + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::VALUES)); + ideal.sync_kit(this); + ideal.make_leaf_call(OptoRuntime::store_unknown_value_Type(), CAST_FROM_FN_PTR(address, OptoRuntime::store_unknown_value), "store_unknown_value", val, ary, idx); + + sync_kit(ideal); + // Same as MemBarCPUOrder above: keep this unknown + // flattened array access correctly ordered with other + // flattened array access + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::VALUES)); + ideal.sync_kit(this); + } } ideal.end_if(); sync_kit(ideal); --- old/src/hotspot/share/opto/parseHelper.cpp 2019-06-11 16:51:18.000000000 +0200 +++ new/src/hotspot/share/opto/parseHelper.cpp 2019-06-11 16:51:13.000000000 +0200 @@ -29,6 +29,7 @@ #include "oops/objArrayKlass.hpp" #include "oops/valueArrayKlass.hpp" #include "opto/addnode.hpp" +#include "opto/castnode.hpp" #include "opto/memnode.hpp" #include "opto/mulnode.hpp" #include "opto/parse.hpp" @@ -213,6 +214,9 @@ } else { // Cast array klass to exactness: // Use the exact constant value we know it is. replace_in_map(array_klass,con); + Node* cast = _gvn.transform(new CheckCastPPNode(control(), ary, extak->as_instance_type())); + replace_in_map(ary, cast); + CompileLog* log = C->log(); if (log != NULL) { log->elem("cast_up reason='monomorphic_array' from='%d' to='(exact)'", --- old/src/hotspot/share/opto/phasetype.hpp 2019-06-11 16:51:24.000000000 +0200 +++ new/src/hotspot/share/opto/phasetype.hpp 2019-06-11 16:51:19.000000000 +0200 @@ -56,6 +56,8 @@ PHASE_BEFORE_MACRO_EXPANSION, PHASE_END, PHASE_FAILURE, + PHASE_SPLIT_VALUES_ARRAY, + PHASE_SPLIT_VALUES_ARRAY_IGVN, PHASE_NUM_TYPES }; @@ -94,6 +96,8 @@ case PHASE_BEFORE_MACRO_EXPANSION: return "Before macro expansion"; case PHASE_END: return "End"; case PHASE_FAILURE: return "Failure"; + case PHASE_SPLIT_VALUES_ARRAY: return "Split values array"; + case PHASE_SPLIT_VALUES_ARRAY_IGVN: return "IGVN after split values array"; default: ShouldNotReachHere(); return NULL; --- old/src/hotspot/share/opto/type.cpp 2019-06-11 16:51:29.000000000 +0200 +++ new/src/hotspot/share/opto/type.cpp 2019-06-11 16:51:24.000000000 +0200 @@ -611,6 +611,8 @@ TypeMetadataPtr::BOTTOM = TypeMetadataPtr::make(TypePtr::BotPTR, NULL, Offset::bottom); + TypeValueType::BOTTOM = TypeValueType::make(NULL); + TypeNarrowOop::NULL_PTR = TypeNarrowOop::make( TypePtr::NULL_PTR ); TypeNarrowOop::BOTTOM = TypeNarrowOop::make( TypeInstPtr::BOTTOM ); @@ -647,6 +649,7 @@ TypeAryPtr::LONGS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeLong::LONG ,TypeInt::POS), ciTypeArrayKlass::make(T_LONG), true, Offset::bottom); TypeAryPtr::FLOATS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::FLOAT ,TypeInt::POS), ciTypeArrayKlass::make(T_FLOAT), true, Offset::bottom); TypeAryPtr::DOUBLES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::DOUBLE ,TypeInt::POS), ciTypeArrayKlass::make(T_DOUBLE), true, Offset::bottom); + TypeAryPtr::VALUES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeValueType::BOTTOM,TypeInt::POS), NULL, false, Offset::bottom); // Nobody should ask _array_body_type[T_NARROWOOP]. Use NULL as assert. TypeAryPtr::_array_body_type[T_NARROWOOP] = NULL; @@ -2386,6 +2389,8 @@ //==============================TypeValueType======================================= +const TypeValueType *TypeValueType::BOTTOM; + //------------------------------make------------------------------------------- const TypeValueType* TypeValueType::make(ciValueKlass* vk, bool larval) { return (TypeValueType*)(new TypeValueType(vk, larval))->hashcons(); @@ -2437,7 +2442,11 @@ case ValueType: { // All value types inherit from Object const TypeValueType* other = t->is_valuetype(); - if (_vk == other->_vk) { + if (_vk == NULL) { + return this; + } else if (other->_vk == NULL) { + return other; + } else if (_vk == other->_vk) { if (_larval == other->_larval || !_larval) { return this; @@ -2488,6 +2497,10 @@ //------------------------------dump2------------------------------------------ #ifndef PRODUCT void TypeValueType::dump2(Dict &d, uint depth, outputStream* st) const { + if (_vk == NULL) { + st->print("BOTTOM valuetype"); + return; + } int count = _vk->nof_declared_nonstatic_fields(); st->print("valuetype[%d]:{", count); st->print("%s", count != 0 ? _vk->declared_nonstatic_field_at(0)->type()->name() : "empty"); @@ -4351,6 +4364,7 @@ const TypeAryPtr *TypeAryPtr::LONGS; const TypeAryPtr *TypeAryPtr::FLOATS; const TypeAryPtr *TypeAryPtr::DOUBLES; +const TypeAryPtr *TypeAryPtr::VALUES; //------------------------------make------------------------------------------- const TypeAryPtr* TypeAryPtr::make(PTR ptr, const TypeAry *ary, ciKlass* k, bool xk, Offset offset, Offset field_offset, @@ -5346,7 +5360,9 @@ bool null_free = el->is_valuetypeptr() && el->isa_instptr()->ptr() != TypePtr::TopPTR && !el->isa_instptr()->maybe_null(); k_ary = ciArrayKlass::make(el->is_oopptr()->klass(), null_free); } else if (el->isa_valuetype()) { - k_ary = ciArrayKlass::make(el->value_klass(), /* null_free */ true); + if (el->value_klass() != NULL) { + k_ary = ciArrayKlass::make(el->value_klass(), /* null_free */ true); + } } else if ((tary = el->isa_aryptr()) != NULL) { // Compute array klass from element klass ciKlass* k_elem = tary->klass(); --- old/src/hotspot/share/opto/type.hpp 2019-06-11 16:51:35.000000000 +0200 +++ new/src/hotspot/share/opto/type.hpp 2019-06-11 16:51:30.000000000 +0200 @@ -785,6 +785,8 @@ virtual bool would_improve_type(ciKlass* exact_kls, int inline_depth) const { return false; } virtual bool would_improve_ptr(ProfilePtrKind ptr_kind) const { return false; } + static const TypeValueType *BOTTOM; + #ifndef PRODUCT virtual void dump2(Dict &d, uint, outputStream* st) const; // Specialized per-Type dumping #endif @@ -1325,6 +1327,7 @@ static const TypeAryPtr *LONGS; static const TypeAryPtr *FLOATS; static const TypeAryPtr *DOUBLES; + static const TypeAryPtr *VALUES; // selects one of the above: static const TypeAryPtr *get_array_body_type(BasicType elem) { assert((uint)elem <= T_CONFLICT && _array_body_type[elem] != NULL, "bad elem type"); --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestLWorld.java 2019-06-11 16:51:41.000000000 +0200 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestLWorld.java 2019-06-11 16:51:36.000000000 +0200 @@ -24,6 +24,8 @@ package compiler.valhalla.valuetypes; import java.lang.invoke.*; +import java.lang.reflect.Method; +import java.util.Arrays; import jdk.experimental.value.MethodHandleBuilder; import jdk.test.lib.Asserts; @@ -2055,4 +2057,142 @@ int result = test81(); Asserts.assertEQ(result, 10*rI); } + + // Test check for null free array when storing to value array + @Test + public void test82(Object[] dst, Object v) { + dst[0] = v; + } + + @DontCompile + public void test82_verifier(boolean warmup) { + MyValue2[] dst = new MyValue2[1]; + test82(dst, testValue2); + if (!warmup) { + try { + test82(dst, null); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + @Warmup(10000) + public void test83(Object[] dst, Object v, boolean flag) { + if (dst == null) { // null check + } + if (flag) { + if (dst.getClass() == MyValue1[].class) { // trigger split if + } + } else { + dst = new MyValue2[1]; // constant null free property + } + dst[0] = v; + } + + @DontCompile + public void test83_verifier(boolean warmup) { + MyValue2[] dst = new MyValue2[1]; + test83(dst, testValue2, false); + if (!warmup) { + try { + test83(dst, null, true); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (NullPointerException e) { + // Expected + } + } + } + + // Following: should make 2 copies of the loop, one for non + // flattened arrays, one for other cases + @Test(match = { COUNTEDLOOP }, matchCount = { 4 } ) + public void test84(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @DontCompile + public void test84_verifier(boolean warmup) { + MyValue2[] src = new MyValue2[100]; + Arrays.fill(src, testValue2); + MyValue2[] dst = new MyValue2[100]; + test84(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); + } + + @Test(valid = G1GCOn, match = { COUNTEDLOOP }, matchCount = { 2 } ) + @Test(valid = G1GCOff, match = { COUNTEDLOOP }, matchCount = { 3 } ) + public void test85(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @DontCompile + public void test85_verifier(boolean warmup) { + Object[] src = new Object[100]; + Arrays.fill(src, new Object()); + src[0] = null; + Object[] dst = new Object[100]; + test85(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); + } + + @Test(valid = G1GCOn, match = { COUNTEDLOOP }, matchCount = { 2 } ) + @Test(valid = G1GCOff, match = { COUNTEDLOOP }, matchCount = { 3 } ) + public void test86(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @DontCompile + public void test86_verifier(boolean warmup) { + MyValue2[] src = new MyValue2[100]; + Arrays.fill(src, testValue2); + Object[] dst = new Object[100]; + test86(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); + } + + @Test(match = { COUNTEDLOOP }, matchCount = { 4 } ) + public void test87(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @DontCompile + public void test87_verifier(boolean warmup) { + Object[] src = new Object[100]; + Arrays.fill(src, testValue2); + MyValue2[] dst = new MyValue2[100]; + test87(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); + } + + @Test(match = { COUNTEDLOOP }, matchCount = { 4 } ) + public void test88(Object[] src1, Object[] dst1, Object[] src2, Object[] dst2) { + for (int i = 0; i < src1.length; i++) { + dst1[i] = src1[i]; + dst2[i] = src2[i]; + } + } + + @DontCompile + public void test88_verifier(boolean warmup) { + MyValue2[] src1 = new MyValue2[100]; + Arrays.fill(src1, testValue2); + MyValue2[] dst1 = new MyValue2[100]; + Object[] src2 = new Object[100]; + Arrays.fill(src2, new Object()); + Object[] dst2 = new Object[100]; + test88(src1, dst1, src2, dst2); + Asserts.assertTrue(Arrays.equals(src1, dst1)); + Asserts.assertTrue(Arrays.equals(src2, dst2)); + } } --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/ValueTypeTest.java 2019-06-11 16:51:46.000000000 +0200 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/ValueTypeTest.java 2019-06-11 16:51:41.000000000 +0200 @@ -151,11 +151,14 @@ protected static final int ValueTypeReturnedAsFieldsOff = 0x20; protected static final int AlwaysIncrementalInlineOn = 0x40; protected static final int AlwaysIncrementalInlineOff = 0x80; + protected static final int G1GCOn = 0x100; + protected static final int G1GCOff = 0x200; static final int AllFlags = ValueTypePassFieldsAsArgsOn | ValueTypePassFieldsAsArgsOff | ValueTypeArrayFlattenOn | ValueTypeArrayFlattenOff | ValueTypeReturnedAsFieldsOn; protected static final boolean ValueTypePassFieldsAsArgs = (Boolean)WHITE_BOX.getVMFlag("ValueTypePassFieldsAsArgs"); protected static final boolean ValueTypeArrayFlatten = (WHITE_BOX.getIntxVMFlag("ValueArrayElemMaxFlatSize") == -1); // FIXME - fix this if default of ValueArrayElemMaxFlatSize is changed protected static final boolean ValueTypeReturnedAsFields = (Boolean)WHITE_BOX.getVMFlag("ValueTypeReturnedAsFields"); protected static final boolean AlwaysIncrementalInline = (Boolean)WHITE_BOX.getVMFlag("AlwaysIncrementalInline"); + protected static final boolean G1GC = (Boolean)WHITE_BOX.getVMFlag("UseG1GC"); protected static final long TieredStopAtLevel = (Long)WHITE_BOX.getVMFlag("TieredStopAtLevel"); protected static final int COMP_LEVEL_ANY = -2; protected static final int COMP_LEVEL_ALL = -2; @@ -180,6 +183,7 @@ protected static final String LOADK = START + "LoadK" + MID + END; protected static final String STORE = START + "Store(B|C|S|I|L|F|D|P|N)" + MID + "@compiler/valhalla/valuetypes/MyValue.*" + END; protected static final String LOOP = START + "Loop" + MID + "" + END; + protected static final String COUNTEDLOOP = START + "CountedLoop\\b" + MID + "" + END; protected static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap.*(unstable_if|predicate)" + END; protected static final String RETURN = START + "Return" + MID + "returns" + END; protected static final String LINKTOSTATIC = START + "CallStaticJava" + MID + "linkToStatic" + END; @@ -474,6 +478,12 @@ } else if ((a.valid() & AlwaysIncrementalInlineOff) != 0 && !AlwaysIncrementalInline) { assert anno == null; anno = a; + } else if ((a.valid() & G1GCOn) != 0 && G1GC) { + assert anno == null; + anno = a; + } else if ((a.valid() & G1GCOff) != 0 && !G1GC) { + assert anno == null; + anno = a; } } assert anno != null;