* Ref: https://github.com/WebAssembly/exception-handling */ + public static final class TryTable extends WasmBlock { + /** + * A catch clause for a certain tag. + *
+ * When an exception is caught, the block branches to the label of the appropriate clause.
+ */
+ public static final class Catch {
+ public final WasmId.Tag tag;
+ public final WasmId.Label label;
+
+ private Catch(WasmId.Tag tag, WasmId.Label label) {
+ this.tag = tag;
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return "Catch{tag=" + tag + ", label=" + label + '}';
+ }
+ }
+
+ public final Instructions instructions = new Instructions();
+ public final List
+ * Ref:
+ * https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/legacy/Exceptions.md
+ */
public static final class Try extends WasmBlock {
/**
@@ -222,7 +285,7 @@ private Catch(WasmId.Tag tag) {
public final List
+ * An unreachable frame satisfies all requirements on the operand stack (i.e.
+ * {@link #popVals(WasmValType...) never errors.}).
+ *
+ * Call this method after visiting any instruction at which execution stops and never returns
+ * (e.g. {@code br}).
+ */
+ private void markUnreachable() {
CtrlFrame top = topFrame();
vals.clear();
top.unreachable = true;
@@ -451,8 +514,15 @@ private void applyTypeUse(TypeUse typeUse) {
typeUse.results.forEach(this::pushVal);
}
- private void assertLabelExists(WasmId.Label label) {
- errorIf(ctrls.stream().noneMatch(frame -> assertIdsEqual(label, frame.label)), "Label " + label + " does not exist.");
+ private CtrlFrame markLabelTargeted(WasmId.Label label) {
+ CtrlFrame frame = ctrls.stream().filter(f -> idsEqual(label, f.label)).findFirst().orElseThrow(() -> error("Label " + label + " does not exist."));
+ frame.targeted = true;
+ return frame;
+ }
+
+ private void markLabelTargetedWithReturnType(WasmId.Label label, WasmValType returnType) {
+ CtrlFrame frame = markLabelTargeted(label);
+ errorIf(!Objects.equals(frame.getReturnType(), returnType), "Label " + label + " has return type " + frame.getReturnType() + " but " + returnType + " was expected");
}
/**
@@ -566,35 +636,79 @@ public void visitInstruction(Instruction inst) {
@Override
public void visitBlock(Instruction.Block block) {
- pushCtrl(block.getLabel());
+ pushBlockCtrl(block);
super.visitBlock(block);
- popCtrl(block.getLabel());
+ popBlockCtrl(block);
}
@Override
public void visitLoop(Instruction.Loop loop) {
- pushCtrl(loop.getLabel());
+ pushBlockCtrl(loop);
super.visitLoop(loop);
- popCtrl(loop.getLabel());
+ popBlockCtrl(loop);
}
@Override
public void visitIf(Instruction.If ifBlock) {
visitInstruction(ifBlock.condition);
popVals(i32);
- pushCtrl(ifBlock.getLabel());
+ pushBlockCtrl(ifBlock);
+
+ /*
+ * Propagating the information about unreachability upward here requires some more work. We
+ * only want to mark the end of the if-block as unreachable if both the end of the then- and
+ * else-branches are unreachable.
+ */
+ boolean thenBlockUnreachable = false;
+ boolean elseBlockUnreachable = false;
+
+ // Control frame around both branches to intercept the unreachable state
+ pushCtrl(null);
+
+ // Control frame around the then-branch
+ pushCtrl(null);
visitInstructions(ifBlock.thenInstructions);
+ if (topFrame().unreachable) {
+ thenBlockUnreachable = true;
+ topFrame().unreachable = false;
+ }
+ popCtrl(null);
+
if (ifBlock.hasElse()) {
- popCtrl(ifBlock.getLabel());
- pushCtrl(ifBlock.getLabel());
+ pushCtrl(null);
visitInstructions(ifBlock.elseInstructions);
+ if (topFrame().unreachable) {
+ elseBlockUnreachable = true;
+ topFrame().unreachable = false;
+ }
+ popCtrl(null);
+ }
+ if (thenBlockUnreachable && elseBlockUnreachable) {
+ // This will mark the parent block as unreachable once we pop this control frame.
+ markUnreachable();
}
- popCtrl(ifBlock.getLabel());
+ popCtrl(null);
+ popBlockCtrl(ifBlock);
+ }
+
+ @Override
+ public void visitTryTable(Instruction.TryTable tryBlock) {
+ tryBlock.catchBlocks.forEach(this::assertCatchValid);
+ pushBlockCtrl(tryBlock);
+ visitInstructions(tryBlock.instructions);
+ popBlockCtrl(tryBlock);
+ }
+
+ private void assertCatchValid(Instruction.TryTable.Catch catchClause) {
+ errorIf(!ctxt.hasTag(catchClause.tag), "No matching tag for catch clause: " + catchClause);
+ List
*
- *
@@ -78,10 +78,24 @@
public class WasmValidator extends WasmVisitor {
static class CtrlFrame {
final WasmId.Label label;
+ final WasmValType returnType;
+ /**
+ * Whether the end of this frame (block) is unreachable. There might still be reachable code
+ * in the frame, but if this is true, execution can never reach the end of the block.
+ */
boolean unreachable = false;
+ /**
+ * Whether any instructions target this block.
+ */
+ boolean targeted = false;
- CtrlFrame(WasmId.Label label) {
+ CtrlFrame(WasmId.Label label, WasmValType returnType) {
this.label = label;
+ this.returnType = returnType;
+ }
+
+ public WasmValType getReturnType() {
+ return returnType;
}
}
@@ -343,7 +357,7 @@ private void errorIf(boolean condition, String msg) {
}
}
- private static boolean assertIdsEqual(WasmId first, WasmId second) {
+ private static boolean idsEqual(WasmId first, WasmId second) {
return Objects.equals(first, second);
}
@@ -351,13 +365,22 @@ private CtrlFrame topFrame() {
return Objects.requireNonNull(ctrls.peek());
}
+ /**
+ * Whether the current frame is marked as unreachable.
+ *
+ * @see #markUnreachable()
+ */
+ private boolean isUnreachable() {
+ return topFrame().unreachable;
+ }
+
private void pushVal(WasmValType t) {
+ errorIf(isUnreachable(), "Tried to push " + t + " when unreachable");
vals.push(t);
}
private WasmValType popVal() {
- CtrlFrame top = topFrame();
- if (vals.isEmpty() && top.unreachable) {
+ if (isUnreachable()) {
return null;
}
@@ -383,6 +406,10 @@ private RuntimeException typeMismatch(WasmValType[] expected, Deque
+ * {@code
+ * (block $exnBlock (result $throwable)
+ * (try_table (catch $exc_tag $exnBlock)
+ * WithExceptionNode();
+ * br Successor
+ * )
+ * )
+ * (local.set $exc_var)
+ * ExceptionEdge();
+ * }
+ *
+ *
+ * The {@link WithExceptionNode} is wrapped in a {@code try_table} block, which is surrounded by
+ * a block that's the target of the catch scope. After the block around the {@code try_table}
+ * instruction, the thrown value is already on the stack, we store it in a dedicated local
+ * variable ({@link WebImageWasmNodeLowerer#exceptionObjectVariable}) so that it can be read
+ * later by {@link ReadExceptionObjectNode}. The
+ * {@link com.oracle.svm.hosted.webimage.wasm.phases.WasmLabeledBlockGeneration} ensures that
+ * the regular successor always requires a forward jump.
+ *
+ * @see #lowerWithException(HIRBlock, WithExceptionNode)
+ */
+ protected void lowerWithExceptionExnRef(HIRBlock currentBlock, WithExceptionNode lastNode) {
+ WebImageWasmIds.InternalLabel exceptionHandlerLabel = masm.idFactory.newInternalLabel("exn" + currentBlock.getId());
+ Instruction.Block exceptionTargetBlock = new Instruction.Block(exceptionHandlerLabel, masm.getWasmProviders().util().getThrowableType());
+ masm.genInst(exceptionTargetBlock);
+
+ masm.childScope(exceptionTargetBlock.instructions, exceptionTargetBlock);
+ Instruction.TryTable tryBlock = new Instruction.TryTable(null);
+ tryBlock.addCatch(masm.getKnownIds().getJavaThrowableTag(), exceptionHandlerLabel);
+ masm.genInst(tryBlock, lastNode);
+
+ masm.childScope(tryBlock.instructions, tryBlock);
+ lowerNode(lastNode);
+ HIRBlock normSucc = cfg.blockFor(lastNode.next());
+ boolean didJump = generateForwardJump(currentBlock, normSucc);
+ GraalError.guarantee(didJump, "No jump was inserted after a WithExceptionNode");
+ masm.parentScope(tryBlock);
+
+ masm.parentScope(exceptionTargetBlock);
+
+ masm.genInst(masm.nodeLowerer().exceptionObjectVariable.setter(new Instruction.Nop()), "Store exception object");
+ masm.lowerCatchPreamble();
+
+ CatchScopeContainer scopeEntry = (CatchScopeContainer) stackifierData.getScopeEntry(lastNode);
+ Scope catchScope = scopeEntry.getCatchScope();
+ if (catchScope != null) {
+ lowerBlocks(catchScope.getSortedBlocks(stackifierData));
+ // Just a sanity check
+ masm.genInst(new Unreachable(), "End of catch block is unreachable, it must break out");
+ } else {
+ HIRBlock excpSucc = cfg.blockFor(lastNode.exceptionEdge());
+ boolean didJumpAfterCatch = generateForwardJump(currentBlock, excpSucc);
+ GraalError.guarantee(didJumpAfterCatch, "No jump was inserted in catch block");
+ }
+ }
+
+ /**
+ * Lower a WithExceptionNode using the legacy exception handling proposal.
+ *
*
* {@code
* (try
@@ -301,13 +385,9 @@ protected void lowerLoopEnd(LoopEndNode loopEnd) {
* ({@link WebImageWasmNodeLowerer#exceptionObjectVariable}) so that it can be read later by
* {@link ReadExceptionObjectNode}.
*
- * @param currentBlock basic block that ends with {@link WithExceptionNode}
- * @param lastNode the {@link WithExceptionNode}
+ * @see #lowerWithException(HIRBlock, WithExceptionNode)
*/
- @Override
- protected void lowerWithException(HIRBlock currentBlock, WithExceptionNode lastNode) {
- assert currentBlock.getEndNode() == lastNode : currentBlock.toString(Verbosity.Name);
-
+ protected void lowerWithExceptionLegacy(HIRBlock currentBlock, WithExceptionNode lastNode) {
Instruction.Try tryBlock = new Instruction.Try(null);
masm.genInst(tryBlock, lastNode);
diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmCodeGen.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmCodeGen.java
index 4d65d92f802f..cfc100d0ffa2 100644
--- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmCodeGen.java
+++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/WebImageWasmCodeGen.java
@@ -185,10 +185,6 @@ protected void emitCode() {
module.constructActiveDataSegments();
((WebImageWasmHeapBreakdownProvider) HeapBreakdownProvider.singleton()).setActualTotalHeapSize((int) getFullImageHeapSize());
- if (WebImageOptions.DebugOptions.VerificationPhases.getValue(options)) {
- validateModule();
- }
-
emitJSCode();
try (Writer writer = Files.newBufferedWriter(watFile)) {
@@ -197,6 +193,10 @@ protected void emitCode() {
throw new RuntimeException(e);
}
+ if (WebImageOptions.DebugOptions.VerificationPhases.getValue(options)) {
+ validateModule();
+ }
+
assembleWasmFile(watFile, wasmFile);
}
diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wasm-as.wast b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wasm-as.wast
index eb165423646a..5211d57c0014 100644
--- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wasm-as.wast
+++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wasm-as.wast
@@ -23,19 +23,23 @@
(start $main)
(func $main
- (try
- (do (call $throwB))
- (catch $tag0
- (drop (call $checkException))
- )
+ (block $catchBlock (result (ref $A))
+ (try_table
+ (catch $tag0 $catchBlock)
+ (call $throwB)
+ (unreachable)
+ )
)
+ (drop (call $checkException))
- (try
- (do (call $throwC))
- (catch $tag0
- (drop (call $checkException))
- )
+ (block $catchBlock (result (ref $A))
+ (try_table
+ (catch $tag0 $catchBlock)
+ (call $throwC)
+ (unreachable)
+ )
)
+ (drop (call $checkException))
)
(func $checkException (param $p0 (ref $A)) (result (ref null $A))
diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wat2wasm.wast b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wat2wasm.wast
index af6d54a3ee8c..10a1a507eea0 100644
--- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wat2wasm.wast
+++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/codegen/verify-wat2wasm.wast
@@ -1,7 +1,7 @@
(;
Wasm text file to verify the wat2wasm assembler works correctly.
- Uses features from the exception handling proposal
+ Uses features from the legacy exception handling proposal
;)
(module
(tag $tag0 (param i32))
diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/phases/WasmLabeledBlockGeneration.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/phases/WasmLabeledBlockGeneration.java
index 7f8893a22d02..babc3534735d 100644
--- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/phases/WasmLabeledBlockGeneration.java
+++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasm/phases/WasmLabeledBlockGeneration.java
@@ -28,6 +28,8 @@
import com.oracle.svm.hosted.webimage.codegen.reconstruction.stackifier.LabeledBlockGeneration;
import com.oracle.svm.hosted.webimage.codegen.reconstruction.stackifier.StackifierData;
+import com.oracle.svm.hosted.webimage.wasm.WebImageWasmOptions;
+import jdk.graal.compiler.nodes.WithExceptionNode;
import jdk.graal.compiler.nodes.cfg.ControlFlowGraph;
import jdk.graal.compiler.nodes.cfg.HIRBlock;
@@ -53,6 +55,19 @@ public boolean isLabeledBlockNeeded(HIRBlock block, HIRBlock successor) {
return true;
}
+ if (!WebImageWasmOptions.LegacyExceptions.getValue() && block.getEndNode() instanceof WithExceptionNode withExceptionNode) {
+ HIRBlock normSucc = stackifierData.getCfg().blockFor(withExceptionNode.next());
+ if (normSucc.equals(successor)) {
+ /*
+ * With the new exception handling, we need an explicit labeled block when going
+ * from the WithExceptionNode to its regular successor because in the Wasm code, the
+ * successor does not appear directly after, the catch block does, and we would then
+ * fall through to that.
+ */
+ return true;
+ }
+ }
+
return false;
}
}