diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md index 6bdae26259908..2a454f2990d5b 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md @@ -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: diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 8e509c66c9a43..7214765207ebd 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java @@ -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 @@ -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". + *

+ * Example: + *

+     *     addCrossProductScenarios(Set.of("", "-Xint", "-Xbatch -XX:-TieredCompilation"),
+     *                              Set.of("-XX:+UseNewCode", "-XX:UseNewCode2"))
+     * 
+ * produces the following Scenarios + *
+     *     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")
+     * 
+ * + * @param sets sets of flags to generate the cross product for. + * @return the same framework instance. + */ + @SafeVarargs + final public TestFramework addCrossProductScenarios(Set... 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> crossProduct = Arrays.stream(flagSets) + .reduce( + Stream.of(Collections.emptyList()), // Initialize Stream> acc with a Stream containing an empty list of Strings. + (acc, set) -> // (Stream>, Stream>) -> Stream> + acc.flatMap(lAcc -> // For each List> lAcc in acc... + set.stream().map(flag -> { // ...and each flag in the current set... + List newList = new ArrayList<>(lAcc); // ...create a new list containing lAcc... + newList.add(flag); // ...and append the flag. + return newList; + }) // This results in one List> for each lAcc... + ), // ...that get flattend into one big List>. + (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 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> into Stream>. + .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. diff --git a/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java b/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java index 482dcf934c59b..3ba9fe7472c4d 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java @@ -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; @@ -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"); } diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java new file mode 100644 index 0000000000000..9562202352e6c --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java @@ -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"); + } 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() { + } +}