--- old/src/hotspot/share/ci/ciTypeFlow.cpp 2019-05-08 13:38:55.655906237 +0200 +++ new/src/hotspot/share/ci/ciTypeFlow.cpp 2019-05-08 13:38:55.303908662 +0200 @@ -333,16 +333,14 @@ ciType* elem1 = k1->as_array_klass()->element_klass(); ciType* elem2 = k2->as_array_klass()->element_klass(); ciType* elem = elem1; - if (elem->is_valuetype() && prop_mismatch) { - elem = object_klass; - } else if (elem1 != elem2) { + if (elem1 != elem2) { elem = type_meet_internal(elem1, elem2, analyzer)->as_klass(); } // Do an easy shortcut if one type is a super of the other. - if (elem == elem1) { + if (elem == elem1 && !prop_mismatch) { assert(k1 == ciArrayKlass::make(elem, never_null), "shortcut is OK"); return k1; - } else if (elem == elem2) { + } else if (elem == elem2 && !prop_mismatch) { assert(k2 == ciArrayKlass::make(elem, never_null), "shortcut is OK"); return k2; } else { --- old/src/hotspot/share/opto/compile.cpp 2019-05-08 13:38:56.451900756 +0200 +++ new/src/hotspot/share/opto/compile.cpp 2019-05-08 13:38:56.291901858 +0200 @@ -1839,7 +1839,7 @@ } int field_offset = flat->is_aryptr()->field_offset().get(); if (elemtype->isa_valuetype() && field_offset != Type::OffsetBot) { - ciValueKlass* vk = elemtype->is_valuetype()->value_klass(); + ciValueKlass* vk = elemtype->value_klass(); field_offset += vk->first_field_offset(); field = vk->get_field_by_offset(field_offset, false); } --- old/src/hotspot/share/opto/doCall.cpp 2019-05-08 13:38:56.847898029 +0200 +++ new/src/hotspot/share/opto/doCall.cpp 2019-05-08 13:38:56.691899103 +0200 @@ -540,7 +540,7 @@ Node* receiver_node = stack(sp() - nargs); const TypeOopPtr* receiver_type = NULL; if (receiver_node->is_ValueType()) { - receiver_type = TypeInstPtr::make(TypePtr::NotNull, _gvn.type(receiver_node)->is_valuetype()->value_klass()); + receiver_type = TypeInstPtr::make(TypePtr::NotNull, _gvn.type(receiver_node)->value_klass()); } else { receiver_type = _gvn.type(receiver_node)->isa_oopptr(); } --- old/src/hotspot/share/opto/escape.cpp 2019-05-08 13:38:57.339894641 +0200 +++ new/src/hotspot/share/opto/escape.cpp 2019-05-08 13:38:57.175895770 +0200 @@ -993,7 +993,7 @@ (aat->isa_aryptr() && aat->isa_aryptr()->klass()->is_obj_array_klass()) || (aat->isa_aryptr() && aat->isa_aryptr()->elem() != NULL && aat->isa_aryptr()->elem()->isa_valuetype() && - aat->isa_aryptr()->elem()->isa_valuetype()->value_klass()->contains_oops())); + aat->isa_aryptr()->elem()->value_klass()->contains_oops())); if (i == TypeFunc::Parms) { src_has_oops = arg_has_oops; } @@ -2131,7 +2131,7 @@ } else { const Type* elemtype = adr_type->isa_aryptr()->elem(); if (elemtype->isa_valuetype() && field_offset != Type::OffsetBot) { - ciValueKlass* vk = elemtype->is_valuetype()->value_klass(); + ciValueKlass* vk = elemtype->value_klass(); field_offset += vk->first_field_offset(); bt = vk->get_field_by_offset(field_offset, false)->layout_type(); } else { --- old/src/hotspot/share/opto/graphKit.cpp 2019-05-08 13:38:57.887890866 +0200 +++ new/src/hotspot/share/opto/graphKit.cpp 2019-05-08 13:38:57.659892436 +0200 @@ -1597,7 +1597,8 @@ const Type* val_type, BasicType bt, DecoratorSet decorators, - bool deoptimize_on_exception) { + bool deoptimize_on_exception, + bool safe_for_replace) { // Transformation of a value which could be NULL pointer (CastPP #NULL) // could be delayed during Parse (for example, in adjust_map_after_if()). // Execute transformation here to avoid barrier generation in such case. @@ -1612,7 +1613,7 @@ assert(val != NULL, "not dead path"); if (val->is_ValueType()) { // Allocate value type and get oop - val = val->as_ValueType()->allocate(this, deoptimize_on_exception)->get_oop(); + val = val->as_ValueType()->allocate(this, deoptimize_on_exception, safe_for_replace)->get_oop(); } C2AccessValuePtr addr(adr, adr_type); @@ -3131,7 +3132,7 @@ // Load the object's klass Node* obj_klass = NULL; if (is_value) { - obj_klass = makecon(TypeKlassPtr::make(_gvn.type(not_null_obj)->is_valuetype()->value_klass())); + obj_klass = makecon(TypeKlassPtr::make(_gvn.type(not_null_obj)->value_klass())); } else { obj_klass = load_object_klass(not_null_obj); } @@ -3185,7 +3186,7 @@ if (tk->singleton()) { ciKlass* klass = NULL; if (is_value) { - klass = _gvn.type(obj)->is_valuetype()->value_klass(); + klass = _gvn.type(obj)->value_klass(); } else { const TypeOopPtr* objtp = _gvn.type(obj)->isa_oopptr(); if (objtp != NULL) { @@ -3238,6 +3239,9 @@ enum { _obj_path = 1, _null_path, PATH_LIMIT }; RegionNode* region = new RegionNode(PATH_LIMIT); Node* phi = new PhiNode(region, toop); + _gvn.set_type(region, Type::CONTROL); + _gvn.set_type(phi, toop); + C->set_has_split_ifs(true); // Has chance for split-if optimization // Use null-cast information if it is available @@ -3303,7 +3307,7 @@ // Load the object's klass Node* obj_klass = NULL; if (is_value) { - obj_klass = makecon(TypeKlassPtr::make(_gvn.type(not_null_obj)->is_valuetype()->value_klass())); + obj_klass = makecon(TypeKlassPtr::make(_gvn.type(not_null_obj)->value_klass())); } else { obj_klass = load_object_klass(not_null_obj); } @@ -3380,45 +3384,38 @@ } } -// Deoptimize if 'ary' is flattened or if 'obj' is null and 'ary' is a value type array -void GraphKit::gen_value_type_array_guard(Node* ary, Node* obj, int nargs) { +// Deoptimize if 'ary' is a null-free value type array and 'val' is null +void GraphKit::gen_value_array_null_guard(Node* ary, Node* val, int nargs) { assert(EnableValhalla, "should only be used if value types are enabled"); - // Load array element klass - Node* kls = load_object_klass(ary); - Node* k_adr = basic_plus_adr(kls, in_bytes(ArrayKlass::element_klass_offset())); - Node* elem_klass = _gvn.transform(LoadKlassNode::make(_gvn, NULL, immutable_memory(), k_adr, TypeInstPtr::KLASS)); - // Check if element is a value type - Node* flags_addr = basic_plus_adr(elem_klass, in_bytes(Klass::access_flags_offset())); - Node* flags = make_load(NULL, flags_addr, TypeInt::INT, T_INT, MemNode::unordered); - Node* is_value_elem = _gvn.transform(new AndINode(flags, intcon(JVM_ACC_VALUE))); - - const Type* objtype = _gvn.type(obj); - if (objtype == TypePtr::NULL_PTR) { - // Object is always null, check if array is a value type array - Node* cmp = _gvn.transform(new CmpINode(is_value_elem, intcon(0))); - Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq)); - { BuildCutout unless(this, bol, PROB_MAX); - // TODO just deoptimize for now if we store null to a value type array - inc_sp(nargs); - uncommon_trap(Deoptimization::Reason_array_check, - Deoptimization::Action_none); - } - } else { - // Check if (is_value_elem && obj_is_null) <=> (!is_value_elem | !obj_is_null == 0) - // TODO what if we later figure out that obj is never null? - Node* not_value = _gvn.transform(new XorINode(is_value_elem, intcon(JVM_ACC_VALUE))); - not_value = _gvn.transform(new ConvI2LNode(not_value)); - Node* not_null = _gvn.transform(new CastP2XNode(NULL, obj)); - Node* both = _gvn.transform(new OrLNode(not_null, not_value)); - Node* cmp = _gvn.transform(new CmpLNode(both, longcon(0))); - Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::ne)); + const Type* val_t = _gvn.type(val); + if (val->is_ValueType() || !TypePtr::NULL_PTR->higher_equal(val_t)) { + return; // Never null + } + RegionNode* region = new RegionNode(3); + Node* null_ctl = top(); + null_check_oop(val, &null_ctl); + 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* value_mirror_adr = basic_plus_adr(elem_mirror, java_lang_Class::value_mirror_offset_in_bytes()); + Node* value_mirror = access_load_at(elem_mirror, value_mirror_adr, _gvn.type(value_mirror_adr)->is_ptr(), TypeInstPtr::MIRROR, T_OBJECT, IN_HEAP); + // Deoptimize if elem_mirror == value_mirror => null-free array + Node* cmp = _gvn.transform(new CmpPNode(elem_mirror, value_mirror)); + Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::ne)); { BuildCutout unless(this, bol, PROB_MAX); - // TODO just deoptimize for now if we store null to a value type array inc_sp(nargs); - uncommon_trap(Deoptimization::Reason_array_check, + uncommon_trap(Deoptimization::Reason_null_check, Deoptimization::Action_none); } + region->init_req(1, control()); } + region->init_req(2, control()); + set_control(_gvn.transform(region)); + record_for_igvn(region); } Node* GraphKit::load_lh_array_tag(Node* kls) { --- old/src/hotspot/share/opto/graphKit.hpp 2019-05-08 13:38:58.371887533 +0200 +++ new/src/hotspot/share/opto/graphKit.hpp 2019-05-08 13:38:58.187888800 +0200 @@ -592,7 +592,8 @@ const Type* val_type, BasicType bt, DecoratorSet decorators, - bool deoptimize_on_exception = false); + bool deoptimize_on_exception = false, + bool safe_for_replace = true); Node* access_load_at(Node* obj, // containing obj Node* adr, // actual adress to load val at @@ -848,7 +849,7 @@ Node* is_always_locked(Node* obj); void gen_value_type_guard(Node* obj, int nargs = 0); - void gen_value_type_array_guard(Node* ary, Node* obj, int nargs); + void gen_value_array_null_guard(Node* ary, Node* val, int nargs); Node* load_lh_array_tag(Node* kls); Node* gen_lh_array_test(Node* kls, unsigned int lh_value); --- old/src/hotspot/share/opto/library_call.cpp 2019-05-08 13:38:58.811884502 +0200 +++ new/src/hotspot/share/opto/library_call.cpp 2019-05-08 13:38:58.607885908 +0200 @@ -2457,7 +2457,7 @@ } else { if (offset->is_Con()) { long off = find_long_con(offset, 0); - ciValueKlass* vk = _gvn.type(vt)->is_valuetype()->value_klass(); + ciValueKlass* vk = vt->type()->value_klass(); if ((long)(int)off != off || !vk->contains_field_offset(off)) { return false; } @@ -2567,14 +2567,13 @@ const Type* elem = adr_type->is_aryptr()->elem(); if (!elem->isa_valuetype()) { mismatched = true; - } else if (elem->is_valuetype()->value_klass() != value_klass) { + } else if (elem->value_klass() != value_klass) { mismatched = true; } } if (is_store) { const Type* val_t = _gvn.type(val); - if (!val_t->isa_valuetype() || - val_t->is_valuetype()->value_klass() != value_klass) { + if (!val_t->isa_valuetype() || val_t->value_klass() != value_klass) { return false; } } @@ -3531,8 +3530,7 @@ ciKlass* obj_klass = NULL; if (obj->is_ValueType()) { - const TypeValueType* tvt = _gvn.type(obj)->is_valuetype(); - obj_klass = tvt->value_klass(); + obj_klass = _gvn.type(obj)->value_klass(); } else { const TypeOopPtr* tp = _gvn.type(obj)->isa_oopptr(); if (tp != NULL) { @@ -4326,7 +4324,7 @@ bool LibraryCallKit::inline_native_getClass() { Node* obj = argument(0); if (obj->is_ValueType()) { - ciKlass* vk = _gvn.type(obj)->is_valuetype()->value_klass(); + ciKlass* vk = _gvn.type(obj)->value_klass(); set_result(makecon(TypeInstPtr::make(vk->java_mirror()))); return true; } @@ -5207,13 +5205,13 @@ if (top_dest != NULL && top_dest->elem()->make_oopptr() != NULL && top_dest->elem()->make_oopptr()->can_be_value_type()) { - generate_valueArray_guard(load_object_klass(dest), slow_region); + generate_valueArray_guard(dest_klass, slow_region); } if (top_src != NULL && top_src->elem()->make_oopptr() != NULL && top_src->elem()->make_oopptr()->can_be_value_type()) { - generate_valueArray_guard(load_object_klass(src), slow_region); + generate_valueArray_guard(src_klass, slow_region); } { --- old/src/hotspot/share/opto/memnode.cpp 2019-05-08 13:38:59.471879957 +0200 +++ new/src/hotspot/share/opto/memnode.cpp 2019-05-08 13:38:59.199881830 +0200 @@ -2236,21 +2236,25 @@ // Check for loading klass from an array const TypeAryPtr *tary = tp->isa_aryptr(); - if( tary != NULL ) { + if (tary != NULL) { ciKlass *tary_klass = tary->klass(); if (tary_klass != NULL // can be NULL when at BOTTOM or TOP && tary->offset() == oopDesc::klass_offset_in_bytes()) { - if (tary->klass_is_exact()) { + ciArrayKlass* ak = tary_klass->as_array_klass(); + // Do not fold klass loads from [V? because the runtime type might be [V due to [V <: [V? + bool can_be_flattened = ak->is_obj_array_klass() && !ak->storage_properties().is_null_free() && ak->element_klass()->is_valuetype(); + + if (tary->klass_is_exact() && !can_be_flattened) { return TypeKlassPtr::make(tary_klass); } - ciArrayKlass *ak = tary->klass()->as_array_klass(); + // If the klass is an object array, we defer the question to the // array component klass. - if( ak->is_obj_array_klass() ) { - assert( ak->is_loaded(), "" ); + if (ak->is_obj_array_klass() && !can_be_flattened) { + assert(ak->is_loaded(), ""); ciKlass *base_k = ak->as_obj_array_klass()->base_element_klass(); - if( base_k->is_loaded() && base_k->is_instance_klass() ) { - ciInstanceKlass* ik = base_k->as_instance_klass(); + if (base_k->is_loaded() && base_k->is_instance_klass()) { + ciInstanceKlass *ik = base_k->as_instance_klass(); // See if we can become precise: no subklasses and no interface if (!ik->is_interface() && !ik->has_subklass()) { //assert(!UseExactTypes, "this code should be useless with exact types"); @@ -2263,9 +2267,8 @@ } } return TypeKlassPtr::make(TypePtr::NotNull, ak, Type::Offset(0)); - } else { // Found a type-array? + } else if (ak->is_type_array_klass()) { //assert(!UseExactTypes, "this code should be useless with exact types"); - assert( ak->is_type_array_klass(), "" ); return TypeKlassPtr::make(ak); // These are always precise } } @@ -2288,6 +2291,10 @@ // The array's TypeKlassPtr was declared 'precise' or 'not precise' // according to the element type's subclassing. return TypeKlassPtr::make(tkls->ptr(), elem, Type::Offset(0)); + } else if (klass->is_value_array_klass() && + tkls->offset() == in_bytes(ObjArrayKlass::element_klass_offset())) { + ciKlass* elem = klass->as_value_array_klass()->element_klass(); + return TypeKlassPtr::make(tkls->ptr(), elem, Type::Offset(0)); } if( klass->is_instance_klass() && tkls->klass_is_exact() && tkls->offset() == in_bytes(Klass::super_offset())) { --- old/src/hotspot/share/opto/parse1.cpp 2019-05-08 13:38:59.891877064 +0200 +++ new/src/hotspot/share/opto/parse1.cpp 2019-05-08 13:38:59.739878112 +0200 @@ -157,7 +157,7 @@ const TypeOopPtr* tp = type->isa_oopptr(); if (type->isa_valuetype() != NULL) { // The interpreter passes value types as oops - tp = TypeOopPtr::make_from_klass(type->isa_valuetype()->value_klass()); + tp = TypeOopPtr::make_from_klass(type->value_klass()); tp = tp->join_speculative(TypePtr::NOTNULL)->is_oopptr(); } @@ -927,7 +927,7 @@ } else { ret->init_req(TypeFunc::Parms, vt->tagged_klass(kit.gvn())); } - const Array* sig_array = vt->type()->is_valuetype()->value_klass()->extended_sig(); + const Array* sig_array = vt->type()->value_klass()->extended_sig(); GrowableArray sig = GrowableArray(sig_array->length()); sig.appendAll(sig_array); ExtendedSignature sig_cc = ExtendedSignature(&sig, SigEntryFilter()); @@ -2327,7 +2327,7 @@ if (return_type->isa_valuetype()) { // Value type is returned as fields, make sure it is scalarized if (!value->is_ValueType()) { - value = ValueTypeNode::make_from_oop(this, value, return_type->is_valuetype()->value_klass()); + value = ValueTypeNode::make_from_oop(this, value, return_type->value_klass()); } if (!_caller->has_method()) { // Value type is returned as fields from root method, make --- old/src/hotspot/share/opto/parse2.cpp 2019-05-08 13:39:00.303874228 +0200 +++ new/src/hotspot/share/opto/parse2.cpp 2019-05-08 13:39:00.139875357 +0200 @@ -66,79 +66,92 @@ const TypeAryPtr* ary_t = _gvn.type(ary)->is_aryptr(); if (elemtype->isa_valuetype() != NULL) { // Load from flattened value type array - ciValueKlass* vk = elemtype->is_valuetype()->value_klass(); - Node* vt = ValueTypeNode::make_from_flattened(this, vk, ary, adr); + Node* vt = ValueTypeNode::make_from_flattened(this, elemtype->value_klass(), ary, adr); push(vt); return; } else if (elemptr != NULL && elemptr->is_valuetypeptr() && !elemptr->maybe_null()) { - // Load from non-flattened value type array (elements can never be null) + // Load from non-flattened but flattenable value type array (elements can never be null) bt = T_VALUETYPE; - } else if (ValueArrayFlatten && elemptr != NULL && elemptr->can_be_value_type() && !ary_t->klass_is_exact()) { + } 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 - assert(!elemptr->is_valuetypeptr(), "we know the exact type"); IdealKit ideal(this); IdealVariable res(ideal); ideal.declarations_done(); Node* kls = load_object_klass(ary); Node* tag = load_lh_array_tag(kls); ideal.if_then(tag, BoolTest::ne, intcon(Klass::_lh_array_tag_vt_value)); { - // non flattened + // non-flattened sync_kit(ideal); const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); - elemtype = ary_t->elem()->make_oopptr(); - Node* ld = access_load_at(ary, adr, adr_type, elemtype, bt, + Node* ld = access_load_at(ary, adr, adr_type, elemptr, bt, IN_HEAP | IS_ARRAY | C2_CONTROL_DEPENDENT_LOAD); ideal.sync_kit(this); ideal.set(res, ld); } ideal.else_(); { // flattened sync_kit(ideal); - Node* k_adr = basic_plus_adr(kls, in_bytes(ArrayKlass::element_klass_offset())); - Node* elem_klass = _gvn.transform(LoadKlassNode::make(_gvn, NULL, immutable_memory(), k_adr, TypeInstPtr::KLASS)); - Node* obj_size = NULL; - kill_dead_locals(); - inc_sp(2); - Node* alloc_obj = new_instance(elem_klass, NULL, &obj_size, /*deoptimize_on_exception=*/true); - dec_sp(2); - - AllocateNode* alloc = AllocateNode::Ideal_allocation(alloc_obj, &_gvn); - assert(alloc->maybe_set_complete(&_gvn), ""); - alloc->initialization()->set_complete_with_arraycopy(); - BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - // Unknown value type so might have reference fields - if (!bs->array_copy_requires_gc_barriers(false, T_OBJECT, false, BarrierSetC2::Parsing)) { - int base_off = sizeof(instanceOopDesc); - Node* dst_base = basic_plus_adr(alloc_obj, base_off); - Node* countx = obj_size; - countx = _gvn.transform(new SubXNode(countx, MakeConX(base_off))); - countx = _gvn.transform(new URShiftXNode(countx, intcon(LogBytesPerLong))); - - assert(Klass::_lh_log2_element_size_shift == 0, "use shift in place"); - Node* lhp = basic_plus_adr(kls, in_bytes(Klass::layout_helper_offset())); - Node* elem_shift = make_load(NULL, lhp, TypeInt::INT, T_INT, MemNode::unordered); - uint header = arrayOopDesc::base_offset_in_bytes(T_VALUETYPE); - Node* base = basic_plus_adr(ary, header); - idx = Compile::conv_I2X_index(&_gvn, idx, TypeInt::POS, control()); - Node* scale = _gvn.transform(new LShiftXNode(idx, elem_shift)); - Node* adr = basic_plus_adr(ary, base, scale); - - access_clone(adr, dst_base, countx, false); + if (elemptr->is_valuetypeptr()) { + // Element type is known, cast and load from flattened representation + assert(elemptr->maybe_null(), "must be nullable"); + ciValueKlass* vk = elemptr->value_klass(); + assert(vk->flatten_array(), "must be flattenable"); + ciArrayKlass* array_klass = ciArrayKlass::make(vk, /* never_null */ true); + const TypeAryPtr* arytype = TypeOopPtr::make_from_klass(array_klass)->isa_aryptr(); + Node* cast = _gvn.transform(new CheckCastPPNode(control(), ary, arytype)); + 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); } else { - ideal.sync_kit(this); - ideal.make_leaf_call(OptoRuntime::load_unknown_value_Type(), - CAST_FROM_FN_PTR(address, OptoRuntime::load_unknown_value), - "load_unknown_value", - ary, idx, alloc_obj); - sync_kit(ideal); - } + // Element type is unknown, emit runtime call + assert(!ary_t->klass_is_exact(), "should not have exact type here"); + Node* k_adr = basic_plus_adr(kls, in_bytes(ArrayKlass::element_klass_offset())); + Node* elem_klass = _gvn.transform(LoadKlassNode::make(_gvn, NULL, immutable_memory(), k_adr, TypeInstPtr::KLASS)); + Node* obj_size = NULL; + kill_dead_locals(); + inc_sp(2); + Node* alloc_obj = new_instance(elem_klass, NULL, &obj_size, /*deoptimize_on_exception=*/true); + dec_sp(2); + + AllocateNode* alloc = AllocateNode::Ideal_allocation(alloc_obj, &_gvn); + assert(alloc->maybe_set_complete(&_gvn), ""); + alloc->initialization()->set_complete_with_arraycopy(); + 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)) { + int base_off = sizeof(instanceOopDesc); + Node* dst_base = basic_plus_adr(alloc_obj, base_off); + Node* countx = obj_size; + countx = _gvn.transform(new SubXNode(countx, MakeConX(base_off))); + countx = _gvn.transform(new URShiftXNode(countx, intcon(LogBytesPerLong))); + + assert(Klass::_lh_log2_element_size_shift == 0, "use shift in place"); + Node* lhp = basic_plus_adr(kls, in_bytes(Klass::layout_helper_offset())); + Node* elem_shift = make_load(NULL, lhp, TypeInt::INT, T_INT, MemNode::unordered); + uint header = arrayOopDesc::base_offset_in_bytes(T_VALUETYPE); + Node* base = basic_plus_adr(ary, header); + idx = Compile::conv_I2X_index(&_gvn, idx, TypeInt::POS, control()); + Node* scale = _gvn.transform(new LShiftXNode(idx, elem_shift)); + Node* adr = basic_plus_adr(ary, base, scale); - insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out_or_null(AllocateNode::RawAddress)); + access_clone(adr, dst_base, countx, false); + } else { + ideal.sync_kit(this); + ideal.make_leaf_call(OptoRuntime::load_unknown_value_Type(), + CAST_FROM_FN_PTR(address, OptoRuntime::load_unknown_value), + "load_unknown_value", + ary, idx, alloc_obj); + sync_kit(ideal); + } + insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out_or_null(AllocateNode::RawAddress)); + + ideal.set(res, alloc_obj); + } ideal.sync_kit(this); - ideal.set(res, alloc_obj); } ideal.end_if(); sync_kit(ideal); - push_node(bt, ideal.value(res)); + push_node(bt, _gvn.transform(ideal.value(res))); return; } @@ -188,77 +201,84 @@ cast_val = null_check(cast_val); if (stopped()) return; dec_sp(3); - cast_val = ValueTypeNode::make_from_oop(this, cast_val, elemtype->is_valuetype()->value_klass()); + cast_val = ValueTypeNode::make_from_oop(this, cast_val, elemtype->value_klass()); } cast_val->as_ValueType()->store_flattened(this, ary, adr); return; } else if (elemptr->is_valuetypeptr() && !elemptr->maybe_null()) { - // Store to non-flattened value type array + // Store to non-flattened but flattenable value type array (elements can never be null) if (!cast_val->is_ValueType()) { - // Can not store null into a value type array inc_sp(3); cast_val = null_check(cast_val); if (stopped()) return; dec_sp(3); } - } else if (elemptr->can_be_value_type() && !ary_t->klass_is_exact() && + } else if (elemptr->can_be_value_type() && (!ary_t->klass_is_exact() || elemptr->is_valuetypeptr()) && (val->is_ValueType() || val_t == TypePtr::NULL_PTR || val_t->is_oopptr()->can_be_value_type())) { - if (ValueArrayFlatten) { + // Cannot statically determine if array is flattened, emit runtime check + ciValueKlass* vk = NULL; + // Try to determine the value klass + if (val->is_ValueType()) { + vk = val_t->value_klass(); + } else if (elemptr->is_valuetypeptr()) { + vk = elemptr->value_klass(); + } + if (ValueArrayFlatten && (vk == NULL || vk->flatten_array())) { IdealKit ideal(this); Node* kls = load_object_klass(ary); Node* layout_val = load_lh_array_tag(kls); ideal.if_then(layout_val, BoolTest::ne, intcon(Klass::_lh_array_tag_vt_value)); { - // non flattened + // non-flattened sync_kit(ideal); - - if (!val->is_ValueType() && TypePtr::NULL_PTR->higher_equal(val_t)) { - gen_value_type_array_guard(ary, val, 3); - } - + gen_value_array_null_guard(ary, val, 3); const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); elemtype = ary_t->elem()->make_oopptr(); - access_store_at(ary, adr, adr_type, val, elemtype, bt, MO_UNORDERED | IN_HEAP | IS_ARRAY); + access_store_at(ary, adr, adr_type, val, elemtype, bt, MO_UNORDERED | IN_HEAP | IS_ARRAY, false, false); ideal.sync_kit(this); } ideal.else_(); { // flattened - // Object/interface array must be flattened, cast it - if (val->is_ValueType()) { + if (!val->is_ValueType() && TypePtr::NULL_PTR->higher_equal(val_t)) { + // Add null check sync_kit(ideal); - const TypeValueType* vt = _gvn.type(val)->is_valuetype(); - ciArrayKlass* array_klass = ciArrayKlass::make(vt->value_klass(), true); + Node* null_ctl = top(); + val = null_check_oop(val, &null_ctl); + if (null_ctl != top()) { + PreserveJVMState pjvms(this); + inc_sp(3); + set_control(null_ctl); + uncommon_trap(Deoptimization::Reason_null_check, Deoptimization::Action_none); + dec_sp(3); + } + ideal.sync_kit(this); + } + if (vk != NULL && !stopped()) { + // Element type is known, cast and store to flattened representation + sync_kit(ideal); + assert(vk->flatten_array(), "must be flattenable"); + assert(elemptr->maybe_null(), "must be nullable"); + ciArrayKlass* array_klass = ciArrayKlass::make(vk, /* never_null */ true); const TypeAryPtr* arytype = TypeOopPtr::make_from_klass(array_klass)->isa_aryptr(); ary = _gvn.transform(new CheckCastPPNode(control(), ary, arytype)); adr = array_element_address(ary, idx, T_OBJECT, arytype->size(), control()); + if (!val->is_ValueType()) { + assert(!gvn().type(val)->maybe_null(), "value type array elements should never be null"); + val = ValueTypeNode::make_from_oop(this, val, vk); + } val->as_ValueType()->store_flattened(this, ary, adr); ideal.sync_kit(this); - } else { - if (TypePtr::NULL_PTR->higher_equal(val_t)) { - sync_kit(ideal); - Node* null_ctl = top(); - val = null_check_oop(val, &null_ctl); - if (null_ctl != top()) { - PreserveJVMState pjvms(this); - inc_sp(3); - set_control(null_ctl); - uncommon_trap(Deoptimization::Reason_null_check, Deoptimization::Action_none); - dec_sp(3); - } - ideal.sync_kit(this); - } - if (!ideal.ctrl()->is_top()) { - ideal.make_leaf_call(OptoRuntime::store_unknown_value_Type(), - CAST_FROM_FN_PTR(address, OptoRuntime::store_unknown_value), - "store_unknown_value", - val, ary, idx); - } + } 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"); + ideal.make_leaf_call(OptoRuntime::store_unknown_value_Type(), + CAST_FROM_FN_PTR(address, OptoRuntime::store_unknown_value), + "store_unknown_value", + val, ary, idx); } } ideal.end_if(); sync_kit(ideal); return; } else { - if (!val->is_ValueType() && TypePtr::NULL_PTR->higher_equal(val_t)) { - gen_value_type_array_guard(ary, val, 3); - } + gen_value_array_null_guard(ary, val, 3); } } } --- old/src/hotspot/share/opto/parseHelper.cpp 2019-05-08 13:39:00.715871390 +0200 +++ new/src/hotspot/share/opto/parseHelper.cpp 2019-05-08 13:39:00.567872409 +0200 @@ -237,8 +237,7 @@ const Type* elemtype = _gvn.type(ary)->is_aryptr()->elem(); if (elemtype->isa_valuetype() != NULL || elemtype->is_valuetypeptr()) { // We statically know that this is a value type array, use precise klass ptr - ciValueKlass* vk = elemtype->isa_valuetype() ? elemtype->is_valuetype()->value_klass() : elemtype->value_klass(); - a_e_klass = makecon(TypeKlassPtr::make(vk)); + a_e_klass = makecon(TypeKlassPtr::make(elemtype->value_klass())); } // Check (the hard way) and throw if not a subklass. --- old/src/hotspot/share/opto/type.cpp 2019-05-08 13:39:01.075868910 +0200 +++ new/src/hotspot/share/opto/type.cpp 2019-05-08 13:39:00.927869930 +0200 @@ -4622,10 +4622,9 @@ off = Offset(elem()->isa_valuetype() ? offset() : tap->offset()); field_off = elem()->isa_valuetype() ? field_offset() : tap->field_offset(); } else if (tary->_elem->make_oopptr() != NULL && tary->_elem->make_oopptr()->isa_instptr() && below_centerline(ptr)) { - // Result is non-flattened (fall back to object) + // Result is non-flattened off = Offset(flattened_offset()).meet(Offset(tap->flattened_offset())); field_off = Offset::bottom; - tary = TypeAry::make(TypeInstPtr::BOTTOM, tary->_size, tary->_stable); } } else // Non integral arrays. // Must fall to bottom if exact klasses in upper lattice @@ -5330,7 +5329,7 @@ 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->is_valuetype()->value_klass(), /* null_free */ true); + 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-05-08 13:39:01.475866156 +0200 +++ new/src/hotspot/share/opto/type.hpp 2019-05-08 13:39:01.323867202 +0200 @@ -345,7 +345,7 @@ virtual bool is_nan() const; // Is not a number (NaN) bool is_valuetypeptr() const; - ciValueKlass* value_klass() const; + virtual ciValueKlass* value_klass() const; // Returns this ptr type or the equivalent ptr type for this compressed pointer. const TypePtr* make_ptr() const; @@ -771,7 +771,7 @@ public: static const TypeValueType* make(ciValueKlass* vk, bool larval = false); - ciValueKlass* value_klass() const { return _vk; } + virtual ciValueKlass* value_klass() const { return _vk; } bool larval() const { return _larval; } virtual bool eq(const Type* t) const; --- old/src/hotspot/share/opto/valuetypenode.cpp 2019-05-08 13:39:01.835863677 +0200 +++ new/src/hotspot/share/opto/valuetypenode.cpp 2019-05-08 13:39:01.687864696 +0200 @@ -359,7 +359,7 @@ } } -ValueTypeBaseNode* ValueTypeBaseNode::allocate(GraphKit* kit, bool deoptimize_on_exception) { +ValueTypeBaseNode* ValueTypeBaseNode::allocate(GraphKit* kit, bool deoptimize_on_exception, bool safe_for_replace) { // Check if value type is already allocated Node* null_ctl = kit->top(); Node* not_null_oop = kit->null_check_oop(get_oop(), &null_ctl); @@ -405,7 +405,9 @@ ValueTypeBaseNode* vt = clone()->as_ValueTypeBase(); vt->set_oop(res_oop); vt = kit->gvn().transform(vt)->as_ValueTypeBase(); - kit->replace_in_map(this, vt); + if (safe_for_replace) { + kit->replace_in_map(this, vt); + } return vt; } --- old/src/hotspot/share/opto/valuetypenode.hpp 2019-05-08 13:39:02.191861225 +0200 +++ new/src/hotspot/share/opto/valuetypenode.hpp 2019-05-08 13:39:02.047862216 +0200 @@ -46,7 +46,7 @@ virtual const TypeInstPtr* value_ptr() const = 0; // Get the klass defining the field layout of the value type - virtual ciValueKlass* value_klass() const = 0; + ciValueKlass* value_klass() const { return type()->value_klass(); } int make_scalar_in_safepoint(PhaseIterGVN* igvn, Unique_Node_List& worklist, SafePointNode* sfpt); @@ -86,7 +86,7 @@ void load(GraphKit* kit, Node* base, Node* ptr, ciInstanceKlass* holder, int holder_offset = 0, DecoratorSet decorators = IN_HEAP | MO_UNORDERED); // Allocates the value type (if not yet allocated) - ValueTypeBaseNode* allocate(GraphKit* kit, bool deoptimize_on_exception = false); + ValueTypeBaseNode* allocate(GraphKit* kit, bool deoptimize_on_exception = false, bool safe_for_replace = true); bool is_allocated(PhaseGVN* phase) const; void replace_call_results(GraphKit* kit, Node* call, Compile* C); @@ -111,7 +111,6 @@ bool is_default(PhaseGVN& gvn) const; const TypeInstPtr* value_ptr() const { return TypeInstPtr::make(TypePtr::BotPTR, value_klass()); } - ciValueKlass* value_klass() const { return type()->is_valuetype()->value_klass(); } public: // Create uninitialized @@ -155,7 +154,6 @@ class ValueTypePtrNode : public ValueTypeBaseNode { private: const TypeInstPtr* value_ptr() const { return type()->isa_instptr(); } - ciValueKlass* value_klass() const { return value_ptr()->value_klass(); } ValueTypePtrNode(ciValueKlass* vk, Node* oop) : ValueTypeBaseNode(TypeInstPtr::make(TypePtr::NotNull, vk), Values + vk->nof_declared_nonstatic_fields()) { --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestIntrinsics.java 2019-05-08 13:39:02.547858772 +0200 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestIntrinsics.java 2019-05-08 13:39:02.399859793 +0200 @@ -897,17 +897,14 @@ Asserts.assertEQ(result, va); result = test50(MyValue1?[].class, vba); Asserts.assertEQ(result, vba); + result = test50(MyValue1?[].class, va); + Asserts.assertEQ(result, va); try { test50(MyValue1.class.asValueType(), null); throw new RuntimeException("should have thrown"); } catch (NullPointerException npe) { } try { - test50(MyValue1?[].class, va); - throw new RuntimeException("should have thrown"); - } catch (ClassCastException cce) { - } - try { test50(MyValue1[].class, vba); throw new RuntimeException("should have thrown"); } catch (ClassCastException cce) { --- old/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestNullableArrays.java 2019-05-08 13:39:02.907856294 +0200 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestNullableArrays.java 2019-05-08 13:39:02.763857285 +0200 @@ -80,6 +80,8 @@ return MyValue1.createWithFieldsInline(x, y).hash(); } + private static final MyValue1 testValue1 = MyValue1.createWithFieldsInline(rI, rL); + // Test nullable value type array creation and initialization @Test(valid = ValueTypeArrayFlattenOff, failOn = LOAD) @Test(valid = ValueTypeArrayFlattenOn) @@ -270,7 +272,7 @@ @DontCompile public void test9_verifier(boolean warmup) { test9_va = new MyValue1?[1]; - test9_va[0] = MyValue1.createWithFieldsInline(rI, rL); + test9_va[0] = testValue1; long result = test9(); Asserts.assertEQ(result, hash()); } @@ -351,7 +353,7 @@ // Array load out of bounds (upper bound) at compile time @Test public int test12() { - int arraySize = Math.abs(rI) % 10;; + int arraySize = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[arraySize]; for (int i = 0; i < arraySize; i++) { @@ -372,7 +374,7 @@ // Array load out of bounds (lower bound) at compile time @Test public int test13() { - int arraySize = Math.abs(rI) % 10;; + int arraySize = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[arraySize]; for (int i = 0; i < arraySize; i++) { @@ -418,7 +420,7 @@ // Array store out of bounds (upper bound) at compile time @Test public int test15() { - int arraySize = Math.abs(rI) % 10;; + int arraySize = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[arraySize]; try { @@ -438,7 +440,7 @@ // Array store out of bounds (lower bound) at compile time @Test public int test16() { - int arraySize = Math.abs(rI) % 10;; + int arraySize = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[arraySize]; try { @@ -496,16 +498,21 @@ @DontCompile public void test18_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue1?[] va = new MyValue1?[len]; + MyValue1?[] va1 = new MyValue1?[len]; + MyValue1[] va2 = new MyValue1[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va1[i] = testValue1; + va2[i] = testValue1; } - MyValue1?[] result = test18(va); + MyValue1?[] result1 = test18(va1); + MyValue1?[] result2 = test18(va2); if (len > 0) { - Asserts.assertEQ(result[0], null); + Asserts.assertEQ(result1[0], null); + Asserts.assertEQ(result2[0].hash(), va2[0].hash()); } for (int i = 1; i < len; ++i) { - Asserts.assertEQ(result[i].hash(), va[i].hash()); + Asserts.assertEQ(result1[i].hash(), va1[i].hash()); + Asserts.assertEQ(result2[i].hash(), va2[i].hash()); } } @@ -541,17 +548,38 @@ @DontCompile public void test20_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue1?[] src = new MyValue1?[len]; - MyValue1?[] dst = new MyValue1?[len]; - for (int i = 1; i < len; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); + MyValue1?[] src1 = new MyValue1?[len]; + MyValue1?[] src2 = new MyValue1?[len]; + MyValue1[] src3 = new MyValue1[len]; + MyValue1[] src4 = new MyValue1[len]; + MyValue1?[] dst1 = new MyValue1?[len]; + MyValue1[] dst2 = new MyValue1[len]; + MyValue1?[] dst3 = new MyValue1?[len]; + MyValue1[] dst4 = new MyValue1[len]; + if (len > 0) { + src2[0] = testValue1; } - test20(src, dst); + for (int i = 1; i < len; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test20(src1, dst1); + test20(src2, dst2); + test20(src3, dst3); + test20(src4, dst4); if (len > 0) { - Asserts.assertEQ(dst[0], null); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); } for (int i = 1; i < len; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); } } @@ -564,17 +592,38 @@ @DontCompile public void test21_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue2?[] src = new MyValue2?[len]; - MyValue2?[] dst = new MyValue2?[len]; - for (int i = 1; i < len; ++i) { - src[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + MyValue2?[] src1 = new MyValue2?[len]; + MyValue2?[] src2 = new MyValue2?[len]; + MyValue2[] src3 = new MyValue2[len]; + MyValue2[] src4 = new MyValue2[len]; + MyValue2?[] dst1 = new MyValue2?[len]; + MyValue2[] dst2 = new MyValue2[len]; + MyValue2?[] dst3 = new MyValue2?[len]; + MyValue2[] dst4 = new MyValue2[len]; + if (len > 0) { + src2[0] = MyValue2.createWithFieldsInline(rI, true); } - test21(src, dst); + for (int i = 1; i < len; ++i) { + src1[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + src2[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + src3[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + src4[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + } + test21(src1, dst1); + test21(src2, dst2); + test21(src3, dst3); + test21(src4, dst4); if (len > 0) { - Asserts.assertEQ(dst[0], null); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); } for (int i = 1; i < len; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); } } @@ -590,16 +639,21 @@ @DontCompile public void test22_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue1?[] src = new MyValue1?[len]; + MyValue1?[] src1 = new MyValue1?[len]; + MyValue1[] src2 = new MyValue1[len]; for (int i = 1; i < len; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); + src1[i] = testValue1; + src2[i] = testValue1; } - MyValue1?[] dst = test22(src); + MyValue1?[] dst1 = test22(src1); + MyValue1?[] dst2 = test22(src2); if (len > 0) { - Asserts.assertEQ(dst[0], null); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), MyValue1.default.hash()); } for (int i = 1; i < len; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); } } @@ -615,16 +669,21 @@ @DontCompile public void test23_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue1?[] src = new MyValue1?[len]; + MyValue1?[] src1 = new MyValue1?[len]; + MyValue1[] src2 = new MyValue1[len]; for (int i = 0; i < len; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); + src1[i] = testValue1; + src2[i] = testValue1; } - MyValue1?[] dst = test23(src); + MyValue1?[] dst1 = test23(src1); + MyValue1?[] dst2 = test23(src2); for (int i = 0; i < 5; ++i) { - Asserts.assertEQ(dst[i], null); + Asserts.assertEQ(dst1[i], null); + Asserts.assertEQ(dst2[i], null); } for (int i = 5; i < len; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); } } @@ -637,17 +696,38 @@ @DontCompile public void test24_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue1?[] src = new MyValue1?[len]; - MyValue1?[] dst = new MyValue1?[len]; - for (int i = 1; i < len; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); + MyValue1?[] src1 = new MyValue1?[len]; + MyValue1?[] src2 = new MyValue1?[len]; + MyValue1[] src3 = new MyValue1[len]; + MyValue1[] src4 = new MyValue1[len]; + MyValue1?[] dst1 = new MyValue1?[len]; + MyValue1[] dst2 = new MyValue1[len]; + MyValue1?[] dst3 = new MyValue1?[len]; + MyValue1[] dst4 = new MyValue1[len]; + if (len > 0) { + src2[0] = testValue1; } - test24(src, dst); + for (int i = 1; i < len; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test24(src1, dst1); + test24(src2, dst2); + test24(src3, dst3); + test24(src4, dst4); if (len > 0) { - Asserts.assertEQ(dst[0], null); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); } for (int i = 1; i < len; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); } } @@ -659,15 +739,34 @@ @DontCompile public void test25_verifier(boolean warmup) { - MyValue2?[] src = new MyValue2?[8]; - MyValue2?[] dst = new MyValue2?[8]; + MyValue2?[] src1 = new MyValue2?[8]; + MyValue2?[] src2 = new MyValue2?[8]; + MyValue2[] src3 = new MyValue2[8]; + MyValue2[] src4 = new MyValue2[8]; + MyValue2?[] dst1 = new MyValue2?[8]; + MyValue2[] dst2 = new MyValue2[8]; + MyValue2?[] dst3 = new MyValue2?[8]; + MyValue2[] dst4 = new MyValue2[8]; + src2[0] = MyValue2.createWithFieldsInline(rI, true); for (int i = 1; i < 8; ++i) { - src[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); - } - test25(src, dst); - Asserts.assertEQ(dst[0], null); + src1[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + src2[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + src3[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + src4[i] = MyValue2.createWithFieldsInline(rI, (i % 2) == 0); + } + test25(src1, dst1); + test25(src2, dst2); + test25(src3, dst3); + test25(src4, dst4); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); for (int i = 1; i < 8; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); } } @@ -679,15 +778,34 @@ @DontCompile public void test26_verifier(boolean warmup) { - MyValue1?[] src = new MyValue1?[8]; - MyValue1?[] dst = new MyValue1?[8]; - for (int i = 1; i < 8; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); - } - test26(src, dst); - Asserts.assertEQ(dst[0], null); + MyValue1?[] src1 = new MyValue1?[8]; + MyValue1?[] src2 = new MyValue1?[8]; + MyValue1[] src3 = new MyValue1[8]; + MyValue1[] src4 = new MyValue1[8]; + MyValue1?[] dst1 = new MyValue1?[8]; + MyValue1[] dst2 = new MyValue1[8]; + MyValue1?[] dst3 = new MyValue1?[8]; + MyValue1[] dst4 = new MyValue1[8]; + src2[0] = testValue1; + for (int i = 1; i < 8 ; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test26(src1, dst1); + test26(src2, dst2); + test26(src3, dst3); + test26(src4, dst4); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); for (int i = 1; i < 8; ++i) { - Asserts.assertEQ(src[i].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); } } @@ -699,17 +817,35 @@ @DontCompile public void test27_verifier(boolean warmup) { - MyValue1?[] src = new MyValue1?[8]; - MyValue1?[] dst = new MyValue1?[8]; - for (int i = 0; i < 8; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); - } - test27(src, dst); + MyValue1?[] src1 = new MyValue1?[8]; + MyValue1?[] src2 = new MyValue1?[8]; + MyValue1[] src3 = new MyValue1[8]; + MyValue1[] src4 = new MyValue1[8]; + MyValue1?[] dst1 = new MyValue1?[8]; + MyValue1[] dst2 = new MyValue1[8]; + MyValue1?[] dst3 = new MyValue1?[8]; + MyValue1[] dst4 = new MyValue1[8]; + for (int i = 1; i < 8; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test27(src1, dst1); + test27(src2, dst2); + test27(src3, dst3); + test27(src4, dst4); for (int i = 0; i < 2; ++i) { - Asserts.assertEQ(dst[i], null); + Asserts.assertEQ(dst1[i], null); + Asserts.assertEQ(dst2[i].hash(), MyValue1.default.hash()); + Asserts.assertEQ(dst3[i], null); + Asserts.assertEQ(dst4[i].hash(), MyValue1.default.hash()); } for (int i = 2; i < 8; ++i) { - Asserts.assertEQ(src[i-1].hash(), dst[i].hash()); + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); } } @@ -806,16 +942,21 @@ @DontCompile public void test32_verifier(boolean warmup) { int len = Math.abs(rI) % 10; - MyValue1?[] va = new MyValue1?[len]; + MyValue1?[] va1 = new MyValue1?[len]; + MyValue1[] va2 = new MyValue1[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va1[i] = testValue1; + va2[i] = testValue1; } - MyValue1?[] result = (MyValue1?[])test32(va); + MyValue1?[] result1 = (MyValue1?[])test32(va1); + MyValue1?[] result2 = (MyValue1?[])test32(va2); if (len > 0) { - Asserts.assertEQ(result[0], null); + Asserts.assertEQ(result1[0], null); + Asserts.assertEQ(result2[0].hash(), MyValue1.default.hash()); } for (int i = 1; i < len; ++i) { - Asserts.assertEQ(((MyValue1)result[i]).hash(), ((MyValue1)va[i]).hash()); + Asserts.assertEQ(((MyValue1)result1[i]).hash(), ((MyValue1)va1[i]).hash()); + Asserts.assertEQ(((MyValue1)result2[i]).hash(), ((MyValue1)va2[i]).hash()); } } @@ -829,7 +970,7 @@ int len = Math.abs(rI) % 10; Object[] va = new Object[len]; for (int i = 0; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } Object[] result = test33(va); for (int i = 0; i < len; ++i) { @@ -950,7 +1091,7 @@ MyValue1?[] src = new MyValue1?[len]; MyValue1?[] dst = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); + src[i] = testValue1; } test35(src, dst, src.length); verify(src, dst); @@ -1134,7 +1275,7 @@ MyValue1?[] src = new MyValue1?[8]; MyValue1?[] dst = new MyValue1?[8]; for (int i = 1; i < 8; ++i) { - src[i] = MyValue1.createWithFieldsInline(rI, rL); + src[i] = testValue1; } test43(src, dst); verify(src, dst); @@ -1310,7 +1451,7 @@ int len = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } MyValue1?[] result = test51(va); verify(va, result); @@ -1326,7 +1467,7 @@ @DontCompile public void test52_verifier(boolean warmup) { for (int i = 1; i < 8; ++i) { - test52_va[i] = MyValue1.createWithFieldsInline(rI, rL); + test52_va[i] = testValue1; } MyValue1?[] result = test52(); verify(test52_va, result); @@ -1342,7 +1483,7 @@ int len = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } MyValue1?[] result = test53(va); verify(result, va); @@ -1358,7 +1499,7 @@ int len = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } Object[] result = test54(va); verify(va, result); @@ -1374,7 +1515,7 @@ int len = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } Object[] result = test55(va); verify(va, result); @@ -1390,7 +1531,7 @@ int len = Math.abs(rI) % 10; Object[] va = new Object[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } MyValue1?[] result = test56(va); verify(result, va); @@ -1406,7 +1547,7 @@ int len = Math.abs(rI) % 10; Object[] va = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } Object[] result = test57(va, MyValue1?[].class); verify(va, result); @@ -1422,7 +1563,7 @@ int len = Math.abs(rI) % 10; MyValue1?[] va = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } for (int i = 1; i < 10; i++) { Object[] result = test58(va, MyValue1?[].class); @@ -1445,7 +1586,7 @@ MyValue1?[] va = new MyValue1?[len]; MyValue1?[] verif = new MyValue1?[len+1]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; verif[i] = va[i]; } Object[] result = test59(va); @@ -1463,7 +1604,7 @@ MyValue1?[] va = new MyValue1?[len]; MyValue1?[] verif = new MyValue1?[len+1]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; verif[i] = (MyValue1)va[i]; } Object[] result = test60(va, MyValue1?[].class); @@ -1551,7 +1692,7 @@ MyValue1?[] va = new MyValue1?[len]; MyValue1?[] verif = new MyValue1?[len+1]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; verif[i] = va[i]; } Integer[] oa = new Integer[len]; @@ -1813,7 +1954,7 @@ MyValue1?[] va = new MyValue1?[len]; MyValue1?[] verif = new MyValue1?[len]; for (int i = 1; i < len; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; verif[i] = va[i]; } Integer[] oa = new Integer[len]; @@ -1846,7 +1987,7 @@ @DontCompile public void test76_verifier(boolean warmup) { - MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 vt = testValue1; Object[] out = new Object[1]; MyValue1[] vva = new MyValue1[42]; MyValue1[] vva_r = new MyValue1[42]; @@ -1874,7 +2015,7 @@ if (b) { va = new MyValue1?[5]; for (int i = 0; i < 5; ++i) { - va[i] = MyValue1.createWithFieldsInline(rI, rL); + va[i] = testValue1; } } else { va = new MyValue1[10]; @@ -1929,7 +2070,7 @@ @DontCompile public void test78_verifier(boolean warmup) { - MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 vt = testValue1; Integer i = new Integer(42); Object[] out = new Object[1]; MyValue1[] vva = new MyValue1[42]; @@ -1954,4 +2095,270 @@ Asserts.assertEQ(result[0], i); Asserts.assertEQ(out[0], null); } + + // Test widening conversions from [Q to [L + @Test(failOn = ALLOC + ALLOCA + STORE) + public static MyValue1?[] test79(MyValue1[] va) { + return va; + } + + @DontCompile + public void test79_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[1]; + va[0] = testValue1; + MyValue1?[] res = test79(va); + Asserts.assertEquals(res[0].hash(), testValue1.hash()); + try { + res[0] = null; + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + res[0] = testValue1; + test79(null); // Should not throw NPE + } + + // Same as test79 but with explicit cast and Object return + @Test(failOn = ALLOC + ALLOCA + STORE) + public static Object[] test80(MyValue1[] va) { + return (MyValue1?[])va; + } + + @DontCompile + public void test80_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[1]; + va[0] = testValue1; + Object[] res = test80(va); + Asserts.assertEquals(((MyValue1)res[0]).hash(), testValue1.hash()); + try { + res[0] = null; + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + res[0] = testValue1; + test80(null); // Should not throw NPE + } + + // Test mixing widened and boxed array type + @Test() + public static long test81(MyValue1[] va1, MyValue1?[] va2, MyValue1 vt, boolean b, boolean shouldThrow) { + MyValue1?[] result = b ? va1 : va2; + try { + result[0] = vt; + } catch (NullPointerException npe) { + // Ignored + } + return result[1].hash(); + } + + @DontCompile + public void test81_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[2]; + MyValue1?[] vaB = new MyValue1?[2]; + va[1] = testValue1; + vaB[1] = testValue1; + long res = test81(va, vaB, testValue1, true, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test81(va, vaB, testValue1, false, false); + Asserts.assertEquals(vaB[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test81(va, va, testValue1, false, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + } + + // Same as test81 but more cases and null writes + @Test() + public static long test82(MyValue1[] va1, MyValue1?[] va2, MyValue1 vt1, MyValue1? vt2, int i, boolean shouldThrow) { + MyValue1?[] result = null; + if (i == 0) { + result = va1; + } else if (i == 1) { + result = va2; + } else if (i == 2) { + result = new MyValue1?[2]; + result[1] = vt1; + } else if (i == 3) { + result = new MyValue1[2]; + result[1] = vt1; + } + try { + result[0] = (i <= 1) ? null : vt2; + if (shouldThrow) { + throw new RuntimeException("NullPointerException expected"); + } + } catch (NullPointerException npe) { + Asserts.assertTrue(shouldThrow, "NullPointerException thrown"); + } + result[0] = vt1; + return result[1].hash(); + } + + @DontCompile + public void test82_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[2]; + MyValue1?[] vaB = new MyValue1?[2]; + va[1] = testValue1; + vaB[1] = testValue1; + long res = test82(va, vaB, testValue1, testValue1, 0, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, vaB, testValue1, testValue1, 1, false); + Asserts.assertEquals(vaB[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, va, testValue1, testValue1, 1, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, va, testValue1, null, 2, false); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, va, testValue1, null, 3, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + } + + @Test(failOn = ALLOC + ALLOCA + STORE) + public static long test83(MyValue1[] va) { + MyValue1?[] result = va; + return result[0].hash(); + } + + @DontCompile + public void test83_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[42]; + va[0] = testValue1; + long res = test83(va); + Asserts.assertEquals(res, testValue1.hash()); + } + + @Test(failOn = ALLOC + ALLOCA + STORE) + public static MyValue1?[] test84(MyValue1 vt1, MyValue1? vt2) { + MyValue1?[] result = new MyValue1[2]; + result[0] = vt1; + result[1] = vt2; + return result; + } + + @DontCompile + public void test84_verifier(boolean warmup) { + MyValue1?[] res = test84(testValue1, testValue1); + Asserts.assertEquals(res[0].hash(), testValue1.hash()); + Asserts.assertEquals(res[1].hash(), testValue1.hash()); + try { + test84(testValue1, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + } + + @Test() + public static long test85(MyValue1?[] va, MyValue1 val) { + va[0] = val; + return va[1].hash(); + } + + @DontCompile + public void test85_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[2]; + MyValue1?[] vab = new MyValue1?[2]; + va[1] = testValue1; + vab[1] = testValue1; + long res = test85(va, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + res = test85(vab, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(vab[0].hash(), testValue1.hash()); + } + + // Same as test85 but with box value + @Test() + public static long test86(MyValue1?[] va, MyValue1? val) { + va[0] = val; + return va[1].hash(); + } + + @DontCompile + public void test86_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[2]; + MyValue1?[] vab = new MyValue1?[2]; + va[1] = testValue1; + vab[1] = testValue1; + long res = test86(va, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + try { + test86(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + res = test86(vab, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(vab[0].hash(), testValue1.hash()); + res = test86(vab, null); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(vab[0], null); + } + + // Test initialization of nullable array with constant + @Test() + public long test87() { + MyValue1?[] va = new MyValue1?[1]; + va[0] = testValue1; + return va[0].hash(); + } + + @DontCompile + public void test87_verifier(boolean warmup) { + long result = test87(); + Asserts.assertEQ(result, hash()); + } + + // Test narrowing conversion from [L to [Q + @Test(failOn = ALLOC + ALLOCA + STORE) + public static MyValue1[] test88(MyValue1?[] va) { + return (MyValue1[])va; + } + + @DontCompile + public void test88_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[1]; + va[0] = testValue1; + MyValue1[] res = test88(va); + Asserts.assertEquals(res[0].hash(), testValue1.hash()); + res[0] = testValue1; + test88(null); // Should not throw NPE + try { + test88(new MyValue1?[1]); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException cce) { + // Expected + } + } + + // Same as test88 but with explicit cast and Object argument + @Test(failOn = ALLOC + ALLOCA + STORE) + public static MyValue1[] test89(Object[] va) { + return (MyValue1[])va; + } + + @DontCompile + public void test89_verifier(boolean warmup) { + MyValue1[] va = new MyValue1[1]; + va[0] = testValue1; + MyValue1[] res = test89(va); + Asserts.assertEquals(((MyValue1)res[0]).hash(), testValue1.hash()); + res[0] = testValue1; + test89(null); // Should not throw NPE + try { + test89(new MyValue1?[1]); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException cce) { + // Expected + } + } } --- /dev/null 2019-05-06 07:52:59.020886919 +0200 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestArrayAccessDeopt.java 2019-05-08 13:39:03.135854723 +0200 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Verify that certain array accesses do not trigger deoptimization. + * @library /test/lib + * @run main/othervm -XX:+EnableValhalla TestArrayAccessDeopt + */ + +import java.io.File; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +final inline class MyValue1 { + public final int x = 0; +} + +public class TestArrayAccessDeopt { + + public static void test1(Object[] va, Object vt) { + va[0] = vt; + } + + public static void test2(Object[] va, MyValue1? vt) { + va[0] = vt; + } + + public static void test3(MyValue1?[] va, Object vt) { + va[0] = (MyValue1?)vt; + } + + public static void test4(MyValue1?[] va, MyValue1? vt) { + va[0] = vt; + } + + public static void test5(Object[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test6(MyValue1[] va, Object vt) { + va[0] = (MyValue1)vt; + } + + public static void test7(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test8(MyValue1?[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test9(MyValue1[] va, MyValue1? vt) { + va[0] = (MyValue1)vt; + } + + public static void test10(Object[] va) { + va[0] = null; + } + + public static void test11(MyValue1?[] va) { + va[0] = null; + } + + static public void main(String[] args) throws Exception { + if (args.length == 0) { + // Run test in new VM instance + String[] arg = {"-XX:+EnableValhalla", "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,TestArrayAccessDeopt::test*", + "-XX:+TraceDeoptimization", "-Xbatch", "-XX:-MonomorphicArrayCheck", "TestArrayAccessDeopt", "run"}; + OutputAnalyzer oa = ProcessTools.executeTestJvm(arg); + String output = oa.getOutput(); + oa.shouldNotContain("Uncommon trap occurred"); + } else { + MyValue1[] va = new MyValue1[1]; + MyValue1?[] vaB = new MyValue1?[1]; + MyValue1 vt = new MyValue1(); + for (int i = 0; i < 10_000; ++i) { + test1(va, vt); + test1(vaB, vt); + test1(vaB, null); + test2(va, vt); + test2(vaB, vt); + test2(vaB, null); + test3(va, vt); + test3(vaB, vt); + test3(vaB, null); + test4(va, vt); + test4(vaB, vt); + test4(vaB, null); + test5(va, vt); + test5(vaB, vt); + test6(va, vt); + try { + test6(va, null); + } catch (NullPointerException npe) { + // Expected + } + test7(va, vt); + test8(va, vt); + test8(vaB, vt); + test9(va, vt); + try { + test9(va, null); + } catch (NullPointerException npe) { + // Expected + } + test10(vaB); + test11(vaB); + } + } + } +} --- /dev/null 2019-05-06 07:52:59.020886919 +0200 +++ new/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayThreshold.java 2019-05-08 13:39:03.523852051 +0200 @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test accessing value type arrays that exceed the flattening threshold. + * @library /test/lib + * @run main/othervm -XX:+EnableValhalla -Xbatch TestFlatArrayThreshold + * @run main/othervm -XX:+EnableValhalla -XX:ValueArrayElemMaxFlatOops=1 -Xbatch TestFlatArrayThreshold + * @run main/othervm -XX:+EnableValhalla -XX:ValueArrayElemMaxFlatSize=1 -Xbatch TestFlatArrayThreshold + */ + +import jdk.test.lib.Asserts; + +final inline class MyValue1 { + final Object o1; + final Object o2; + + public MyValue1() { + o1 = new Integer(42); + o2 = new Integer(43); + } +} + +public class TestFlatArrayThreshold { + + public static MyValue1 test1(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + return va[1]; + } + + public static MyValue1? test2(MyValue1?[] va, MyValue1? vt) { + va[0] = vt; + return va[1]; + } + + public static Object test3(Object[] va, MyValue1 vt) { + va[0] = vt; + return va[1]; + } + + public static Object test4(Object[] va, MyValue1? vt) { + va[0] = vt; + return va[1]; + } + + public static MyValue1 test5(MyValue1[] va, Object vt) { + va[0] = (MyValue1)vt; + return va[1]; + } + + public static MyValue1? test6(MyValue1?[] va, Object vt) { + va[0] = (MyValue1?)vt; + return va[1]; + } + + public static Object test7(Object[] va, Object vt) { + va[0] = vt; + return va[1]; + } + + static public void main(String[] args) { + MyValue1 vt = new MyValue1(); + MyValue1[] va = new MyValue1[2]; + MyValue1?[] vaB = new MyValue1?[2]; + va[1] = vt; + for (int i = 0; i < 10_000; ++i) { + MyValue1 result1 = test1(va, vt); + Asserts.assertEQ(result1.o1, 42); + Asserts.assertEQ(result1.o2, 43); + + MyValue1? result2 = test2(va, vt); + Asserts.assertEQ(result2.o1, 42); + Asserts.assertEQ(result2.o2, 43); + result2 = test2(vaB, null); + Asserts.assertEQ(result2, null); + + MyValue1? result3 = (MyValue1?)test3(va, vt); + Asserts.assertEQ(result3.o1, 42); + Asserts.assertEQ(result3.o2, 43); + result3 = (MyValue1?)test3(vaB, vt); + Asserts.assertEQ(result3, null); + + MyValue1? result4 = (MyValue1?)test4(va, vt); + Asserts.assertEQ(result4.o1, 42); + Asserts.assertEQ(result4.o2, 43); + result4 = (MyValue1?)test4(vaB, null); + Asserts.assertEQ(result4, null); + + MyValue1 result5 = test5(va, vt); + Asserts.assertEQ(result5.o1, 42); + Asserts.assertEQ(result5.o2, 43); + + MyValue1? result6 = test6(va, vt); + Asserts.assertEQ(result6.o1, 42); + Asserts.assertEQ(result6.o2, 43); + result6 = test6(vaB, null); + Asserts.assertEQ(result6, null); + + MyValue1? result7 = (MyValue1?)test7(va, vt); + Asserts.assertEQ(result7.o1, 42); + Asserts.assertEQ(result7.o2, 43); + result7 = (MyValue1?)test7(vaB, null); + Asserts.assertEQ(result7, null); + } + try { + test2(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test4(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test5(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test6(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test7(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + } +}