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 extends Value> 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 extends Value> 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);
+ }
+}