jdk/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java
Roland Westrelin f155661151 8342692: C2: long counted loop/long range checks: don't create loop-nest for short running loops
Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org>
Co-authored-by: Christian Hagedorn <chagedorn@openjdk.org>
Reviewed-by: chagedorn, thartmann
2025-07-22 08:35:36 +00:00

580 lines
22 KiB
Java

/*
* Copyright (c) 2025, Red Hat, Inc. 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.longcountedloops;
import compiler.lib.ir_framework.*;
import compiler.whitebox.CompilerWhiteBoxTest;
import jdk.test.whitebox.WhiteBox;
import java.util.Objects;
/*
* @test
* @bug 8342692
* @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops
* @library /test/lib /
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI compiler.longcountedloops.TestShortRunningLongCountedLoop
*/
public class TestShortRunningLongCountedLoop {
private static volatile int volatileField;
private final static WhiteBox wb = WhiteBox.getWhiteBox();
public static void main(String[] args) {
// IR rules expect a single loop so disable unrolling
// IR rules expect strip mined loop to be enabled
// testIntLoopUnknownBoundsShortUnswitchedLoop and testLongLoopUnknownBoundsShortUnswitchedLoop need -XX:-UseProfiledLoopPredicate
TestFramework.runWithFlags("-XX:LoopMaxUnroll=0", "-XX:LoopStripMiningIter=1000", "-XX:+UseCountedLoopSafepoints", "-XX:-UseProfiledLoopPredicate");
}
// Check IR only has a counted loop when bounds are known and loop run for a short time
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopConstantBoundsShortLoop1() {
int j = 0;
for (long i = 0; i < 100; i++) {
volatileField = 42;
j++;
}
return j;
}
@Check(test = "testLongLoopConstantBoundsShortLoop1")
public static void checkTestLongLoopConstantBoundsShortLoop1(int res) {
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Same with stride > 1
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopConstantBoundsShortLoop2() {
int j = 0;
for (long i = 0; i < 2000; i += 20) {
volatileField = 42;
j++;
}
return j;
}
@Check(test = "testLongLoopConstantBoundsShortLoop2")
public static void checkTestLongLoopConstantBoundsShortLoop2(int res) {
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Same with loop going downward
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopConstantBoundsShortLoop3() {
int j = 0;
for (long i = 99; i >= 0; i--) {
volatileField = 42;
j++;
}
return j;
}
@Check(test = "testLongLoopConstantBoundsShortLoop3")
public static void checkTestLongLoopConstantBoundsShortLoop3(int res) {
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Same with loop going downward and stride > 1
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopConstantBoundsShortLoop4() {
int j = 0;
for (long i = 1999; i >= 0; i-=20) {
volatileField = 42;
j++;
}
return j;
}
@Check(test = "testLongLoopConstantBoundsShortLoop4")
public static void checkTestLongLoopConstantBoundsShortLoop4(int res) {
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Check IR only has a counted loop when bounds are known but not exact and loop run for a short time
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopConstantBoundsShortLoop5(int start, int stop) {
start= Integer.max(start, 0);
stop= Integer.min(stop, 999);
int j = 0;
for (long i = start; i < stop; i++) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopConstantBoundsShortLoop5")
public static void testLongLoopConstantBoundsShortLoop5_runner() {
int res = testLongLoopConstantBoundsShortLoop5(0, 100);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Check that loop nest is created when bounds are known and loop is not short run
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1"})
@IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP, IRNode.OUTER_STRIP_MINED_LOOP })
public static int testLongLoopConstantBoundsLongLoop1() {
final long stride = Integer.MAX_VALUE / 1000;
int j = 0;
for (long i = 0; i < stride * 1001; i += stride) {
volatileField = 42;
j++;
}
return j;
}
@Check(test = "testLongLoopConstantBoundsLongLoop1")
public static void checkTestLongLoopConstantBoundsLongLoop1(int res) {
if (res != 1001) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Same with negative stride
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1"})
@IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP, IRNode.OUTER_STRIP_MINED_LOOP })
public static int testLongLoopConstantBoundsLongLoop2() {
final long stride = Integer.MAX_VALUE / 1000;
int j = 0;
for (long i = stride * 1000; i >= 0; i -= stride) {
volatileField = 42;
j++;
}
return j;
}
@Check(test = "testLongLoopConstantBoundsLongLoop2")
public static void checkTestLongLoopConstantBoundsLongLoop2(int res) {
if (res != 1001) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Check IR only has a counted loop when bounds are unknown but profile reports a short running loop
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP })
public static int testLongLoopUnknownBoundsShortLoop(long start, long stop) {
int j = 0;
for (long i = start; i < stop; i++) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsShortLoop")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortLoop_runner() {
int res = testLongLoopUnknownBoundsShortLoop(0, 100);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// same with stride > 1
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP })
public static int testLongLoopUnknownBoundsShortLoop2(long start, long stop) {
int j = 0;
for (long i = start; i < stop; i+=20) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsShortLoop2")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortLoop2_runner() {
int res = testLongLoopUnknownBoundsShortLoop2(0, 2000);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// same with negative stride
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP })
public static int testLongLoopUnknownBoundsShortLoop3(long start, long stop) {
int j = 0;
for (long i = start; i >= stop; i--) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsShortLoop3")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortLoop3_runner() {
int res = testLongLoopUnknownBoundsShortLoop3(99, 0);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// same with negative stride > 1
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP })
public static int testLongLoopUnknownBoundsShortLoop4(long start, long stop) {
int j = 0;
for (long i = start; i >= stop; i -= 20) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsShortLoop4")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortLoop4_runner() {
int res = testLongLoopUnknownBoundsShortLoop4(1999, 0);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Check that loop nest is created when bounds are not known but profile reports loop is not short run
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1", IRNode.LOOP, "1"})
@IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopUnknownBoundsLongLoop1(long start, long stop, long range) {
int j = 0;
for (long i = start; i < stop; i++) {
volatileField = 42;
Objects.checkIndex(i * (1024 * 1024), range); // max number of iteration of inner loop is roughly Integer.MAX_VALUE / 1024 / 1024
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsLongLoop1")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsLongLoop1_runner() {
int res = testLongLoopUnknownBoundsLongLoop1(0, 3000, Long.MAX_VALUE);
if (res != 3000) {
throw new RuntimeException("incorrect result: " + res);
}
}
// same with negative stride
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1", IRNode.LOOP, "1"})
@IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopUnknownBoundsLongLoop2(long start, long stop, long range) {
int j = 0;
for (long i = start; i >= stop; i--) {
volatileField = 42;
Objects.checkIndex(i * (1024 * 1024), range); // max number of iteration of inner loop is roughly Integer.MAX_VALUE / 1024 / 1024
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsLongLoop2")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsLongLoop2_runner() {
int res = testLongLoopUnknownBoundsLongLoop2(2999, 0, Long.MAX_VALUE);
if (res != 3000) {
throw new RuntimeException("incorrect result: " + res);
}
}
// Check IR has a loop nest when bounds are unknown, profile reports a short running loop but trap is taken
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopUnknownBoundsShortLoopFailedSpeculation(long start, long stop, long range) {
int j = 0;
for (long i = start; i < stop; i++) {
volatileField = 42;
Objects.checkIndex(i * (1024 * 1024), range); // max number of iteration of inner loop is roughly Integer.MAX_VALUE / 1024 / 1024
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsShortLoopFailedSpeculation")
@Warmup(1)
public static void testLongLoopUnknownBoundsShortLoopFailedSpeculation_runner(RunInfo info) {
if (info.isWarmUp()) {
for (int i = 0; i < 10_0000; i++) {
int res = testLongLoopUnknownBoundsShortLoopFailedSpeculation(0, 100, Long.MAX_VALUE);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
wb.enqueueMethodForCompilation(info.getTest(), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);
if (!wb.isMethodCompiled(info.getTest())) {
throw new RuntimeException("Should be compiled now");
}
for (int i = 0; i < 10; i++) {
int res = testLongLoopUnknownBoundsShortLoopFailedSpeculation(0, 10_000, Long.MAX_VALUE);
if (res != 10_000) {
throw new RuntimeException("incorrect result: " + res);
}
}
} else {
int res = testLongLoopUnknownBoundsShortLoopFailedSpeculation(0, 100, Long.MAX_VALUE);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
}
// Check IR has a loop nest when bounds are known, is short running loop but trap was taken
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static int testLongLoopKnownBoundsShortLoopFailedSpeculation() {
return testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(0, 100);
}
@ForceInline
private static int testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(long start, long stop) {
int j = 0;
for (long i = start; i < stop; i++) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopKnownBoundsShortLoopFailedSpeculation")
@Warmup(1)
public static void testLongLoopKnownBoundsShortLoopFailedSpeculation_runner(RunInfo info) {
if (info.isWarmUp()) {
for (int i = 0; i < 10_0000; i++) {
int res = testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(0, 100);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
for (int i = 0; i < 10; i++) {
int res = testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(0, 10_000);
if (res != 10_000) {
throw new RuntimeException("incorrect result: " + res);
}
}
for (int i = 0; i < 10_0000; i++) {
int res = testLongLoopKnownBoundsShortLoopFailedSpeculation();
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
} else {
int res = testLongLoopKnownBoundsShortLoopFailedSpeculation();
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
}
// Check range check can be eliminated by predication
@Test
@IR(counts = { IRNode.PREDICATE_TRAP, "1" })
@IR(failOn = { IRNode.COUNTED_LOOP, IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static void testLongLoopConstantBoundsPredication(long range) {
for (long i = 0; i < 100; i++) {
Objects.checkIndex(i, range);
}
}
@Run(test = "testLongLoopConstantBoundsPredication")
public static void testLongLoopConstantBoundsPredication_runner() {
testLongLoopConstantBoundsPredication(100);
}
@Test
@IR(counts = { IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1" })
@IR(failOn = { IRNode.COUNTED_LOOP, IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP })
public static void testLongLoopUnknownBoundsShortLoopPredication(long start, long stop, long range) {
for (long i = start; i < stop; i++) {
Objects.checkIndex(i, range);
}
}
@Run(test = "testLongLoopUnknownBoundsShortLoopPredication")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortLoopPredication_runner() {
testLongLoopUnknownBoundsShortLoopPredication(0, 100, 100);
}
// If scale too large, transformation can't happen
static final long veryLargeScale = Integer.MAX_VALUE / 99;
@Test
@IR(counts = { IRNode.LOOP, "1", IRNode.PREDICATE_TRAP, "2"})
@IR(failOn = { IRNode.COUNTED_LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static void testLongLoopConstantBoundsLargeScale(long range) {
for (long i = 0; i < 100; i++) {
Objects.checkIndex(veryLargeScale * i, range);
}
}
@Run(test = "testLongLoopConstantBoundsLargeScale")
public static void testLongLoopConstantBoundsLargeScale_runner() {
testLongLoopConstantBoundsLargeScale(veryLargeScale * 100);
}
@Test
@IR(counts = { IRNode.LOOP, "1", IRNode.PREDICATE_TRAP, "2"})
@IR(failOn = { IRNode.COUNTED_LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static void testLongLoopUnknownBoundsShortLoopLargeScale(long start, long stop, long range) {
for (long i = start; i < stop; i++) {
Objects.checkIndex(veryLargeScale * i, range);
}
}
@Run(test = "testLongLoopUnknownBoundsShortLoopLargeScale")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortLoopLargeScale_runner() {
testLongLoopUnknownBoundsShortLoopLargeScale(0, 100, veryLargeScale * 100);
}
// Check IR only has a counted loop when bounds are known and loop run for a short time (int loop case)
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.PREDICATE_TRAP, "1" })
@IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP })
public static void testIntLoopConstantBoundsShortLoop1(long range) {
for (int i = 0; i < 100; i++) {
Objects.checkIndex(i, range);
volatileField = 42;
}
}
@Run(test = "testIntLoopConstantBoundsShortLoop1")
public static void testIntLoopConstantBoundsShortLoop1_runner() {
testIntLoopConstantBoundsShortLoop1(100);
}
// Check IR only has a counted loop when bounds are unknown but profile reports a short running loop (int loop case)
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP })
public static void testIntLoopUnknownBoundsShortLoop(int start, int stop, long range) {
for (int i = start; i < stop; i++) {
Objects.checkIndex(i, range);
volatileField = 42;
}
}
@Run(test = "testIntLoopUnknownBoundsShortLoop")
@Warmup(10_000)
public static void testIntLoopUnknownBoundsShortLoop_runner() {
testIntLoopUnknownBoundsShortLoop(0, 100, 100);
}
// Same with unswitched loop
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "2", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "2" })
@IR(failOn = { IRNode.LOOP })
public static void testIntLoopUnknownBoundsShortUnswitchedLoop(int start, int stop, long range, boolean flag) {
for (int i = start; i < stop; i++) {
if (flag) {
Objects.checkIndex(i, range);
volatileField = 42;
} else {
Objects.checkIndex(i, range);
volatileField = 42;
}
}
}
@Run(test = "testIntLoopUnknownBoundsShortUnswitchedLoop")
@Warmup(10_000)
public static void testIntLoopUnknownBoundsShortUnswitchedLoop_runner() {
testIntLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, true);
testIntLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, false);
}
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "2", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "2" })
@IR(failOn = { IRNode.LOOP })
public static void testLongLoopUnknownBoundsShortUnswitchedLoop(long start, long stop, long range, boolean flag) {
for (long i = start; i < stop; i++) {
if (flag) {
Objects.checkIndex(i, range);
volatileField = 42;
} else {
Objects.checkIndex(i, range);
volatileField = 42;
}
}
}
@Run(test = "testLongLoopUnknownBoundsShortUnswitchedLoop")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsShortUnswitchedLoop_runner() {
testLongLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, true);
testLongLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, false);
}
@Test
@IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" })
@IR(failOn = { IRNode.LOOP })
public static int testLongLoopUnknownBoundsAddLimitShortLoop(int stop1, long stop2) {
int j = 0;
for (long i = 0; i < stop1 + stop2; i++) {
volatileField = 42;
j++;
}
return j;
}
@Run(test = "testLongLoopUnknownBoundsAddLimitShortLoop")
@Warmup(10_000)
public static void testLongLoopUnknownBoundsAddLimitShortLoop_runner() {
int res = testLongLoopUnknownBoundsAddLimitShortLoop(100, 0);
if (res != 100) {
throw new RuntimeException("incorrect result: " + res);
}
}
}