diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 9a4f8cec713..afea52b9cca 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5177,32 +5177,33 @@ 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. 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); + const TypeAryPtr* src_t = _gvn.type(original)->is_aryptr(); + const TypeKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->is_klassptr(); - // 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). - // 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) && - // 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()); - Node* not_objArray = exclude_flat ? generate_non_refArray_guard(klass_node, bailout) : generate_typeArray_guard(klass_node, bailout); + 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; @@ -5238,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()) { @@ -5319,6 +5317,60 @@ 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) { + // 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) { + // 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; + } + + // 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..7e3d65060d9 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -304,6 +304,8 @@ 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); void copy_to_clone(Node* obj, Node* alloc_obj, Node* obj_size, bool is_array); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java index 0523777d4ad..2f49b5fe09c 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); + } } 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..a37249a0e36 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArraysCopyOf.java @@ -0,0 +1,387 @@ +/* + * 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 driver ${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 driver ${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 driver ${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 driver ${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 driver ${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 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.*; + +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 { + 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"); + 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()); + instanceAndPrimitiveClasses.add("void"); + + 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 {} + + """, + // Define some classes, each containing primitive type fields. + 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(), + // Constructor + """ + + public #klass(\ + """, + concat(primitiveTypes, (type, _) -> scope(type.name() + " _" + type.name())), + ") {\n", + loop(primitiveTypes, (type, _) -> scope( + let("type", type.name()), + " this._#type = _#type;\n")), + // Initializer + """ + } + + static #klass init() { + return new #klass(\ + """, + concat(primitiveTypes, (type, _) -> scope(type.con())), + ");\n", + """ + } + } + """)).toList(), + """ + + 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)), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + try { + #test(oArr, 1); + Asserts.fail("Should throw"); + } catch (NullPointerException e) { + // expected + } + } + """)), + // 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)), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + try { + #test(oArr, 1); + Asserts.fail("Should throw"); + } catch (ArrayStoreException e) { + // expected + } + } + """)), + // 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("length", RANDOM_LENGTH.next()), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + int length = #length; + #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]); + } + } + """)), + // 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("length", RANDOM_LENGTH.next()), + generateTestMethodString(), + """ + + @Run(test = "#test") + public static void run#i() { + int length = #length; + """, + " #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