jdk/test/hotspot/jtreg/compiler/vectorapi/VectorExpressionFuzzer.java
Emanuel Peter 4a4701106c 8369699: Template Framework Library: add VectorAPI types and operations
Reviewed-by: mhaessig, vlivanov, galder
2026-04-03 12:39:49 +00:00

346 lines
15 KiB
Java

/*
* 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 id=AVX2
* @bug 8369699
* @key randomness
* @summary Test the Template Library's expression generation for the Vector API.
* @requires os.family == "linux" & os.simpleArch == "x64"
* @modules jdk.incubator.vector
* @modules java.base/jdk.internal.misc
* @library /test/lib /
* @compile ../../compiler/lib/verify/Verify.java
* @run driver ${test.main.class} -XX:UseAVX=2
*/
// TODO: remove the x64 and linux restriction above. I added that for now so we are not flooded
// with failures in the CI. We should remove these restriction once more bugs are fixed.
// x64 linux is the easiest to debug on for me, that's why I picked it.
// In addition, I put a UseAVX=2 restriction below, to avoid AVX512 bugs for now.
//
// A trick to extend this to other platforms: create a new run block, so you have full
// freedom to restrict it as necessary for platform and vector features.
package compiler.vectorapi;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Set;
import java.util.Random;
import jdk.test.lib.Utils;
import java.util.stream.IntStream;
import compiler.lib.compile_framework.CompileFramework;
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.CodeGenerationDataNameType;
import compiler.lib.template_framework.library.Expression;
import compiler.lib.template_framework.library.Expression.Nesting;
import compiler.lib.template_framework.library.Operations;
import compiler.lib.template_framework.library.TestFrameworkClass;
import compiler.lib.template_framework.library.PrimitiveType;
import compiler.lib.template_framework.library.VectorType;
/**
* Basic Expression Fuzzer for Vector API.
*/
public class VectorExpressionFuzzer {
private static final Random RANDOM = Utils.getRandomInstance();
public static void main(String[] args) {
// Create a new CompileFramework instance.
CompileFramework comp = new CompileFramework();
// Add a java source file.
comp.addJavaSourceCode("compiler.vectorapi.templated.Templated", generate(comp));
// Compile the source file.
comp.compile("--add-modules=jdk.incubator.vector");
List<String> vmArgs = new ArrayList<>(List.of(
"--add-modules=jdk.incubator.vector",
"--add-opens", "jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
));
vmArgs.addAll(Arrays.asList(args)); // Forward args
String[] vmArgsArray = vmArgs.toArray(new String[0]);
// compiler.vectorapi.templated.InnterTest.main(new String[] {});
comp.invoke("compiler.vectorapi.templated.Templated", "main", new Object[] { vmArgsArray } );
}
// For Example 2 below.
record TestArgument(
Object defineAndFill, // used once: to define and fill the input value
Object passMethodArgument, // used twice: to pass argument to test and reference
Object receiveMethodArgument, // used twice: to receive argument in test and reference
Object use) {} // used twice: to use the value as expression argument
// Generate a source Java file as String
public static String generate(CompileFramework comp) {
// Generate a list of test methods.
List<TemplateToken> tests = new ArrayList<>();
// We are going to use some random numbers in our tests, so import some good methods for that.
tests.add(PrimitiveType.generateLibraryRNG());
// Example 1:
// To start simple, we just call the expression with the same constant arguments
// every time. We can still compare the "gold" value obtained via interpreter with
// later results obtained from the compiled method.
var template1 = Template.make("type", (VectorType.Vector type) -> {
// The depth determines roughly how many operations are going to be used in the expression.
int depth = RANDOM.nextInt(1, 10);
Expression expression = Expression.nestRandomly(type, Operations.ALL_OPERATIONS, depth, Nesting.EXACT);
List<Object> expressionArguments = expression.argumentTypes.stream().map(CodeGenerationDataNameType::con).toList();
return scope(
"""
// --- $test start ---
// Using $GOLD
// type: #type
static final Object $GOLD = $test();
@Test
public static Object $test() {
try {
""",
" return ", expression.asToken(expressionArguments), ";\n",
expression.info.exceptions.stream().map(exception ->
" } catch (" + exception + " e) { return e;\n"
).toList(),
"""
} finally {
// Just so javac is happy if there are no exceptions to catch.
}
}
@Check(test = "$test")
public static void $check(Object result) {
""",
expression.info.isResultDeterministic
? " Verify.checkEQ(result, $GOLD);\n"
: " // result not deterministic - don't verify.\n",
"""
}
// --- $test end ---
"""
);
});
// Example 2:
// Now a more complicated case. We want:
// - For every invocation of the test method, we want to have different inputs for the arguments.
// - We check correctness with a reference method that does the same but runs in the interpreter.
// - Input values are delivered via fields or array loads.
// - The final vector is written into an array, and that array is returned.
var template2Body = Template.make("expression", "arguments", (Expression expression, List<Object> arguments) -> scope(
let("elementType", ((VectorType.Vector)expression.returnType).elementType),
"""
try {
#elementType[] out = new #elementType[1000];
""",
expression.asToken(arguments), ".intoArray(out, 0);\n",
"return out;\n",
expression.info.exceptions.stream().map(exception ->
"} catch (" + exception + " e) { return e;\n"
).toList(),
"""
} finally {
// Just javac is happy if there are no exceptions to catch.
}
"""
));
var template2 = Template.make("type", (VectorType.Vector type) -> {
// The depth determines roughly how many operations are going to be used in the expression.
int depth = RANDOM.nextInt(1, 10);
Expression expression = Expression.nestRandomly(type, Operations.ALL_OPERATIONS, depth, Nesting.EXACT);
List<TestArgument> arguments = new ArrayList<>();
for (int i = 0; i < expression.argumentTypes.size(); i++) {
String name = "arg_" + i;
CodeGenerationDataNameType argumentType = expression.argumentTypes.get(i);
switch(RANDOM.nextInt(4)) {
case 0 -> {
// Use the constant directly, no argument passing needed.
// To make the logic of passing arguments easy, we just pass null and receive an unused argument anyway.
arguments.add(new TestArgument(
"",
"null",
"Object unused_" + i,
argumentType.con()
));
}
case 1 -> {
// Create the constant outside, and pass it.
arguments.add(new TestArgument(
List.of(argumentType.name(), " ", name, " = ", argumentType.con(), ";\n"),
name,
List.of(argumentType.name(), " ", name),
name
));
}
default -> {
if (argumentType instanceof PrimitiveType t) {
// We can use the LibraryRGN to create a new value for the primitive in each
// invocation. We have to make sure to call the LibraryRNG in the "defineAndFill",
// so we get the same value for both test and reference. If we called LibraryRNG
// for "use", we would get separate values, which is not helpful.
arguments.add(new TestArgument(
List.of(t.name(), " ", name, " = ", t.callLibraryRNG(), ";\n"),
name,
List.of(t.name(), " ", name),
name
));
} else if (argumentType instanceof VectorType.Vector t) {
PrimitiveType et = t.elementType;
arguments.add(new TestArgument(
List.of(et.name(), "[] ", name, " = new ", et.name(), "[1000];\n",
"LibraryRNG.fill(", name,");\n"),
name,
List.of(et.name(), "[] ", name),
List.of(t.name(), ".fromArray(", t.speciesName, ", ", name, ", 0)")
));
} else if (argumentType instanceof VectorType.Mask t) {
arguments.add(new TestArgument(
List.of("boolean[] ", name, " = new boolean[1000];\n",
"LibraryRNG.fill(", name,");\n"),
name,
List.of("boolean[] ", name),
List.of("VectorMask.fromArray(", t.vectorType.speciesName, ", ", name, ", 0)")
));
} else if (argumentType instanceof VectorType.Shuffle t) {
arguments.add(new TestArgument(
List.of("int[] ", name, " = new int[1000];\n",
"LibraryRNG.fill(", name,");\n"),
name,
List.of("int[] ", name),
List.of("VectorShuffle.fromArray(", t.vectorType.speciesName, ", ", name, ", 0)")
));
} else {
// We don't know anything special how to create different values
// each time, so let's just crate the same constant each time.
arguments.add(new TestArgument(
List.of(argumentType.name(), " ", name, " = ", argumentType.con(), ";\n"),
name,
List.of(argumentType.name(), " ", name),
name
));
}
}
}
}
// "join" together the arguments to make a comma separated list.
List<Object> passArguments = IntStream.range(0, arguments.size() * 2 - 1).mapToObj(i ->
(i % 2 == 0) ? arguments.get(i / 2).passMethodArgument() : ", "
).toList();
List<Object> receiveArguments = IntStream.range(0, arguments.size() * 2 - 1).mapToObj(i ->
(i % 2 == 0) ? arguments.get(i / 2).receiveMethodArgument() : ", "
).toList();
List<Object> useArguments = arguments.stream().map(TestArgument::use).toList();
return scope(
"""
// --- $test start ---
// type: #type
@Run(test = "$test")
public void $run() {
""",
arguments.stream().map(TestArgument::defineAndFill).toList(),
"""
Object v0 = $test(
""",
passArguments,
"""
);
Object v1 = $reference(
""",
passArguments,
"""
);
""",
expression.info.isResultDeterministic
? "Verify.checkEQ(v0, v1);\n"
: "// result not deterministic - don't verify.\n",
"""
}
@Test
public static Object $test(
""",
receiveArguments,
"""
) {
""",
template2Body.asToken(expression, useArguments),
"""
}
@DontCompile
public static Object $reference(
""",
receiveArguments,
"""
) {
""",
template2Body.asToken(expression, useArguments),
"""
}
// --- $test end ---
"""
);
});
for (VectorType.Vector type : CodeGenerationDataNameType.VECTOR_VECTOR_TYPES) {
tests.add(template1.asToken(type));
tests.add(template2.asToken(type));
}
// Create the test class, which runs all tests.
return TestFrameworkClass.render(
// package and class name.
"compiler.vectorapi.templated", "Templated",
// Set of imports.
Set.of("compiler.lib.verify.*",
"java.util.Random",
"jdk.test.lib.Utils",
"compiler.lib.generators.*",
"jdk.incubator.vector.*"),
// classpath, so the Test VM has access to the compiled class files.
comp.getEscapedClassPathOfCompiledClasses(),
// The list of tests.
tests);
}
}