Skip to content

8365262: [IR-Framework] Add simple way to add cross-product of flags #26762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion test/hotspot/jtreg/compiler/lib/ir_framework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ More information about IR matching can be found in the Javadocs of [IR](./IR.jav
### 2.3 Test VM Flags and Scenarios
The recommended way to use the framework is by defining a single `@run driver` statement in the JTreg header which, however, does not allow the specification of additional test VM flags. Instead, the user has the possibility to provide VM flags by calling `TestFramework.runWithFlags()` or by creating a `TestFramework` builder object on which `addFlags()` can be called.

If a user wants to provide multiple flag combinations for a single test, he or she has the option to provide different scenarios. A scenario based flag will always have precedence over other user defined flags. More information about scenarios can be found in the Javadocs of [Scenario](./Scenario.java).
If a user wants to provide multiple flag combinations for a single test, he or she has the option to provide different scenarios. A scenario based flag will always have precedence over other user defined flags. More information about scenarios can be found in the Javadocs of [Scenario](./Scenario.java). If a user wants to test all combinations of multiple sets of flags, they can use `TestFramework.addCrossProductScenarios()`.

### 2.4 Compiler Controls
The framework allows the use of additional compiler control annotations for helper method and classes in the same fashion as JMH does. The following annotations are supported and described in the referenced Javadocs for the annotation class:
Expand Down
68 changes: 68 additions & 0 deletions test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* This class represents the main entry point to the test framework whose main purpose is to perform regex-based checks on
Expand Down Expand Up @@ -333,6 +335,72 @@ public TestFramework addScenarios(Scenario... scenarios) {
return this;
}

/**
* Add the cross-product (cartesian product) of sets of flags as Scenarios. Unlike when when constructing
* scenarios directly a string can contain multiple flags separated with a space. This allows grouping
* flags that have to be specified togeher. Further, an empty string in a set stands in for "no flag".
* <p>
* Example:
* <pre>
* addCrossProductScenarios(Set.of("", "-Xint", "-Xbatch -XX:-TieredCompilation"),
* Set.of("-XX:+UseNewCode", "-XX:UseNewCode2"))
* </pre>
* produces the following Scenarios
* <pre>
* Scenario(0, "-XX:+UseNewCode")
* Scenario(1, "-XX:+UseNewCode2")
* Scenario(2, "-Xint", "-XX:+UseNewCode")
* Scenario(3, "-Xint", "-XX:+UseNewCode2")
* Scenario(4, "-Xbatch -XX:-TieredCompilation", "-XX:+UseNewCode")
* Scenario(5, "-Xbatch -XX:-TieredCompilation", "-XX:+UseNewCode2")
* </pre>
*
* @param sets sets of flags to generate the cross product for.
* @return the same framework instance.
*/
@SafeVarargs
final public TestFramework addCrossProductScenarios(Set<String>... flagSets) {
TestFormat.checkAndReport(flagSets != null &&
Arrays.stream(flagSets).noneMatch(Objects::isNull) &&
Arrays.stream(flagSets).flatMap(Set::stream).noneMatch(Objects::isNull),
"Flags must not be null");
if (flagSets.length == 0) {
return this;
}

int initIdx = 0;
if (this.scenarioIndices != null && !this.scenarioIndices.isEmpty()) {
initIdx = this.scenarioIndices.stream().max(Comparator.comparingInt(Integer::intValue)).get() + 1;
}
AtomicInteger idx = new AtomicInteger(initIdx);

Stream<List<String>> crossProduct = Arrays.stream(flagSets)
.reduce(
Stream.of(Collections.<String>emptyList()), // Initialize Stream<List<String>> acc with a Stream containing an empty list of Strings.
(acc, set) -> // (Stream<List<String>>, Stream<List<String>>) -> Stream<List<String>>
acc.flatMap(lAcc -> // For each List<String>> lAcc in acc...
set.stream().map(flag -> { // ...and each flag in the current set...
List<String> newList = new ArrayList<>(lAcc); // ...create a new list containing lAcc...
newList.add(flag); // ...and append the flag.
return newList;
}) // This results in one List<List<String>> for each lAcc...
), // ...that get flattend into one big List<List<String>>.
(a, b) -> Stream.concat(a, b)); // combiner; if any reduction steps are executed in parallel, just concat two streams.

Scenario[] newScenarios = crossProduct
.map(flags -> new Scenario( // For each List<String> flags in crossProduct create a new Scenario.
idx.getAndIncrement(),
flags.stream() // Process flags
.map(s -> Set.of(s.split("[ ]"))) // Split muliple flags in the same string into separate strings.
.flatMap(Collection::stream) // Flatten the Stream<List<String>> into Stream<String>>.
.filter(s -> !s.isEmpty()) // Remove empty string flags.
.distinct()
.collect(Collectors.toList())
.toArray(new String[0])))
.collect(Collectors.toList()).toArray(new Scenario[0]);
return addScenarios(newScenarios);
}

/**
* Add test classes to boot classpath. This adds all classes found on path {@link jdk.test.lib.Utils#TEST_CLASSES}
* to the boot classpath with "-Xbootclasspath/a". This is useful when trying to run tests in a privileged mode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@
* @summary Auto-vectorize Float.floatToFloat16, Float.float16ToFloat APIs
* @requires vm.compiler2.enabled
* @library /test/lib /
* @run driver compiler.vectorization.TestFloatConversionsVector nCOH_nAV
* @run driver compiler.vectorization.TestFloatConversionsVector nCOH_yAV
* @run driver compiler.vectorization.TestFloatConversionsVector yCOH_nAV
* @run driver compiler.vectorization.TestFloatConversionsVector yCOH_yAV
* @run driver compiler.vectorization.TestFloatConversionsVector
*/

package compiler.vectorization;

import java.util.Set;

import compiler.lib.ir_framework.*;
import jdk.test.lib.Asserts;

Expand All @@ -49,13 +48,8 @@ public class TestFloatConversionsVector {
public static void main(String args[]) {
TestFramework framework = new TestFramework(TestFloatConversionsVector.class);
framework.addFlags("-XX:-TieredCompilation", "-XX:CompileThresholdScaling=0.3");
switch (args[0]) {
case "nCOH_nAV" -> { framework.addFlags("-XX:-UseCompactObjectHeaders", "-XX:-AlignVector"); }
case "nCOH_yAV" -> { framework.addFlags("-XX:-UseCompactObjectHeaders", "-XX:+AlignVector"); }
case "yCOH_nAV" -> { framework.addFlags("-XX:+UseCompactObjectHeaders", "-XX:-AlignVector"); }
case "yCOH_yAV" -> { framework.addFlags("-XX:+UseCompactObjectHeaders", "-XX:+AlignVector"); }
default -> { throw new RuntimeException("Test argument not recognized: " + args[0]); }
};
framework.addCrossProductScenarios(Set.of("-XX:-UseCompactObjectHeaders", "-XX:+UseCompactObjectHeaders"),
Set.of("-XX:-AlignVector", "-XX:+AlignVector"));
framework.start();
System.out.println("PASSED");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* 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 ir_framework.tests;

import java.util.Set;

import compiler.lib.ir_framework.*;
import compiler.lib.ir_framework.shared.TestRunException;
import compiler.lib.ir_framework.shared.TestFormatException;
import jdk.test.lib.Asserts;

/*
* @test
* @requires vm.debug == true & vm.compMode != "Xint" & vm.compiler2.enabled & vm.flagless
* @summary Test cross product scenarios with the framework.
* @library /test/lib /testlibrary_tests /
* @run driver ir_framework.tests.TestScenariosCrossProduct
*/

public class TestScenariosCrossProduct {
static void hasNFailures(String s, int count) {
if (!s.matches("The following scenarios have failed: (#[0-9](, )?){" + count + "}. Please check stderr for more information.")) {
throw new RuntimeException("Expected " + count + " failures in \"" + s + "\"");
}
}

public static void main(String[] args) {
// Test argument handling
try {
TestFramework t = new TestFramework();
t.addCrossProductScenarios(null);
Asserts.fail("Should have thrown exception");
} catch (TestFormatException e) {}
try {
TestFramework t = new TestFramework();
t.addCrossProductScenarios(Set.of("foo", "bar"), null);
Asserts.fail("Should have thrown exception");
} catch (TestFormatException e) {}
try {
TestFramework t = new TestFramework();
t.addCrossProductScenarios(Set.of("blub"), Set.of("foo", null));
Asserts.fail("Should have thrown exception");
} catch (NullPointerException e) {} // Set.of prevents null elements
try {
TestFramework t = new TestFramework();
t.addCrossProductScenarios();
} catch (TestFormatException e) {
Asserts.fail("Should not have thrown exception");
}

// Single set should test all flags in the set by themselves.
try {
TestFramework t1 = new TestFramework();
t1.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=51",
"-XX:TLABRefillWasteFraction=53",
"-XX:TLABRefillWasteFraction=64"));
t1.start();
Asserts.fail("Should have thrown exception");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, why do the tests fail? I'm wondering if a simpler way to test the functionality is possible that doesn't require having to figure out failure modes? Maybe some kind of positive test that counts number of test scenarios run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except in the first run, all scenarios fail. That is the only way we currently have to count the scenarios we are executing.

} catch (TestRunException e) {
hasNFailures(e.getMessage(), 3);
}

// The cross product of a set with one element and a set with three elements is three sets.
try {
TestFramework t2 = new TestFramework();
t2.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=53"),
Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2", "-XX:+UseNewCode3"));
t2.start();
Asserts.fail("Should have thrown exception");
} catch (TestRunException e) {
hasNFailures(e.getMessage(), 3);
}

// The cross product of two sets with two elements is four sets.
try {
TestFramework t3 = new TestFramework();
t3.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=53", "-XX:TLABRefillWasteFraction=64"),
Set.of("-XX:+UseNewCode", "-XX:-UseNewCode"));
t3.start();
Asserts.fail("Should have thrown exception");
} catch (TestRunException e) {
hasNFailures(e.getMessage(), 4);
}

// Test with a pair of flags.
try {
TestFramework t4 = new TestFramework();
t4.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=50 -XX:+UseNewCode", "-XX:TLABRefillWasteFraction=40"),
Set.of("-XX:+UseNewCode2"));
t4.start();
Asserts.fail("Should have thrown exception");
} catch (TestRunException e) {
hasNFailures(e.getMessage(), 1);
}

// Test with an empty string. All 6 scenarios fail because 64 is the default value for TLABRefillWasteFraction.
try {
TestFramework t5 = new TestFramework();
t5.addCrossProductScenarios(Set.of("", "-XX:TLABRefillWasteFraction=51", "-XX:TLABRefillWasteFraction=53"),
Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2"));
t5.start();
Asserts.fail("Should have thrown exception");
} catch (TestRunException e) {
hasNFailures(e.getMessage(), 6);
}

try {
TestFramework t6 = new TestFramework();
t6.addScenarios(new Scenario(0, "-XX:TLABRefillWasteFraction=50", "-XX:+UseNewCode")); // failPair
t6.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=51", "-XX:TLABRefillWasteFraction=53"),
Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2"));
try {
t6.addScenarios(new Scenario(4, "-XX:+UseNewCode3")); // fails because index 4 is already used
Asserts.fail("Should have thrown exception");
} catch (TestFormatException e) {}
t6.addScenarios(new Scenario(5, "-XX:+UseNewCode3")); // fail default
t6.start();
Asserts.fail("Should have thrown exception");
} catch (TestRunException e) {
hasNFailures(e.getMessage(), 6);
}
}

@Test
@IR(applyIf = {"TLABRefillWasteFraction", "64"}, counts = {IRNode.CALL, "1"})
public void failDefault() {
}

@Test
@IR(applyIf = {"TLABRefillWasteFraction", "51"}, counts = {IRNode.CALL, "1"})
public void fail1() {
}

@Test
@IR(applyIf = {"TLABRefillWasteFraction", "53"}, counts = {IRNode.CALL, "1"})
public void fail2() {
}

@Test
@IR(applyIfAnd = {"TLABRefillWasteFraction", "50", "UseNewCode", "true"}, counts = {IRNode.CALL, "1"})
public void failPair() {
}
}