diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java index 48f7852d5098a..8ee2689eb2fce 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java @@ -75,7 +75,7 @@ public record Hook(String name) { * @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s. */ public Token anchor(Object... tokens) { - return new HookAnchorToken(this, Token.parse(tokens)); + return new HookAnchorToken(this, TokenParser.parse(tokens)); } /** diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java index f01c5ccffd3d0..57d06e732bb1f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java @@ -615,7 +615,7 @@ static Template.ThreeArgs make(String arg1Name, String * @throws IllegalArgumentException if the list of tokens contains an unexpected object. */ static TemplateBody body(Object... tokens) { - return new TemplateBody(Token.parse(tokens)); + return new TemplateBody(TokenParser.parse(tokens)); } /** diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java index dc750c7f79f33..0e9f9b272c54f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java @@ -23,17 +23,11 @@ package compiler.lib.template_framework; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; - /** * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either - * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed - * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens - * {@link List}s. + * {@link Token}s or {@link String}s or some permitted boxed primitives. */ -sealed interface Token permits StringToken, +public sealed interface Token permits StringToken, TemplateToken, TemplateToken.ZeroArgs, TemplateToken.OneArg, @@ -42,37 +36,4 @@ sealed interface Token permits StringToken, HookAnchorToken, HookInsertToken, AddNameToken, - NothingToken -{ - static List parse(Object[] objects) { - if (objects == null) { - throw new IllegalArgumentException("Unexpected tokens: null"); - } - List outputList = new ArrayList<>(); - parseToken(Arrays.asList(objects), outputList); - return outputList; - } - - private static void parseList(List inputList, List outputList) { - for (Object o : inputList) { - parseToken(o, outputList); - } - } - - private static void parseToken(Object o, List outputList) { - if (o == null) { - throw new IllegalArgumentException("Unexpected token: null"); - } - switch (o) { - case Token t -> outputList.add(t); - case String s -> outputList.add(new StringToken(Renderer.format(s))); - case Integer s -> outputList.add(new StringToken(Renderer.format(s))); - case Long s -> outputList.add(new StringToken(Renderer.format(s))); - case Double s -> outputList.add(new StringToken(Renderer.format(s))); - case Float s -> outputList.add(new StringToken(Renderer.format(s))); - case Boolean s -> outputList.add(new StringToken(Renderer.format(s))); - case List l -> parseList(l, outputList); - default -> throw new IllegalArgumentException("Unexpected token: " + o); - } - } -} + NothingToken {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java new file mode 100644 index 0000000000000..0c335bd4fb89e --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java @@ -0,0 +1,71 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for {@link Token}, to keep the parsing methods package private. + * + *

+ * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed + * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens + * {@link List}s. + */ +final class TokenParser { + static List parse(Object[] objects) { + if (objects == null) { + throw new IllegalArgumentException("Unexpected tokens: null"); + } + List outputList = new ArrayList<>(); + parseToken(Arrays.asList(objects), outputList); + return outputList; + } + + private static void parseList(List inputList, List outputList) { + for (Object o : inputList) { + parseToken(o, outputList); + } + } + + private static void parseToken(Object o, List outputList) { + if (o == null) { + throw new IllegalArgumentException("Unexpected token: null"); + } + switch (o) { + case Token t -> outputList.add(t); + case String s -> outputList.add(new StringToken(Renderer.format(s))); + case Integer s -> outputList.add(new StringToken(Renderer.format(s))); + case Long s -> outputList.add(new StringToken(Renderer.format(s))); + case Double s -> outputList.add(new StringToken(Renderer.format(s))); + case Float s -> outputList.add(new StringToken(Renderer.format(s))); + case Boolean s -> outputList.add(new StringToken(Renderer.format(s))); + case List l -> parseList(l, outputList); + default -> throw new IllegalArgumentException("Unexpected token: " + o); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java new file mode 100644 index 0000000000000..56f7afcbaeb9f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java @@ -0,0 +1,157 @@ +/* + * 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 compiler.lib.template_framework.library; + +import java.util.List; + +import compiler.lib.template_framework.DataName; +import compiler.lib.template_framework.Template; + +/** + * The {@link CodeGenerationDataNameType} extends the {@link DataName.Type} with + * additional functionality for code generation. These types with their extended + * functionality can be used with many other code generation facilities in the + * library, such as generating random {@code Expression}s. + */ +public interface CodeGenerationDataNameType extends DataName.Type { + + /** + * This method provides a random constant value for the type, which can + * be used as a token inside a {@link Template}. + * + * @return A random constant value. + */ + Object con(); + + /** + * The byte {@link PrimitiveType}. + * + * @return The byte {@link PrimitiveType}. + */ + static PrimitiveType bytes() { return PrimitiveType.BYTES; } + + /** + * The short {@link PrimitiveType}. + * + * @return The short {@link PrimitiveType}. + */ + static PrimitiveType shorts() { return PrimitiveType.SHORTS; } + + /** + * The char {@link PrimitiveType}. + * + * @return The char {@link PrimitiveType}. + */ + static PrimitiveType chars() { return PrimitiveType.CHARS; } + + /** + * The int {@link PrimitiveType}. + * + * @return The int {@link PrimitiveType}. + */ + static PrimitiveType ints() { return PrimitiveType.INTS; } + + /** + * The long {@link PrimitiveType}. + * + * @return The long {@link PrimitiveType}. + */ + static PrimitiveType longs() { return PrimitiveType.LONGS; } + + /** + * The float {@link PrimitiveType}. + * + * @return The float {@link PrimitiveType}. + */ + static PrimitiveType floats() { return PrimitiveType.FLOATS; } + + /** + * The double {@link PrimitiveType}. + * + * @return The double {@link PrimitiveType}. + */ + static PrimitiveType doubles() { return PrimitiveType.DOUBLES; } + + /** + * The boolean {@link PrimitiveType}. + * + * @return The boolean {@link PrimitiveType}. + */ + static PrimitiveType booleans() { return PrimitiveType.BOOLEANS; } + + /** + * List of all {@link PrimitiveType}s. + */ + List PRIMITIVE_TYPES = List.of( + bytes(), + chars(), + shorts(), + ints(), + longs(), + floats(), + doubles(), + booleans() + ); + + /** + * List of all integral {@link PrimitiveType}s (byte, char, short, int, long). + */ + List INTEGRAL_TYPES = List.of( + bytes(), + chars(), + shorts(), + ints(), + longs() + ); + + /** + * List of all subword {@link PrimitiveType}s (byte, char, short). + */ + List SUBWORD_TYPES = List.of( + bytes(), + chars(), + shorts() + ); + + /** + * List of all floating {@link PrimitiveType}s (float, double). + */ + List FLOATING_TYPES = List.of( + floats(), + doubles() + ); + + /** + * List of all integral and floating {@link PrimitiveType}s. + */ + List INTEGRAL_AND_FLOATING_TYPES = List.of( + bytes(), + chars(), + shorts(), + ints(), + longs(), + floats(), + doubles() + ); +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java new file mode 100644 index 0000000000000..3bf6c7f62886e --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -0,0 +1,151 @@ +/* + * 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 compiler.lib.template_framework.library; + +import java.util.Random; +import jdk.test.lib.Utils; + +import compiler.lib.generators.Generators; +import compiler.lib.generators.Generator; +import compiler.lib.generators.RestrictableGenerator; + +import compiler.lib.template_framework.DataName; + +/** + * The {@link PrimitiveType} models Java's primitive types, and provides a set + * of useful methods for code generation, such as the {@link #byteSize} and + * {@link #boxedTypeName}. + */ +public final class PrimitiveType implements CodeGenerationDataNameType { + private static final Random RANDOM = Utils.getRandomInstance(); + private static final RestrictableGenerator GEN_BYTE = Generators.G.safeRestrict(Generators.G.ints(), Byte.MIN_VALUE, Byte.MAX_VALUE); + private static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); + private static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE); + private static final RestrictableGenerator GEN_INT = Generators.G.ints(); + private static final RestrictableGenerator GEN_LONG = Generators.G.longs(); + private static final Generator GEN_DOUBLE = Generators.G.doubles(); + private static final Generator GEN_FLOAT = Generators.G.floats(); + + private static enum Kind { BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE, BOOLEAN }; + + // We have one static instance each, so we do not have duplicated instances. + static final PrimitiveType BYTES = new PrimitiveType(Kind.BYTE ); + static final PrimitiveType SHORTS = new PrimitiveType(Kind.SHORT ); + static final PrimitiveType CHARS = new PrimitiveType(Kind.CHAR ); + static final PrimitiveType INTS = new PrimitiveType(Kind.INT ); + static final PrimitiveType LONGS = new PrimitiveType(Kind.LONG ); + static final PrimitiveType FLOATS = new PrimitiveType(Kind.FLOAT ); + static final PrimitiveType DOUBLES = new PrimitiveType(Kind.DOUBLE ); + static final PrimitiveType BOOLEANS = new PrimitiveType(Kind.BOOLEAN); + + final Kind kind; + + // Private constructor so nobody can create duplicate instances. + private PrimitiveType(Kind kind) { + this.kind = kind; + } + + @Override + public boolean isSubtypeOf(DataName.Type other) { + return (other instanceof PrimitiveType pt) && pt.kind == kind; + } + + @Override + public String name() { + return switch (kind) { + case BYTE -> "byte"; + case SHORT -> "short"; + case CHAR -> "char"; + case INT -> "int"; + case LONG -> "long"; + case FLOAT -> "float"; + case DOUBLE -> "double"; + case BOOLEAN -> "boolean"; + }; + } + + @Override + public String toString() { + return name(); + } + + public Object con() { + return switch (kind) { + case BYTE -> "(byte)" + GEN_BYTE.next(); + case SHORT -> "(short)" + GEN_SHORT.next(); + case CHAR -> "(char)" + GEN_CHAR.next(); + case INT -> GEN_INT.next(); + case LONG -> GEN_LONG.next(); + case FLOAT -> GEN_FLOAT.next(); + case DOUBLE -> GEN_DOUBLE.next(); + case BOOLEAN -> RANDOM.nextBoolean(); + }; + } + + /** + * Provides the size of the type in bytes. + * + * @return Size of the type in bytes. + * @throws UnsupportedOperationException for boolean which has no defined size. + */ + public int byteSize() { + return switch (kind) { + case BYTE -> 1; + case SHORT, CHAR -> 2; + case INT, FLOAT -> 4; + case LONG, DOUBLE -> 8; + case BOOLEAN -> { throw new UnsupportedOperationException("boolean does not have a defined 'size'"); } + }; + } + + /** + * Provides the name of the boxed type. + * + * @return the name of the boxed type. + */ + public String boxedTypeName() { + return switch (kind) { + case BYTE -> "Byte"; + case SHORT -> "Short"; + case CHAR -> "Character"; + case INT -> "Integer"; + case LONG -> "Long"; + case FLOAT -> "Float"; + case DOUBLE -> "Double"; + case BOOLEAN -> "Boolean"; + }; + } + + /** + * Indicates if the type is a floating point type. + * + * @return true iff the type is a floating point type. + */ + public boolean isFloating() { + return switch (kind) { + case BYTE, SHORT, CHAR, INT, LONG, BOOLEAN -> false; + case FLOAT, DOUBLE -> true; + }; + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java new file mode 100644 index 0000000000000..5cd3f3c2a226b --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -0,0 +1,201 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8358772 + * @summary Demonstrate the use of PrimitiveTypes form the Template Library. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/verify/Verify.java + * @run main template_framework.examples.TestPrimitiveTypes + */ + +package template_framework.examples; + +import java.util.List; +import java.util.Map; +import java.util.Collections; +import java.util.HashMap; + +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.dataNames; +import static compiler.lib.template_framework.Template.let; +import static compiler.lib.template_framework.Template.$; +import static compiler.lib.template_framework.Template.addDataName; +import static compiler.lib.template_framework.DataName.Mutability.MUTABLE; + +import compiler.lib.template_framework.library.Hooks; +import compiler.lib.template_framework.library.CodeGenerationDataNameType; +import compiler.lib.template_framework.library.PrimitiveType; + +/** + * This test shows the use of {@link PrimitiveType}. + */ +public class TestPrimitiveTypes { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate()); + + // Compile the source file. + comp.compile(); + + // p.xyz.InnerTest.main(); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {}); + } + + // Generate a Java source file as String + public static String generate() { + // Generate a list of test methods. + Map tests = new HashMap<>(); + + // The boxing tests check if we can autobox with "boxedTypeName". + var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + let("CON1", type.con()), + let("CON2", type.con()), + let("Boxed", type.boxedTypeName()), + """ + public static void #name() { + #type c1 = #CON1; + #type c2 = #CON2; + #Boxed b1 = c1; + #Boxed b2 = c2; + Verify.checkEQ(c1, b1); + Verify.checkEQ(c2, b2); + } + """ + )); + + for (PrimitiveType type : CodeGenerationDataNameType.PRIMITIVE_TYPES) { + String name = "test_boxing_" + type.name(); + tests.put(name, boxingTemplate.asToken(name, type)); + } + + // Integral and Float types have a size. Also test if "isFloating" is correct. + var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + let("size", type.byteSize()), + let("isFloating", type.isFloating()), + """ + public static void #name() { + // Test byteSize via creation of array. + #type[] array = new #type[1]; + MemorySegment ms = MemorySegment.ofArray(array); + if (#size != ms.byteSize()) { + throw new RuntimeException("byteSize mismatch #type"); + } + + // Test isFloating via rounding. + double value = 1.5; + #type rounded = (#type)value; + boolean isFloating = value != rounded; + if (isFloating == #isFloating) { + throw new RuntimeException("isFloating mismatch #type"); + } + } + """ + )); + + for (PrimitiveType type : CodeGenerationDataNameType.INTEGRAL_AND_FLOATING_TYPES) { + String name = "test_integral_floating_" + type.name(); + tests.put(name, integralFloatTemplate.asToken(name, type)); + } + + // Finally, test the type by creating some DataNames (variables), and sampling + // from them. There should be no cross-over between the types. + var variableTemplate = Template.make("type", (PrimitiveType type) -> body( + let("CON", type.con()), + addDataName($("var"), type, MUTABLE), + """ + #type $var = #CON; + """ + )); + + var sampleTemplate = Template.make("type", (PrimitiveType type) -> body( + let("var", dataNames(MUTABLE).exactOf(type).sample().name()), + let("CON", type.con()), + """ + #var = #CON; + """ + )); + + var namesTemplate = Template.make(() -> body( + """ + public static void test_names() { + """, + Hooks.METHOD_HOOK.anchor( + Collections.nCopies(10, + CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type -> + Hooks.METHOD_HOOK.insert(variableTemplate.asToken(type)) + ).toList() + ), + """ + // Now sample: + """, + Collections.nCopies(10, + CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList() + ) + ), + """ + } + """ + )); + + tests.put("test_names", namesTemplate.asToken()); + + // Finally, put all the tests together in a class, and invoke all + // tests from the main method. + var template = Template.make(() -> body( + """ + package p.xyz; + + import compiler.lib.verify.*; + import java.lang.foreign.MemorySegment; + + public class InnerTest { + public static void main() { + """, + // Call all test methods from main. + tests.keySet().stream().map( + n -> List.of(n, "();\n") + ).toList(), + """ + } + """, + // Now add all the test methods. + tests.values().stream().toList(), + """ + } + """ + )); + + // Render the template to a String. + return template.render(); + } +}