From c9a2ff8381f8764339a7f78daeee495c51d57e9d Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 14 Sep 2026 18:52:26 +0100 Subject: [PATCH] Send a non-nullable (ref exn) to catch_ref and catch_all_ref handlers --- .../codegen/ModuleInterfaceCodegen.java | 57 +++++++++------ compiler-tests/pom.xml | 1 + .../endive/testing/CatchRefNonNullTest.java | 67 ++++++++++++++++++ runtime-tests/pom.xml | 2 + .../compiled/catch_ref_non_null.wat.wasm | Bin 0 -> 340 bytes .../main/resources/wat/catch_ref_non_null.wat | 63 ++++++++++++++++ .../main/java/run/endive/wasm/Validator.java | 4 +- .../java/run/endive/wasm/types/ValType.java | 4 ++ 8 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 machine-tests/src/test/java/run/endive/testing/CatchRefNonNullTest.java create mode 100644 wasm-corpus/src/main/resources/compiled/catch_ref_non_null.wat.wasm create mode 100644 wasm-corpus/src/main/resources/wat/catch_ref_non_null.wat diff --git a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java index ea3878c3e..bb24223ed 100644 --- a/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java +++ b/codegen/src/main/java/run/endive/codegen/ModuleInterfaceCodegen.java @@ -830,31 +830,42 @@ private Expression listOfValueTypes(List valTypes) { return new MethodCallExpr(new NameExpr("List"), "of", NodeList.nodeList(values)); } + /** The {@code ValType} shorthand constant for an abstract heap type, or null if there is none. */ + private static String abstractRefShorthand(int typeIdx) { + if (typeIdx == ValType.TypeIdxCode.EXTERN.code()) { + return "ExternRef"; + } else if (typeIdx == ValType.TypeIdxCode.ANY.code()) { + return "AnyRef"; + } else if (typeIdx == ValType.TypeIdxCode.EQ.code()) { + return "EqRef"; + } else if (typeIdx == ValType.TypeIdxCode.I31.code()) { + return "I31Ref"; + } else if (typeIdx == ValType.TypeIdxCode.STRUCT.code()) { + return "StructRef"; + } else if (typeIdx == ValType.TypeIdxCode.ARRAY.code()) { + return "ArrayRef"; + } else if (typeIdx == ValType.TypeIdxCode.NONE.code()) { + return "NoneRef"; + } else if (typeIdx == ValType.TypeIdxCode.FUNC.code()) { + return "FuncRef"; + } else if (typeIdx == ValType.TypeIdxCode.EXN.code()) { + return "ExnRef"; + } else if (typeIdx == ValType.TypeIdxCode.NOFUNC.code()) { + return "NoFuncRef"; + } else if (typeIdx == ValType.TypeIdxCode.NOEXTERN.code()) { + return "NoExternRef"; + } else { + return null; + } + } + private static Expression valTypeRefExpr(ValType vt) { int ti = vt.typeIdx(); - if (ti == ValType.TypeIdxCode.EXTERN.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "ExternRef"); - } else if (ti == ValType.TypeIdxCode.ANY.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "AnyRef"); - } else if (ti == ValType.TypeIdxCode.EQ.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "EqRef"); - } else if (ti == ValType.TypeIdxCode.I31.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "I31Ref"); - } else if (ti == ValType.TypeIdxCode.STRUCT.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "StructRef"); - } else if (ti == ValType.TypeIdxCode.ARRAY.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "ArrayRef"); - } else if (ti == ValType.TypeIdxCode.NONE.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "NoneRef"); - } else if (ti == ValType.TypeIdxCode.FUNC.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "FuncRef"); - } else if (ti == ValType.TypeIdxCode.EXN.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "ExnRef"); - } else if (ti == ValType.TypeIdxCode.NOFUNC.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "NoFuncRef"); - } else if (ti == ValType.TypeIdxCode.NOEXTERN.code()) { - return new FieldAccessExpr(new NameExpr("ValType"), "NoExternRef"); - } else if (ti >= 0) { + String shorthand = abstractRefShorthand(ti); + // the shorthands are all nullable, a non-nullable ref has to be built explicitly + if (shorthand != null && vt.opcode() == ValType.ID.RefNull) { + return new FieldAccessExpr(new NameExpr("ValType"), shorthand); + } else if (shorthand != null || ti >= 0) { String opcName = vt.opcode() == ValType.ID.Ref ? "Ref" : "RefNull"; return new MethodCallExpr( new MethodCallExpr( diff --git a/compiler-tests/pom.xml b/compiler-tests/pom.xml index 188e1c202..122221b97 100644 --- a/compiler-tests/pom.xml +++ b/compiler-tests/pom.xml @@ -262,6 +262,7 @@ proposals/threads/exports.wast proposals/threads/imports.wast proposals/threads/memory.wast + proposals/wasm-3.0/try_table.wast ref_func.wast ref_is_null.wast ref_null.wast diff --git a/machine-tests/src/test/java/run/endive/testing/CatchRefNonNullTest.java b/machine-tests/src/test/java/run/endive/testing/CatchRefNonNullTest.java new file mode 100644 index 000000000..84f0900e5 --- /dev/null +++ b/machine-tests/src/test/java/run/endive/testing/CatchRefNonNullTest.java @@ -0,0 +1,67 @@ +package run.endive.testing; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.ImportValues; +import run.endive.runtime.Instance; +import run.endive.runtime.InterpreterMachine; +import run.endive.wasm.Parser; +import run.endive.wasm.WasmModule; + +/** Tests for `catch_ref` and `catch_all_ref` sending a non-nullable `(ref exn)`. */ +public class CatchRefNonNullTest { + + private static final WasmModule MODULE = + Parser.parse(CorpusResources.getResource("compiled/catch_ref_non_null.wat.wasm")); + + private static Stream machineImplementations() { + return Stream.of( + Arguments.of( + (Function) + (b) -> b.withMachineFactory(InterpreterMachine::new)), + Arguments.of( + (Function) + (b) -> b.withMachineFactory(MachineFactoryCompiler::compile))); + } + + private static Instance instance(Function machineInject) { + return machineInject + .apply(Instance.builder(MODULE).withImportValues(ImportValues.builder().build())) + .build(); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void catchRefNonNull(Function machineInject) { + var instance = instance(machineInject); + assertEquals(42, instance.export("catch-ref-non-null").apply(42)[0]); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void catchAllRefNonNull(Function machineInject) { + var instance = instance(machineInject); + assertEquals(42, instance.export("catch-all-ref-non-null").apply(42)[0]); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void rethrowNonNull(Function machineInject) { + var instance = instance(machineInject); + assertEquals(42, instance.export("rethrow-non-null").apply(42)[0]); + } + + @ParameterizedTest + @MethodSource("machineImplementations") + public void catchRefNullable(Function machineInject) { + var instance = instance(machineInject); + assertEquals(42, instance.export("catch-ref-nullable").apply(42)[0]); + } +} diff --git a/runtime-tests/pom.xml b/runtime-tests/pom.xml index 1541847c7..31354d968 100644 --- a/runtime-tests/pom.xml +++ b/runtime-tests/pom.xml @@ -267,6 +267,7 @@ proposals/threads/exports.wast proposals/threads/imports.wast proposals/threads/memory.wast + proposals/wasm-3.0/try_table.wast ref_func.wast ref_is_null.wast ref_null.wast @@ -737,6 +738,7 @@ proposals/threads/exports.wast proposals/threads/imports.wast proposals/threads/memory.wast + proposals/wasm-3.0/try_table.wast ref_func.wast ref_is_null.wast ref_null.wast diff --git a/wasm-corpus/src/main/resources/compiled/catch_ref_non_null.wat.wasm b/wasm-corpus/src/main/resources/compiled/catch_ref_non_null.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5d5316724ad6b35238d7e7a1aaab2038de9dfc13 GIT binary patch literal 340 zcmZWk%TB{E5S&>%rK*LfP^r{j8ZJm2a^VZ;S8(7`(;7LF>!MH|xBNPr1o2QU?L#xO zvupU*Jpu5_ZrDPD?FQ2B>Mqx2RDk-DF zgkx+DAHQ1j*FcB;Qwp$?mtuqxW4IYaRxNc|i7D1_7jEh1kX^mHr|ChrhJq#B4OyQM z*ZeWyq@Pwxb7F}{oBcj;Ocecza;qPOdG|?|cSVL`WB^V0%=Q~vSk*A9f>C+xB;F~> I=(-%f0JRT1F8}}l literal 0 HcmV?d00001 diff --git a/wasm-corpus/src/main/resources/wat/catch_ref_non_null.wat b/wasm-corpus/src/main/resources/wat/catch_ref_non_null.wat new file mode 100644 index 000000000..1603567a2 --- /dev/null +++ b/wasm-corpus/src/main/resources/wat/catch_ref_non_null.wat @@ -0,0 +1,63 @@ +(module + ;; `catch_ref` and `catch_all_ref` send a non-nullable `(ref exn)` to the handler label. + + (tag $e (param i32)) + + (func $throw (param $val i32) + (throw $e (local.get $val)) + ) + + (func (export "catch-ref-non-null") (param $val i32) (result i32) + (block $h (result i32 (ref exn)) + (try_table (result i32) (catch_ref $e $h) + (call $throw (local.get $val)) + (i32.const 0) + ) + (return) + ) + (drop) + ) + + (func (export "catch-all-ref-non-null") (param $val i32) (result i32) + (block $h (result (ref exn)) + (try_table (catch_all_ref $h) + (call $throw (local.get $val)) + ) + (return (i32.const 0)) + ) + (drop) + (local.get $val) + ) + + ;; the caught `(ref exn)` is usable: store it in a nullable local and rethrow it + (func (export "rethrow-non-null") (param $val i32) (result i32) + (local $exn exnref) + (block $h (result (ref exn)) + (try_table (catch_all_ref $h) + (call $throw (local.get $val)) + ) + (return (i32.const 0)) + ) + (local.set $exn) + (block $h2 (result i32 (ref exn)) + (try_table (result i32) (catch_ref $e $h2) + (local.get $exn) + (throw_ref) + ) + (return) + ) + (drop) + ) + + ;; the nullable spelling has to keep working, `(ref exn)` is a subtype of `exnref` + (func (export "catch-ref-nullable") (param $val i32) (result i32) + (block $h (result i32 exnref) + (try_table (result i32) (catch_ref $e $h) + (call $throw (local.get $val)) + (i32.const 0) + ) + (return) + ) + (drop) + ) +) diff --git a/wasm/src/main/java/run/endive/wasm/Validator.java b/wasm/src/main/java/run/endive/wasm/Validator.java index 994b44c1b..417a01123 100644 --- a/wasm/src/main/java/run/endive/wasm/Validator.java +++ b/wasm/src/main/java/run/endive/wasm/Validator.java @@ -1009,13 +1009,13 @@ void validateFunction(int funcIdx, FunctionBody body, FunctionType functionType) getTagType(currentCatch.tag()) .typeIdx()); pushVals(tagType.params()); - pushVal(ValType.ExnRef); + pushVal(ValType.RefExn); break; } case CATCH_ALL: break; case CATCH_ALL_REF: - pushVal(ValType.ExnRef); + pushVal(ValType.RefExn); break; } popCtrl(); diff --git a/wasm/src/main/java/run/endive/wasm/types/ValType.java b/wasm/src/main/java/run/endive/wasm/types/ValType.java index e66f04a10..40a49a64f 100644 --- a/wasm/src/main/java/run/endive/wasm/types/ValType.java +++ b/wasm/src/main/java/run/endive/wasm/types/ValType.java @@ -37,6 +37,10 @@ public final class ValType { public static final ValType RefBot = new ValType(ValType.ID.Ref, ValType.TypeIdxCode.BOT.code()); + /** The non-nullable {@code (ref exn)}. */ + public static final ValType RefExn = + new ValType(ValType.ID.Ref, ValType.TypeIdxCode.EXN.code()); + private final long id; // defined function type. This is not representable in the binary or textual representation