From 61240a2df782c80a1572e1b7de6ee0e0e6589324 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Fri, 19 Jun 2026 09:34:04 +0200 Subject: [PATCH 1/4] 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/4] 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/4] 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/4] 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);