mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-02 07:10:23 +00:00
8386597: C2: TestTruncationWrapFuzzer.java for CountedLoop detection of subword truncated iv
Reviewed-by: mhaessig, qamai
This commit is contained in:
parent
c60dc06d51
commit
ad075c96bf
@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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 8386597 8385855 8386482 8386591 8386830
|
||||
* @summary Fuzz patterns for CountedLoopConverter::has_truncation_wrap
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @library /test/lib /
|
||||
* @compile ../lib/ir_framework/TestFramework.java
|
||||
* @compile ../lib/generators/Generators.java
|
||||
* @run driver ${test.main.class}
|
||||
*/
|
||||
|
||||
package compiler.loopopts;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.generators.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import static compiler.lib.template_framework.Template.$;
|
||||
|
||||
import compiler.lib.template_framework.library.TestFrameworkClass;
|
||||
|
||||
/**
|
||||
* For more basic examples, see:
|
||||
* - TestHasTruncationWrap.java
|
||||
* - TestTruncationWrapEmptyType.java
|
||||
* - TestTruncationWrapPhiTypeUnion.java
|
||||
* - TestTruncationWrapBadCharWrap.java
|
||||
*
|
||||
* So far, this test does not have IR verification, only result verification.
|
||||
*
|
||||
* This test generates a wide range of patterns, and will require a lot of
|
||||
* runs to find a specific code shape.
|
||||
*
|
||||
* Features:
|
||||
* - Truncation patterns, see TRUNCATIONS and randomIVMutation.
|
||||
* - Stride: positive, negative, small and large, see ivMutationWithRandomStride.
|
||||
* - Reference (not compiled) vs test (compiled), and result verification.
|
||||
* - Loop Shapes: for, while, do-while, see LOOP_SHAPES.
|
||||
* - Exit checks: random Comparison, see Comparator and Comparison (signed and unsigned).
|
||||
* - For endless loops / loops that would take too long: early exit via opaqueCheck,
|
||||
* Note: it is verified that this does not hinder optimization, see:
|
||||
* TestHasTruncationWrap.java -> testIRShort7.
|
||||
* - Interesting loop bounds: init/limit
|
||||
* - constant
|
||||
* - variable, sampled (see getInputTemplate), and modified (no-op, truncate, clamp).
|
||||
* - Extra check dominating the loop: compare against constant of limit.
|
||||
* Note: has_truncation_wrap can use such checks to constrain the entry type.
|
||||
* Note2: We've had bugs around this, confusing CmpI/CmpU, see JDK-8385855.
|
||||
*/
|
||||
public class TestTruncationWrapFuzzer {
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
private static final RestrictableGenerator<Integer> INT_GEN = Generators.G.ints();
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Create a new CompileFramework instance.
|
||||
CompileFramework comp = new CompileFramework();
|
||||
|
||||
long t0 = System.nanoTime();
|
||||
// Add a java source file.
|
||||
comp.addJavaSourceCode("compiler.loopopts.templated.Generated", generate(comp));
|
||||
|
||||
long t1 = System.nanoTime();
|
||||
// Compile the source file.
|
||||
comp.compile();
|
||||
|
||||
long t2 = System.nanoTime();
|
||||
|
||||
// Run the tests without any additional VM flags.
|
||||
comp.invoke("compiler.loopopts.templated.Generated", "main", new Object[] {new String[] {}});
|
||||
long t3 = System.nanoTime();
|
||||
|
||||
System.out.println("Code Generation: " + (t1-t0) * 1e-9f);
|
||||
System.out.println("Code Compilation: " + (t2-t1) * 1e-9f);
|
||||
System.out.println("Running Tests: " + (t3-t2) * 1e-9f);
|
||||
}
|
||||
|
||||
public static String generate(CompileFramework comp) {
|
||||
// Create a list to collect all tests.
|
||||
List<TemplateToken> testTemplateTokens = new ArrayList<>();
|
||||
|
||||
// Some utilities, to help us get an additional exit, in case the
|
||||
// generated loops spin too long, or are infinite loops.
|
||||
Template.ZeroArgs utilsTemplate = Template.make(() -> scope(
|
||||
"""
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
|
||||
public static int opaqueCounter;
|
||||
public static int opaqueCounterMax;
|
||||
|
||||
@DontInline
|
||||
public static void opaqueReset() {
|
||||
opaqueCounter = 0;
|
||||
}
|
||||
|
||||
@DontInline
|
||||
public static boolean opaqueCheck() {
|
||||
return (opaqueCounter++) > opaqueCounterMax;
|
||||
}
|
||||
|
||||
@DontInline
|
||||
public static int opaqueSum(int i, int j) {
|
||||
return i + j + 1;
|
||||
}
|
||||
"""
|
||||
));
|
||||
testTemplateTokens.add(utilsTemplate.asToken());
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
testTemplateTokens.add(generateTest(/* no warmup, like -Xcomp */ 0));
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
testTemplateTokens.add(generateTest(/* with warmup, slower */ 100));
|
||||
}
|
||||
|
||||
// Create the test class, which runs all testTemplateTokens.
|
||||
return TestFrameworkClass.render(
|
||||
// package and class name.
|
||||
"compiler.loopopts.templated", "Generated",
|
||||
// List of imports.
|
||||
Set.of("compiler.lib.generators.*",
|
||||
"java.util.Random",
|
||||
"jdk.test.lib.Utils"),
|
||||
// classpath, so the Test VM has access to the compiled class files.
|
||||
comp.getEscapedClassPathOfCompiledClasses(),
|
||||
// The list of tests.
|
||||
testTemplateTokens);
|
||||
}
|
||||
|
||||
// This is copied from TestFoldComparesFuzzer.java, and we should
|
||||
// refactor this out into the template framework library, in a
|
||||
// future RFE.
|
||||
enum Comparator {
|
||||
ULT(" < 0", false),
|
||||
ULE(" <= 0", false),
|
||||
UGT(" > 0", false),
|
||||
UGE(" >= 0", false),
|
||||
UEQ(" == 0", false),
|
||||
UNE(" != 0", false),
|
||||
LT(" < ", true),
|
||||
LE(" <= ", true),
|
||||
GT(" > ", true),
|
||||
GE(" >= ", true),
|
||||
EQ(" == ", true),
|
||||
NE(" != ", true);
|
||||
|
||||
private final String token;
|
||||
private final boolean signed;
|
||||
|
||||
Comparator(String token, boolean signed) {
|
||||
this.token = token;
|
||||
this.signed = signed;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return signed;
|
||||
}
|
||||
|
||||
public Comparator negate() {
|
||||
return switch(this) {
|
||||
case ULT -> UGE;
|
||||
case ULE -> UGT;
|
||||
case UGT -> ULE;
|
||||
case UGE -> ULT;
|
||||
case UEQ -> UNE;
|
||||
case UNE -> UEQ;
|
||||
case LT -> GE;
|
||||
case LE -> GT;
|
||||
case GT -> LE;
|
||||
case GE -> LT;
|
||||
case EQ -> NE;
|
||||
case NE -> EQ;
|
||||
};
|
||||
}
|
||||
|
||||
public Comparator flip() {
|
||||
return switch(this) {
|
||||
case ULT -> UGT;
|
||||
case ULE -> UGE;
|
||||
case UGT -> ULT;
|
||||
case UGE -> ULE;
|
||||
case UEQ -> UEQ;
|
||||
case UNE -> UNE;
|
||||
case LT -> GT;
|
||||
case LE -> GE;
|
||||
case GT -> LT;
|
||||
case GE -> LE;
|
||||
case EQ -> EQ;
|
||||
case NE -> NE;
|
||||
};
|
||||
}
|
||||
|
||||
static Comparator random() {
|
||||
return values()[RANDOM.nextInt(values().length)];
|
||||
}
|
||||
}
|
||||
|
||||
record Comparison(String lhs, Comparator cmp, String rhs, boolean negated) {
|
||||
public Comparison(String lhs, Comparator cmp, String rhs) {
|
||||
this(lhs, cmp, rhs, false);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return cmp.isSigned()
|
||||
? ((negated ? "!" : "") + "(" + lhs + " "+ cmp.getToken() + " " + rhs + ")")
|
||||
: ((negated ? "!" : "") + "(Integer.compareUnsigned(" + lhs + ", " + rhs + ")" + cmp.getToken() + ")");
|
||||
}
|
||||
|
||||
// Keep the same semantics of the test, but change its form.
|
||||
Comparison permuteRandom() {
|
||||
return flipRandom().complementRandom();
|
||||
}
|
||||
|
||||
Comparison flipRandom() {
|
||||
return RANDOM.nextBoolean() ? this : new Comparison(rhs, cmp.flip(), lhs);
|
||||
}
|
||||
|
||||
Comparison complementRandom() {
|
||||
return RANDOM.nextBoolean() ? this : new Comparison(lhs, cmp.negate(), rhs, true);
|
||||
}
|
||||
}
|
||||
|
||||
interface TestMethodGenerator {
|
||||
Template.OneArg<String> getTestTemplate();
|
||||
|
||||
default Template.ZeroArgs getInputTemplate() {
|
||||
return Template.make(() -> scope(
|
||||
switch (RANDOM.nextInt(5)) {
|
||||
case 0 -> """
|
||||
RestrictableGenerator<Integer> gen = Generators.G.ints();
|
||||
int init = gen.next();
|
||||
int limit = gen.next();
|
||||
""";
|
||||
case 1 -> """
|
||||
int init = (byte)RANDOM.nextInt();
|
||||
int limit = (byte)RANDOM.nextInt();
|
||||
""";
|
||||
case 2 -> """
|
||||
int init = (short)RANDOM.nextInt();
|
||||
int limit = (short)RANDOM.nextInt();
|
||||
""";
|
||||
case 3 -> """
|
||||
int init = (char)RANDOM.nextInt();
|
||||
int limit = (char)RANDOM.nextInt();
|
||||
""";
|
||||
case 4 -> """
|
||||
int e0 = RANDOM.nextInt(32);
|
||||
int e1 = RANDOM.nextInt(32);
|
||||
int r0 = RANDOM.nextInt(32);
|
||||
int r1 = RANDOM.nextInt(32);
|
||||
int init = (1 << e0) + r0;
|
||||
int limit = (1 << e1) + r1;
|
||||
""";
|
||||
default -> throw new RuntimeException("not expected");
|
||||
}
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
private static record Truncation(String s0, String s1) {
|
||||
public String ivMutationWithRandomStride() {
|
||||
int stride = switch(RANDOM.nextInt(3)) {
|
||||
case 0 -> INT_GEN.next();
|
||||
case 1 -> RANDOM.nextInt(9) - 4;
|
||||
case 2 -> RANDOM.nextInt(129) - 64;
|
||||
default -> throw new RuntimeException("not expected");
|
||||
};
|
||||
|
||||
return "i = " + s0 + "i + " + stride + s1;
|
||||
}
|
||||
|
||||
public String truncate(String val) {
|
||||
return val + " = " + s0 + val + s1;
|
||||
}
|
||||
}
|
||||
|
||||
// Different patterns relevant for triggering truncation/wrap.
|
||||
private static final Truncation[] TRUNCATIONS = new Truncation[] {
|
||||
new Truncation("", ""),
|
||||
new Truncation("(byte)(", ")"),
|
||||
new Truncation("(short)(", ")"),
|
||||
new Truncation("(char)(", ")"),
|
||||
new Truncation("((", ") << 8) >> 8"),
|
||||
new Truncation("((", ") << 16) >> 16"),
|
||||
new Truncation("((", ") << 24) >> 24"),
|
||||
new Truncation("((", ") & 0x7f)"),
|
||||
new Truncation("((", ") & 0xff)"),
|
||||
new Truncation("((", ") & 0x7fff)"),
|
||||
new Truncation("((", ") & 0xffff)")
|
||||
};
|
||||
|
||||
private static Truncation randomTruncation() {
|
||||
return TRUNCATIONS[RANDOM.nextInt(TRUNCATIONS.length)];
|
||||
}
|
||||
|
||||
private static String randomIVMutation() {
|
||||
return randomTruncation().ivMutationWithRandomStride();
|
||||
}
|
||||
|
||||
private static String randomTruncation(String val) {
|
||||
return randomTruncation().truncate(val);
|
||||
}
|
||||
|
||||
private static final String[] LOOP_SHAPES = new String[] {
|
||||
"""
|
||||
// Loop Shape: For
|
||||
int i;
|
||||
for (i = init; #exitCheck; #ivMutation) {
|
||||
sum = opaqueSum(sum, #addValue);
|
||||
if (opaqueCheck()) { break; }
|
||||
}
|
||||
""",
|
||||
"""
|
||||
// Loop Shape: While:
|
||||
int i = init;
|
||||
while (#exitCheck) {
|
||||
sum = opaqueSum(sum, #addValue);
|
||||
if (opaqueCheck()) { break; }
|
||||
#ivMutation;
|
||||
}
|
||||
""",
|
||||
"""
|
||||
// Loop Shape: Do-While:
|
||||
int i = init;
|
||||
do {
|
||||
sum = opaqueSum(sum, #addValue);
|
||||
if (opaqueCheck()) { break; }
|
||||
#ivMutation;
|
||||
} while (#exitCheck);
|
||||
""",
|
||||
"""
|
||||
// Loop Shape: Do-While + pre-loop check.
|
||||
int i = init;
|
||||
if (!(#exitCheck)) { return sum; }
|
||||
do {
|
||||
sum = opaqueSum(sum, #addValue);
|
||||
if (opaqueCheck()) { break; }
|
||||
#ivMutation;
|
||||
} while (#exitCheck);
|
||||
"""
|
||||
};
|
||||
|
||||
private static String randomLoopShape() {
|
||||
return LOOP_SHAPES[RANDOM.nextInt(LOOP_SHAPES.length)];
|
||||
}
|
||||
|
||||
// Loop init/limit are constants.
|
||||
static class TestMethodGeneratorConst implements TestMethodGenerator {
|
||||
private final int init = INT_GEN.next();
|
||||
private final int limit = INT_GEN.next();
|
||||
|
||||
private final String ivMutation = randomIVMutation();
|
||||
private final String loopShape = randomLoopShape();
|
||||
private final String addValue = RANDOM.nextBoolean() ? "0" : "i";
|
||||
|
||||
private final Comparison exitCheck = new Comparison("i", Comparator.random(), "limit").permuteRandom();
|
||||
|
||||
private final Template.OneArg<String> testTemplate = Template.make("methodName", (String methodName) -> scope(
|
||||
let("init", init),
|
||||
let("limit", limit),
|
||||
let("ivMutation", ivMutation),
|
||||
let("exitCheck", exitCheck),
|
||||
let("addValue", addValue),
|
||||
"""
|
||||
static int #methodName(int unused0, int unused1) {
|
||||
opaqueReset();
|
||||
int init = #init;
|
||||
int limit = #limit;
|
||||
int sum = 0;
|
||||
""",
|
||||
loopShape,
|
||||
"""
|
||||
return sum + #addValue;
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
public Template.OneArg<String> getTestTemplate() { return testTemplate; }
|
||||
}
|
||||
|
||||
// Clamp randomly, but not always on both sides.
|
||||
private static String randomClamping(String value) {
|
||||
String clamp = value;
|
||||
if (RANDOM.nextBoolean()) {
|
||||
clamp = "Math.max(" + clamp + ", " + INT_GEN.next() + ")";
|
||||
}
|
||||
if (RANDOM.nextBoolean()) {
|
||||
clamp = "Math.min(" + clamp + ", " + INT_GEN.next() + ")";
|
||||
}
|
||||
return value + " = " + clamp;
|
||||
}
|
||||
|
||||
// We want to be able to modify the incoming init/limit.
|
||||
// - nothing
|
||||
// - truncate
|
||||
// - clamp with min/max, maybe even only one-sided
|
||||
private static String randomModifyValue(String value) {
|
||||
return switch(RANDOM.nextInt(3)) {
|
||||
case 0 -> "// Don't modify " + value + "\n";
|
||||
case 1 -> randomTruncation(value) + ";\n";
|
||||
case 2 -> randomClamping(value) + ";\n";
|
||||
default -> throw new RuntimeException("not expected");
|
||||
};
|
||||
}
|
||||
|
||||
private static String randomExtraCheck() {
|
||||
// We can constrain the init value with limit or a constant.
|
||||
String other = RANDOM.nextBoolean() ? "limit" : INT_GEN.next().toString();
|
||||
Comparison check = new Comparison("init", Comparator.random(), other).permuteRandom();
|
||||
return RANDOM.nextBoolean()
|
||||
? "// No extra check.\n"
|
||||
: "if (" + check + ") { return -1; }\n";
|
||||
}
|
||||
|
||||
// Loop init/limit are variables.
|
||||
static class TestMethodGeneratorVars implements TestMethodGenerator {
|
||||
private final String ivMutation = randomIVMutation();
|
||||
private final String loopShape = randomLoopShape();
|
||||
private final String addValue = RANDOM.nextBoolean() ? "0" : "i";
|
||||
private final String modifyInit = randomModifyValue("init");
|
||||
private final String modifyLimit = randomModifyValue("limit");
|
||||
private final String extraCheck = randomExtraCheck();
|
||||
|
||||
private final Comparison exitCheck = new Comparison("i", Comparator.random(), "limit").permuteRandom();
|
||||
|
||||
private final Template.OneArg<String> testTemplate = Template.make("methodName", (String methodName) -> scope(
|
||||
let("ivMutation", ivMutation),
|
||||
let("exitCheck", exitCheck),
|
||||
let("addValue", addValue),
|
||||
"""
|
||||
static int #methodName(int init, int limit) {
|
||||
opaqueReset();
|
||||
int sum = 0;
|
||||
""",
|
||||
modifyInit, // modify type of init
|
||||
modifyLimit, // modify type of limit
|
||||
extraCheck, // extra CmpI/CmpU dominating the loop, might constrain entry value.
|
||||
loopShape,
|
||||
"""
|
||||
return sum + #addValue;
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
public Template.OneArg<String> getTestTemplate() { return testTemplate; }
|
||||
}
|
||||
public static TemplateToken generateTest(int warmup) {
|
||||
TestMethodGenerator tg = switch(RANDOM.nextInt(2)) {
|
||||
case 0 -> new TestMethodGeneratorConst();
|
||||
case 1 -> new TestMethodGeneratorVars();
|
||||
default -> throw new RuntimeException("not expected");
|
||||
};
|
||||
Template.ZeroArgs testInputTemplate = tg.getInputTemplate();
|
||||
Template.OneArg<String> testMethodTemplate = tg.getTestTemplate();
|
||||
|
||||
var testTemplate = Template.make(() -> scope(
|
||||
let("warmup", warmup),
|
||||
"""
|
||||
// --- $test start ---
|
||||
@Run(test = "$test")
|
||||
@Warmup(#warmup)
|
||||
public static void $run(RunInfo info) {
|
||||
int reps = info.isWarmUp() ? 1 : 100;
|
||||
for (int i = 0; i < reps; i++) {
|
||||
// Generate random values for init and limit.
|
||||
""",
|
||||
testInputTemplate.asToken(),
|
||||
"""
|
||||
|
||||
// Limit how long we can spin in the loop:
|
||||
opaqueCounterMax = 10_000 + RANDOM.nextInt(1000);
|
||||
|
||||
// Run test and compare with interpreter results.
|
||||
var result = $test(init, limit);
|
||||
var expected = $reference(init, limit);
|
||||
if (result != expected) {
|
||||
throw new RuntimeException("wrong result: " + result + " vs " + expected
|
||||
+ "\\ninit: " + init
|
||||
+ "\\nlimit: " + limit
|
||||
+ "\\nopaqueCounterMax: " + opaqueCounterMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
""",
|
||||
testMethodTemplate.asToken($("test")),
|
||||
"""
|
||||
|
||||
@DontCompile
|
||||
""",
|
||||
testMethodTemplate.asToken($("reference")),
|
||||
"""
|
||||
// --- $test end ---
|
||||
"""
|
||||
));
|
||||
return testTemplate.asToken();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user