Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -830,31 +830,42 @@ private Expression listOfValueTypes(List<ValType> 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(
Expand Down
1 change: 1 addition & 0 deletions compiler-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@
<wast>proposals/threads/exports.wast</wast>
<wast>proposals/threads/imports.wast</wast>
<wast>proposals/threads/memory.wast</wast>
<wast>proposals/wasm-3.0/try_table.wast</wast>
<wast>ref_func.wast</wast>
<wast>ref_is_null.wast</wast>
<wast>ref_null.wast</wast>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Arguments> machineImplementations() {
return Stream.of(
Arguments.of(
(Function<Instance.Builder, Instance.Builder>)
(b) -> b.withMachineFactory(InterpreterMachine::new)),
Arguments.of(
(Function<Instance.Builder, Instance.Builder>)
(b) -> b.withMachineFactory(MachineFactoryCompiler::compile)));
}

private static Instance instance(Function<Instance.Builder, Instance.Builder> machineInject) {
return machineInject
.apply(Instance.builder(MODULE).withImportValues(ImportValues.builder().build()))
.build();
}

@ParameterizedTest
@MethodSource("machineImplementations")
public void catchRefNonNull(Function<Instance.Builder, Instance.Builder> machineInject) {
var instance = instance(machineInject);
assertEquals(42, instance.export("catch-ref-non-null").apply(42)[0]);
}

@ParameterizedTest
@MethodSource("machineImplementations")
public void catchAllRefNonNull(Function<Instance.Builder, Instance.Builder> machineInject) {
var instance = instance(machineInject);
assertEquals(42, instance.export("catch-all-ref-non-null").apply(42)[0]);
}

@ParameterizedTest
@MethodSource("machineImplementations")
public void rethrowNonNull(Function<Instance.Builder, Instance.Builder> machineInject) {
var instance = instance(machineInject);
assertEquals(42, instance.export("rethrow-non-null").apply(42)[0]);
}

@ParameterizedTest
@MethodSource("machineImplementations")
public void catchRefNullable(Function<Instance.Builder, Instance.Builder> machineInject) {
var instance = instance(machineInject);
assertEquals(42, instance.export("catch-ref-nullable").apply(42)[0]);
}
}
2 changes: 2 additions & 0 deletions runtime-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
<wast>proposals/threads/exports.wast</wast>
<wast>proposals/threads/imports.wast</wast>
<wast>proposals/threads/memory.wast</wast>
<wast>proposals/wasm-3.0/try_table.wast</wast>
<wast>ref_func.wast</wast>
<wast>ref_is_null.wast</wast>
<wast>ref_null.wast</wast>
Expand Down Expand Up @@ -737,6 +738,7 @@
<wast>proposals/threads/exports.wast</wast>
<wast>proposals/threads/imports.wast</wast>
<wast>proposals/threads/memory.wast</wast>
<wast>proposals/wasm-3.0/try_table.wast</wast>
<wast>ref_func.wast</wast>
<wast>ref_is_null.wast</wast>
<wast>ref_null.wast</wast>
Expand Down
Binary file not shown.
63 changes: 63 additions & 0 deletions wasm-corpus/src/main/resources/wat/catch_ref_non_null.wat
Original file line number Diff line number Diff line change
@@ -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)
)
)
4 changes: 2 additions & 2 deletions wasm/src/main/java/run/endive/wasm/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions wasm/src/main/java/run/endive/wasm/types/ValType.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading