diff --git a/aot-tests/pom.xml b/aot-tests/pom.xml
index 1c7b876a3..16f652297 100644
--- a/aot-tests/pom.xml
+++ b/aot-tests/pom.xml
@@ -152,8 +152,9 @@
utf8-import-module.wast
utf8-invalid-encoding.wast
+
- SpecV1TcReturnCallTest.test19,SpecV1TcReturnCallTest.test24,SpecV1TcReturnCallTest.test25,SpecV1TcReturnCallTest.test30,SpecV1TcReturnCallTest.test31,SpecV1TcReturnCallIndirectTest.test40,SpecV1TcReturnCallIndirectTest.test41,SpecV1TcReturnCallIndirectTest.test46,SpecV1TcReturnCallIndirectTest.test47
+ SpecV1BinaryTest.test40,SpecV1TcReturnCallTest.test19,SpecV1TcReturnCallTest.test24,SpecV1TcReturnCallTest.test25,SpecV1TcReturnCallTest.test30,SpecV1TcReturnCallTest.test31,SpecV1TcReturnCallIndirectTest.test40,SpecV1TcReturnCallIndirectTest.test41,SpecV1TcReturnCallIndirectTest.test46,SpecV1TcReturnCallIndirectTest.test47
diff --git a/runtime-tests/pom.xml b/runtime-tests/pom.xml
index 6a6759684..1879f940a 100644
--- a/runtime-tests/pom.xml
+++ b/runtime-tests/pom.xml
@@ -115,7 +115,11 @@
proposals/exception-handling/binary.wast
proposals/exception-handling/exports.wast
proposals/exception-handling/imports.wast
+ proposals/exception-handling/ref_null.wast
proposals/exception-handling/tag.wast
+ proposals/exception-handling/throw.wast
+ proposals/exception-handling/throw_ref.wast
+ proposals/exception-handling/try_table.wast
proposals/tail-call/return_call.wast
proposals/tail-call/return_call_indirect.wast
ref_func.wast
@@ -149,7 +153,9 @@
utf8-import-module.wast
utf8-invalid-encoding.wast
-
+
+
+ SpecV1BinaryTest.test40,SpecV1EhBinaryTest.test40
@@ -328,7 +334,11 @@
proposals/exception-handling/binary.wast
proposals/exception-handling/exports.wast
proposals/exception-handling/imports.wast
+ proposals/exception-handling/ref_null.wast
proposals/exception-handling/tag.wast
+ proposals/exception-handling/throw.wast
+ proposals/exception-handling/throw_ref.wast
+ proposals/exception-handling/try_table.wast
proposals/tail-call/return_call.wast
proposals/tail-call/return_call_indirect.wast
ref_func.wast
@@ -419,7 +429,9 @@
utf8-import-module.wast
utf8-invalid-encoding.wast
-
+
+
+ SpecV1BinaryTest.test40,SpecV1EhBinaryTest.test40
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/CtrlFrame.java b/runtime/src/main/java/com/dylibso/chicory/runtime/CtrlFrame.java
index 55e701ec0..0a67a2d30 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/CtrlFrame.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/CtrlFrame.java
@@ -12,10 +12,19 @@ final class CtrlFrame {
// the height of the stack before entering the current Control Flow instruction
public final int height;
+ // the program counter of a TRY_TABLE block
+ // TODO: do we have a better way of doing this?
+ public final int pc;
+
public CtrlFrame(OpCode opCode, int startValues, int endValues, int height) {
+ this(opCode, startValues, endValues, height, 0);
+ }
+
+ public CtrlFrame(OpCode opCode, int startValues, int endValues, int height, int pc) {
this.opCode = opCode;
this.startValues = startValues;
this.endValues = endValues;
this.height = height;
+ this.pc = pc;
}
}
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
index 3176e69ec..b52d6ea1f 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
@@ -65,6 +65,8 @@ public class Instance {
private final ExecutionListener listener;
private final Exports fluentExports;
+ private final Map exnRefs;
+
Instance(
WasmModule module,
Global[] globalInitializers,
@@ -106,6 +108,8 @@ public class Instance {
this.listener = listener;
this.fluentExports = new Exports(this);
+ this.exnRefs = new HashMap<>();
+
if (initialize) {
initialize(start);
}
@@ -317,6 +321,19 @@ public TagInstance tag(int idx) {
return tags[idx - imports.tagCount()];
}
+ public int tagCount() {
+ return tags.length;
+ }
+
+ public int registerException(WasmException ex) {
+ exnRefs.put(ex.tagIdx(), ex);
+ return ex.tagIdx();
+ }
+
+ public WasmException exn(int idx) {
+ return exnRefs.get(idx);
+ }
+
public Machine getMachine() {
return machine;
}
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java b/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java
index 5e9b1dcb6..6c7bcdec0 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/InterpreterMachine.java
@@ -7,6 +7,7 @@
import com.dylibso.chicory.wasm.ChicoryException;
import com.dylibso.chicory.wasm.types.AnnotatedInstruction;
+import com.dylibso.chicory.wasm.types.CatchOpCode;
import com.dylibso.chicory.wasm.types.FunctionType;
import com.dylibso.chicory.wasm.types.Instruction;
import com.dylibso.chicory.wasm.types.OpCode;
@@ -96,13 +97,18 @@ private long[] call(
callStack.push(stackFrame);
var imprt = instance.imports().function(funcId);
- var results = imprt.handle().apply(instance, args);
- // a host function can return null or an array of ints
- // which we will push onto the stack
- if (results != null) {
- for (var result : results) {
- stack.push(result);
+
+ try {
+ var results = imprt.handle().apply(instance, args);
+ // a host function can return null or an array of ints
+ // which we will push onto the stack
+ if (results != null) {
+ for (var result : results) {
+ stack.push(result);
+ }
}
+ } catch (WasmException e) {
+ THROW_REF(instance, instance.registerException(e), stack, stackFrame, callStack);
}
}
@@ -162,6 +168,9 @@ protected void eval(MStack stack, Instance instance, Deque callStack
case BLOCK:
BLOCK(frame, stack, instance, instruction);
break;
+ case TRY_TABLE:
+ TRY_TABLE(frame, stack, instance, instruction, frame.currentPc());
+ break;
case IF:
IF(frame, stack, instance, instruction);
break;
@@ -207,6 +216,24 @@ protected void eval(MStack stack, Instance instance, Deque callStack
// swap in place the current frame
frame = RETURN_CALL_INDIRECT(stack, instance, callStack, operands, frame);
break;
+ case THROW:
+ {
+ int tagNumber = (int) operands.get(0);
+ var tag = instance.tag(tagNumber);
+ var type = instance.type(tag.tagType().typeIdx());
+
+ var args = extractArgsForParams(stack, type.params());
+ var exception = new WasmException(instance, tagNumber, args);
+ var exceptionIdx = instance.registerException(exception);
+ frame = THROW_REF(instance, exceptionIdx, stack, frame, callStack);
+ break;
+ }
+ case THROW_REF:
+ {
+ var exceptionIdx = (int) stack.pop();
+ frame = THROW_REF(instance, exceptionIdx, stack, frame, callStack);
+ break;
+ }
case CALL_INDIRECT:
CALL_INDIRECT(stack, instance, callStack, operands);
break;
@@ -2035,6 +2062,93 @@ private static int numberOfValuesToReturn(Instance instance, AnnotatedInstructio
return sizeOf(instance.type(typeId).returns());
}
+ private static StackFrame THROW_REF(
+ Instance instance,
+ int exceptionIdx,
+ MStack stack,
+ StackFrame frame,
+ Deque callStack) {
+ var exception = instance.exn(exceptionIdx);
+ boolean found = false;
+ while (!found) {
+ while (frame.ctrlStackSize() > 0) {
+ var ctrlFrame = frame.popCtrl();
+ if (ctrlFrame.opCode != OpCode.TRY_TABLE) {
+ continue;
+ }
+
+ frame.jumpTo(ctrlFrame.pc);
+ var tryInst = frame.loadCurrentInstruction();
+
+ var catches = CatchOpCode.decode(tryInst.operands());
+ for (int i = 0; i < catches.size() && !found; i++) {
+ var currentCatch = catches.get(i);
+
+ // verify import compatibility
+ var compatibleImport = false;
+ if ((currentCatch.opcode() == CatchOpCode.CATCH
+ || currentCatch.opcode() == CatchOpCode.CATCH_REF)) {
+ var currentCatchTag = instance.tag(currentCatch.tag());
+ var exceptionTag = exception.instance().tag(exception.tagIdx());
+
+ // if it's an import we verify the compatibility
+ if (currentCatch.tag() < instance.imports().tagCount()
+ && currentCatchTag.type().paramsMatch(exceptionTag.type())
+ && currentCatchTag.type().returnsMatch(exceptionTag.type())) {
+ compatibleImport = true;
+ } else if (exceptionTag != currentCatchTag) {
+ // if it's not an import the tag should be the same
+ continue;
+ }
+ }
+
+ switch (currentCatch.opcode()) {
+ case CATCH:
+ if (currentCatch.tag() == exception.tagIdx() || compatibleImport) {
+ found = true;
+ for (var arg : exception.args()) {
+ stack.push(arg);
+ }
+ }
+ break;
+ case CATCH_REF:
+ if (currentCatch.tag() == exception.tagIdx() || compatibleImport) {
+ found = true;
+ for (var arg : exception.args()) {
+ stack.push(arg);
+ }
+ stack.push(exceptionIdx);
+ }
+ break;
+ case CATCH_ALL:
+ found = true;
+ break;
+ case CATCH_ALL_REF:
+ found = true;
+ stack.push(exceptionIdx);
+ break;
+ }
+
+ if (found) {
+ var resolvedLabel = tryInst.labelTable().get(i);
+ // BR l
+ ctrlJump(frame, stack, currentCatch.label());
+ frame.jumpTo(resolvedLabel);
+ return frame;
+ }
+ }
+ }
+ if (!found) {
+ if (callStack.isEmpty()) {
+ throw exception;
+ } else {
+ frame = callStack.pop();
+ }
+ }
+ }
+ throw new RuntimeException("unreacheable");
+ }
+
private static void BLOCK(
StackFrame frame, MStack stack, Instance instance, AnnotatedInstruction instruction) {
var paramsSize = numberOfParams(instance, instruction);
@@ -2042,6 +2156,18 @@ private static void BLOCK(
frame.pushCtrl(instruction.opcode(), paramsSize, returnsSize, stack.size() - paramsSize);
}
+ private static void TRY_TABLE(
+ StackFrame frame,
+ MStack stack,
+ Instance instance,
+ AnnotatedInstruction instruction,
+ int pc) {
+ var paramsSize = numberOfParams(instance, instruction);
+ var returnsSize = numberOfValuesToReturn(instance, instruction);
+ frame.pushCtrl(
+ instruction.opcode(), paramsSize, returnsSize, stack.size() - paramsSize, pc);
+ }
+
private static void IF(
StackFrame frame, MStack stack, Instance instance, AnnotatedInstruction instruction) {
var predValue = stack.pop();
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/StackFrame.java b/runtime/src/main/java/com/dylibso/chicory/runtime/StackFrame.java
index b7e494050..8291062b6 100644
--- a/runtime/src/main/java/com/dylibso/chicory/runtime/StackFrame.java
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/StackFrame.java
@@ -136,6 +136,10 @@ AnnotatedInstruction loadCurrentInstruction() {
return currentInstruction;
}
+ int currentPc() {
+ return pc - 1;
+ }
+
boolean isLastBlock() {
return currentInstruction.depth() == 0;
}
@@ -152,6 +156,14 @@ void pushCtrl(OpCode opcode, int startValues, int returnValues, int height) {
ctrlStack.add(new CtrlFrame(opcode, startValues, returnValues, height));
}
+ void pushCtrl(OpCode opcode, int startValues, int returnValues, int height, int pc) {
+ ctrlStack.add(new CtrlFrame(opcode, startValues, returnValues, height, pc));
+ }
+
+ int ctrlStackSize() {
+ return ctrlStack.size();
+ }
+
CtrlFrame popCtrl() {
var ctrlFrame = ctrlStack.remove(ctrlStack.size() - 1);
return ctrlFrame;
diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/WasmException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/WasmException.java
new file mode 100644
index 000000000..c49479780
--- /dev/null
+++ b/runtime/src/main/java/com/dylibso/chicory/runtime/WasmException.java
@@ -0,0 +1,26 @@
+package com.dylibso.chicory.runtime;
+
+public class WasmException extends RuntimeException {
+ private final int tagIdx;
+ private final long[] args;
+ private final Instance instance;
+
+ public WasmException(Instance instance, int tagIdx, long[] args) {
+ this.instance = instance;
+ this.tagIdx = tagIdx;
+ this.args = args.clone();
+ this.setStackTrace(new StackTraceElement[0]);
+ }
+
+ public Instance instance() {
+ return instance;
+ }
+
+ public int tagIdx() {
+ return tagIdx;
+ }
+
+ public long[] args() {
+ return args;
+ }
+}
diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java
index 5ce827e53..6a92508a2 100644
--- a/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java
+++ b/runtime/src/test/java/com/dylibso/chicory/runtime/WasmModuleTest.java
@@ -11,6 +11,7 @@
import com.dylibso.chicory.wasm.Parser;
import com.dylibso.chicory.wasm.UninstantiableException;
import com.dylibso.chicory.wasm.WasmModule;
+import com.dylibso.chicory.wasm.types.CatchOpCode;
import com.dylibso.chicory.wasm.types.MemoryLimits;
import com.dylibso.chicory.wasm.types.Table;
import com.dylibso.chicory.wasm.types.TableLimits;
@@ -541,4 +542,18 @@ public void shouldResolveMultipleAliasesByTypeForAllImports() {
assertEquals(0, instance.imports().tag(0).tag().tagType().typeIdx());
assertEquals(1, instance.imports().tag(1).tag().tagType().typeIdx());
}
+
+ @Test
+ public void correctlyReturnAllLabels() {
+ // Arrange
+ var operands = new long[] {64, 2, 0, 0, 0, 0, 0, 0};
+
+ // Act
+ var result = CatchOpCode.allLabels(operands);
+
+ // Assert
+ assertEquals(2, result.size());
+ assertEquals(0, result.get(0));
+ assertEquals(0, result.get(1));
+ }
}
diff --git a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/JavaTestGen.java b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/JavaTestGen.java
index 657429450..9b91c37ac 100644
--- a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/JavaTestGen.java
+++ b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/JavaTestGen.java
@@ -83,6 +83,7 @@ public CompilationUnit generate(String name, Wast wast, String wasmClasspath) {
// runtime imports
cu.addImport("com.dylibso.chicory.wasm.ChicoryException");
+ cu.addImport("com.dylibso.chicory.runtime.WasmException");
cu.addImport("com.dylibso.chicory.runtime.ExportFunction");
cu.addImport("com.dylibso.chicory.runtime.Instance");
@@ -189,6 +190,7 @@ public CompilationUnit generate(String name, Wast wast, String wasmClasspath) {
case ASSERT_RETURN:
case ASSERT_TRAP:
case ASSERT_EXHAUSTION:
+ case ASSERT_EXCEPTION:
{
method =
createTestMethod(
@@ -290,6 +292,7 @@ private boolean getExcluded(CommandType typ, String name) {
return excludedUnlinkableWasts.contains(name + ".wast");
case ASSERT_EXHAUSTION:
case ASSERT_TRAP:
+ case ASSERT_EXCEPTION:
return false;
default:
throw new IllegalArgumentException(typ + "not implemented");
@@ -309,6 +312,8 @@ private String getExceptionType(CommandType typ) {
case ASSERT_TRAP:
case ASSERT_EXHAUSTION:
return "ChicoryException";
+ case ASSERT_EXCEPTION:
+ return "WasmException";
default:
throw new IllegalArgumentException(typ + "not implemented");
}
@@ -365,6 +370,7 @@ private Optional generateFieldExport(
private List generateAssert(String varName, Command cmd) {
assert (cmd.type() == CommandType.ASSERT_RETURN
|| cmd.type() == CommandType.ASSERT_TRAP
+ || cmd.type() == CommandType.ASSERT_EXCEPTION
|| cmd.type() == CommandType.ASSERT_EXHAUSTION);
assert (cmd.expected() != null);
assert (cmd.expected().length > 0);
@@ -388,7 +394,9 @@ private List generateAssert(String varName, Command cmd) {
? ".apply(ArgsAdapter.builder()" + adaptedArgs + ".build()" + ")"
: ".getValue()";
- if (cmd.type() == CommandType.ASSERT_TRAP || cmd.type() == CommandType.ASSERT_EXHAUSTION) {
+ if (cmd.type() == CommandType.ASSERT_TRAP
+ || cmd.type() == CommandType.ASSERT_EXHAUSTION
+ || cmd.type() == CommandType.ASSERT_EXCEPTION) {
var assertDecl =
new NameExpr(
"var exception ="
diff --git a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/CommandType.java b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/CommandType.java
index fb76680d9..bd0da3298 100644
--- a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/CommandType.java
+++ b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/CommandType.java
@@ -20,6 +20,8 @@ public enum CommandType {
ASSERT_EXHAUSTION("assert_exhaustion"),
@JsonProperty("assert_unlinkable")
ASSERT_UNLINKABLE("assert_unlinkable"),
+ @JsonProperty("assert_exception")
+ ASSERT_EXCEPTION("assert_exception"),
@JsonProperty("action")
ACTION("action"),
@JsonProperty("register")
diff --git a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValue.java b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValue.java
index d401d4258..21c9437cf 100644
--- a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValue.java
+++ b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValue.java
@@ -34,6 +34,7 @@ public String toResultValue(String result) {
case F64:
return "Double.longBitsToDouble(" + result + "), 0.0";
case EXTERN_REF:
+ case EXN_REF:
case FUNC_REF:
if (result.equals("null")) {
return "Value.REF_NULL_VALUE";
@@ -154,6 +155,7 @@ public String toExpectedValue() {
return "null";
}
case EXTERN_REF:
+ case EXN_REF:
case FUNC_REF:
if (value[0].equals("null")) {
return "Value.REF_NULL_VALUE";
@@ -258,6 +260,7 @@ public String toArgsValue() {
return "null";
}
case EXTERN_REF:
+ case EXN_REF:
case FUNC_REF:
if (value[0].toString().equals("null")) {
return "Value.REF_NULL_VALUE";
diff --git a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValueType.java b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValueType.java
index 24475334a..38485afb3 100644
--- a/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValueType.java
+++ b/test-gen-lib/src/main/java/com/dylibso/chicory/testgen/wast/WasmValueType.java
@@ -17,7 +17,9 @@ public enum WasmValueType {
@JsonProperty("externref")
EXTERN_REF("externref"),
@JsonProperty("funcref")
- FUNC_REF("funcref");
+ FUNC_REF("funcref"),
+ @JsonProperty("exnref")
+ EXN_REF("exnref");
private final String value;
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java b/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java
index 313f3499a..c45b95fb9 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/Parser.java
@@ -16,6 +16,7 @@
import com.dylibso.chicory.wasm.types.ActiveDataSegment;
import com.dylibso.chicory.wasm.types.ActiveElement;
import com.dylibso.chicory.wasm.types.AnnotatedInstruction;
+import com.dylibso.chicory.wasm.types.CatchOpCode;
import com.dylibso.chicory.wasm.types.CodeSection;
import com.dylibso.chicory.wasm.types.CustomSection;
import com.dylibso.chicory.wasm.types.DataCountSection;
@@ -828,6 +829,7 @@ private static CodeSection parseCodeSection(ByteBuffer buffer) {
case BLOCK:
case LOOP:
case IF:
+ case TRY_TABLE:
{
depth++;
instruction.withDepth(depth);
@@ -926,6 +928,32 @@ private static CodeSection parseCodeSection(ByteBuffer buffer) {
instruction.withLabelTable(labelTable);
break;
}
+ case TRY_TABLE:
+ {
+ // labels computation
+ var allLabels = CatchOpCode.allLabels(baseInstruction.operands());
+ var labelTable = new ArrayList();
+ for (var idx = 0; idx < allLabels.size(); idx++) {
+ labelTable.add(null);
+ var offset = allLabels.get(idx);
+ ControlTree reference = currentControlFlow;
+ while (offset > 0) {
+ if (reference == null) {
+ throw new InvalidException("unknown label");
+ }
+ reference = reference.parent();
+ offset--;
+ }
+ int finalIdx = idx;
+ reference.addCallback(end -> labelTable.set(finalIdx, end));
+ }
+ instruction.withLabelTable(labelTable);
+
+ // block start
+ currentControlFlow =
+ currentControlFlow.spawn(instructions.size(), instruction);
+ break;
+ }
case END:
{
currentControlFlow.setFinalInstructionNumber(
@@ -1072,6 +1100,27 @@ private static Instruction parseInstruction(ByteBuffer buffer) {
}
break;
}
+ case VEC_CATCH:
+ {
+ var n = readVarUInt32(buffer);
+ operands.add(n);
+ for (var j = 0; j < n; j++) {
+ var catchOp = readByte(buffer);
+ operands.add(0L | catchOp);
+ var catchOpcode = CatchOpCode.byOpCode(catchOp);
+ switch (catchOpcode) {
+ case CATCH:
+ case CATCH_REF:
+ operands.add(readVarUInt32(buffer)); // tag
+ // intentional fall-through
+ case CATCH_ALL:
+ case CATCH_ALL_REF:
+ operands.add(readVarUInt32(buffer)); // label
+ break;
+ }
+ }
+ break;
+ }
case V128:
{
byte[] bytes = new byte[16];
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/Validator.java b/wasm/src/main/java/com/dylibso/chicory/wasm/Validator.java
index f74e02f2b..d73d41fce 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/Validator.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/Validator.java
@@ -8,6 +8,7 @@
import com.dylibso.chicory.wasm.types.ActiveDataSegment;
import com.dylibso.chicory.wasm.types.ActiveElement;
import com.dylibso.chicory.wasm.types.AnnotatedInstruction;
+import com.dylibso.chicory.wasm.types.CatchOpCode;
import com.dylibso.chicory.wasm.types.DeclarativeElement;
import com.dylibso.chicory.wasm.types.Element;
import com.dylibso.chicory.wasm.types.ExternalType;
@@ -20,6 +21,8 @@
import com.dylibso.chicory.wasm.types.MutabilityType;
import com.dylibso.chicory.wasm.types.OpCode;
import com.dylibso.chicory.wasm.types.TableImport;
+import com.dylibso.chicory.wasm.types.TagImport;
+import com.dylibso.chicory.wasm.types.TagSection;
import com.dylibso.chicory.wasm.types.TagType;
import com.dylibso.chicory.wasm.types.Value;
import com.dylibso.chicory.wasm.types.ValueType;
@@ -27,6 +30,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
@@ -84,6 +88,7 @@ public CtrlFrame(
private final List globalImports;
private final List functionImports;
private final List tableImports;
+ private final List tagImports;
private final int memoryImports;
private final Set declaredFunctions;
@@ -119,6 +124,13 @@ public CtrlFrame(
.flatMap(element -> element.initializers().stream())
.flatMap(this::declaredFunctions)
.collect(toSet());
+
+ this.tagImports =
+ module.importSection().stream()
+ .filter(TagImport.class::isInstance)
+ .map(TagImport.class::cast)
+ .map(TagImport::tagType)
+ .collect(toList());
}
private Stream declaredFunctions(List init) {
@@ -144,7 +156,7 @@ private ValueType popVal() {
if (valueTypeStack.size() == frame.height) {
errors.add(
new InvalidException(
- "type mismatch, popVal(), stack reached limit at " + frame.height));
+ "type mismatch: instruction requires [i32] but stack has []"));
return ValueType.UNKNOWN;
}
return valueTypeStack.remove(valueTypeStack.size() - 1);
@@ -155,10 +167,11 @@ private ValueType popVal(ValueType expected) {
if (actual != expected && actual != ValueType.UNKNOWN && expected != ValueType.UNKNOWN) {
errors.add(
new InvalidException(
- "type mismatch, popVal(expected), expected: "
- + expected
- + " but got: "
- + actual));
+ "type mismatch: instruction requires ["
+ + expected.toString().toLowerCase(Locale.ROOT)
+ + "] but stack has ["
+ + actual.toString().toLowerCase(Locale.ROOT)
+ + "]"));
}
return actual;
}
@@ -312,6 +325,16 @@ private ValueType getTableType(int idx) {
return module.tableSection().getTable(idx - tableImports.size()).elementType();
}
+ private TagType getTagType(int idx) {
+ if (idx < 0 || idx >= tagImports.size() + module.tagSection().get().tagCount()) {
+ throw new InvalidException("unknown tag " + idx);
+ }
+ if (idx < tagImports.size()) {
+ return tagImports.get(idx);
+ }
+ return module.tagSection().get().getTag(idx - tagImports.size());
+ }
+
private Element getElement(int idx) {
if (idx < 0 || idx >= module.elementSection().elementCount()) {
throw new InvalidException("unknown elem segment " + idx);
@@ -445,7 +468,9 @@ private void validateConstantExpression(
{
exprType = ValueType.refTypeForId((int) instruction.operand(0));
constInstrCount++;
- if (exprType != ValueType.ExternRef && exprType != ValueType.FuncRef) {
+ if (exprType != ValueType.ExternRef
+ && exprType != ValueType.FuncRef
+ && exprType != ValueType.ExnRef) {
throw new IllegalStateException(
"Unexpected wrong type for ref.null instruction");
}
@@ -530,6 +555,79 @@ void validateFunction(int funcIdx, FunctionBody body, FunctionType functionType)
case UNREACHABLE:
unreachable();
break;
+ case TRY_TABLE:
+ {
+ var t1 = getParams(op);
+ var t2 = getReturns(op);
+ popVals(t1);
+ // and now the catches
+ var catches = CatchOpCode.decode(op.operands());
+
+ for (int idx = 0; idx < catches.size(); idx++) {
+ var currentCatch = catches.get(idx);
+ if (ctrlFrameStack.size() < currentCatch.label()) {
+ throw new InvalidException("something something");
+ }
+ // push_ctrl(catch, [], label_types(ctrls[handler.label]))
+ // using THROW instead of CATCH ... doesn't matter as it's removed right
+ // after
+ pushCtrl(
+ OpCode.THROW,
+ List.of(),
+ labelTypes(getCtrl(currentCatch.label())));
+ switch (currentCatch.opcode()) {
+ case CATCH:
+ {
+ var tagType =
+ module.typeSection()
+ .getType(
+ getTagType(currentCatch.tag())
+ .typeIdx());
+ pushVals(tagType.params());
+ break;
+ }
+ case CATCH_REF:
+ {
+ var tagType =
+ module.typeSection()
+ .getType(
+ getTagType(currentCatch.tag())
+ .typeIdx());
+ pushVals(tagType.params());
+ pushVal(ValueType.ExnRef);
+ break;
+ }
+ case CATCH_ALL:
+ break;
+ case CATCH_ALL_REF:
+ pushVal(ValueType.ExnRef);
+ break;
+ }
+ popCtrl();
+ }
+ pushCtrl(op.opcode(), t1, t2);
+ break;
+ }
+ case THROW:
+ {
+ var tagNumber = (int) op.operand(0);
+ if ((tagImports.size()
+ + module.tagSection().map(TagSection::tagCount).orElse(0))
+ <= tagNumber) {
+ throw new InvalidException("unknown tag " + tagNumber);
+ }
+ var type = module.typeSection().getType(getTagType(tagNumber).typeIdx());
+ popVals(type.params());
+ assert (type.returns().size() == 0);
+ unreachable();
+ break;
+ }
+ case THROW_REF:
+ {
+ popVal(ValueType.ExnRef);
+ unreachable();
+ break;
+ }
case IF:
popVal(ValueType.I32);
// fallthrough
@@ -735,6 +833,9 @@ void validateFunction(int funcIdx, FunctionBody body, FunctionType functionType)
switch (op.opcode()) {
case NOP:
case UNREACHABLE:
+ case THROW:
+ case THROW_REF:
+ case TRY_TABLE:
case LOOP:
case BLOCK:
case IF:
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/AnnotatedInstruction.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/AnnotatedInstruction.java
index 428724d90..b0747ea19 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/AnnotatedInstruction.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/AnnotatedInstruction.java
@@ -138,6 +138,7 @@ public AnnotatedInstruction build() {
case LOOP:
case END:
case IF:
+ case TRY_TABLE:
assert (scope.isPresent());
break;
default:
@@ -164,6 +165,7 @@ public AnnotatedInstruction build() {
}
switch (base.opcode()) {
case BR_TABLE:
+ case TRY_TABLE:
if (labelTable.isEmpty()) {
throw new InvalidException("unknown label table" + base);
}
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/CatchOpCode.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/CatchOpCode.java
new file mode 100644
index 000000000..b0489004b
--- /dev/null
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/CatchOpCode.java
@@ -0,0 +1,99 @@
+package com.dylibso.chicory.wasm.types;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public enum CatchOpCode {
+ CATCH(0x00),
+ CATCH_REF(0x01),
+ CATCH_ALL(0x02),
+ CATCH_ALL_REF(0x03);
+
+ private static final int OP_CODES_SIZE = 4;
+
+ // trick: the enum constructor cannot access its own static fields
+ // but can access another class
+ private static final class CatchOpCodes {
+ private CatchOpCodes() {}
+
+ private static final CatchOpCode[] byOpCode = new CatchOpCode[OP_CODES_SIZE];
+ }
+
+ private final int opcode;
+
+ CatchOpCode(int opcode) {
+ this.opcode = opcode;
+ CatchOpCodes.byOpCode[opcode] = this;
+ }
+
+ public int opcode() {
+ return opcode;
+ }
+
+ public static CatchOpCode byOpCode(int opcode) {
+ return CatchOpCodes.byOpCode[opcode];
+ }
+
+ public static final class Catch {
+ private final CatchOpCode opcode;
+ private final int tag;
+ private final int label;
+
+ private Catch(CatchOpCode opcode, int label) {
+ this(opcode, -1, label);
+ assert (opcode == CATCH_ALL || opcode == CATCH_ALL_REF);
+ }
+
+ private Catch(CatchOpCode opcode, int tag, int label) {
+ assert (tag == -1 || opcode == CATCH || opcode == CATCH_REF);
+ this.opcode = opcode;
+ this.tag = tag;
+ this.label = label;
+ }
+
+ public CatchOpCode opcode() {
+ return opcode;
+ }
+
+ public int tag() {
+ return tag;
+ }
+
+ public int label() {
+ return label;
+ }
+ }
+
+ @SuppressWarnings("ModifiedControlVariable")
+ public static List decode(long[] operands) {
+ var length = operands[1];
+ var result = new ArrayList();
+ for (int i = 2; i < operands.length; i++) {
+ var catchEnum = CatchOpCode.byOpCode((int) operands[i++]);
+ switch (catchEnum) {
+ case CATCH:
+ case CATCH_REF:
+ {
+ var tag = (int) operands[i++];
+ var label = (int) operands[i];
+ result.add(new Catch(catchEnum, tag, label));
+ break;
+ }
+ case CATCH_ALL:
+ case CATCH_ALL_REF:
+ {
+ var label = (int) operands[i];
+ result.add(new Catch(catchEnum, label));
+ break;
+ }
+ }
+ }
+ assert (result.size() == length);
+ return result;
+ }
+
+ public static List allLabels(long[] operands) {
+ return decode(operands).stream().map(Catch::label).collect(Collectors.toList());
+ }
+}
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionType.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionType.java
index 41ea661d8..5c0dc366e 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionType.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/FunctionType.java
@@ -51,6 +51,7 @@ public int hashCode() {
static {
EnumMap map = new EnumMap<>(ValueType.class);
map.put(ValueType.ExternRef, new FunctionType(List.of(), List.of(ValueType.ExternRef)));
+ map.put(ValueType.ExnRef, new FunctionType(List.of(), List.of(ValueType.ExnRef)));
map.put(ValueType.FuncRef, new FunctionType(List.of(), List.of(ValueType.FuncRef)));
map.put(ValueType.V128, new FunctionType(List.of(), List.of(ValueType.V128)));
map.put(ValueType.F64, new FunctionType(List.of(), List.of(ValueType.F64)));
@@ -60,6 +61,7 @@ public int hashCode() {
returning = map;
map = new EnumMap<>(ValueType.class);
map.put(ValueType.ExternRef, new FunctionType(List.of(ValueType.ExternRef), List.of()));
+ map.put(ValueType.ExnRef, new FunctionType(List.of(ValueType.ExnRef), List.of()));
map.put(ValueType.FuncRef, new FunctionType(List.of(ValueType.FuncRef), List.of()));
map.put(ValueType.V128, new FunctionType(List.of(ValueType.V128), List.of()));
map.put(ValueType.F64, new FunctionType(List.of(ValueType.F64), List.of()));
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/OpCode.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/OpCode.java
index d9c1f641d..fc7d2d59d 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/OpCode.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/OpCode.java
@@ -7,6 +7,7 @@
import static com.dylibso.chicory.wasm.types.WasmEncoding.VARSINT32;
import static com.dylibso.chicory.wasm.types.WasmEncoding.VARSINT64;
import static com.dylibso.chicory.wasm.types.WasmEncoding.VARUINT;
+import static com.dylibso.chicory.wasm.types.WasmEncoding.VEC_CATCH;
import static com.dylibso.chicory.wasm.types.WasmEncoding.VEC_VARUINT;
import java.util.List;
@@ -18,6 +19,8 @@ public enum OpCode {
LOOP(0x03, List.of(VARUINT)),
IF(0x04, List.of(VARUINT)),
ELSE(0x05),
+ THROW(0x08, List.of(VARUINT)),
+ THROW_REF(0x0A),
END(0x0B),
BR(0x0C, List.of(VARUINT)),
BR_IF(0x0D, List.of(VARUINT)),
@@ -31,6 +34,7 @@ public enum OpCode {
DROP(0x1A),
SELECT(0x1B),
SELECT_T(0x1C, List.of(VEC_VARUINT)),
+ TRY_TABLE(0x1F, List.of(VARUINT, VEC_CATCH)),
LOCAL_GET(0x20, List.of(VARUINT)),
LOCAL_SET(0x21, List.of(VARUINT)),
LOCAL_TEE(0x22, List.of(VARUINT)),
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/Value.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/Value.java
index bfbf042df..26f716d30 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/Value.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/Value.java
@@ -274,6 +274,7 @@ public static long zero(ValueType valueType) {
case I64:
case F64:
return 0L;
+ case ExnRef:
case FuncRef:
case ExternRef:
return REF_NULL_VALUE;
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ValueType.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ValueType.java
index 303d261f0..234e76c7e 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/ValueType.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/ValueType.java
@@ -1,5 +1,7 @@
package com.dylibso.chicory.wasm.types;
+import static com.dylibso.chicory.wasm.types.ValueType.ID.ExnRef;
+
import com.dylibso.chicory.wasm.MalformedException;
import java.util.List;
@@ -14,6 +16,7 @@ public enum ValueType {
I32(ID.I32),
V128(ID.V128),
FuncRef(ID.FuncRef),
+ ExnRef(ID.ExnRef),
ExternRef(ID.ExternRef);
private final int id;
@@ -96,6 +99,7 @@ public boolean isFloatingPoint() {
public boolean isReference() {
switch (this) {
case FuncRef:
+ case ExnRef:
case ExternRef:
return true;
default:
@@ -108,13 +112,14 @@ public boolean isReference() {
*/
public static boolean isValid(int typeId) {
switch (typeId) {
- case ID.F64:
case ID.ExternRef:
+ case ID.ExnRef:
case ID.FuncRef:
case ID.V128:
case ID.I32:
case ID.I64:
case ID.F32:
+ case ID.F64:
return true;
default:
return false;
@@ -140,6 +145,8 @@ public static ValueType forId(int id) {
return V128;
case ID.FuncRef:
return FuncRef;
+ case ID.ExnRef:
+ return ExnRef;
case ID.ExternRef:
return ExternRef;
default:
@@ -158,6 +165,8 @@ public static ValueType refTypeForId(int id) {
return FuncRef;
case ID.ExternRef:
return ExternRef;
+ case ID.ExnRef:
+ return ExnRef;
default:
throw new MalformedException("malformed reference type " + id);
}
@@ -184,6 +193,8 @@ static final class ID {
private ID() {}
static final int ExternRef = 0x6f;
+ // From the Exception Handling proposal
+ static final int ExnRef = 0x69; // -0x17
static final int FuncRef = 0x70;
static final int V128 = 0x7b;
static final int F64 = 0x7c;
diff --git a/wasm/src/main/java/com/dylibso/chicory/wasm/types/WasmEncoding.java b/wasm/src/main/java/com/dylibso/chicory/wasm/types/WasmEncoding.java
index 754a9c151..56040b364 100644
--- a/wasm/src/main/java/com/dylibso/chicory/wasm/types/WasmEncoding.java
+++ b/wasm/src/main/java/com/dylibso/chicory/wasm/types/WasmEncoding.java
@@ -7,6 +7,7 @@ public enum WasmEncoding {
FLOAT32,
FLOAT64,
VEC_VARUINT,
+ VEC_CATCH,
BYTE,
V128
}