changeset 55119:39702d8b184c lworld

8223029: [lworld] C2 support for widening/narrowing conversion "[QFoo;" <: "[LFoo;? Reviewed-by: roland
author thartmann
date Mon, 13 May 2019 16:15:57 +0200
parents baa7d191cc28
children d8e4edb5d8fe
files src/hotspot/share/ci/ciArrayKlass.cpp src/hotspot/share/ci/ciArrayKlass.hpp src/hotspot/share/ci/ciTypeFlow.cpp src/hotspot/share/oops/arrayKlass.cpp src/hotspot/share/oops/arrayKlass.hpp src/hotspot/share/opto/compile.cpp src/hotspot/share/opto/doCall.cpp src/hotspot/share/opto/escape.cpp src/hotspot/share/opto/graphKit.cpp src/hotspot/share/opto/graphKit.hpp src/hotspot/share/opto/library_call.cpp src/hotspot/share/opto/memnode.cpp src/hotspot/share/opto/parse1.cpp src/hotspot/share/opto/parse2.cpp src/hotspot/share/opto/parseHelper.cpp src/hotspot/share/opto/subnode.cpp src/hotspot/share/opto/type.cpp src/hotspot/share/opto/type.hpp src/hotspot/share/opto/valuetypenode.cpp src/hotspot/share/opto/valuetypenode.hpp test/hotspot/jtreg/compiler/valhalla/valuetypes/TestArrayAccessDeopt.java test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayThreshold.java test/hotspot/jtreg/compiler/valhalla/valuetypes/TestIntrinsics.java test/hotspot/jtreg/compiler/valhalla/valuetypes/TestNullableArrays.java
diffstat 24 files changed, 1077 insertions(+), 275 deletions(-) [+]
line wrap: on
line diff
--- a/src/hotspot/share/ci/ciArrayKlass.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/ci/ciArrayKlass.cpp	Mon May 13 16:15:57 2019 +0200
@@ -27,6 +27,7 @@
 #include "ci/ciObjArrayKlass.hpp"
 #include "ci/ciTypeArrayKlass.hpp"
 #include "ci/ciUtilities.hpp"
+#include "ci/ciUtilities.inline.hpp"
 #include "ci/ciValueArrayKlass.hpp"
 #include "ci/ciValueKlass.hpp"
 
@@ -118,3 +119,9 @@
   return get_ArrayKlass()->storage_properties();
 }
 
+ciInstance* ciArrayKlass::component_mirror_instance() const {
+  GUARDED_VM_ENTRY(
+    oop component_mirror = ArrayKlass::cast(get_Klass())->component_mirror();
+    return CURRENT_ENV->get_instance(component_mirror);
+  )
+}
--- a/src/hotspot/share/ci/ciArrayKlass.hpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/ci/ciArrayKlass.hpp	Mon May 13 16:15:57 2019 +0200
@@ -63,6 +63,7 @@
 
   int array_header_in_bytes();
   ArrayStorageProperties storage_properties();
+  ciInstance* component_mirror_instance() const;
 };
 
 #endif // SHARE_CI_CIARRAYKLASS_HPP
--- a/src/hotspot/share/ci/ciTypeFlow.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/ci/ciTypeFlow.cpp	Mon May 13 16:15:57 2019 +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 {
--- a/src/hotspot/share/oops/arrayKlass.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/oops/arrayKlass.cpp	Mon May 13 16:15:57 2019 +0200
@@ -178,6 +178,10 @@
   }
 }
 
+oop ArrayKlass::component_mirror() const {
+  return java_lang_Class::component_mirror(java_mirror());
+}
+
 // JVM support
 
 jint ArrayKlass::compute_modifier_flags(TRAPS) const {
--- a/src/hotspot/share/oops/arrayKlass.hpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/oops/arrayKlass.hpp	Mon May 13 16:15:57 2019 +0200
@@ -120,6 +120,8 @@
   GrowableArray<Klass*>* compute_secondary_supers(int num_extra_slots,
                                                   Array<InstanceKlass*>* transitive_interfaces);
 
+  oop component_mirror() const;
+
   // Sizing
   static int static_size(int header_size);
 
--- a/src/hotspot/share/opto/compile.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/compile.cpp	Mon May 13 16:15:57 2019 +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);
       }
@@ -4302,8 +4302,15 @@
   }
 
   ciType* superelem = superk;
-  if (superelem->is_array_klass())
+  if (superelem->is_array_klass()) {
+    ciArrayKlass* ak = superelem->as_array_klass();
+    // Do not perform the subtype check on the element klasses for [V? arrays. The runtime type might
+    // be [V due to [V <: [V? and the klass for [V? and [V is the same but the component mirror is not.
+    if (ak->is_obj_array_klass() && !ak->storage_properties().is_null_free() && ak->element_klass()->is_valuetype()) {
+      return SSC_full_test;
+    }
     superelem = superelem->as_array_klass()->base_element_type();
+  }
 
   if (!subk->is_interface()) {  // cannot trust static interface types yet
     if (subk->is_subtype_of(superk)) {
--- a/src/hotspot/share/opto/doCall.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/doCall.cpp	Mon May 13 16:15:57 2019 +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();
     }
--- a/src/hotspot/share/opto/escape.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/escape.cpp	Mon May 13 16:15:57 2019 +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 {
--- a/src/hotspot/share/opto/graphKit.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/graphKit.cpp	Mon May 13 16:15:57 2019 +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));
+  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->cast_to_ptr_type(TypePtr::BotPTR), 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);
     }
-  } 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));
-    { 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);
-    }
+    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) {
@@ -3624,12 +3621,12 @@
     ciKlass* klass = inst_klass->klass();
     assert(klass != NULL, "klass should not be NULL");
     bool    xklass = inst_klass->klass_is_exact();
-    bool can_be_value_array = false;
-    if (klass->is_array_klass() && EnableValhalla && ValueArrayFlatten) {
-      ciKlass* elem = klass->as_array_klass()->element_klass();
-      can_be_value_array = elem != NULL && (elem->is_java_lang_Object() || elem->is_interface());
+    bool can_be_flattened = false;
+    if (ValueArrayFlatten && klass->is_obj_array_klass()) {
+      ciKlass* elem = klass->as_obj_array_klass()->element_klass();
+      can_be_flattened = elem->is_java_lang_Object() || elem->is_interface();
     }
-    if (xklass || (klass->is_array_klass() && !can_be_value_array)) {
+    if (xklass || (klass->is_array_klass() && !can_be_flattened)) {
       jint lhelper = klass->layout_helper();
       if (lhelper != Klass::_lh_neutral_value) {
         constant_value = lhelper;
@@ -4053,7 +4050,7 @@
 
     // Check if element mirror is a value mirror
     Node* p = basic_plus_adr(elem_mirror, java_lang_Class::value_mirror_offset_in_bytes());
-    Node* value_mirror = access_load_at(elem_mirror, p, _gvn.type(p)->is_ptr(), TypeInstPtr::MIRROR, T_OBJECT, IN_HEAP);
+    Node* value_mirror = access_load_at(elem_mirror, p, _gvn.type(p)->is_ptr(), TypeInstPtr::MIRROR->cast_to_ptr_type(TypePtr::BotPTR), T_OBJECT, IN_HEAP);
     Node* cmp = _gvn.transform(new CmpPNode(elem_mirror, value_mirror));
     Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq));
     IfNode* iff = create_and_map_if(control(), bol, PROB_FAIR, COUNT_UNKNOWN);
--- a/src/hotspot/share/opto/graphKit.hpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/graphKit.hpp	Mon May 13 16:15:57 2019 +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);
 
--- a/src/hotspot/share/opto/library_call.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/library_call.cpp	Mon May 13 16:15:57 2019 +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) {
@@ -3598,7 +3596,7 @@
       // Check if (mirror == value_mirror && obj == null)
       RegionNode* r = new RegionNode(3);
       Node* p = basic_plus_adr(mirror, java_lang_Class::value_mirror_offset_in_bytes());
-      Node* value_mirror = access_load_at(mirror, p, _gvn.type(p)->is_ptr(), TypeInstPtr::MIRROR, T_OBJECT, IN_HEAP);
+      Node* value_mirror = access_load_at(mirror, p, _gvn.type(p)->is_ptr(), TypeInstPtr::MIRROR->cast_to_ptr_type(TypePtr::BotPTR), T_OBJECT, IN_HEAP);
       Node* cmp = _gvn.transform(new CmpPNode(mirror, value_mirror));
       Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::ne));
       Node* if_ne = generate_fair_guard(bol, 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);
     }
 
     {
--- a/src/hotspot/share/opto/memnode.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/memnode.cpp	Mon May 13 16:15:57 2019 +0200
@@ -1848,11 +1848,28 @@
             "Field accesses must be precise" );
     // For oop loads, we expect the _type to be precise.
 
+    const TypeInstPtr* tinst = tp->is_instptr();
+    BasicType bt = memory_type();
+
+    // Fold component and value mirror loads
+    ciInstanceKlass* ik = tinst->klass()->as_instance_klass();
+    if (ik == phase->C->env()->Class_klass() && (off == java_lang_Class::component_mirror_offset_in_bytes() ||
+                                                 off == java_lang_Class::value_mirror_offset_in_bytes())) {
+      ciType* mirror_type = tinst->java_mirror_type();
+      if (mirror_type != NULL) {
+        const Type* const_oop = TypePtr::NULL_PTR;
+        if (mirror_type->is_array_klass()) {
+          const_oop = TypeInstPtr::make(mirror_type->as_array_klass()->component_mirror_instance());
+        } else if (mirror_type->is_valuetype()) {
+          const_oop = TypeInstPtr::make(mirror_type->as_value_klass()->value_mirror_instance());
+        }
+        return (bt == T_NARROWOOP) ? const_oop->make_narrowoop() : const_oop;
+      }
+    }
+
     // Optimize loads from constant fields.
-    const TypeInstPtr* tinst = tp->is_instptr();
     ciObject* const_oop = tinst->const_oop();
     if (!is_mismatched_access() && off != Type::OffsetBot && const_oop != NULL && const_oop->is_instance()) {
-      BasicType bt = memory_type();
       ciType* mirror_type = const_oop->as_instance()->java_mirror_type();
       if (mirror_type != NULL && mirror_type->is_valuetype()) {
         ciValueKlass* vk = mirror_type->as_value_klass();
@@ -2236,21 +2253,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_null_free = !tary->is_known_instance() && ak->is_obj_array_klass() && !ak->storage_properties().is_null_free() && ak->element_klass()->is_valuetype();
+
+      if (tary->klass_is_exact() && !can_be_null_free) {
         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_null_free) {
+        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 +2284,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 +2308,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())) {
--- a/src/hotspot/share/opto/parse1.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/parse1.cpp	Mon May 13 16:15:57 2019 +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<SigEntry>* sig_array = vt->type()->is_valuetype()->value_klass()->extended_sig();
+      const Array<SigEntry>* sig_array = vt->type()->value_klass()->extended_sig();
       GrowableArray<SigEntry> sig = GrowableArray<SigEntry>(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
--- a/src/hotspot/share/opto/parse2.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/parse2.cpp	Mon May 13 16:15:57 2019 +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);
+
+          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);
       }
-
-      insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out_or_null(AllocateNode::RawAddress));
-
       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);
       }
     }
   }
--- a/src/hotspot/share/opto/parseHelper.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/parseHelper.cpp	Mon May 13 16:15:57 2019 +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.
--- a/src/hotspot/share/opto/subnode.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/subnode.cpp	Mon May 13 16:15:57 2019 +0200
@@ -1068,6 +1068,15 @@
   if (con2 != (intptr_t) superklass->super_check_offset())
     return NULL;                // Might be element-klass loading from array klass
 
+  // Do not normalize comparisons between Java mirror loads from [V? to klass comparisons. The runtime type
+  // might be [V due to [V <: [V? and the klass for [V? and [V is the same but the component mirror is not.
+  if (superklass->is_obj_array_klass()) {
+    ciObjArrayKlass* ak = superklass->as_obj_array_klass();
+    if (!ak->storage_properties().is_null_free() && ak->element_klass()->is_valuetype()) {
+      return NULL;
+    }
+  }
+
   // If 'superklass' has no subklasses and is not an interface, then we are
   // assured that the only input which will pass the type check is
   // 'superklass' itself.
--- a/src/hotspot/share/opto/type.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/type.cpp	Mon May 13 16:15:57 2019 +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();
--- a/src/hotspot/share/opto/type.hpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/type.hpp	Mon May 13 16:15:57 2019 +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;
--- a/src/hotspot/share/opto/valuetypenode.cpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/valuetypenode.cpp	Mon May 13 16:15:57 2019 +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;
 }
 
--- a/src/hotspot/share/opto/valuetypenode.hpp	Mon May 06 16:40:29 2019 +0800
+++ b/src/hotspot/share/opto/valuetypenode.hpp	Mon May 13 16:15:57 2019 +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()) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestArrayAccessDeopt.java	Mon May 13 16:15:57 2019 +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	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayThreshold.java	Mon May 13 16:15:57 2019 +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
+        }
+    }
+}
--- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestIntrinsics.java	Mon May 06 16:40:29 2019 +0800
+++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestIntrinsics.java	Mon May 13 16:15:57 2019 +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) {
--- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestNullableArrays.java	Mon May 06 16:40:29 2019 +0800
+++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestNullableArrays.java	Mon May 13 16:15:57 2019 +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);
-        }
-        test20(src, dst);
+        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) {
-            Asserts.assertEQ(dst[0], null);
+            src2[0] = testValue1;
         }
         for (int i = 1; i < len; ++i) {
-            Asserts.assertEQ(src[i].hash(), dst[i].hash());
+            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(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(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);
-        }
-        test21(src, dst);
+        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) {
-            Asserts.assertEQ(dst[0], null);
+            src2[0] = MyValue2.createWithFieldsInline(rI, true);
         }
         for (int i = 1; i < len; ++i) {
-            Asserts.assertEQ(src[i].hash(), dst[i].hash());
+            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(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(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);
-        }
-        test24(src, dst);
+        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) {
-            Asserts.assertEQ(dst[0], null);
+            src2[0] = testValue1;
         }
         for (int i = 1; i < len; ++i) {
-            Asserts.assertEQ(src[i].hash(), dst[i].hash());
+            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(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(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);
+            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(src, dst);
-        Asserts.assertEQ(dst[0], null);
+        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];
+        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) {
-            src[i] = MyValue1.createWithFieldsInline(rI, rL);
-        }
-        test26(src, dst);
-        Asserts.assertEQ(dst[0], null);
-        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,22 +817,41 @@
 
     @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);
+        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(src, dst);
+        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());
         }
     }
 
     // non escaping allocations
-    @Test(failOn = ALLOCA + LOOP + LOAD + TRAP)
+// TODO fix
+//    @Test(failOn = ALLOCA + LOOP + LOAD + TRAP)
     public MyValue2? test28() {
         MyValue2?[] src = new MyValue2?[10];
         src[0] = null;
@@ -806,16 +943,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 +971,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 +1092,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 +1276,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 +1452,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 +1468,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 +1484,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 +1500,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 +1516,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 +1532,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 +1548,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 +1564,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 +1587,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 +1605,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 +1693,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 +1955,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 +1988,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 +2016,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 +2071,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 +2096,311 @@
         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
+        }
+    }
+
+    // More cast tests
+    @Test()
+    public static MyValue1?[] test90(Object va) {
+        return (MyValue1?[])va;
+    }
+
+    @DontCompile
+    public void test90_verifier(boolean warmup) {
+        MyValue1[] va = new MyValue1[1];
+        MyValue1?[] vab = new MyValue1?[1];
+        try {
+          // Trigger some ClassCastExceptions so C2 does not add an uncommon trap
+          test90(new Integer[0]);
+        } catch (ClassCastException cce) {
+          // Expected
+        }
+        test90(va);
+        test90(vab);
+        test90(null);
+    }
+
+    @Test()
+    public static MyValue1?[] test91(Object[] va) {
+        return (MyValue1?[])va;
+    }
+
+    @DontCompile
+    public void test91_verifier(boolean warmup) {
+        MyValue1[] va = new MyValue1[1];
+        MyValue1?[] vab = new MyValue1?[1];
+        try {
+          // Trigger some ClassCastExceptions so C2 does not add an uncommon trap
+          test91(new Integer[0]);
+        } catch (ClassCastException cce) {
+          // Expected
+        }
+        test91(va);
+        test91(vab);
+        test91(null);
+    }
 }