diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Block.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Block.java index d1b1e2f0b72..b05d1a3ac3e 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Block.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Block.java @@ -684,11 +684,12 @@ public void body(Body body, List values, */ public Op.Result op(Op op) { check(); + final Op.Result oprToTransform = op.result(); Op transformedOp = op; - if (oprToTransform != null) { - // If operation is assigned to block, then copy it and transform its contents + if (op.isSealed() || oprToTransform != null) { + // If operation is assigned to block, or it's sealed, then copy it and transform its contents transformedOp = op.transform(cc, ot); assert transformedOp.result == null; } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index b5570d645e5..a8e54c6ce64 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -224,8 +224,19 @@ public interface BlockTerminating extends Terminating { * A value that is the result of an operation. */ public static final class Result extends Value { + + /** + * If assigned to an operation result, it indicates the operation is sealed + */ + private static final Result SEALED_RESULT = new Result(); + final Op op; + private Result() { + super(null, null); + this.op = null; + } + Result(Block block, Op op) { super(block, op.resultType()); @@ -332,11 +343,11 @@ protected Op(String name, List operands) { * Sets the originating source location of this operation, if unbound. * * @param l the location, the {@link Location#NO_LOCATION} value indicates the location is not specified. - * @throws IllegalStateException if this operation is bound + * @throws IllegalStateException if this operation is bound or sealed */ public final void setLocation(Location l) { // @@@ Fail if location != null? - if (result != null && result.block.isBound()) { + if (isSealed() || (result != null && result.block.isBound())) { throw new IllegalStateException(); } @@ -351,13 +362,13 @@ public final Location location() { } /** - * Returns this operation's parent block, otherwise {@code null} if the operation is not assigned to a block. + * Returns this operation's parent block, otherwise {@code null} if the operation is unbound or sealed. * - * @return operation's parent block, or {@code null} if the operation is not assigned to a block. + * @return operation's parent block, or {@code null} if the operation is unbound or sealed. */ @Override public final Block parent() { - if (result == null) { + if (isSealed() || result == null) { return null; } @@ -382,12 +393,12 @@ public List bodies() { } /** - * Returns the operation's result, otherwise {@code null} if the operation is not assigned to a block. + * Returns the operation's result, otherwise {@code null} if the operation is unbound or sealed. * - * @return the operation's result, or {@code null} if not assigned to a block. + * @return the operation's result, or {@code null} if unbound or sealed. */ public final Result result() { - return result; + return result == Result.SEALED_RESULT ? null : result; } /** @@ -589,4 +600,33 @@ public static Optional ofElement(ProcessingEnvironment processingEnviron return Optional.empty(); } } + + /** + * Seals this operation. After this operation is sealed its {@link #result result} and {@link #parent parent} are guaranteed to always be {@code null}. + *

+ * If a sealed operation is {@link Block.Builder#op appended} to a {@link Block.Builder} then it is + * treated as if the operation is bound, and therefore the sealed operation will be transformed. + *

+ * Sealing is idempotent if the operation is already sealed. + * + * @throws IllegalStateException if this operation is bound. + */ + public void seal() { + if (result == Result.SEALED_RESULT) { + return; + } + if (result != null) { + throw new IllegalStateException("Operation cannot be sealed since it bound to a parent block"); + } + result = Result.SEALED_RESULT; + } + + /** + * Returns {@code true} if this operation is sealed. + * @return {@code true} if this operation is sealed. + * @see #seal() + * */ + public boolean isSealed() { + return result == Result.SEALED_RESULT; + } } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/OpBuilder.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/OpBuilder.java index 1bfaffb0529..c12d07359a2 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/OpBuilder.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/OpBuilder.java @@ -175,6 +175,8 @@ public static FuncOp createBuilderFunction(Op op, Function FuncOp build(Op op) { Value ancestorBody = builder.op(constant(type(Body.Builder.class), null)); Value result = buildOp(ancestorBody, op); + // seal op + builder.op(invoke(MethodRef.method(Op.class, "seal", void.class), result)); builder.op(return_(result)); return func("builder." + op.opName(), builder.parentBody()); diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java index 6fb0b3e65b6..f00c12477fd 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java @@ -2729,6 +2729,7 @@ class CodeModelTranslator { Op.Result.class, Op.class); private static final MethodRef M_BLOCK_BUILDER_PARAM = MethodRef.method(Block.Builder.class, "parameter", Block.Parameter.class, TypeElement.class); + private static final MethodRef M_OP_SEAL = MethodRef.method(Op.class, "seal", void.class); private final Map valueToTree = new HashMap<>(); private int localVarCount = 0; // used to name variables we introduce in the AST @@ -2771,14 +2772,14 @@ private void setVarargs(JCExpression tree, FunctionType type) { } } + private static final Set mRefs = Set.of(M_BLOCK_BUILDER_OP, M_BLOCK_BUILDER_PARAM, M_OP_SEAL); public JCTree.JCStatement translateFuncOp(CoreOp.FuncOp funcOp, MethodSymbol ms) { Assert.check(funcOp.parameters().isEmpty()); Assert.check(funcOp.body().blocks().size() == 1); java.util.List rootValues = funcOp.traverse(new ArrayList<>(), (l, ce) -> { boolean isRoot = switch (ce) { - case JavaOp.InvokeOp invokeOp when invokeOp.invokeDescriptor().equals(M_BLOCK_BUILDER_OP) - || invokeOp.invokeDescriptor().equals(M_BLOCK_BUILDER_PARAM) -> true; + case JavaOp.InvokeOp invokeOp when mRefs.contains(invokeOp.invokeDescriptor()) -> true; case CoreOp.ReturnOp _, JavaOp.ArrayAccessOp.ArrayStoreOp _ -> true; case Op op when op.result() != null && op.result().uses().size() > 1 -> true; default -> false; diff --git a/test/jdk/java/lang/reflect/code/TestSealOp.java b/test/jdk/java/lang/reflect/code/TestSealOp.java new file mode 100644 index 00000000000..098e00fde3e --- /dev/null +++ b/test/jdk/java/lang/reflect/code/TestSealOp.java @@ -0,0 +1,91 @@ +import jdk.incubator.code.*; +import jdk.incubator.code.dialect.core.CoreOp; +import jdk.incubator.code.dialect.core.FunctionType; +import jdk.incubator.code.dialect.java.JavaType; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntUnaryOperator; + +/* + * @test + * @modules jdk.incubator.code + * @run testng TestSealOp + */ +public class TestSealOp { + + @CodeReflection + static List f(int i) { + return new ArrayList<>(i); + } + + @Test + void test0() throws NoSuchMethodException { + Method m = this.getClass().getDeclaredMethod("f", int.class); + CoreOp.FuncOp f = Op.ofMethod(m).get(); + assertOpIsCopiedWhenAddedToBlock(f); + } + + @Test + void test1() { + Quotable q = (IntUnaryOperator & Quotable) i -> i / 2; + Quoted quoted = Op.ofQuotable(q).get(); + CoreOp.QuotedOp quotedOp = (CoreOp.QuotedOp) quoted.op().ancestorBody().ancestorOp(); + CoreOp.FuncOp funcOp = (CoreOp.FuncOp) quotedOp.ancestorBody().ancestorOp(); + assertOpIsCopiedWhenAddedToBlock(funcOp); + } + + @Test + void test2() { + CoreOp.ConstantOp constant = CoreOp.constant(JavaType.INT, 7); + constant.seal(); + assertOpIsCopiedWhenAddedToBlock(constant); + } + + @Test + void test3() { + CoreOp.FuncOp funcOp = CoreOp.func("f", FunctionType.FUNCTION_TYPE_VOID).body(b -> { + b.op(CoreOp.return_()); + }); + funcOp.seal(); + funcOp.seal(); + } + + @Test + void test4() { + Quoted q = (int a, int b) -> { + return a + b; + }; + CoreOp.QuotedOp quotedOp = (CoreOp.QuotedOp) q.op().ancestorBody().ancestorOp(); + CoreOp.FuncOp funcOp = (CoreOp.FuncOp) quotedOp.ancestorBody().ancestorOp(); + Assert.assertTrue(funcOp.isSealed()); + assertOpIsCopiedWhenAddedToBlock(funcOp); + } + + @Test + void test5() { // freezing an already bound op should throw + Body.Builder body = Body.Builder.of(null, FunctionType.FUNCTION_TYPE_VOID); + Op.Result r = body.entryBlock().op(CoreOp.constant(JavaType.DOUBLE, 1d)); + Assert.assertThrows(() -> r.op().seal()); + } + + @Test + void test6() { + CoreOp.ConstantOp cop = CoreOp.constant(JavaType.LONG, 1L); + cop.setLocation(Location.NO_LOCATION); + cop.seal(); + Assert.assertThrows(() -> cop.setLocation(Location.NO_LOCATION)); + } + + void assertOpIsCopiedWhenAddedToBlock(Op op) { + Body.Builder body = Body.Builder.of(null, FunctionType.FUNCTION_TYPE_VOID); + body.entryBlock().op(op); + body.entryBlock().op(CoreOp.return_()); + CoreOp.FuncOp funcOp = CoreOp.func("t", body); + boolean b = funcOp.body().entryBlock().ops().stream().allMatch(o -> o != op); + Assert.assertTrue(b); + } +}