From 61240a2df782c80a1572e1b7de6ee0e0e6589324 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Fri, 19 Jun 2026 09:34:04 +0200 Subject: [PATCH 1/9] fix --- src/hotspot/share/opto/library_call.cpp | 13 +- .../valhalla/inlinetypes/TestArrays.java | 172 +++++++++++++++++- 2 files changed, 181 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 1fbddb8d16a..bf2dad45485 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5197,15 +5197,22 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { // write barrier. Conservatively, go to slow path. // TODO 8251971: Optimize for the case when flat src/dst are later found // to not contain oops (i.e., move this check to the macro expansion phase). - // TODO 8382226: Revisit for flat abstract value class arrays BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr(); - bool exclude_flat = UseArrayFlattening && bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && + const bool is_src_abstract_flat_value_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && orig_t->is_flat(); + const bool can_dest_be_value_class_array = tklass->can_be_inline_array(); + const bool is_dest_abstract_flat_value_class_array = can_dest_be_value_class_array && tklass->is_flat() && + !tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); + const bool is_abstract_flat_value_class_array_involved = is_src_abstract_flat_value_array || is_dest_abstract_flat_value_class_array; + const bool exclude_flat = UseArrayFlattening && + // We do not know the exact layout of an abstract flat value class array. Bail out. + (is_abstract_flat_value_class_array_involved || + (bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && // Can src array be flat and contain oops? (orig_t == nullptr || (!orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops()))) && // Can dest array be flat and contain oops? - tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); + can_dest_be_value_class_array && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()))); Node* not_objArray = exclude_flat ? generate_non_refArray_guard(klass_node, bailout) : generate_typeArray_guard(klass_node, bailout); Node* refined_klass_node = load_default_refined_array_klass(klass_node, /* type_array_guard= */ false); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java index c6f2220fa74..1a032719320 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java @@ -3901,10 +3901,50 @@ static value class BadCastV2 extends BadCastA { } } + @LooselyConsistentValue + static value class BadCastV3 { + byte a; + byte b; + byte c; + byte d; + + BadCastV3(int i) { + a = (byte)i; + b = (byte)(i + 1); + c = (byte)(i + 2); + d = (byte)(i + 3); + } + + BadCastV3() { + this(1); + } + } + + @LooselyConsistentValue + static value class BadCastV4 { + byte a; + byte b; + byte c; + byte d; + + BadCastV4(int i) { + a = (byte)i; + b = (byte)(i + 1); + c = (byte)(i + 2); + d = (byte)(i + 3); + } + + BadCastV4() { + this(1); + } + } + static BadCastV1 badCastv1 = new BadCastV1(11); static BadCastV1 badCastv11 = new BadCastV1(21); static BadCastV2 badCastv2 = new BadCastV2(31); static BadCastV2 badCastv22 = new BadCastV2(41); + static BadCastV3 badCastv3 = new BadCastV3(11); + static BadCastV4 badCastv4 = new BadCastV4(11); static int badCastLen = Math.abs(rI) % 10; @Test @@ -3959,10 +3999,101 @@ static BadCastA[] testBadCastAbstractArray4(boolean flag) { return aArr2; } + // Needs -XX:-ReduceInitialCardMarks to trigger. + @Test + static BadCastA[] testBadCastAbstractArray5(boolean flag) { + BadCastA[] aArr; + if (flag) { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV1.class, badCastLen, badCastv1); + } else { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV2.class, badCastLen, badCastv2); + } + + return Arrays.copyOf(aArr, badCastLen, BadCastA[].class); + } + + @Test + static BadCastA[] testBadCastAbstractArray5a(boolean flag) { + BadCastA[] aArr; + if (flag) { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV1.class, badCastLen, badCastv1); + } else { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV2.class, badCastLen, badCastv2); + } + + return Arrays.copyOfRange(aArr, 0, badCastLen, BadCastA[].class); + } + + @Test + static BadCastA[] testBadCastAbstractArray6(boolean flag) { + BadCastA[] aArr; + Class c; + if (flag) { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV1.class, badCastLen, badCastv1); + c = BadCastV1[].class; + } else { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV2.class, badCastLen, badCastv2); + c = BadCastV2[].class; + } + + return Arrays.copyOf(aArr, badCastLen, c); + } + + @Test + static BadCastA[] testBadCastAbstractArray6a(boolean flag) { + BadCastA[] aArr; + Class c; + if (flag) { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV1.class, badCastLen, badCastv1); + c = BadCastV1[].class; + } else { + aArr = (BadCastA[]) ValueClass.newNullRestrictedNonAtomicArray(BadCastV2.class, badCastLen, badCastv2); + c = BadCastV2[].class; + } + + return Arrays.copyOfRange(aArr, 0, badCastLen, c); + } + + @Test + static Object[] testBadCastAbstractArray7(boolean flag) { + Object[] oArr; + Class c; + if (flag) { + oArr = ValueClass.newNullRestrictedNonAtomicArray(BadCastV3.class, badCastLen, badCastv3); + c = BadCastV3[].class; + } else { + oArr = ValueClass.newNullRestrictedNonAtomicArray(BadCastV4.class, badCastLen, badCastv4); + c = BadCastV4[].class; + } + + return Arrays.copyOf(oArr, badCastLen, c); + } + + @Test + static Object[] testBadCastAbstractArray7a(boolean flag) { + Object[] oArr; + Class c; + if (flag) { + oArr = ValueClass.newNullRestrictedNonAtomicArray(BadCastV3.class, badCastLen, badCastv3); + c = BadCastV3[].class; + } else { + oArr = ValueClass.newNullRestrictedNonAtomicArray(BadCastV4.class, badCastLen, badCastv4); + c = BadCastV4[].class; + } + + return Arrays.copyOfRange(oArr, 0, badCastLen, c); + } + @Run(test = {"testBadCastAbstractArray1", "testBadCastAbstractArray2", "testBadCastAbstractArray3", - "testBadCastAbstractArray4"}) + "testBadCastAbstractArray4", + "testBadCastAbstractArray5", + "testBadCastAbstractArray5a", + "testBadCastAbstractArray6", + "testBadCastAbstractArray6a", + "testBadCastAbstractArray7", + "testBadCastAbstractArray7a"}) @Warmup(0) static void testBadCastAbstractArray_verifier() { boolean flag = true; @@ -3996,6 +4127,37 @@ static void testBadCastAbstractArray_verifier() { for (int j = 0; j < badCastLen; ++j) { Asserts.assertEQ(src[j], dst[j]); } + + dst = testBadCastAbstractArray5(flag); + for (int j = 0; j < badCastLen; ++j) { + Asserts.assertEQ(src[j], dst[j]); + } + + dst = testBadCastAbstractArray5a(flag); + for (int j = 0; j < badCastLen; ++j) { + Asserts.assertEQ(src[j], dst[j]); + } + + dst = testBadCastAbstractArray6(flag); + for (int j = 0; j < badCastLen; ++j) { + Asserts.assertEQ(src[j], dst[j]); + } + + dst = testBadCastAbstractArray6a(flag); + for (int j = 0; j < badCastLen; ++j) { + Asserts.assertEQ(src[j], dst[j]); + } + + Object[] srcObj = flag ? resetBadCastV3() : resetBadCastV4(); + Object[] dstObj = testBadCastAbstractArray7(flag); + for (int j = 0; j < badCastLen; ++j) { + Asserts.assertEQ(srcObj[j], dstObj[j]); + } + + dstObj = testBadCastAbstractArray7a(flag); + for (int j = 0; j < badCastLen; ++j) { + Asserts.assertEQ(srcObj[j], dstObj[j]); + } flag = !flag; } } @@ -4015,4 +4177,12 @@ static BadCastA[] resetBadCastV2() { static BadCastA[] resetBadCastV22() { return (BadCastA[])ValueClass.newNullRestrictedNonAtomicArray(BadCastV2.class, badCastLen, badCastv22); } + + static Object[] resetBadCastV3() { + return ValueClass.newNullRestrictedNonAtomicArray(BadCastV3.class, badCastLen, badCastv3); + } + + static Object[] resetBadCastV4() { + return ValueClass.newNullRestrictedNonAtomicArray(BadCastV4.class, badCastLen, badCastv4); + } } From bf89360a07ff413db5f58c809689935842ea1eb0 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Fri, 19 Jun 2026 15:11:48 +0200 Subject: [PATCH 2/9] Review Quan-Anh --- src/hotspot/share/opto/library_call.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index bf2dad45485..f2c812b8439 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5200,11 +5200,11 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr(); - const bool is_src_abstract_flat_value_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && orig_t->is_flat(); + const bool can_src_be_abstract_flat_value_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); const bool can_dest_be_value_class_array = tklass->can_be_inline_array(); - const bool is_dest_abstract_flat_value_class_array = can_dest_be_value_class_array && tklass->is_flat() && - !tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); - const bool is_abstract_flat_value_class_array_involved = is_src_abstract_flat_value_array || is_dest_abstract_flat_value_class_array; + const bool can_dest_be_abstract_flat_value_class_array = can_dest_be_value_class_array && !tklass->is_not_flat() && + !tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); + const bool is_abstract_flat_value_class_array_involved = can_src_be_abstract_flat_value_array || can_dest_be_abstract_flat_value_class_array; const bool exclude_flat = UseArrayFlattening && // We do not know the exact layout of an abstract flat value class array. Bail out. (is_abstract_flat_value_class_array_involved || From b1de11c75d99bca4cd86cf64bb973a628e911d2b Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Fri, 19 Jun 2026 16:05:20 +0200 Subject: [PATCH 3/9] fix tklass --- src/hotspot/share/opto/library_call.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index f2c812b8439..ad77538505b 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5200,11 +5200,11 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr(); - const bool can_src_be_abstract_flat_value_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); + const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); const bool can_dest_be_value_class_array = tklass->can_be_inline_array(); - const bool can_dest_be_abstract_flat_value_class_array = can_dest_be_value_class_array && !tklass->is_not_flat() && - !tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); - const bool is_abstract_flat_value_class_array_involved = can_src_be_abstract_flat_value_array || can_dest_be_abstract_flat_value_class_array; + const bool can_dest_be_abstract_flat_value_class_array = can_dest_be_value_class_array && !tklass->is_not_flat() && tklass->isa_aryklassptr() != nullptr && + !tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); + const bool is_abstract_flat_value_class_array_involved = can_src_be_abstract_flat_value_class_array || can_dest_be_abstract_flat_value_class_array; const bool exclude_flat = UseArrayFlattening && // We do not know the exact layout of an abstract flat value class array. Bail out. (is_abstract_flat_value_class_array_involved || From e3b4f8828ecdb2058eb760afc074f09379207c36 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Sat, 20 Jun 2026 01:07:00 +0200 Subject: [PATCH 4/9] improve checks and further refactor exclude_flat to make it easier to read --- src/hotspot/share/opto/library_call.cpp | 34 ++++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index ad77538505b..359db151c62 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5193,26 +5193,30 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { // Despite the generic type of Arrays.copyOf, the mirror might be int, int[], etc. // Bail out if that is so. - // Inline type array may have object field that would require a - // write barrier. Conservatively, go to slow path. - // TODO 8251971: Optimize for the case when flat src/dst are later found - // to not contain oops (i.e., move this check to the macro expansion phase). BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); - const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr(); + const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr(); const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); - const bool can_dest_be_value_class_array = tklass->can_be_inline_array(); - const bool can_dest_be_abstract_flat_value_class_array = can_dest_be_value_class_array && !tklass->is_not_flat() && tklass->isa_aryklassptr() != nullptr && - !tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); + const bool can_dest_be_value_class_array = dest_klass_t != nullptr && dest_klass_t->can_be_inline_array(); + const bool can_dest_be_abstract_flat_value_class_array = can_dest_be_value_class_array && !dest_klass_t->is_not_flat() && + !dest_klass_t->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); const bool is_abstract_flat_value_class_array_involved = can_src_be_abstract_flat_value_class_array || can_dest_be_abstract_flat_value_class_array; + const bool can_src_be_flat_with_oops = orig_t == nullptr || + (!can_src_be_abstract_flat_value_class_array && + !orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops())); + const bool can_dest_be_flat_with_oops = can_dest_be_value_class_array && !can_dest_be_abstract_flat_value_class_array && + !dest_klass_t->is_not_flat() && + (!dest_klass_t->is_flat() || dest_klass_t->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); + const bool exclude_flat = UseArrayFlattening && - // We do not know the exact layout of an abstract flat value class array. Bail out. - (is_abstract_flat_value_class_array_involved || - (bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && - // Can src array be flat and contain oops? - (orig_t == nullptr || (!orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops()))) && - // Can dest array be flat and contain oops? - can_dest_be_value_class_array && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()))); + // We do not know the exact layout of an abstract flat value class array. Bail out. + (is_abstract_flat_value_class_array_involved || + // Value class array may have object field that would require a write barrier. + // Conservatively, go to slow path. + // TODO 8251971: Optimize for the case when flat src/dst are later found to not contain + // oops (i.e., move this check to the macro expansion phase). + (bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && + can_src_be_flat_with_oops && can_dest_be_flat_with_oops)); Node* not_objArray = exclude_flat ? generate_non_refArray_guard(klass_node, bailout) : generate_typeArray_guard(klass_node, bailout); Node* refined_klass_node = load_default_refined_array_klass(klass_node, /* type_array_guard= */ false); From 0819467acb2220fac0077bcb2fefcdb56642de3b Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Fri, 26 Jun 2026 19:48:39 +0200 Subject: [PATCH 5/9] Update --- src/hotspot/share/opto/library_call.cpp | 87 ++-- src/hotspot/share/opto/library_call.hpp | 1 + .../inlinetypes/TestArraysCopyOf.java | 373 ++++++++++++++++++ 3 files changed, 436 insertions(+), 25 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 359db151c62..537b15d92b1 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5186,6 +5186,13 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { Node* orig_length = load_array_length(original); Node* klass_node = load_klass_from_mirror(array_type_mirror, false, nullptr, 0); + if (stopped()) { + // Arrays.copyOf() uses a generic type Class parameter which is erased to the raw type Class. This also + // allows passing in primitive class mirrors like int.class which do not have corresponding Klass* pointers. + // In these cases, klass_node will be top and we bail out. + return true; + } + klass_node = null_check(klass_node); RegionNode* bailout = new RegionNode(1); @@ -5194,34 +5201,19 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { // Despite the generic type of Arrays.copyOf, the mirror might be int, int[], etc. // Bail out if that is so. BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); - const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr(); - const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); - const bool can_dest_be_value_class_array = dest_klass_t != nullptr && dest_klass_t->can_be_inline_array(); - const bool can_dest_be_abstract_flat_value_class_array = can_dest_be_value_class_array && !dest_klass_t->is_not_flat() && - !dest_klass_t->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); - const bool is_abstract_flat_value_class_array_involved = can_src_be_abstract_flat_value_class_array || can_dest_be_abstract_flat_value_class_array; - const bool can_src_be_flat_with_oops = orig_t == nullptr || - (!can_src_be_abstract_flat_value_class_array && - !orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops())); - const bool can_dest_be_flat_with_oops = can_dest_be_value_class_array && !can_dest_be_abstract_flat_value_class_array && - !dest_klass_t->is_not_flat() && - (!dest_klass_t->is_flat() || dest_klass_t->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); - - const bool exclude_flat = UseArrayFlattening && - // We do not know the exact layout of an abstract flat value class array. Bail out. - (is_abstract_flat_value_class_array_involved || - // Value class array may have object field that would require a write barrier. - // Conservatively, go to slow path. - // TODO 8251971: Optimize for the case when flat src/dst are later found to not contain - // oops (i.e., move this check to the macro expansion phase). - (bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && - can_src_be_flat_with_oops && can_dest_be_flat_with_oops)); - Node* not_objArray = exclude_flat ? generate_non_refArray_guard(klass_node, bailout) : generate_typeArray_guard(klass_node, bailout); + const TypeAryPtr* src_t = _gvn.type(original)->is_aryptr(); + const TypeKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->is_klassptr(); + + Node* success_proj; + if (should_bail_out_on_non_ref_arrays(src_t, dest_klass_t)) { + success_proj = generate_non_refArray_guard(klass_node, bailout); + } else { + success_proj = generate_typeArray_guard(klass_node, bailout); + } Node* refined_klass_node = load_default_refined_array_klass(klass_node, /* type_array_guard= */ false); - if (not_objArray != nullptr) { + if (success_proj != nullptr) { // Improve the klass node's type from the new optimistic assumption: ciKlass* ak = ciArrayKlass::make(env()->Object_klass()); bool not_flat = !UseArrayFlattening; @@ -5338,6 +5330,51 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { return true; } +bool LibraryCallKit::should_bail_out_on_non_ref_arrays(const TypeAryPtr* src_type, const TypeKlassPtr* dest_klass_type) { + if (UseArrayFlattening) { + return false; + } + + const TypeAryKlassPtr* dest_ary_klass_type = dest_klass_type->isa_aryklassptr(); + if (dest_ary_klass_type == nullptr) { + // Dest class is not known to be an array class. There are multiple cases: + // - Primitive type mirror: We already bailed out before. + // - Instance klass mirror: We should bail out. + // - java.lang.Object (possible due to type erasure): Could be anything including primitive type or instance klass + // mirror or also flat arrays. Bail out. + return true; + } + + // We now know that src and dest are proper array pointers. + const bool src_maybe_flat = !src_type->is_not_flat(); + const bool dest_maybe_flat = !dest_ary_klass_type->is_not_flat(); + + // We could have abstract flat value class arrays whose layout we don't know. Bail out. + const bool can_src_be_abstract_flat_value_class_array = src_maybe_flat && !src_type->elem()->is_inlinetypeptr(); + const bool can_dest_be_abstract_flat_value_class_array = dest_maybe_flat && + !dest_ary_klass_type->elem()->is_instklassptr()->instance_klass()->is_inlinetype(); + if (can_src_be_abstract_flat_value_class_array || can_dest_be_abstract_flat_value_class_array) { + return true; + } + + // Value class array may have object field that would require a write barrier. Conservatively bail out. + // TODO 8251971: Optimize for the case when flat src/dst are later found to not contain + // oops (i.e., move this check to the macro expansion phase). + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + if (bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing)) { + // No barriers required. + return false; + } + + const bool can_src_be_flat_with_oops = src_maybe_flat && src_type->elem()->inline_klass()->contains_oops(); + const bool can_dest_be_flat_with_oops = dest_maybe_flat && dest_ary_klass_type->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops(); + if (can_src_be_flat_with_oops || can_dest_be_flat_with_oops) { + return true; + } + + // Can handle remaining flat arrays. + return false; +} //----------------------generate_virtual_guard--------------------------- // Helper for hashCode and clone. Peeks inside the vtable to avoid a call. diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 7767b46f2f4..3468b23eb4f 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -304,6 +304,7 @@ class LibraryCallKit : public GraphKit { bool inline_native_subtype_check(); bool inline_native_getLength(); bool inline_array_copyOf(bool is_copyOfRange); + static bool should_bail_out_on_non_ref_arrays(const TypeAryPtr* src_type, const TypeKlassPtr* dest_klass_type); bool inline_array_equals(StrIntrinsicNode::ArgEnc ae); bool inline_preconditions_checkIndex(BasicType bt); void copy_to_clone(Node* obj, Node* alloc_obj, Node* obj_size, bool is_array); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java new file mode 100644 index 00000000000..7b9a9231c66 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2026, 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 + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 0 + */ + +/* + * @test + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 1 + */ + +/* + * @test + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 2 + */ + +/* + * @test + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 3 + */ + +/* + * @test + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 4 + */ + +/* + * @test + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 5 + */ + +/* + * @test + * @bug 8382226 + * @summary Test Arrays.copyOf with template tests + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} 6 + */ + + +package compiler.valhalla.inlinetypes; + +import compiler.lib.compile_framework.CompileFramework; +import compiler.lib.ir_framework.Scenario; +import compiler.lib.template_framework.*; +import compiler.lib.template_framework.library.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.IntFunction; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static compiler.lib.template_framework.Template.*; +import static compiler.lib.template_framework.Template.scope; + +public class TestArraysCopyOf { + public static void main(String[] args) { + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[2].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); + scenarios[3].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:+UnlockDiagnosticVMOptions", "-XX:+UseArrayFlattening", "-XX:-UncommonNullCast"); + scenarios[4].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast"); + scenarios[5].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); + + String[] flagsForScenario = scenarios[Integer.parseInt(args[0])].getFlags().toArray(new String[0]); + CompileFramework comp = new CompileFramework(); + comp.addJavaSourceCode("compiler.valhalla.inlinetypes.TestArraysCopyOfGenerated", generate(comp)); + comp.compile("--enable-preview", "--source", "28", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED"); + comp.invoke("compiler.valhalla.inlinetypes.TestArraysCopyOfGenerated", "main", + new Object[]{ flagsForScenario }); + } + + private record Klass(String name, String definition) {} + + private static String generate(CompileFramework comp) { + List primitiveTypes = CodeGenerationDataNameType.PRIMITIVE_TYPES; + List primitiveTypeClasses = primitiveTypes.stream() + .map(PrimitiveType::name).toList(); + List instanceAndPrimitiveClasses = + new ArrayList<>(CodeGenerationDataNameType.PRIMITIVE_TYPES.stream() + .map(PrimitiveType::name).toList()); + + List nonValueClasses = List.of( + new Klass("V", "static class V extends AV"), + new Klass("W", "static class W"), + new Klass("X", "static class X extends A"), + new Klass("Y", "static class Y implements I"), + new Klass("Z", "static class Z extends AV")); + + List valueClasses = List.of( + new Klass("V1", "static value class V1"), + new Klass("V2", "static value class V2 extends AV"), + new Klass("V3", "static value class V3 implements I"), + new Klass("V4", "@LooselyConsistentValue\nstatic value class V4"), + new Klass("V5", "@LooselyConsistentValue\nstatic value class V5 extends AV"), + new Klass("V6", "@LooselyConsistentValue\nstatic value class V6 implements I") + ); + + List concreteInstanceClasses = new ArrayList<>(nonValueClasses); + concreteInstanceClasses.addAll(valueClasses); + + List abstractClasses = new ArrayList<>(List.of( + new Klass("A", "static abstract class A"), + new Klass("AV", "static abstract value class AV") + )); + + instanceAndPrimitiveClasses.addAll(concreteInstanceClasses.stream().map(Klass::name).toList()); + instanceAndPrimitiveClasses.addAll(abstractClasses.stream().map(Klass::name).toList()); + + List newValueClassArrayScopes = List.of( + scope("(#klass)ValueClass.newReferenceArray(#klass_name.class, length)"), + scope("(#klass)ValueClass.newNullableAtomicArray(#klass_name.class, length)"), + scope("(#klass)ValueClass.newNullRestrictedAtomicArray(#klass_name.class, length, #klass_name.init())"), + scope("(#klass)ValueClass.newNullRestrictedNonAtomicArray(#klass_name.class, length, #klass_name.init())")); + + AtomicInteger uniqueIndex = new AtomicInteger(0); + var template = Template.make(() -> scope( + """ + + static interface I {} + + """, + abstractClasses.stream().map(klass -> scope( + let("def", klass.definition()), + let("klass", klass.name()), + """ + + #def { + """, + primitiveTypes.stream().map(type -> scope( + let("type", type.name()), + let("con", type.con()), + """ + #type _#type = #con; + """)).toList(), + """ + } + """)).toList(), + concreteInstanceClasses.stream().map(klass -> scope( + let("def", klass.definition()), + let("klass", klass.name()), + """ + + #def { + """, + primitiveTypes.stream().map(type -> scope( + let("type", type.name()), + """ + #type _#type; + """)).toList(), + """ + + public #klass(\ + """, + concat(primitiveTypes, (type, _) -> scope(type.name() + " _" + type.name())), + ") {\n", + loop(primitiveTypes, (type, _) -> scope( + let("type", type.name()), + " this._#type = _#type;\n")), + """ + } + + static #klass init() { + return new #klass(\ + """, + concat(primitiveTypes, (type, _) -> scope(type.con())), + ");\n", + """ + } + } + """)).toList(), + """ + + static Object[] oArr = new Object[1]; + """, + loop(instanceAndPrimitiveClasses.size(), i -> scope( + let("i", uniqueIndex.getAndIncrement()), + let("klass", instanceAndPrimitiveClasses.get(i)), + let("test", "testNonArrayClass" + instanceAndPrimitiveClasses.get(i)), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + try { + #test(oArr, 1); + Asserts.fail("Should throw"); + } catch (NullPointerException e) { + // expected + } + } + """)), + loop(primitiveTypeClasses.size(), i -> scope( + let("i", uniqueIndex.getAndIncrement()), + let("klass", primitiveTypeClasses.get(i) + "[]"), + let("test", "testPrimitiveArrayClass" + primitiveTypeClasses.get(i)), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + try { + #test(oArr, 1); + Asserts.fail("Should throw"); + } catch (ArrayStoreException e) { + // expected + } + } + """)), + loop(concreteInstanceClasses.size(), i -> scope( + let("i", uniqueIndex.getAndIncrement()), + let("klass_name", concreteInstanceClasses.get(i).name()), + let("klass", concreteInstanceClasses.get(i).name() + "[]"), + let("test", "testInstanceArrayClass" + concreteInstanceClasses.get(i).name()), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + int length = 4; + #klass arr = new #klass_name[length]; + #klass golden = new #klass_name[length]; + for (int i = 0; i < length; i++) { + arr[i] = #klass_name.init(); + golden[i] = #klass_name.init(); + } + + #klass res = (#klass)#test(arr, length); + + for (int i = 0; i < length; i++) { + Verify.checkEQ(res[i], golden[i]); + } + } + """)), + loop(newValueClassArrayScopes, (init, i) -> scope( + let("i", uniqueIndex.getAndIncrement()), + let("klass_name", valueClasses.get(i).name()), + let("klass", valueClasses.get(i).name() + "[]"), + let("test", "testValueArrayClass" + valueClasses.get(i).name()), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + int length = 4; + """, + " #klass arr =", init, ";\n", + " #klass golden =", init, ";\n", + """ + + #klass res = (#klass)#test(arr, length); + + for (int i = 0; i < length; i++) { + Asserts.assertEQ(res[i], golden[i]); + } + } + """)) + )); + + return TestFrameworkClass.render("compiler.valhalla.inlinetypes", + "TestArraysCopyOfGenerated", + Set.of("jdk.test.lib.Asserts", + "java.util.Arrays", + "compiler.lib.verify.Verify", + "jdk.internal.value.ValueClass", + "jdk.internal.vm.annotation.LooselyConsistentValue"), + comp.getEscapedClassPathOfCompiledClasses(), + List.of(template.asToken()) + ); + } + + static String generateTestMethodString() { + return """ + + @Test + public static Object #test(Object[] arr, int length) { + Class c = #klass.class; + return Arrays.copyOf(arr, length, c); + } + """; + } + + static List loop(int limit, IntFunction function) { + return IntStream.range(0, limit) + .mapToObj(function) + .toList(); + } + + static List loop(List items, BiFunction function) { + return IntStream.range(0, items.size()) + .mapToObj(i -> function.apply(items.get(i), i)) + .toList(); + } + + static List concat(List items, BiFunction function) { + return IntStream.range(0, items.size()) + .boxed() + .flatMap(i -> { + ScopeToken token = function.apply(items.get(i), i); + return i == items.size() - 1 ? + Stream.of(token) : + Stream.of(token, scope(", ")); + }) + .toList(); + } +} \ No newline at end of file From 28688448eb411698342f0d6e710654455b2ad443 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Mon, 29 Jun 2026 10:40:23 +0200 Subject: [PATCH 6/9] Fix endless deopts with -XX:-UseArrayFlattening --- src/hotspot/share/opto/library_call.cpp | 22 +++++++++---------- .../inlinetypes/TestArraysCopyOf.java | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 537b15d92b1..5f7bce641b9 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5187,8 +5187,8 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { Node* klass_node = load_klass_from_mirror(array_type_mirror, false, nullptr, 0); if (stopped()) { - // Arrays.copyOf() uses a generic type Class parameter which is erased to the raw type Class. This also - // allows passing in primitive class mirrors like int.class which do not have corresponding Klass* pointers. + // Arrays.copyOf() uses a generic Class parameter which is erased to the raw type Class. This also allows + // passing in primitive class mirrors like int.class which do not have corresponding Klass* pointers. // In these cases, klass_node will be top and we bail out. return true; } @@ -5331,20 +5331,20 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { } bool LibraryCallKit::should_bail_out_on_non_ref_arrays(const TypeAryPtr* src_type, const TypeKlassPtr* dest_klass_type) { - if (UseArrayFlattening) { - return false; - } - const TypeAryKlassPtr* dest_ary_klass_type = dest_klass_type->isa_aryklassptr(); if (dest_ary_klass_type == nullptr) { - // Dest class is not known to be an array class. There are multiple cases: - // - Primitive type mirror: We already bailed out before. - // - Instance klass mirror: We should bail out. - // - java.lang.Object (possible due to type erasure): Could be anything including primitive type or instance klass - // mirror or also flat arrays. Bail out. + // Dest klass is not known to be an array class. There are multiple cases: + // - Primitive class mirror: We already bailed out before. + // - Instance class mirror: We should bail out. + // - java.lang.Object (possible due to type erasure): Could be anything including primitive or instance class mirror + // or also flat arrays. Bail out. return true; } + if (UseArrayFlattening) { + return false; + } + // We now know that src and dest are proper array pointers. const bool src_maybe_flat = !src_type->is_not_flat(); const bool dest_maybe_flat = !dest_ary_klass_type->is_not_flat(); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java index 7b9a9231c66..80b00cf5635 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java @@ -152,7 +152,8 @@ private static String generate(CompileFramework comp) { new Klass("W", "static class W"), new Klass("X", "static class X extends A"), new Klass("Y", "static class Y implements I"), - new Klass("Z", "static class Z extends AV")); + new Klass("Z", "static class Z extends AV") + ); List valueClasses = List.of( new Klass("V1", "static value class V1"), @@ -168,8 +169,7 @@ private static String generate(CompileFramework comp) { List abstractClasses = new ArrayList<>(List.of( new Klass("A", "static abstract class A"), - new Klass("AV", "static abstract value class AV") - )); + new Klass("AV", "static abstract value class AV"))); instanceAndPrimitiveClasses.addAll(concreteInstanceClasses.stream().map(Klass::name).toList()); instanceAndPrimitiveClasses.addAll(abstractClasses.stream().map(Klass::name).toList()); From 02fdea6342542d4bad20f5cb8ab9e9ace86d22b4 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Tue, 30 Jun 2026 13:51:50 +0200 Subject: [PATCH 7/9] Update --- src/hotspot/share/opto/library_call.cpp | 26 +++++++------ src/hotspot/share/opto/library_call.hpp | 1 + .../inlinetypes/TestArraysCopyOf.java | 37 +++++++++++++------ 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 9222ce57194..8d54e951bf5 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5177,22 +5177,20 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { Node* orig_length = load_array_length(original); - Node* klass_node = load_klass_from_mirror(array_type_mirror, false, nullptr, 0); + RegionNode* bailout = new RegionNode(2); + record_for_igvn(bailout); + + Node* klass_node = load_klass_from_mirror(array_type_mirror, false, bailout, 1); if (stopped()) { // Arrays.copyOf() uses a generic Class parameter which is erased to the raw type Class. This also allows // passing in primitive class mirrors like int.class which do not have corresponding Klass* pointers. - // In these cases, klass_node will be top and we bail out. + // In these cases, klass_node will be top. Emit a trap to throw in the interpreter in this case. + bail_out_from_array_copyOf(bailout); return true; } klass_node = null_check(klass_node); - RegionNode* bailout = new RegionNode(1); - record_for_igvn(bailout); - - // Despite the generic type of Arrays.copyOf, the mirror might be int, int[], etc. - // Bail out if that is so. - BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); const TypeAryPtr* src_t = _gvn.type(original)->is_aryptr(); const TypeKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->is_klassptr(); @@ -5241,10 +5239,7 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { generate_negative_guard(orig_tail, bailout, &orig_tail); if (bailout->req() > 1) { - PreserveJVMState pjvms(this); - set_control(_gvn.transform(bailout)); - uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_maybe_recompile); + bail_out_from_array_copyOf(bailout); } if (!stopped()) { @@ -5322,6 +5317,13 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { return true; } +void LibraryCallKit::bail_out_from_array_copyOf(RegionNode* bailout_region) { + PreserveJVMState pjvms(this); + set_control(_gvn.transform(bailout_region)); + uncommon_trap(Deoptimization::Reason_intrinsic, + Deoptimization::Action_maybe_recompile); +} + bool LibraryCallKit::should_bail_out_on_non_ref_arrays(const TypeAryPtr* src_type, const TypeKlassPtr* dest_klass_type) { const TypeAryKlassPtr* dest_ary_klass_type = dest_klass_type->isa_aryklassptr(); if (dest_ary_klass_type == nullptr) { diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 3468b23eb4f..7e3d65060d9 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -304,6 +304,7 @@ class LibraryCallKit : public GraphKit { bool inline_native_subtype_check(); bool inline_native_getLength(); bool inline_array_copyOf(bool is_copyOfRange); + void bail_out_from_array_copyOf(RegionNode* bailout_region); static bool should_bail_out_on_non_ref_arrays(const TypeAryPtr* src_type, const TypeKlassPtr* dest_klass_type); bool inline_array_equals(StrIntrinsicNode::ArgEnc ae); bool inline_preconditions_checkIndex(BasicType bt); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java index 80b00cf5635..a951edf95c2 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java @@ -40,7 +40,7 @@ * @enablePreview * @modules java.base/jdk.internal.value * java.base/jdk.internal.vm.annotation - * @run main ${test.main.class} 1 + * @run driver ${test.main.class} 1 */ /* @@ -51,7 +51,7 @@ * @enablePreview * @modules java.base/jdk.internal.value * java.base/jdk.internal.vm.annotation - * @run main ${test.main.class} 2 + * @run driver ${test.main.class} 2 */ /* @@ -62,7 +62,7 @@ * @enablePreview * @modules java.base/jdk.internal.value * java.base/jdk.internal.vm.annotation - * @run main ${test.main.class} 3 + * @run driver ${test.main.class} 3 */ /* @@ -73,7 +73,7 @@ * @enablePreview * @modules java.base/jdk.internal.value * java.base/jdk.internal.vm.annotation - * @run main ${test.main.class} 4 + * @run driver ${test.main.class} 4 */ /* @@ -84,7 +84,7 @@ * @enablePreview * @modules java.base/jdk.internal.value * java.base/jdk.internal.vm.annotation - * @run main ${test.main.class} 5 + * @run driver ${test.main.class} 5 */ /* @@ -95,13 +95,15 @@ * @enablePreview * @modules java.base/jdk.internal.value * java.base/jdk.internal.vm.annotation - * @run main ${test.main.class} 6 + * @run driver ${test.main.class} 6 */ package compiler.valhalla.inlinetypes; import compiler.lib.compile_framework.CompileFramework; +import compiler.lib.generators.Generators; +import compiler.lib.generators.RestrictableGenerator; import compiler.lib.ir_framework.Scenario; import compiler.lib.template_framework.*; import compiler.lib.template_framework.library.*; @@ -119,6 +121,8 @@ import static compiler.lib.template_framework.Template.scope; public class TestArraysCopyOf { + private static final RestrictableGenerator RANDOM_LENGTH = Generators.G.ints().restricted(0, 32); + public static void main(String[] args) { Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; scenarios[2].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); @@ -187,6 +191,7 @@ private static String generate(CompileFramework comp) { static interface I {} """, + // Define some classes, each containing primitive type fields. abstractClasses.stream().map(klass -> scope( let("def", klass.definition()), let("klass", klass.name()), @@ -215,6 +220,7 @@ static interface I {} """ #type _#type; """)).toList(), + // Constructor """ public #klass(\ @@ -224,6 +230,7 @@ static interface I {} loop(primitiveTypes, (type, _) -> scope( let("type", type.name()), " this._#type = _#type;\n")), + // Initializer """ } @@ -240,10 +247,11 @@ static interface I {} static Object[] oArr = new Object[1]; """, + // Passing A.class, int.class etc. Should always throw. loop(instanceAndPrimitiveClasses.size(), i -> scope( let("i", uniqueIndex.getAndIncrement()), let("klass", instanceAndPrimitiveClasses.get(i)), - let("test", "testNonArrayClass" + instanceAndPrimitiveClasses.get(i)), + let("test", "testNonArrayClass_" + instanceAndPrimitiveClasses.get(i)), generateTestMethodString(), """ @@ -257,10 +265,11 @@ static interface I {} } } """)), + // Passing in primitive type arrays which like int[].class which should throw. loop(primitiveTypeClasses.size(), i -> scope( let("i", uniqueIndex.getAndIncrement()), let("klass", primitiveTypeClasses.get(i) + "[]"), - let("test", "testPrimitiveArrayClass" + primitiveTypeClasses.get(i)), + let("test", "testPrimitiveArrayClass_" + primitiveTypeClasses.get(i)), generateTestMethodString(), """ @@ -274,17 +283,19 @@ static interface I {} } } """)), + // Normal tests with non-primitive type arrays. loop(concreteInstanceClasses.size(), i -> scope( let("i", uniqueIndex.getAndIncrement()), let("klass_name", concreteInstanceClasses.get(i).name()), let("klass", concreteInstanceClasses.get(i).name() + "[]"), - let("test", "testInstanceArrayClass" + concreteInstanceClasses.get(i).name()), + let("test", "testInstanceArrayClass_" + concreteInstanceClasses.get(i).name()), + let("length", RANDOM_LENGTH.next()), generateTestMethodString(), """ @Run(test = "#test") public static void run#i() { - int length = 4; + int length = #length; #klass arr = new #klass_name[length]; #klass golden = new #klass_name[length]; for (int i = 0; i < length; i++) { @@ -299,17 +310,19 @@ static interface I {} } } """)), + // Normal tests with value class arrays. loop(newValueClassArrayScopes, (init, i) -> scope( let("i", uniqueIndex.getAndIncrement()), let("klass_name", valueClasses.get(i).name()), let("klass", valueClasses.get(i).name() + "[]"), - let("test", "testValueArrayClass" + valueClasses.get(i).name()), + let("test", "testValueArrayClass_" + valueClasses.get(i).name()), + let("length", RANDOM_LENGTH.next()), generateTestMethodString(), """ @Run(test = "#test") public static void run#i() { - int length = 4; + int length = #length; """, " #klass arr =", init, ";\n", " #klass golden =", init, ";\n", From 53493bc4518fc2fb2f01e614290fb20f0a515f0b Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Tue, 30 Jun 2026 14:52:47 +0200 Subject: [PATCH 8/9] Improve comments --- src/hotspot/share/opto/library_call.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 8d54e951bf5..afea52b9cca 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5336,6 +5336,8 @@ bool LibraryCallKit::should_bail_out_on_non_ref_arrays(const TypeAryPtr* src_typ } if (UseArrayFlattening) { + // The remaining checks revolve around array flatness. Without array flatness, we don't need the stronger non-ref + // runtime check excluding flat arrays. return false; } From 20cd92176dbf5cb37ab9eb3edcc49cfef2be8a88 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Wed, 1 Jul 2026 10:34:24 +0200 Subject: [PATCH 9/9] Add test for void.class as well --- .../jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java index a951edf95c2..a37249a0e36 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java @@ -177,6 +177,7 @@ private static String generate(CompileFramework comp) { instanceAndPrimitiveClasses.addAll(concreteInstanceClasses.stream().map(Klass::name).toList()); instanceAndPrimitiveClasses.addAll(abstractClasses.stream().map(Klass::name).toList()); + instanceAndPrimitiveClasses.add("void"); List newValueClassArrayScopes = List.of( scope("(#klass)ValueClass.newReferenceArray(#klass_name.class, length)"),