From 262b31be3de42012bf9460e5deb0b43bec6a3fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Wed, 18 Mar 2026 12:33:41 +0000 Subject: [PATCH] 8359335: Template-Framework Library: Primitive Types subtyping Reviewed-by: chagedorn, epeter --- .../jtreg/compiler/igvn/ExpressionFuzzer.java | 5 ++- .../library/Expression.java | 31 +++++++++++-- .../library/Operations.java | 13 +++++- .../library/PrimitiveType.java | 20 ++++++++- .../examples/TestPrimitiveTypes.java | 20 ++++++++- .../tests/TestExpression.java | 45 ++++++++++++++++--- 6 files changed, 116 insertions(+), 18 deletions(-) diff --git a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java index 875ef57c865..33be24a0367 100644 --- a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java +++ b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java @@ -52,6 +52,7 @@ 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.PrimitiveType; import compiler.lib.template_framework.library.TestFrameworkClass; @@ -335,7 +336,7 @@ public class ExpressionFuzzer { for (int i = 0; i < 10; i++) { // The depth determines roughly how many operations are going to be used in the expression. int depth = RANDOM.nextInt(1, 20); - Expression expression = Expression.nestRandomly(type, Operations.PRIMITIVE_OPERATIONS, depth); + Expression expression = Expression.nestRandomly(type, Operations.PRIMITIVE_OPERATIONS, depth, Nesting.EXACT); tests.add(testTemplate.asToken(expression)); } } @@ -350,7 +351,7 @@ public class ExpressionFuzzer { for (int i = 0; i < 2; i++) { // The depth determines roughly how many operations are going to be used in the expression. int depth = RANDOM.nextInt(1, 20); - Expression expression = Expression.nestRandomly(type, Operations.SCALAR_NUMERIC_OPERATIONS, depth); + Expression expression = Expression.nestRandomly(type, Operations.SCALAR_NUMERIC_OPERATIONS, depth, Nesting.EXACT); tests.add(testTemplate.asToken(expression)); } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java index 43ab16af415..37ad4debde6 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java @@ -382,6 +382,23 @@ public class Expression { return sb.toString(); } + /** + * {@link Nesting} defines the different ways of selecting {@link Expression}s + * to nest based on their types. + */ + public enum Nesting { + /** + * Only nest {@Expression}s where the argument and return types match exactly + * based on the implementation of {@link CodeGenerateionDataNameType#isSubtypeOf}. + */ + EXACT, + /** + * Only nest {@Expression}s where the return type is a subtype of the argument + * type based on the implemetation of {@link CodeGenerateionDataNameType#isSubtypeOf}. + */ + SUBTYPE + } + /** * Create a nested {@link Expression} with a specified {@code returnType} from a * set of {@code expressions}. @@ -391,11 +408,13 @@ public class Expression { * the nested {@link Expression}. * @param maxNumberOfUsedExpressions the maximal number of {@link Expression}s from the * {@code expressions} are nested. + * @param nesting control the {@link Nesting} of the sampled {@link Expression}s. * @return a new randomly nested {@link Expression}. */ public static Expression nestRandomly(CodeGenerationDataNameType returnType, List expressions, - int maxNumberOfUsedExpressions) { + int maxNumberOfUsedExpressions, + Nesting nesting) { List filtered = expressions.stream().filter(e -> e.returnType.isSubtypeOf(returnType)).toList(); if (filtered.isEmpty()) { @@ -406,7 +425,7 @@ public class Expression { Expression expression = filtered.get(r); for (int i = 1; i < maxNumberOfUsedExpressions; i++) { - expression = expression.nestRandomly(expressions); + expression = expression.nestRandomly(expressions, nesting); } return expression; } @@ -416,12 +435,16 @@ public class Expression { * {@code this} {@link Expression}, ensuring compatibility of argument and return type. * * @param nestingExpressions list of expressions we sample from for the inner {@link Expression}. + * @param nesting control the {@link Nesting} of the sampled {@link Expression}s. * @return a new nested {@link Expression}. */ - public Expression nestRandomly(List nestingExpressions) { + public Expression nestRandomly(List nestingExpressions, Nesting nesting) { int argumentIndex = RANDOM.nextInt(this.argumentTypes.size()); CodeGenerationDataNameType argumentType = this.argumentTypes.get(argumentIndex); - List filtered = nestingExpressions.stream().filter(e -> e.returnType.isSubtypeOf(argumentType)).toList(); + List filtered = nestingExpressions.stream() + .filter(e -> e.returnType.isSubtypeOf(argumentType) && + (nesting == Nesting.EXACT ? argumentType.isSubtypeOf(e.returnType) : true)) + .toList(); if (filtered.isEmpty()) { // Found no expression that has a matching returnType. diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java index 1fe05cc2b6c..2ea251cc5e5 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java @@ -129,8 +129,17 @@ public final class Operations { ops.add(Expression.make(type, "(", type, " + ", type, ")")); ops.add(Expression.make(type, "(", type, " - ", type, ")")); ops.add(Expression.make(type, "(", type, " * ", type, ")")); - ops.add(Expression.make(type, "(", type, " / ", type, ")")); - ops.add(Expression.make(type, "(", type, " % ", type, ")")); + // Because of subtyping, we can sample an expression like `(float)((int)(3) / (int)(0))`. Floating point + // division and modulo do not throw an ArithmeticException on division by zero, integer division and modulo + // do. In the expression above, the division has an integer on both sides, so it is executed as an integer + // division and throws an ArithmeticException even though we would expect the float division not to do so. + // To prevent this issue, we provide two versions of floating point division operations: one that casts + // its operands and one that expects that an ArithmeticException might be thrown when we get unlucky when + // sampling subtypes. + ops.add(Expression.make(type, "((" + type.name() + ")(", type, ") / (" + type.name() +")(", type, "))")); + ops.add(Expression.make(type, "((" + type.name() + ")(", type, ") % (" + type.name() +")(", type, "))")); + ops.add(Expression.make(type, "(", type, " / ", type, ")", WITH_ARITHMETIC_EXCEPTION)); + ops.add(Expression.make(type, "(", type, " % ", type, ")", WITH_ARITHMETIC_EXCEPTION)); // Relational / Comparison Operators ops.add(Expression.make(BOOLEANS, "(", type, " == ", type, ")")); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java index c8541ac1fa6..b20dbb28d22 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -71,7 +71,25 @@ public final class PrimitiveType implements CodeGenerationDataNameType { @Override public boolean isSubtypeOf(DataName.Type other) { - return (other instanceof PrimitiveType pt) && pt.kind == kind; + // Implement other >: this according to JLS ยง4.10.1. + if (other instanceof PrimitiveType superType) { + if (superType.kind == Kind.BOOLEAN || kind == Kind.BOOLEAN) { + // Boolean does not have a supertype and only itself as a subtype. + return superType.kind == this.kind; + } + if (superType.kind == Kind.CHAR || kind == Kind.CHAR) { + // Char does not have a subtype, but it is itself a subtype of any primitive type with + // a larger byte size. The following is correct for the subtype relation to floats, + // since chars are 16 bits wide and floats 32 bits or more. + return superType.kind == this.kind || (superType.byteSize() > this.byteSize() && this.kind != Kind.BYTE); + } + // Due to float >: long, all integers are subtypes of floating point types. + return (superType.isFloating() && !this.isFloating()) || + // Generally, narrower types are subtypes of wider types. + (superType.isFloating() == this.isFloating() && superType.byteSize() >= this.byteSize()); + } + + return false; } @Override diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java index 6193b6aad0c..facaefaa8f3 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -48,6 +48,7 @@ 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 static compiler.lib.template_framework.DataName.Mutability.MUTABLE_OR_IMMUTABLE; import compiler.lib.template_framework.library.Hooks; import compiler.lib.template_framework.library.CodeGenerationDataNameType; @@ -129,7 +130,8 @@ public class TestPrimitiveTypes { } // Finally, test the type by creating some DataNames (variables), and sampling - // from them. There should be no cross-over between the types. + // from them. Sampling exactly should not lead to any conversion and sampling + // subtypes should only lead to widening conversions. // IMPORTANT: since we are adding the DataName via an inserted Template, we // must chose a "transparentScope", so that the DataName escapes. If we // instead chose "scope", the test would fail, because it later @@ -150,6 +152,14 @@ public class TestPrimitiveTypes { """ )); + var assignmentTemplate = Template.make("lhsType", (PrimitiveType lhsType) -> scope( + dataNames(MUTABLE).exactOf(lhsType).sampleAndLetAs("lhs"), + dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(lhsType).sampleAndLetAs("rhs"), + """ + #lhs = #rhs; + """ + )); + var namesTemplate = Template.make(() -> scope( """ public static void test_names() { @@ -161,10 +171,16 @@ public class TestPrimitiveTypes { ).toList() ), """ - // Now sample: + // Sample exactly: """, Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList() + ), + """ + // Sample subtypes: + """, + Collections.nCopies(10, + CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(assignmentTemplate::asToken).toList() ) )), """ diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java index b34538c39c1..609b0936079 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java @@ -41,6 +41,7 @@ import compiler.lib.template_framework.TemplateToken; import static compiler.lib.template_framework.Template.scope; import compiler.lib.template_framework.library.CodeGenerationDataNameType; import compiler.lib.template_framework.library.Expression; +import compiler.lib.template_framework.library.Expression.Nesting; /** * This tests the use of the {@link Expression} from the template library. This is @@ -174,44 +175,74 @@ public class TestExpression { Expression e4 = Expression.make(myTypeA1, "<", myTypeA, ">"); Expression e5 = Expression.make(myTypeA, "[", myTypeB, "]"); - Expression e1e2 = e1.nestRandomly(List.of(e2)); - Expression e1ex = e1.nestRandomly(List.of(e3, e2, e3)); - Expression e1e4 = e1.nestRandomly(List.of(e3, e4, e3)); - Expression e1ey = e1.nestRandomly(List.of(e3, e3)); + Expression e1e2 = e1.nestRandomly(List.of(e2), Nesting.SUBTYPE); + Expression e1e2Exact = e1.nestRandomly(List.of(e2), Nesting.EXACT); + Expression e1ex = e1.nestRandomly(List.of(e3, e2, e3), Nesting.SUBTYPE); + Expression e1exExact = e1.nestRandomly(List.of(e3, e2, e3), Nesting.EXACT); + Expression e1e4 = e1.nestRandomly(List.of(e3, e4, e3), Nesting.SUBTYPE); + Expression e1e4Exact = e1.nestRandomly(List.of(e3, e4, e3), Nesting.EXACT); + Expression e1ey = e1.nestRandomly(List.of(e3, e3), Nesting.SUBTYPE); + Expression e1eyExact = e1.nestRandomly(List.of(e3, e3), Nesting.EXACT); // 5-deep nesting of e1 - Expression deep1 = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5); + Expression deep1 = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5, Nesting.SUBTYPE); + Expression deep1Exact = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5, Nesting.EXACT); // Alternating pattern - Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5); + Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5, Nesting.SUBTYPE); + Expression deep2Exact = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5, Nesting.SUBTYPE); var template = Template.make(() -> scope( "xx", e1e2.toString(), "yy\n", + "xx", e1e2Exact.toString(), "yy\n", "xx", e1ex.toString(), "yy\n", + "xx", e1exExact.toString(), "yy\n", "xx", e1e4.toString(), "yy\n", + "xx", e1e4Exact.toString(), "yy\n", "xx", e1ey.toString(), "yy\n", + "xx", e1eyExact.toString(), "yy\n", "xx", deep1.toString(), "yy\n", + "xx", deep1Exact.toString(), "yy\n", "xx", deep2.toString(), "yy\n", + "xx", deep2Exact.toString(), "yy\n", "xx", e1e2.asToken(List.of("a")), "yy\n", + "xx", e1e2Exact.asToken(List.of("a")), "yy\n", "xx", e1ex.asToken(List.of("a")), "yy\n", + "xx", e1exExact.asToken(List.of("a")), "yy\n", "xx", e1e4.asToken(List.of("a")), "yy\n", + "xx", e1e4Exact.asToken(List.of("a")), "yy\n", "xx", e1ey.asToken(List.of("a")), "yy\n", + "xx", e1eyExact.asToken(List.of("a")), "yy\n", "xx", deep1.asToken(List.of("a")), "yy\n", - "xx", deep2.asToken(List.of("a")), "yy\n" + "xx", deep1Exact.asToken(List.of("a")), "yy\n", + "xx", deep2.asToken(List.of("a")), "yy\n", + "xx", deep2Exact.asToken(List.of("a")), "yy\n" )); String expected = """ xxExpression["[(", MyTypeA, ")]"]yy xxExpression["[(", MyTypeA, ")]"]yy + xxExpression["[(", MyTypeA, ")]"]yy + xxExpression["[(", MyTypeA, ")]"]yy xxExpression["[<", MyTypeA, ">]"]yy xxExpression["[", MyTypeA, "]"]yy + xxExpression["[", MyTypeA, "]"]yy + xxExpression["[", MyTypeA, "]"]yy + xxExpression["[[[[[", MyTypeA, "]]]]]"]yy xxExpression["[[[[[", MyTypeA, "]]]]]"]yy xxExpression["[{[{[", MyTypeB, "]}]}]"]yy + xxExpression["[{[{[", MyTypeB, "]}]}]"]yy + xx[(a)]yy + xx[(a)]yy xx[(a)]yy xx[(a)]yy xx[]yy xx[a]yy + xx[a]yy + xx[a]yy xx[[[[[a]]]]]yy + xx[[[[[a]]]]]yy + xx[{[{[a]}]}]yy xx[{[{[a]}]}]yy """; String code = template.render();