From 9e18e41d3d7e79d082e66097c5e7bed3bd043d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Mon, 10 Mar 2025 16:21:42 +0100 Subject: [PATCH 1/7] Compute basic type from the store's address type, circumventing the innacurate MemNode::memory_type() --- src/hotspot/share/opto/loopTransform.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 436d8758df3ab..2d64311f24518 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -3848,7 +3848,9 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { #endif } - BasicType t = store->as_Mem()->memory_type(); + BasicType t = store->adr_type()->isa_aryptr()->elem()->array_element_basic_type(); + BasicType memory_type = store->as_Mem()->memory_type(); + assert((memory_type == T_CHAR && t == T_SHORT) || memory_type == t, "unexpected type mismatch"); bool aligned = false; if (offset != nullptr && head->init_trip()->is_Con()) { int element_size = type2aelembytes(t); From fd8d3818931890b553215f73b36b5ccc913dfc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Tue, 11 Mar 2025 11:48:00 +0100 Subject: [PATCH 2/7] Refine assertion to deal with aliasing byte/Boolean types --- src/hotspot/share/opto/loopTransform.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 2d64311f24518..5ef3a6c4d52e5 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -3850,7 +3850,10 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { BasicType t = store->adr_type()->isa_aryptr()->elem()->array_element_basic_type(); BasicType memory_type = store->as_Mem()->memory_type(); - assert((memory_type == T_CHAR && t == T_SHORT) || memory_type == t, "unexpected type mismatch"); + assert((memory_type == T_CHAR && t == T_SHORT) || + (memory_type == T_BYTE && t == T_BOOLEAN) || + memory_type == t, + "unexpected type mismatch: t == %u vs. memory_type == %u", t, memory_type); bool aligned = false; if (offset != nullptr && head->init_trip()->is_Con()) { int element_size = type2aelembytes(t); From 54de1cd8e4e468eaeeb568137820cdc5471bc1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Tue, 11 Mar 2025 18:34:45 +0100 Subject: [PATCH 3/7] Add test --- .../loopopts/TestArrayFillAntiDependence.java | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java diff --git a/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java new file mode 100644 index 0000000000000..189b1ac275ffa --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2025, 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. + */ + +package compiler.loopopts; + +import jdk.test.lib.Asserts; + +/** + * @test + * @bug 8351468 + * @summary Test that loads anti-dependent on array fill intrinsics are + * scheduled correctly, for different load and array fill types. + * See detailed comments in testShort() below. + * @requires vm.compiler2.enabled + * @library /test/lib + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:CompileOnly=compiler.loopopts.TestArrayFillAntiDependence::test* + * -XX:CompileCommand=quiet -XX:LoopUnrollLimit=0 -XX:+OptimizeFill + * compiler.loopopts.TestArrayFillAntiDependence + * @run main/othervm compiler.loopopts.TestArrayFillAntiDependence + */ + +public class TestArrayFillAntiDependence { + + static int N = 10; + static short M = 4; + static boolean BOOLEAN_VAL = true; + static char CHAR_VAL = 42; + static float FLOAT_VAL = 42.0f; + static double DOUBLE_VAL = 42.0; + static byte BYTE_VAL = 42; + static short SHORT_VAL = 42; + static int INT_VAL = 42; + static long LONG_VAL = 42; + + static boolean testBoolean(int pos, int samePos) { + assert pos == samePos; + boolean total = false; + boolean[] array = new boolean[N]; + array[pos] = BOOLEAN_VAL; + for (int i = 0; i < M; i++) { + total |= array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = false; + } + } + return total; + } + + static char testChar(int pos, int samePos) { + assert pos == samePos; + char total = 0; + char[] array = new char[N]; + array[pos] = CHAR_VAL; + for (int i = 0; i < M; i++) { + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0; + } + } + return total; + } + + static float testFloat(int pos, int samePos) { + assert pos == samePos; + float total = 0.0f; + float[] array = new float[N]; + array[pos] = FLOAT_VAL; + for (int i = 0; i < M; i++) { + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0.0f; + } + } + return total; + } + + static double testDouble(int pos, int samePos) { + assert pos == samePos; + double total = 0.0; + double[] array = new double[N]; + array[pos] = DOUBLE_VAL; + for (int i = 0; i < M; i++) { + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0.0; + } + } + return total; + } + + static byte testByte(int pos, int samePos) { + assert pos == samePos; + byte total = 0; + byte[] array = new byte[N]; + array[pos] = BYTE_VAL; + for (int i = 0; i < M; i++) { + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0; + } + } + return total; + } + + static short testShort(int pos, int samePos) { + // This pre-condition is necessary to reproduce the miscompilation, but + // should not be exploited by C2 for optimization. + assert pos == samePos; + short total = 0; + short[] array = new short[N]; + array[pos] = SHORT_VAL; + for (int i = 0; i < M; i++) { + // This load is wrongly scheduled after the loop below, which is + // transformed into a call to arrayof_jshort_fill and clears the + // entire array. As a consequence, the function returns 0 instead of + // the expected SHORT_VAL. + // The load is wrongly allowed to be moved beyond the loop + // (arrayof_jshort_fill call) because their anti-dependence is + // missed. This is because the call operates on a different memory + // slice (char[] instead of the expected short[]). + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0; + } + } + return total; + } + + static int testInt(int pos, int samePos) { + assert pos == samePos; + int total = 0; + int[] array = new int[N]; + array[pos] = INT_VAL; + for (int i = 0; i < M; i++) { + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0; + } + } + return total; + } + + static long testLong(int pos, int samePos) { + assert pos == samePos; + long total = 0; + long[] array = new long[N]; + array[pos] = LONG_VAL; + for (int i = 0; i < M; i++) { + total += array[samePos]; + for (int t = 0; t < array.length; t++) { + array[t] = 0; + } + } + return total; + } + + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + boolean result = testBoolean(0, 0); + Asserts.assertEquals(BOOLEAN_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + char result = testChar(0, 0); + Asserts.assertEquals(CHAR_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + float result = testFloat(0, 0); + Asserts.assertEquals(FLOAT_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + double result = testDouble(0, 0); + Asserts.assertEquals(DOUBLE_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + byte result = testByte(0, 0); + Asserts.assertEquals(BYTE_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + short result = testShort(0, 0); + Asserts.assertEquals(SHORT_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + int result = testInt(0, 0); + Asserts.assertEquals(INT_VAL, result); + } + for (int i = 0; i < 10_000; i++) { + long result = testLong(0, 0); + Asserts.assertEquals(LONG_VAL, result); + } + } +} From 47e478a613b1b4fc1554526d8e18e98e0e8d1c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Tue, 11 Mar 2025 18:35:48 +0100 Subject: [PATCH 4/7] Remove temporary assertion --- src/hotspot/share/opto/loopTransform.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 5ef3a6c4d52e5..0ad0ec13ec8fe 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -3849,11 +3849,6 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { } BasicType t = store->adr_type()->isa_aryptr()->elem()->array_element_basic_type(); - BasicType memory_type = store->as_Mem()->memory_type(); - assert((memory_type == T_CHAR && t == T_SHORT) || - (memory_type == T_BYTE && t == T_BOOLEAN) || - memory_type == t, - "unexpected type mismatch: t == %u vs. memory_type == %u", t, memory_type); bool aligned = false; if (offset != nullptr && head->init_trip()->is_Con()) { int element_size = type2aelembytes(t); From 38c9b475c32e0cb342e40f9510063f05f4977782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Fri, 14 Mar 2025 16:32:31 +0100 Subject: [PATCH 5/7] Explicitly disable optimization for mismatching stores; add positive and negative tests --- src/hotspot/share/opto/loopTransform.cpp | 9 +- .../loopopts/TestArrayFillIntrinsic.java | 254 ++++++++++++++++++ 2 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 0ad0ec13ec8fe..564ad1d97fbe4 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -3574,6 +3574,10 @@ bool PhaseIdealLoop::match_fill_loop(IdealLoopTree* lpt, Node*& store, Node*& st return false; } + if (msg == nullptr && store->as_Mem()->is_mismatched_access()) { + msg = "mismatched store"; + } + if (msg == nullptr && head->stride_con() != 1) { // could handle negative strides too if (head->stride_con() < 0) { @@ -3596,7 +3600,8 @@ bool PhaseIdealLoop::match_fill_loop(IdealLoopTree* lpt, Node*& store, Node*& st } // Make sure there is an appropriate fill routine - BasicType t = store->as_Mem()->memory_type(); + BasicType t = msg == nullptr ? + store->adr_type()->isa_aryptr()->elem()->array_element_basic_type() : T_VOID; const char* fill_name; if (msg == nullptr && StubRoutines::select_fill_function(t, false, fill_name) == nullptr) { @@ -3642,7 +3647,7 @@ bool PhaseIdealLoop::match_fill_loop(IdealLoopTree* lpt, Node*& store, Node*& st if (value != head->phi()) { msg = "unhandled shift in address"; } else { - if (type2aelembytes(store->as_Mem()->memory_type(), true) != (1 << n->in(2)->get_int())) { + if (type2aelembytes(t, true) != (1 << n->in(2)->get_int())) { msg = "scale doesn't match"; } else { found_index = true; diff --git a/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java new file mode 100644 index 0000000000000..b8f9fcf30a9f5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2025, 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. + */ + +package compiler.loopopts; + +import java.lang.foreign.*; +import java.util.*; +import jdk.test.lib.Asserts; +import compiler.lib.ir_framework.*; + +/** + * @test + * @bug 8351468 + * @summary Test replacement of array-filling loops with intrinsic calls in the + * face of matching and mismatching stores. + * @library /test/lib / + * @run driver compiler.loopopts.TestArrayFillIntrinsic + */ + +public class TestArrayFillIntrinsic { + + public static void main(String[] args) { + // Disabling unrolling is necessary for test robustness, otherwise the + // compiler might decide to unroll the array-filling loop instead of + // replacing it with an intrinsic call even if OptimizeFill is enabled. + TestFramework.runWithFlags("-XX:LoopUnrollLimit=0", "-XX:+OptimizeFill"); + } + + @Test + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.CALL_OF, "(arrayof_)?jbyte_fill", "1"}) + static void testFillBooleanArray(boolean[] array, boolean val) { + for (int i = 0; i < array.length; i++) { + array[i] = val; + } + } + + @Test + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.CALL_OF, "(arrayof_)?jbyte_fill", "1"}) + static void testFillByteArray(byte[] array, byte val) { + for (int i = 0; i < array.length; i++) { + array[i] = val; + } + } + + @Test + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.CALL_OF, "(arrayof_)?jshort_fill", "1"}) + static void testFillCharArray(char[] array, char val) { + for (int i = 0; i < array.length; i++) { + array[i] = val; + } + } + + @Test + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.CALL_OF, "(arrayof_)?jshort_fill", "1"}) + static void testFillShortArray(short[] array, short val) { + for (int i = 0; i < array.length; i++) { + array[i] = val; + } + } + + @Test + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.CALL_OF, "(arrayof_)?jint_fill", "1"}) + static void testFillIntArray(int[] array, int val) { + for (int i = 0; i < array.length; i++) { + array[i] = val; + } + } + + @Test + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.CALL_OF, "(arrayof_)?jint_fill", "1"}) + static void testFillFloatArray(float[] array, float val) { + for (int i = 0; i < array.length; i++) { + array[i] = val; + } + } + + @Run(test = {"testFillByteArray", + "testFillBooleanArray", + "testFillCharArray", + "testFillShortArray", + "testFillIntArray", + "testFillFloatArray"}) + public void runPositiveTests() { + Random r = RunInfo.getRandom(); + int N = r.ints(1, 1024).findFirst().getAsInt(); + { + boolean[] array = new boolean[N]; + boolean val = r.nextBoolean(); + testFillBooleanArray(array, val); + for (int i = 0; i < array.length; i++) { + Asserts.assertEquals(val, array[i]); + } + } + { + byte[] array = new byte[N]; + byte val = (byte)r.nextInt(); + testFillByteArray(array, val); + for (int i = 0; i < array.length; i++) { + Asserts.assertEquals(val, array[i]); + } + } + { + char[] array = new char[N]; + char val = (char)r.nextInt(); + testFillCharArray(array, val); + for (int i = 0; i < array.length; i++) { + Asserts.assertEquals(val, array[i]); + } + } + { + short[] array = new short[N]; + short val = (short)r.nextInt(); + testFillShortArray(array, val); + for (int i = 0; i < array.length; i++) { + Asserts.assertEquals(val, array[i]); + } + } + { + int[] array = new int[N]; + int val = r.nextInt(); + testFillIntArray(array, val); + for (int i = 0; i < array.length; i++) { + Asserts.assertEquals(val, array[i]); + } + } + { + float[] array = new float[N]; + float val = r.nextFloat(); + testFillFloatArray(array, val); + for (int i = 0; i < array.length; i++) { + Asserts.assertEquals(val, array[i]); + } + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillShortArrayWithByte(MemorySegment array, int n, byte val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_BYTE, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillIntArrayWithByte(MemorySegment array, int n, byte val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_BYTE, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillIntArrayWithShort(MemorySegment array, int n, short val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_SHORT, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillByteArrayWithBoolean(MemorySegment array, int n, boolean val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_BOOLEAN, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillShortArrayWithChar(MemorySegment array, int n, char val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_CHAR, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillCharArrayWithShort(MemorySegment array, int n, short val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_SHORT, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillIntArrayWithFloat(MemorySegment array, int n, float val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_FLOAT, i, val); + } + } + + @Test + @IR(failOn = {IRNode.CALL_OF, "(arrayof_)?.*_fill"}) + static void testFillFloatArrayWithInt(MemorySegment array, int n, int val) { + for (int i = 0; i < n; i++) { + array.setAtIndex(ValueLayout.JAVA_INT, i, val); + } + } + + @Run(test = {"testFillShortArrayWithByte", + "testFillIntArrayWithByte", + "testFillIntArrayWithShort", + "testFillByteArrayWithBoolean", + "testFillShortArrayWithChar", + "testFillCharArrayWithShort", + "testFillIntArrayWithFloat", + "testFillFloatArrayWithInt"}) + public void runTypeMismatchTests() { + Random r = RunInfo.getRandom(); + int N = r.ints(1, 1024).findFirst().getAsInt(); + testFillShortArrayWithByte(MemorySegment.ofArray(new short[N]), N, + (byte)r.nextInt()); + testFillIntArrayWithByte(MemorySegment.ofArray(new int[N]), N, + (byte)r.nextInt()); + testFillIntArrayWithShort(MemorySegment.ofArray(new int[N]), N, + (short)r.nextInt()); + testFillByteArrayWithBoolean(MemorySegment.ofArray(new byte[N]), N, + r.nextBoolean()); + testFillShortArrayWithChar(MemorySegment.ofArray(new short[N]), N, + (char)r.nextInt()); + testFillCharArrayWithShort(MemorySegment.ofArray(new char[N]), N, + (short)r.nextInt()); + testFillIntArrayWithFloat(MemorySegment.ofArray(new int[N]), N, + r.nextFloat()); + testFillFloatArrayWithInt(MemorySegment.ofArray(new float[N]), N, + r.nextInt()); + } +} From b59d2eb28baaf0bb369a32ea70da5a40fae6e089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Fri, 21 Mar 2025 15:25:37 +0100 Subject: [PATCH 6/7] Relax test cases --- .../loopopts/TestArrayFillAntiDependence.java | 4 ++-- .../compiler/loopopts/TestArrayFillIntrinsic.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java index 189b1ac275ffa..bde15c03fd4bf 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillAntiDependence.java @@ -31,9 +31,9 @@ * @summary Test that loads anti-dependent on array fill intrinsics are * scheduled correctly, for different load and array fill types. * See detailed comments in testShort() below. - * @requires vm.compiler2.enabled * @library /test/lib - * @run main/othervm -Xbatch -XX:-TieredCompilation + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions + * -Xbatch -XX:-TieredCompilation * -XX:CompileOnly=compiler.loopopts.TestArrayFillAntiDependence::test* * -XX:CompileCommand=quiet -XX:LoopUnrollLimit=0 -XX:+OptimizeFill * compiler.loopopts.TestArrayFillAntiDependence diff --git a/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java index b8f9fcf30a9f5..a24e791f98dd6 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestArrayFillIntrinsic.java @@ -43,11 +43,15 @@ public static void main(String[] args) { // Disabling unrolling is necessary for test robustness, otherwise the // compiler might decide to unroll the array-filling loop instead of // replacing it with an intrinsic call even if OptimizeFill is enabled. - TestFramework.runWithFlags("-XX:LoopUnrollLimit=0", "-XX:+OptimizeFill"); + TestFramework framework = new TestFramework(); + framework.addScenarios(new Scenario(0), + new Scenario(1, "-XX:LoopUnrollLimit=0", "-XX:+OptimizeFill")); + framework.start(); } @Test @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + applyIfAnd = {"LoopUnrollLimit", "0", "OptimizeFill", "true"}, counts = {IRNode.CALL_OF, "(arrayof_)?jbyte_fill", "1"}) static void testFillBooleanArray(boolean[] array, boolean val) { for (int i = 0; i < array.length; i++) { @@ -57,6 +61,7 @@ static void testFillBooleanArray(boolean[] array, boolean val) { @Test @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + applyIfAnd = {"LoopUnrollLimit", "0", "OptimizeFill", "true"}, counts = {IRNode.CALL_OF, "(arrayof_)?jbyte_fill", "1"}) static void testFillByteArray(byte[] array, byte val) { for (int i = 0; i < array.length; i++) { @@ -66,6 +71,7 @@ static void testFillByteArray(byte[] array, byte val) { @Test @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + applyIfAnd = {"LoopUnrollLimit", "0", "OptimizeFill", "true"}, counts = {IRNode.CALL_OF, "(arrayof_)?jshort_fill", "1"}) static void testFillCharArray(char[] array, char val) { for (int i = 0; i < array.length; i++) { @@ -75,6 +81,7 @@ static void testFillCharArray(char[] array, char val) { @Test @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + applyIfAnd = {"LoopUnrollLimit", "0", "OptimizeFill", "true"}, counts = {IRNode.CALL_OF, "(arrayof_)?jshort_fill", "1"}) static void testFillShortArray(short[] array, short val) { for (int i = 0; i < array.length; i++) { @@ -84,6 +91,7 @@ static void testFillShortArray(short[] array, short val) { @Test @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + applyIfAnd = {"LoopUnrollLimit", "0", "OptimizeFill", "true"}, counts = {IRNode.CALL_OF, "(arrayof_)?jint_fill", "1"}) static void testFillIntArray(int[] array, int val) { for (int i = 0; i < array.length; i++) { @@ -93,6 +101,7 @@ static void testFillIntArray(int[] array, int val) { @Test @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + applyIfAnd = {"LoopUnrollLimit", "0", "OptimizeFill", "true"}, counts = {IRNode.CALL_OF, "(arrayof_)?jint_fill", "1"}) static void testFillFloatArray(float[] array, float val) { for (int i = 0; i < array.length; i++) { From c0b3cf96bda4b2a885203878753be285e1a9df15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Mon, 24 Mar 2025 11:03:06 +0100 Subject: [PATCH 7/7] Add comment about mismatched store handling --- src/hotspot/share/opto/loopTransform.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 564ad1d97fbe4..6bbc432d96c06 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -3575,6 +3575,12 @@ bool PhaseIdealLoop::match_fill_loop(IdealLoopTree* lpt, Node*& store, Node*& st } if (msg == nullptr && store->as_Mem()->is_mismatched_access()) { + // This optimization does not currently support mismatched stores, where the + // type of the value to be stored differs from the element type of the + // destination array. Such patterns arise for example from memory segment + // initialization. This limitation could be overcome by extending this + // function's address matching logic and ensuring that the fill intrinsic + // implementations support mismatched array filling. msg = "mismatched store"; }