summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/exceptions
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/exceptions')
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1744663-extended.js224
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1744663.js63
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1747562.js35
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1747704.js14
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1751699.js8
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1767446.js26
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1788213.js5
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1791361.js12
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/bug-1797685.js8
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/caching.js28
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/calls.js407
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/directives.txt1
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/events.js160
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/example.js28
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/import-export.js100
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/instructions.js1528
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/ion-loop-phi.js59
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/js-api.js490
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/memory.js78
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/prototypes.js10
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/reftypes.js141
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/side-effects-in-try.js234
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/stack.js28
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/throw-to-js.js343
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/validation.js662
25 files changed, 4692 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1744663-extended.js b/js/src/jit-test/tests/wasm/exceptions/bug-1744663-extended.js
new file mode 100644
index 0000000000..6efd8f0bb1
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1744663-extended.js
@@ -0,0 +1,224 @@
+load(libdir + "eqArrayHelper.js");
+
+// These tests ensure that the gc related errors in bug 1744663 are resolved.
+
+{
+ let catchlessTry = `
+ (try
+ (do (call $gc)
+ (throw $exn)))`;
+ let rethrow0 = `
+ (try
+ (do (call $gc)
+ (throw $exn))
+ (catch $exn (rethrow 0)))`;
+ let rethrow1 = `
+ (try
+ (do (throw $exn))
+ (catch_all
+ (try
+ (do (throw $exn))
+ (catch $exn (rethrow 1)))))`;
+ let delegate0 = `
+ (try
+ (do (call $gc)
+ (throw $exn))
+ (delegate 0))`;
+ let delegate1 = `
+ (block
+ (try
+ (do (call $gc)
+ (throw $exn))
+ (delegate 1)))`;
+ let delegate0InCatch = `
+ (try
+ (do (throw $exn))
+ (catch_all
+ (try
+ (do (call $gc)
+ (throw $exn))
+ (delegate 0))))`;
+ let delegate1InCatch = `
+ (try
+ (do (throw $exn))
+ (catch_all
+ (try
+ (do (call $gc)
+ (throw $exn))
+ (delegate 1))))`;
+
+ let rethrowingBodies = [catchlessTry, rethrow0, delegate0, delegate1,
+ rethrow1, delegate0InCatch, delegate1InCatch];
+
+ function rethrowingIndirectly(rethrowingBody) {
+ let exports = wasmEvalText(
+ `(module
+ (tag $exn (export "exn"))
+ (import "js" "gc" (func $gc))
+ (func $throwExn (export "throwExn") ${rethrowingBody}))`,
+ {js: {gc: () => { gc(); }}}
+ ).exports;
+
+ let mod =
+ `(module
+ (type $exnType (func))
+ (type $indirectFunctype (func))
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
+ (table funcref (elem $throwExn))
+ (func (export "testFunc") (result i32)
+ (try (result i32)
+ (do (call_indirect (type $indirectFunctype) (i32.const 0))
+ (i32.const 0))
+ (catch $exn (i32.const 1)))))`;
+
+ let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc;
+ assertEq(testFunction(), 1);
+ };
+
+ for (let rethrowingBody of rethrowingBodies) {
+ //console.log("Calling rethrowingIndirectly with rethrowingBody = " + rethrowingBody);
+ rethrowingIndirectly(rethrowingBody);
+ }
+}
+
+// The full test case that caused the original failure.
+
+{
+ gczeal(2,1); // Collect after every allocation.
+
+ let v128Type = " i32";
+ let wrongV128 = "(i32.const 0)";
+ let correctV128 = "(i32.const 1)";
+ let checkV128Value = "";
+
+ if (wasmSimdEnabled()) {
+ v128Type = " v128";
+ wrongV128 = "(v128.const i32x4 11 22 33 44)";
+ correctV128 = "(v128.const i32x4 55 66 77 88)";
+ checkV128Value = `;; Check the V128 value
+ (v128.const i32x4 55 66 77 88)
+ (i32x4.eq)
+ (i32x4.all_true)`;
+ }
+
+ let exports = wasmEvalText(
+ `(module
+ (type $exnType (func (param i32 i64 f32 f64 externref ${v128Type})))
+ (type $indirectFunctype (func (param i32 i64 f32 f64 externref ${v128Type})
+ (result i32 i64 f32 f64 externref ${v128Type})))
+ (tag $exn (export "exn") (type $exnType))
+ (tag $emptyExn (export "emptyExn"))
+ (func $throwExn (export "throwExn") (param i32 i64 f32 f64 externref ${v128Type})
+ (result i32 i64 f32 f64 externref ${v128Type})
+ (local $ifPredicate i32)
+ (local.get 0) ;; i32
+ (local.get 1) ;; i64
+ (local.get 2) ;; f32
+ (local.get 3) ;; f64
+ (local.get 4) ;; ref
+ (local.get 5) ;; v128 or i32
+ (try (param i32 i64 f32 f64 externref ${v128Type})
+ (do
+ (if (param i32 i64 f32 f64 externref ${v128Type})
+ (local.get $ifPredicate)
+ (then (throw $exn))
+ (else (throw $exn))))
+ (catch $exn
+ (try (param i32 i64 f32 f64 externref ${v128Type})
+ (do (throw $exn))
+ (catch_all (rethrow 1))))
+ (catch_all))
+ unreachable)
+ (func $throwEmptyExn (export "throwEmptyExn")
+ (param i32 i64 f32 f64 externref ${v128Type})
+ (result i32 i64 f32 f64 externref ${v128Type})
+ (throw $emptyExn)
+ unreachable)
+ (func $returnArgs (export "returnArgs")
+ (param i32 i64 f32 f64 externref ${v128Type})
+ (result i32 i64 f32 f64 externref ${v128Type})
+ (local.get 0) ;; i32
+ (local.get 1) ;; i64
+ (local.get 2) ;; f32
+ (local.get 3) ;; f64
+ (local.get 4) ;; ref
+ (local.get 5))
+ (table (export "tab") funcref (elem $throwExn ;; 0
+ $throwEmptyExn ;; 1
+ $returnArgs)) ;; 2
+ )`).exports;
+
+ var mod =
+ `(module
+ (type $exnType (func (param i32 i64 f32 f64 externref ${v128Type})))
+ (type $indirectFunctype (func (param i32 i64 f32 f64 externref ${v128Type})
+ (result i32 i64 f32 f64 externref ${v128Type})))
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (import "m" "emptyExn" (tag $emptyExn))
+ (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
+ (import "m" "throwEmptyExn"
+ (func $throwEmptyExn (type $indirectFunctype)))
+ (import "m" "returnArgs"
+ (func $returnArgs (type $indirectFunctype)))
+ (import "m" "tab" (table 3 funcref))
+ (func (export "testFunc") (param $correctRef externref)
+ (param $wrongRef externref)
+ ;; The last i32 result is the v128 check.
+ (result i32 i64 f32 f64 externref i32)
+ (local $ifPredicate i32)
+ (try (result i32 i64 f32 f64 externref i32)
+ (do
+ ;; Wrong values
+ (i32.const 5)
+ (i64.const 6)
+ (f32.const 0.1)
+ (f64.const 0.6437)
+ (local.get $wrongRef)
+ ${wrongV128}
+ ;; throwEmptyExn
+ (call_indirect (type $indirectFunctype) (i32.const 1))
+ drop ;; Drop the last v128 value.
+ (i32.const 0))
+ (catch_all
+ (try (result i32 i64 f32 f64 externref ${v128Type})
+ (do ;; Values to throw.
+ (i32.const 2)
+ (i64.const 3)
+ (f32.const 4)
+ (f64.const 13.37)
+ (local.get $correctRef)
+ ${correctV128}
+ (call_indirect (type $indirectFunctype) (i32.const 2)) ;; returnArgs
+ (call_indirect (type $indirectFunctype) (i32.const 0)) ;; throwExn
+ drop drop ;; Drop v128 and externref to do trivial and irrelevant ops.
+ (f64.const 5)
+ (f64.add)
+ (local.get $wrongRef)
+ ${wrongV128}
+ ;; throwEmptyExn
+ (call_indirect (type $indirectFunctype) (i32.const 1))
+ unreachable)
+ (catch $emptyExn
+ ;; Wrong values
+ (i32.const 5)
+ (i64.const 6)
+ (f32.const 0.1)
+ (f64.const 0.6437)
+ (local.get $wrongRef)
+ ${wrongV128})
+ (catch $exn)
+ (catch_all
+ ;; Wrong values
+ (i32.const 5)
+ (i64.const 6)
+ (f32.const 0.1)
+ (f64.const 0.6437)
+ (local.get $wrongRef)
+ ${wrongV128}))
+ ${checkV128Value}))))`;
+
+ let testAllValtypes = wasmEvalText(mod, { m : exports}).exports.testFunc;
+ assertEqArray(testAllValtypes("foo", "bar"),
+ [2, 3n, 4, 13.37, "foo", 1]);
+}
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1744663.js b/js/src/jit-test/tests/wasm/exceptions/bug-1744663.js
new file mode 100644
index 0000000000..a63dc24e19
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1744663.js
@@ -0,0 +1,63 @@
+gczeal(2,1);
+
+function failureCallingTestFunction() {
+ let exports = wasmEvalText(
+ `(module
+ (tag $exn (export "exn"))
+ (func $throwExn (export "throwExn")
+ ;; Note that this does not fail if this function body is a plain (throw $exn).
+ (try
+ (do (throw $exn)))))`
+ ).exports;
+
+ let mod =
+ `(module
+ (type $exnType (func))
+ (type $indirectFunctype (func))
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
+ (table funcref (elem $throwExn))
+ (func (export "testFunc") (result i32)
+ (try
+ (do (call_indirect (type $indirectFunctype) (i32.const 0)))
+ (catch_all))
+ (i32.const 1)))`;
+
+ let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc;
+ testFunction();
+};
+
+function failureRethrow1() {
+ let exports = wasmEvalText(
+ `(module
+ (tag $exn (export "exn"))
+ (func $throwExn (export "throwExn")
+ (try
+ (do (throw $exn))
+ (catch_all
+ (try
+ (do (throw $exn))
+ (catch_all (rethrow 1)))))))`
+ ).exports;
+
+ let mod =
+ `(module
+ (type $exnType (func))
+ (type $indirectFunctype (func))
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
+ (table funcref (elem $throwExn))
+ (func (export "testFunc") (result i32)
+ (try
+ (do (call_indirect (type $indirectFunctype) (i32.const 0)))
+ (catch_all))
+ (i32.const 1)))`;
+
+ let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc;
+ testFunction();
+};
+
+console.log("Calling failureCallingTestFunction.");
+failureCallingTestFunction();
+console.log("Calling failureRethrow1.");
+failureRethrow1();
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1747562.js b/js/src/jit-test/tests/wasm/exceptions/bug-1747562.js
new file mode 100644
index 0000000000..e54b860664
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1747562.js
@@ -0,0 +1,35 @@
+let bytes = wasmTextToBinary(`(module
+ (type (;0;) (func))
+ (func (;0;) (type 0)
+ block ;; label = @1
+ try ;; label = @2
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ call 0
+ delegate 0
+ end)
+ (export "" (func 0)))`);
+let module = new WebAssembly.Module(bytes);
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1747704.js b/js/src/jit-test/tests/wasm/exceptions/bug-1747704.js
new file mode 100644
index 0000000000..f9f4292961
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1747704.js
@@ -0,0 +1,14 @@
+let {shouldTrap} = wasmEvalText(`(module
+ (func
+ try
+ i32.const 0
+ table.get 0
+ ref.is_null
+ drop
+ catch_all
+ end
+ )
+ (table 0 funcref)
+ (export "shouldTrap" (func 0))
+)`).exports;
+assertErrorMessage(shouldTrap, WebAssembly.RuntimeError, /table index/);
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1751699.js b/js/src/jit-test/tests/wasm/exceptions/bug-1751699.js
new file mode 100644
index 0000000000..38268e7905
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1751699.js
@@ -0,0 +1,8 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+oomTest(() => {
+ wasmEvalText(`
+ (import "" "" (func $d))
+ (func try call $d end)
+ `);
+});
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1767446.js b/js/src/jit-test/tests/wasm/exceptions/bug-1767446.js
new file mode 100644
index 0000000000..5cf38a757c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1767446.js
@@ -0,0 +1,26 @@
+// Dead code elimination can remove wasm calls that may leave behind dangling
+// try notes.
+wasmEvalText(`(module
+ (type (;0;) (func (result i32 i32 i32)))
+ (func $main (type 0) (result i32 i32 i32)
+ try (result i32 i32 i32) ;; label = @1
+ call $main
+ call $main
+ i32.const 541
+ i32.const 0
+ br_if 0 (;@1;)
+ br_if 0 (;@1;)
+ br_if 0 (;@1;)
+ br_if 0 (;@1;)
+ call $main
+ call $main
+ br_if 0 (;@1;)
+ br_if 0 (;@1;)
+ call $main
+ call $main
+ call $main
+ unreachable
+ end
+ unreachable
+ )
+)`);
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1788213.js b/js/src/jit-test/tests/wasm/exceptions/bug-1788213.js
new file mode 100644
index 0000000000..4d0b4abcdc
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1788213.js
@@ -0,0 +1,5 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+oomTest(() => {
+ wasmEvalText(`(import "" "" (tag $undef)) (func throw 0) (func (try (do)))`);
+});
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1791361.js b/js/src/jit-test/tests/wasm/exceptions/bug-1791361.js
new file mode 100644
index 0000000000..9c4432de91
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1791361.js
@@ -0,0 +1,12 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+oomTest(() => {
+ wasmEvalText(`
+ (tag $d)
+ (func $anotherLocalFuncThrowsExn)
+ (func throw $d)
+ (func (try (do
+ call $anotherLocalFuncThrowsExn
+ )))
+ `);
+});
diff --git a/js/src/jit-test/tests/wasm/exceptions/bug-1797685.js b/js/src/jit-test/tests/wasm/exceptions/bug-1797685.js
new file mode 100644
index 0000000000..d2f02a8fd6
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/bug-1797685.js
@@ -0,0 +1,8 @@
+assertErrorMessage(() => wasmEvalText(`(module
+ (func)
+ (func
+ try
+ call 0
+ delegate 4294967295
+ )
+)`), WebAssembly.CompileError, /delegate/);
diff --git a/js/src/jit-test/tests/wasm/exceptions/caching.js b/js/src/jit-test/tests/wasm/exceptions/caching.js
new file mode 100644
index 0000000000..5427de5102
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/caching.js
@@ -0,0 +1,28 @@
+// |jit-test| skip-if: !wasmCachingEnabled()
+
+load(libdir + "wasm-caching.js");
+
+// Test that the tag section is cached correctly
+testCached(`(module
+ (tag $t (export "t"))
+ (func (export "r")
+ throw $t
+ )
+)`, {}, i => {
+ assertErrorMessage(() => i.exports.r(), WebAssembly.Exception, /.*/);
+});
+
+// Test that try notes are cached correctly
+testCached(`(module
+ (tag $t)
+ (func (export "r") (result i32)
+ try (result i32)
+ throw $t
+ i32.const 0
+ catch $t
+ i32.const 1
+ end
+ )
+)`, {}, i => {
+ assertEq(i.exports.r(), 1, "caught");
+});
diff --git a/js/src/jit-test/tests/wasm/exceptions/calls.js b/js/src/jit-test/tests/wasm/exceptions/calls.js
new file mode 100644
index 0000000000..f12e5d1dbe
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/calls.js
@@ -0,0 +1,407 @@
+// -----------------------------------------------------------------------------
+// This file contains tests checking Wasm functions with throwing functions and
+// try-catch code involving more complex control flow, testing that multiple
+// values returned from calls in try code are not affected by multiple branching
+// towards the landing pad, as well as making sure exceptions carrying multiple
+// values of any Wasm numtype transport the exception values correctly across
+// calls.
+//
+// There are tests for local direct calls, for imported direct calls, for
+// indirect calls in a local table with local functions, for indirect calls in a
+// local table of imported functions, and for indirect calls in an imported
+// table of imported functions.
+//
+// - TODO: Add reftype values, when support for reftypes in exceptions is
+// implemented.
+// -----------------------------------------------------------------------------
+
+load(libdir + "eqArrayHelper.js");
+
+// All individual tests take a string 'localThrow' as argument, and will be run
+// with each element of the result of the following function.
+
+function generateLocalThrows(types, baseThrow) {
+ // Arguments:
+ // - 'types': A string of space separated Wasm types.
+ // - 'baseThrow': A string with a Wasm instruction sequence of Wasm functype
+ // `[${types}]-> [t*], which takes `types` arguments and ends
+ // up throwing the tag '$exn'.
+ // Result:
+ // - A JS array of strings, each representing a Wasm instruction sequence
+ // which is like `baseThrow', i.e., a Wasm instruction sequence of Wasm
+ // functype `[${types}]-> [t*], which takes `types` arguments and ends up
+ // throwing the tag '$exn'. The result does not include 'baseThrow'.
+ //
+ // All strings in Wasm text format.
+
+ // Basic throws;
+ let catchlessTryThrow =
+ `(try (param ${types})
+ (do ${baseThrow}))`;
+
+ let catchAndThrow =
+ `(try (param ${types})
+ (do ${baseThrow})
+ (catch $exn
+ ${baseThrow})
+ (catch_all))`;
+
+ let blockThrow =
+ `(block (param ${types})
+ ${baseThrow})`;
+
+ // This Wasm code requires that the function it appears in has an i32 local
+ // with name "$ifPredicate".
+ let conditionalThrow =
+ `(if (param ${types})
+ (local.get $ifPredicate)
+ (then ${baseThrow})
+ (else ${baseThrow}))`;
+
+ // Including try-delegate.
+ let baseDelegate =
+ `(try (param ${types})
+ (do ${baseThrow})
+ (delegate 0))`;
+
+ // Delegate just outside the block.
+ let nestedDelegate1InBlock =
+ `(block $label1 (param ${types})
+ (try (param ${types})
+ (do ${baseThrow})
+ (delegate $label1)))`;
+
+ let basicThrows = [catchlessTryThrow, blockThrow, conditionalThrow,
+ baseDelegate, nestedDelegate1InBlock];
+
+ // Secondary throws (will end up inside a catch block).
+
+ let baseRethrow =
+ `(rethrow 0)`;
+
+ let nestedRethrow =
+ `(try (param ${types})
+ (do
+ ${baseThrow})
+ (catch $exn
+ (rethrow 1))
+ (catch_all
+ (rethrow 0)))`;
+
+ let catchAllRethrowOriginal =
+ `(try (param ${types})
+ (do
+ ${baseThrow})
+ (catch_all
+ (rethrow 1)))`;
+
+ let secondaryThrows =
+ [].concat(basicThrows,
+ [baseRethrow, nestedRethrow, catchAllRethrowOriginal]);
+
+ // Nestings.
+
+ function basicNesting (basicThrow, secondaryThrow) {
+ return `(try (param ${types})
+ (do ${basicThrow})
+ (catch $exn
+ ${secondaryThrow})
+ (catch_all))`;
+ };
+
+ let result = [];
+
+ for (let basicThrow of basicThrows) {
+ result.push(basicThrow);
+ for (let secondaryThrow of secondaryThrows) {
+ result.push(basicNesting(basicThrow, secondaryThrow));
+ }
+ }
+
+ return result;
+};
+
+{
+ // Some variables to be used in all tests.
+ let typesJS = ["i32", "i64", "f32", "f64", "externref"];
+ let types = typesJS.join(" ");
+
+ // The following depend on whether simd is enabled or not. We write it like
+ // this so we can run this test also when SIMD is not enabled.
+ let exntype = "";
+ let wrongV128 = "";
+ let throwV128 = "";
+ let checkV128Value = "";
+
+ if (wasmSimdEnabled()) {
+ exntype = types + " v128";
+ wrongV128 = `(v128.const i32x4 11 22 33 44)`;
+ throwV128 = `(v128.const i32x4 55 66 77 88)`;
+ checkV128Value = `;; Check the V128 value
+ ${throwV128}
+ (i32x4.eq)
+ (i32x4.all_true)`;
+ } else {
+ exntype = types + " i32";
+ wrongV128 = "(i32.const 0)";
+ throwV128 = "(i32.const 1)";
+ checkV128Value = "";
+ }
+
+ let exnTypeDef = `(type $exnType (func (param ${exntype})))`;
+
+ let throwValues =
+ `;; Values to throw.
+ (i32.const 2)
+ (i64.const 3)
+ (f32.const 4)
+ (f64.const 13.37)
+ (local.get $correctRef)
+ ${throwV128}`;
+
+ // The last 1 is the result of the test that the v128 value is correct, done
+ // in wasm code (if simd is enabled).
+ let correctResultsJS = [2, 3n, 4, 13.37, "foo", 1];
+
+ let wrongValues =
+ `;; Wrong values
+ (i32.const 5)
+ (i64.const 6)
+ (f32.const 0.1)
+ (f64.const 0.6437)
+ (local.get $wrongRef)
+ ${wrongV128}`;
+
+ // The individual tests. -----------------------------------------------------
+
+ function testDirectCallsThrowing(localThrow) {
+ // Test direct function calls throwing any numeric value.
+
+ let throwifTypeInline =
+ // The result of the "throwif" function will be used as an argument the
+ // second time "throwif" is called.
+ `(param $ifPredicate i32) (param $correctRef externref) (result i32)`;
+
+ let moduleHeaderThrowif =
+ `(module
+ ${exnTypeDef}
+ (tag $exn (export "exn") (type $exnType))
+ (func $throwif (export "throwif") ${throwifTypeInline}
+ (if
+ (local.get $ifPredicate)
+ (then
+ ${throwValues}
+ ${localThrow}))
+ (i32.const 1))`;
+
+ let testModuleRest =
+ `(tag $notThrownExn)
+ (func $doNothing)
+ (func (export "testFunc") (param $correctRef externref)
+ (param $wrongRef externref)
+ (result ${types} i32)
+ (local $ifPredicate i32)
+ (local.get $ifPredicate)
+ (try (param i32) (result ${exntype})
+ (do
+ (local.get $wrongRef)
+ (call $throwif) ;; Returns 1.
+ (call $doNothing) ;; Does nothing.
+ (local.get $correctRef)
+ (call $throwif) ;; Throws $exn.
+ (drop)
+ ${wrongValues} ;; Won't reach this point.
+ ${localThrow}
+ unreachable)
+ (catch $notThrownExn
+ ${wrongValues})
+ (catch $exn))
+ ${checkV128Value}))`;
+
+ function testDirectLocalCallsThrowing() {
+ let mod = moduleHeaderThrowif + testModuleRest;
+ // console.log("DIRECT LOCAL MOD = " + mod); // Uncomment for debugging.
+
+ assertEqArray(wasmEvalText(mod).exports.testFunc("foo", "bar"),
+ correctResultsJS);
+ };
+
+ function testDirectImportedCallsThrowing() {
+ let exports = wasmEvalText(moduleHeaderThrowif + `)`).exports;
+ // Uncomment for debugging.
+ //console.log("DIRECT EXPORTS = " + moduleHeaderThrowif + ")");
+
+ let mod =
+ `(module
+ ${exnTypeDef}
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (import "m" "throwif" (func $throwif ${throwifTypeInline}))` +
+ testModuleRest;
+ // console.log("DIRECT IMPORT MOD = " + mod); // Uncomment for debugging.
+
+ assertEqArray(
+ wasmEvalText(mod, { m : exports}).exports.testFunc("foo", "bar"),
+ correctResultsJS);
+ };
+
+ testDirectLocalCallsThrowing();
+ testDirectImportedCallsThrowing();
+ };
+
+ function testIndirectCallsThrowing(localThrow) {
+ // Test indirect calls throwing exceptions.
+
+ let indirectFunctypeInline = `(param ${exntype})
+ (result ${exntype})`;
+ let getIndirectArgs = `(local.get 0) ;; i32
+ (local.get 1) ;; i64
+ (local.get 2) ;; f32
+ (local.get 3) ;; f64
+ (local.get 4) ;; ref
+ ;; v128
+ (local.get 5)`;
+
+ let testFunctypeInline = `(param $correctRef externref)
+ (param $wrongRef externref)
+ ;; The last i32 result is the v128 check.
+ (result ${types} i32)`;
+
+ let moduleHeader =
+ `(module
+ ${exnTypeDef}
+ (type $indirectFunctype (func ${indirectFunctypeInline}))
+ (tag $exn (export "exn") (type $exnType))
+ (tag $emptyExn (export "emptyExn"))
+ (func $throwExn (export "throwExn") ${indirectFunctypeInline}
+ (local $ifPredicate i32)
+ ${getIndirectArgs}
+ ${localThrow}
+ unreachable)
+ (func $throwEmptyExn (export "throwEmptyExn")
+ ${indirectFunctypeInline}
+ (throw $emptyExn)
+ unreachable)
+ (func $returnArgs (export "returnArgs") ${indirectFunctypeInline}
+ ${getIndirectArgs})
+ (table (export "tab") funcref (elem $throwExn ;; 0
+ $throwEmptyExn ;; 1
+ $returnArgs)) ;; 2
+ `;
+
+ // The main test function happens to have the same Wasm functype as the
+ // indirect calls.
+ let testFuncHeader = `(func (export "testFunc") ${testFunctypeInline}
+ (local $ifPredicate i32)
+ `;
+
+ // To test indirect calls to a local table of local functions
+ function moduleIndirectLocalLocal(functionBody) {
+ return moduleHeader + testFuncHeader + functionBody + `))`;
+ };
+
+ let exports = wasmEvalText(moduleHeader + ")").exports;
+ // Uncomment for debugging.
+ //console.log("INDIRECT EXPORTS = " + moduleHeader + ")");
+
+ let moduleHeaderImporting =
+ `(module
+ ${exnTypeDef}
+ (type $indirectFunctype (func ${indirectFunctypeInline}))
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (import "m" "emptyExn" (tag $emptyExn))
+ (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
+ (import "m" "throwEmptyExn"
+ (func $throwEmptyExn (type $indirectFunctype)))
+ (import "m" "returnArgs"
+ (func $returnArgs (type $indirectFunctype)))`;
+
+ // To test indirect calls to a local table of imported functions.
+ function moduleIndirectLocalImport(functionBody) {
+ return moduleHeaderImporting +
+ `(table funcref (elem $throwExn $throwEmptyExn $returnArgs))` +
+ testFuncHeader + functionBody + `))`;
+ };
+
+ // To test indirect calls to an imported table of imported functions.
+ function moduleIndirectImportImport(functionBody) {
+ return moduleHeaderImporting +
+ `(import "m" "tab" (table 3 funcref))` +
+ testFuncHeader + functionBody + `))`;
+ };
+
+ function getModuleTextsForFunctionBody(functionBody) {
+ return [moduleIndirectLocalLocal(functionBody),
+ moduleIndirectLocalImport(functionBody),
+ moduleIndirectImportImport(functionBody)];
+ };
+
+ // The function bodies for the tests.
+
+ // Three indirect calls, the middle of which throws and will be caught. The
+ // results of the first and second indirect calls are used by the next
+ // indirect call. This should be called from try code, to check that the
+ // pad-branches don't interfere with the results of each call.
+ let indirectThrow = `${throwValues}
+ (call_indirect (type $indirectFunctype) (i32.const 2)) ;; returnArgs
+ (call_indirect (type $indirectFunctype) (i32.const 0)) ;; throwExn
+ drop drop ;; Drop v128 and externref to do trivial and irrelevant ops.
+ (f64.const 5)
+ (f64.add)
+ (local.get $wrongRef)
+ ${wrongV128}
+ ;; throwEmptyExn
+ (call_indirect (type $indirectFunctype) (i32.const 1))
+ unreachable`;
+
+ // Simple try indirect throw and catch.
+ let simpleTryIndirect = `(try (result ${exntype})
+ (do ${indirectThrow})
+ (catch $emptyExn
+ ${wrongValues})
+ (catch $exn)
+ (catch_all
+ ${wrongValues}))
+ ${checkV128Value}`;
+
+ // Indirect throw/catch_all, with a simple try indirect throw nested in the
+ // catch_all.
+ let nestedTryIndirect =
+ `(try (result ${types} i32)
+ (do
+ ${wrongValues}
+ ;; throwEmptyExn
+ (call_indirect (type $indirectFunctype) (i32.const 1))
+ drop ;; Drop the last v128 value.
+ (i32.const 0))
+ (catch_all
+ ${simpleTryIndirect}))`;
+
+ let functionBodies = [simpleTryIndirect, nestedTryIndirect];
+
+ // Test throwing from indirect calls.
+ for (let functionBody of functionBodies) {
+ console.log("functionBody = : " + functionBody); // Uncomment for debugging.
+
+ for (let mod of getModuleTextsForFunctionBody(functionBody)) {
+ //console.log("mod = : " + mod); // Uncomment for debugging.
+
+ let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc;
+ assertEqArray(testFunction("foo", "bar"),
+ correctResultsJS);
+ }
+ }
+ };
+
+ // Run all tests. ------------------------------------------------------------
+
+ let localThrows =
+ ["(throw $exn)"].concat(generateLocalThrows(exntype, "(throw $exn)"));
+
+ for (let localThrow of localThrows) {
+ // Uncomment for debugging.
+ //console.log("Testing with localThrow = " + localThrow);
+
+ testDirectCallsThrowing(localThrow);
+ testIndirectCallsThrowing(localThrow);
+ }
+}
diff --git a/js/src/jit-test/tests/wasm/exceptions/directives.txt b/js/src/jit-test/tests/wasm/exceptions/directives.txt
new file mode 100644
index 0000000000..b8ee0d8d7c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/directives.txt
@@ -0,0 +1 @@
+|jit-test| --wasm-exceptions; test-also=--wasm-compiler=optimizing; test-also=--wasm-compiler=baseline; test-also=--wasm-test-serialization; test-also=--test-wasm-await-tier2; include:wasm.js; skip-if: !wasmExceptionsEnabled()
diff --git a/js/src/jit-test/tests/wasm/exceptions/events.js b/js/src/jit-test/tests/wasm/exceptions/events.js
new file mode 100644
index 0000000000..00bb244546
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/events.js
@@ -0,0 +1,160 @@
+// Tests for tag section support
+
+load(libdir + "wasm-binary.js");
+
+function wasmEval(code, imports) {
+ new WebAssembly.Instance(new WebAssembly.Module(code), imports).exports;
+}
+
+function wasmError(code, errorType, regexp) {
+ assertErrorMessage(() => wasmEval(code, {}), errorType, regexp);
+}
+
+const emptyType = { args: [], ret: VoidCode };
+const badExnType = { args: [], ret: I32Code };
+
+wasmEvalText(`
+ (module
+ (type (func (param i32)))
+ (tag $exn (type 0)))
+`);
+
+wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ { name: tagId, body: [] },
+ ]),
+ WebAssembly.CompileError,
+ /expected number of tags/
+);
+
+wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ { name: tagId, body: [1, 1] },
+ ]),
+ WebAssembly.CompileError,
+ /illegal tag kind/
+);
+
+wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ { name: tagId, body: [1, 0] },
+ ]),
+ WebAssembly.CompileError,
+ /expected function index in tag/
+);
+
+wasmEval(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ tagSection([{ type: 0 }]),
+ ])
+);
+
+wasmError(
+ moduleWithSections([
+ sigSection([badExnType]),
+ memorySection(0),
+ tagSection([{ type: 0 }]),
+ ]),
+ WebAssembly.CompileError,
+ /tag function types must not return anything/
+);
+
+wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ tagSection([{ type: 1 }]),
+ ]),
+ WebAssembly.CompileError,
+ /function type index in tag out of bounds/
+);
+
+wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ tagSection([{ type: 0 }]),
+ memorySection(0),
+ ]),
+ WebAssembly.CompileError,
+ /expected custom section/
+);
+
+(() => {
+ const body = [1];
+ body.push(...string("mod"));
+ body.push(...string("exn"));
+ body.push(...varU32(TagCode));
+
+ wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ { name: importId, body: body },
+ ]),
+ WebAssembly.CompileError,
+ /expected tag kind/
+ );
+
+ body.push(...varU32(0));
+ wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ { name: importId, body: body },
+ ]),
+ WebAssembly.CompileError,
+ /expected function index in tag/
+ );
+
+ body.push(...varU32(1));
+ wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ { name: importId, body: body },
+ ]),
+ WebAssembly.CompileError,
+ /function type index in tag out of bounds/
+ );
+})();
+
+wasmEval(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ tagSection([{ type: 0 }]),
+ exportSection([{ tagIndex: 0, name: "exn" }]),
+ ])
+);
+
+wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ tagSection([{ type: 0 }]),
+ exportSection([{ tagIndex: 1, name: "exn" }]),
+ ]),
+ WebAssembly.CompileError,
+ /exported tag index out of bounds/
+);
+
+(() => {
+ const body = [1];
+ body.push(...string("exn"));
+ body.push(...varU32(TagCode));
+ wasmError(
+ moduleWithSections([
+ sigSection([emptyType]),
+ memorySection(0),
+ tagSection([{ type: 0 }]),
+ { name: exportId, body: body },
+ ]),
+ WebAssembly.CompileError,
+ /expected tag index/
+ );
+})();
diff --git a/js/src/jit-test/tests/wasm/exceptions/example.js b/js/src/jit-test/tests/wasm/exceptions/example.js
new file mode 100644
index 0000000000..f1c9f144b9
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/example.js
@@ -0,0 +1,28 @@
+// A diagram depicting the control flow graph of the function g below can be
+// found in "js/src/wasm/WasmIonCompile.cpp". If you make any changes to this
+// test file be sure to adjust the SMDOC documentation there as well.
+
+let g = wasmEvalText(
+ `(module
+ (tag $exn (param f64))
+ (func $f)
+ (func (export "g") (param $arg i32) (result f64)
+ (local.get $arg)
+ (try (param i32) (result f64)
+ (do
+ (if (result f64)
+ (then
+ (f64.const 3))
+ (else
+ (throw $exn (f64.const 6))))
+ (call $f)
+ (f64.sub (f64.const 2))) ;; If $arg is 0 we end here, subtracting 3.
+ ;; If $arg is not 0 then the else-block throws $exn, caught below.
+ (catch $exn
+ (f64.add (f64.const 4))) ;; Adds 4 to the value in the $exn (6).
+ (catch_all ;; This shouldn't occur.
+ (f64.const 5)))))`
+).exports.g;
+
+assertEq(g(0), 10);
+assertEq(g(1), 1);
diff --git a/js/src/jit-test/tests/wasm/exceptions/import-export.js b/js/src/jit-test/tests/wasm/exceptions/import-export.js
new file mode 100644
index 0000000000..fe7646678f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/import-export.js
@@ -0,0 +1,100 @@
+// Tests for Wasm exception import and export.
+
+function testImports() {
+ var mod = `
+ (module
+ (type (func (param i32 i32)))
+ (import "m" "exn" (tag (type 0))))
+ `;
+
+ assertErrorMessage(
+ () => wasmEvalText(mod, { m: { exn: "not a tag" } }),
+ WebAssembly.LinkError,
+ /import object field 'exn' is not a Tag/
+ );
+}
+
+function testExports() {
+ var exports1 = wasmEvalText(`
+ (module (type (func)) (tag (export "exn") (type 0)))
+ `).exports;
+
+ assertEq(typeof exports1.exn, "object");
+ assertEq(exports1.exn instanceof WebAssembly.Tag, true);
+
+ var exports2 = wasmEvalText(`
+ (module
+ (type (func (param i32 i32)))
+ (tag (export "exn") (type 0)))
+ `).exports;
+
+ assertEq(typeof exports2.exn, "object");
+ assertEq(exports2.exn instanceof WebAssembly.Tag, true);
+}
+
+function testImportExport() {
+ var exports = wasmEvalText(`
+ (module
+ (type (func (param i32)))
+ (tag (export "exn") (type 0)))
+ `).exports;
+
+ wasmEvalText(
+ `
+ (module
+ (type (func (param i32)))
+ (import "m" "exn" (tag (type 0))))
+ `,
+ { m: exports }
+ );
+
+ assertErrorMessage(
+ () => {
+ wasmEvalText(
+ `
+ (module
+ (type (func (param)))
+ (import "m" "exn" (tag (type 0))))
+ `,
+ { m: exports }
+ );
+ },
+ WebAssembly.LinkError,
+ /imported tag 'm.exn' signature mismatch/
+ );
+}
+
+// Test imports/exports descriptions.
+function testDescriptions() {
+ const imports = WebAssembly.Module.imports(
+ new WebAssembly.Module(
+ wasmTextToBinary(`
+ (module $m
+ (type (func))
+ (import "m" "e" (tag (type 0))))
+ `)
+ )
+ );
+
+ const exports = WebAssembly.Module.exports(
+ new WebAssembly.Module(
+ wasmTextToBinary(`
+ (module
+ (type (func))
+ (tag (export "e") (type 0)))
+ `)
+ )
+ );
+
+ assertEq(imports[0].module, "m");
+ assertEq(imports[0].name, "e");
+ assertEq(imports[0].kind, "tag");
+
+ assertEq(exports[0].name, "e");
+ assertEq(exports[0].kind, "tag");
+}
+
+testImports();
+testExports();
+testImportExport();
+testDescriptions();
diff --git a/js/src/jit-test/tests/wasm/exceptions/instructions.js b/js/src/jit-test/tests/wasm/exceptions/instructions.js
new file mode 100644
index 0000000000..a184b2e99e
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/instructions.js
@@ -0,0 +1,1528 @@
+// Tests for Wasm exception proposal instructions.
+
+// Test try blocks with no handlers.
+assertEq(
+ wasmEvalText(
+ `(module
+ (func (export "f") (result i32)
+ try (result i32) (i32.const 0) end))`
+ ).exports.f(),
+ 0
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (func (export "f") (result i32)
+ try (result i32) (i32.const 0) (br 0) (i32.const 1) end))`
+ ).exports.f(),
+ 0
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ (throw $exn)
+ (i32.const 1)
+ end
+ drop
+ (i32.const 2)
+ catch $exn
+ (i32.const 0)
+ end))`
+ ).exports.f(),
+ 0
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ try
+ try
+ (throw $exn)
+ end
+ end
+ (i32.const 1)
+ end
+ drop
+ (i32.const 2)
+ catch $exn
+ (i32.const 0)
+ end))`
+ ).exports.f(),
+ 0
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ try
+ try
+ (throw $exn)
+ end
+ catch_all
+ rethrow 0
+ end
+ (i32.const 1)
+ end
+ drop
+ (i32.const 2)
+ catch $exn
+ (i32.const 0)
+ end))`
+ ).exports.f(),
+ 0
+);
+
+// Test trivial try-catch with empty bodies.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try nop catch $exn end
+ (i32.const 0)))`
+ ).exports.f(),
+ 0
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (func (export "f") (result i32)
+ try nop catch_all end
+ (i32.const 0)))`
+ ).exports.f(),
+ 0
+);
+
+// Test try block with no throws
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ (i32.const 0)
+ catch $exn
+ (i32.const 1)
+ end))`
+ ).exports.f(),
+ 0
+);
+
+// Ensure catch block is really not run when no throw occurs.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32) (local i32)
+ try
+ (local.set 0 (i32.const 42))
+ catch $exn
+ (local.set 0 (i32.const 99))
+ end
+ (local.get 0)))`
+ ).exports.f(),
+ 42
+);
+
+// Simple uses of throw.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ (i32.const 42)
+ (throw $exn)
+ catch $exn
+ drop
+ (i32.const 1)
+ end))`
+ ).exports.f(),
+ 1
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func $foo (param i32) (result i32)
+ (local.get 0) (throw $exn))
+ (func (export "f") (result i32)
+ try (result i32)
+ (i32.const 42)
+ (call $foo)
+ catch $exn
+ drop
+ (i32.const 1)
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Simple uses of throw of some Wasm vectortype values (Simd128).
+if (wasmSimdEnabled()) {
+ assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param v128 v128 v128 v128)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ (v128.const i32x4 42 41 40 39)
+ (v128.const f32x4 4.2 4.1 0.40 3.9)
+ (v128.const i64x2 42 41)
+ (v128.const f64x2 4.2 4.1)
+ (throw $exn)
+ catch $exn
+ drop drop drop
+ (i64x2.all_true)
+ end))`
+ ).exports.f(),
+ 1
+ );
+
+ assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param v128 v128 v128 v128)))
+ (tag $exn (type 0))
+ (func $foo (param v128 v128 v128 v128) (result i32)
+ (throw $exn (local.get 0)
+ (local.get 1)
+ (local.get 2)
+ (local.get 3)))
+ (func (export "f") (result i32)
+ try (result i32)
+ (v128.const i32x4 42 41 40 39)
+ (v128.const f32x4 4.2 4.1 0.40 3.9)
+ (v128.const i64x2 42 41)
+ (v128.const f64x2 4.2 4.1)
+ (call $foo)
+ catch $exn
+ drop drop drop
+ (i64x2.all_true)
+ end))`
+ ).exports.f(),
+ 1
+ );
+
+ {
+ let imports =
+ wasmEvalText(
+ `(module
+ (tag $exn (export "exn") (param v128))
+ (func (export "throws") (param v128) (result v128)
+ (throw $exn (local.get 0))
+ (v128.const i32x4 9 10 11 12)))`).exports;
+
+ let mod =
+ `(module
+ (import "m" "exn" (tag $exn (param v128)))
+ (import "m" "throws" (func $throws (param v128) (result v128)))
+ (func (export "f") (result i32) (local v128)
+ (v128.const i32x4 1 2 3 4)
+ (local.tee 0)
+ (try (param v128) (result v128)
+ (do (call $throws))
+ (catch $exn)
+ (catch_all (v128.const i32x4 5 6 7 8)))
+ (local.get 0)
+ (i32x4.eq)
+ (i32x4.all_true)))`;
+
+ assertEq(wasmEvalText(mod, { m : imports }).exports.f(), 1);
+ }
+}
+
+// Further nested call frames should be ok.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func $foo (param i32) (result i32)
+ (local.get 0) (call $bar))
+ (func $bar (param i32) (result i32)
+ (local.get 0) (call $quux))
+ (func $quux (param i32) (result i32)
+ (local.get 0) (throw $exn))
+ (func (export "f") (result i32)
+ try (result i32)
+ (i32.const 42)
+ (call $foo)
+ catch $exn
+ drop
+ (i32.const 1)
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Basic throwing from loop.
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn)
+ ;; For the purpose of this test, the params below should be increasing.
+ (func (export "f") (param $depth_to_throw_exn i32)
+ (param $maximum_loop_iterations i32)
+ (result i32)
+ (local $loop_counter i32)
+ ;; The loop is counting down.
+ (local.get $maximum_loop_iterations)
+ (local.set $loop_counter)
+ (block $catch
+ (loop $loop
+ (if (i32.eqz (local.get $loop_counter))
+ (then
+ (return (i32.const 440)))
+ (else
+ (try
+ (do
+ (if (i32.eq (local.get $depth_to_throw_exn)
+ (local.get $loop_counter))
+ (then
+ (throw $exn))
+ (else
+ (local.set $loop_counter
+ (i32.sub (local.get $loop_counter)
+ (i32.const 1))))))
+ (catch $exn (br $catch))
+ (catch_all))))
+ (br $loop))
+ (return (i32.const 10001)))
+ (i32.const 10000)))`
+ ).exports.f(2, 4),
+ 10000
+);
+
+// Ensure conditional throw works.
+let conditional = wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f") (param i32) (result i32)
+ try (result i32)
+ (local.get 0)
+ if (result i32)
+ (throw $exn)
+ else
+ (i32.const 42)
+ end
+ catch $exn
+ (i32.const 99)
+ end))`
+).exports.f;
+
+assertEq(conditional(0), 42);
+assertEq(conditional(1), 99);
+
+// Ensure multiple & nested try-catch blocks work.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func $foo (throw $exn))
+ (func (export "f") (result i32) (local i32)
+ try
+ nop
+ catch $exn
+ (local.set 0 (i32.const 99))
+ end
+ try
+ (call $foo)
+ catch $exn
+ (local.set 0 (i32.const 42))
+ end
+ (local.get 0)))`
+ ).exports.f(),
+ 42
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32) (local i32)
+ try
+ try
+ try
+ (throw $exn)
+ catch $exn
+ (local.set 0 (i32.const 42))
+ end
+ catch $exn
+ (local.set 0 (i32.const 97))
+ end
+ catch $exn
+ (local.set 0 (i32.const 98))
+ end
+ (local.get 0)))`
+ ).exports.f(),
+ 42
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn)
+ (func (export "f") (result i32)
+ (try
+ (do throw $exn)
+ (catch $exn
+ (try
+ (do (throw $exn))
+ (catch $exn))))
+ (i32.const 27)))`
+ ).exports.f(),
+ 27
+);
+
+{
+ let nested_throw_in_block_and_in_catch =
+ wasmEvalText(
+ `(module
+ (tag $exn)
+ (func $throw
+ (throw $exn))
+ (func (export "f") (param $arg i32) (result i32)
+ (block (result i32)
+ (try (result i32)
+ (do
+ (call $throw)
+ (unreachable))
+ (catch $exn
+ (if (result i32)
+ (local.get $arg)
+ (then
+ (try (result i32)
+ (do
+ (call $throw)
+ (unreachable))
+ (catch $exn
+ (i32.const 27))))
+ (else
+ (i32.const 11))))))))`
+ ).exports.f;
+
+ assertEq(nested_throw_in_block_and_in_catch(1), 27);
+ assertEq(nested_throw_in_block_and_in_catch(0), 11);
+}
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $thrownExn)
+ (tag $notThrownExn)
+ (func (export "f") (result i32)
+ (try (result i32)
+ (do
+ (try (result i32)
+ (do (throw $thrownExn))
+ (catch $notThrownExn
+ (i32.const 19))
+ (catch $thrownExn
+ (i32.const 20))
+ (catch_all
+ (i32.const 21)))))))`
+ ).exports.f(),
+ 20
+);
+
+// Test that uncaught exceptions get propagated.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $thrownExn)
+ (tag $notThrownExn)
+ (func (export "f") (result i32) (local i32)
+ (try
+ (do
+ (try
+ (do
+ (try
+ (do (throw $thrownExn))
+ (catch $notThrownExn
+ (local.set 0
+ (i32.or (local.get 0)
+ (i32.const 1))))))
+ (catch $notThrownExn
+ (local.set 0
+ (i32.or (local.get 0)
+ (i32.const 2))))))
+ (catch $thrownExn
+ (local.set 0
+ (i32.or (local.get 0)
+ (i32.const 4)))))
+ (local.get 0)))`
+ ).exports.f(),
+ 4
+);
+
+// Test tag dispatch for catches.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (tag $exn3 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ throw $exn1
+ catch $exn1
+ i32.const 1
+ catch $exn2
+ i32.const 2
+ catch $exn3
+ i32.const 3
+ end))`
+ ).exports.f(),
+ 1
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (tag $exn3 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ throw $exn2
+ catch $exn1
+ i32.const 1
+ catch $exn2
+ i32.const 2
+ catch $exn3
+ i32.const 3
+ end))`
+ ).exports.f(),
+ 2
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (tag $exn3 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ throw $exn3
+ catch $exn1
+ i32.const 1
+ catch $exn2
+ i32.const 2
+ catch $exn3
+ i32.const 3
+ end))`
+ ).exports.f(),
+ 3
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (tag $exn3 (type 0))
+ (tag $exn4 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ throw $exn4
+ catch $exn1
+ i32.const 1
+ catch $exn2
+ i32.const 2
+ catch $exn3
+ i32.const 3
+ end
+ catch $exn4
+ i32.const 4
+ end))`
+ ).exports.f(),
+ 4
+);
+
+// Test usage of br before a throw.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try $l (result i32)
+ (i32.const 2)
+ (br $l)
+ (throw $exn)
+ catch $exn
+ drop
+ (i32.const 1)
+ end))`
+ ).exports.f(),
+ 2
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try $l (result i32)
+ (throw $exn)
+ catch $exn
+ (i32.const 2)
+ (br $l)
+ rethrow 0
+ end))`
+ ).exports.f(),
+ 2
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try $l (result i32)
+ (throw $exn)
+ catch_all
+ (i32.const 2)
+ (br $l)
+ rethrow 0
+ end))`
+ ).exports.f(),
+ 2
+);
+
+// Test br branching out of a catch block.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ block $l (result i32)
+ block (result i32)
+ try (result i32)
+ (i32.const 42)
+ (throw $exn)
+ catch $exn
+ br $l
+ (i32.const 99)
+ end
+ end
+ end))`
+ ).exports.f(),
+ 42
+);
+
+// Test dead catch block.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ i32.const 0
+ return
+ try nop catch $exn end))`
+ ).exports.f(),
+ 0
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (func (export "f") (result i32)
+ i32.const 0
+ return
+ try nop catch_all end))`
+ ).exports.f(),
+ 0
+);
+
+// Test catch with exception values pushed to stack.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (type (func (param i32)))
+ (type (func (param i64)))
+ (tag $exn (type 0))
+ (tag $foo (type 1))
+ (tag $bar (type 2))
+ (func (export "f") (result i32)
+ try $l (result i32)
+ (i32.const 42)
+ (throw $exn)
+ catch $exn
+ catch_all
+ (i32.const 99)
+ end))`
+ ).exports.f(),
+ 42
+);
+
+// Throw an exception carrying more than one value.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32 i64 f32 f64)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try $l (result i32 i64 f32 f64)
+ (i32.const 42)
+ (i64.const 84)
+ (f32.const 42.2)
+ (f64.const 84.4)
+ (throw $exn)
+ catch $exn
+ catch_all
+ (i32.const 99)
+ (i64.const 999)
+ (f32.const 99.9)
+ (f64.const 999.9)
+ end
+ drop drop drop))`
+ ).exports.f(),
+ 42
+);
+
+// This should also work inside nested frames.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32 i64 f32 f64)))
+ (tag $exn (type 0))
+ (func $foo (param i32 i64 f32 f64) (result i32 i64 f32 f64)
+ (local.get 0)
+ (local.get 1)
+ (local.get 2)
+ (local.get 3)
+ (throw $exn))
+ (func (export "f") (result i32)
+ try $l (result i32 i64 f32 f64)
+ (i32.const 42)
+ (i64.const 84)
+ (f32.const 42.2)
+ (f64.const 84.4)
+ (call $foo)
+ catch $exn
+ catch_all
+ (i32.const 99)
+ (i64.const 999)
+ (f32.const 99.9)
+ (f64.const 999.9)
+ end
+ drop drop drop))`
+ ).exports.f(),
+ 42
+);
+
+// Multiple tagged catch in succession.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ (i32.const 42)
+ (throw $exn2)
+ catch $exn1
+ catch $exn2
+ catch_all
+ (i32.const 99)
+ end))`
+ ).exports.f(),
+ 42
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn0)
+ (tag $exn1)
+ (tag $exn2)
+ (tag $exn3)
+ (tag $exn4)
+ (tag $exn5)
+ (func (export "f") (result i32)
+ (try (result i32)
+ (do (throw $exn4))
+ (catch $exn5 (i32.const 5))
+ (catch $exn2 (i32.const 2))
+ (catch $exn4 (i32.const 4)) ;; Caught here.
+ (catch $exn4 (i32.const 44)))))`
+ ).exports.f(),
+ 4
+);
+
+// Try catch with block parameters.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ (i32.const 42)
+ try (param i32) (result i32)
+ nop
+ catch $exn
+ (i32.const 99)
+ end))`
+ ).exports.f(),
+ 42
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ (i32.const 42)
+ try $l (param i32) (result i32)
+ (throw $exn)
+ catch $exn
+ catch_all
+ (i32.const 99)
+ end))`
+ ).exports.f(),
+ 42
+);
+
+// Test the catch_all case.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (func (export "f") (result i32)
+ try $l (result i32)
+ (i32.const 42)
+ (throw $exn2)
+ catch $exn1
+ catch_all
+ (i32.const 99)
+ end))`
+ ).exports.f(),
+ 99
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param i32))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ (i32.const 42)
+ (throw $exn)
+ catch_all
+ (i32.const 99)
+ end
+ catch $exn
+ end))`
+ ).exports.f(),
+ 99
+);
+
+// Test foreign exception catch.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (import "m" "foreign" (func $foreign))
+ (tag $exn (type 0))
+ (func (export "f") (result i32) (local i32)
+ try $l
+ (call $foreign)
+ catch $exn
+ catch_all
+ (local.set 0 (i32.const 42))
+ end
+ (local.get 0)))`,
+ {
+ m: {
+ foreign() {
+ throw 5;
+ },
+ },
+ }
+ ).exports.f(),
+ 42
+);
+
+// Exception handlers should not catch traps.
+assertErrorMessage(
+ () =>
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32) (local i32)
+ try $l
+ unreachable
+ catch $exn
+ (local.set 0 (i32.const 98))
+ catch_all
+ (local.set 0 (i32.const 99))
+ end
+ (local.get 0)))`
+ ).exports.f(),
+ WebAssembly.RuntimeError,
+ "unreachable executed"
+);
+
+// Ensure that a RuntimeError created by the user is not filtered out
+// as a trap emitted by the runtime (i.e., the filtering predicate is not
+// observable from JS).
+assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "foreign" (func $foreign))
+ (func (export "f") (result i32)
+ try (result i32)
+ (call $foreign)
+ (i32.const 99)
+ catch_all
+ (i32.const 42)
+ end))`,
+ {
+ m: {
+ foreign() {
+ throw new WebAssembly.RuntimeError();
+ },
+ },
+ }
+ ).exports.f(),
+ 42
+);
+
+// Test uncatchable JS exceptions (OOM & stack overflow).
+{
+ let f = wasmEvalText(
+ `(module
+ (import "m" "foreign" (func $foreign))
+ (func (export "f") (result)
+ try
+ (call $foreign)
+ catch_all
+ end))`,
+ {
+ m: {
+ foreign() {
+ throwOutOfMemory();
+ },
+ },
+ }
+ ).exports.f;
+
+ var thrownVal;
+ try {
+ f();
+ } catch (exn) {
+ thrownVal = exn;
+ }
+
+ assertEq(thrownVal, "out of memory");
+}
+
+assertErrorMessage(
+ () =>
+ wasmEvalText(
+ `(module
+ (import "m" "foreign" (func $foreign))
+ (func (export "f")
+ try
+ (call $foreign)
+ catch_all
+ end))`,
+ {
+ m: {
+ foreign: function foreign() {
+ foreign();
+ },
+ },
+ }
+ ).exports.f(),
+ Error,
+ "too much recursion"
+);
+
+// Test all implemented instructions in a single module.
+{
+ let divFunctypeInline =
+ `(param $numerator i32) (param $denominator i32) (result i32)`;
+
+ let safediv = wasmEvalText(
+ `(module
+ (tag $divexn (param i32 i32))
+ (tag $notThrownExn (param i32))
+ (func $throwingdiv ${divFunctypeInline}
+ (local.get $numerator)
+ (local.get $denominator)
+ (if (param i32 i32) (result i32)
+ (i32.eqz (local.get $denominator))
+ (then
+ (try (param i32 i32)
+ (do (throw $divexn))
+ (delegate 0))
+ (i32.const 9))
+ (else
+ i32.div_u)))
+ (func $safediv (export "safediv") ${divFunctypeInline}
+ (local.get $numerator)
+ (local.get $denominator)
+ (try (param i32 i32) (result i32)
+ (do
+ (call $throwingdiv))
+ (catch $notThrownExn)
+ (catch $divexn
+ i32.add)
+ (catch_all
+ (i32.const 44)))))`
+ ).exports.safediv;
+
+ assertEq(safediv(6, 3), 2);
+ assertEq(safediv(6, 0), 6);
+}
+
+// Test simple rethrow.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn
+ catch $exn
+ rethrow 0
+ end
+ i32.const 1
+ catch $exn
+ i32.const 27
+ end))`
+ ).exports.f(),
+ 27
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn
+ catch_all
+ rethrow 0
+ end
+ i32.const 1
+ catch $exn
+ i32.const 27
+ end))`
+ ).exports.f(),
+ 27
+);
+
+// Test rethrows in nested blocks.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn
+ catch $exn
+ block
+ rethrow 1
+ end
+ end
+ i32.const 1
+ catch $exn
+ i32.const 27
+ end))`
+ ).exports.f(),
+ 27
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn
+ catch_all
+ block
+ rethrow 1
+ end
+ end
+ i32.const 1
+ catch $exn
+ i32.const 27
+ end))`
+ ).exports.f(),
+ 27
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn1
+ catch $exn1
+ try
+ throw $exn2
+ catch $exn2
+ rethrow 1
+ end
+ end
+ i32.const 0
+ catch $exn1
+ i32.const 1
+ catch $exn2
+ i32.const 2
+ end))`
+ ).exports.f(),
+ 1
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn1 (type 0))
+ (tag $exn2 (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn1
+ catch $exn1
+ try
+ throw $exn2
+ catch_all
+ rethrow 1
+ end
+ end
+ i32.const 0
+ catch $exn1
+ i32.const 1
+ catch $exn2
+ i32.const 2
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Test that rethrow makes the rest of the block dead code.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag (param i32))
+ (func (export "f") (result i32)
+ (try (result i32)
+ (do (i32.const 1))
+ (catch 0
+ (rethrow 0)
+ (i32.const 2)))))`
+ ).exports.f(),
+ 1
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag (param i32))
+ (func (export "f") (result i32)
+ (try (result i32)
+ (do (try
+ (do (i32.const 13)
+ (throw 0))
+ (catch 0
+ (rethrow 0)))
+ (unreachable))
+ (catch 0))))`
+ ).exports.f(),
+ 13
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag)
+ (func (export "f") (result i32)
+ (try (result i32)
+ (do
+ (try
+ (do (throw 0))
+ (catch 0
+ (i32.const 4)
+ (rethrow 0)))
+ (unreachable))
+ (catch 0
+ (i32.const 13)))))`
+ ).exports.f(),
+ 13
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag (param i32))
+ (func (export "f") (result i32)
+ (try (result i32)
+ (do (try
+ (do (i32.const 13)
+ (throw 0))
+ (catch 0
+ (i32.const 111)
+ (rethrow 0)))
+ (i32.const 222))
+ (catch 0))))`
+ ).exports.f(),
+ 13
+);
+
+// Test try-delegate blocks.
+
+// Dead delegate to caller
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ i32.const 1
+ br 0
+ try
+ throw $exn
+ delegate 0))`
+ ).exports.f(),
+ 1
+);
+
+// Nested try-delegate.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ try
+ throw $exn
+ delegate 0
+ end
+ i32.const 0
+ catch $exn
+ i32.const 1
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Non-throwing and breaking try-delegate.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ i32.const 1
+ br 0
+ delegate 0))`
+ ).exports.f(),
+ 1
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ i32.const 1
+ return
+ delegate 0))`
+ ).exports.f(),
+ 1
+ );
+
+// More nested try-delegate.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ i32.const 42
+ throw $exn
+ delegate 0
+ i32.const 0
+ catch $exn
+ i32.const 1
+ i32.add
+ end))`
+ ).exports.f(),
+ 43
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ try
+ i32.const 42
+ throw $exn
+ delegate 1
+ i32.const 0
+ catch $exn
+ i32.const 1
+ i32.add
+ end
+ catch $exn
+ i32.const 2
+ i32.add
+ end))`
+ ).exports.f(),
+ 44
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ try (result i32)
+ try (result i32)
+ try
+ i32.const 42
+ throw $exn
+ delegate 1
+ i32.const 0
+ catch $exn
+ i32.const 1
+ i32.add
+ end
+ delegate 0
+ catch $exn
+ i32.const 2
+ i32.add
+ end))`
+ ).exports.f(),
+ 44
+);
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func $g (param i32) (result i32) (i32.const 42))
+ (func (export "f") (result i32)
+ try (result i32)
+ try $t
+ block (result i32)
+ (i32.const 4)
+ (call $g)
+ try
+ throw $exn
+ delegate $t
+ end
+ drop
+ end
+ i32.const 0
+ catch_all
+ i32.const 1
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Test delegation to function body and blocks.
+
+// Non-throwing.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ i32.const 1
+ delegate 0))`
+ ).exports.f(),
+ 1
+);
+
+// Block target.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param i32))
+ (func (export "f") (result i32)
+ try (result i32)
+ block
+ try
+ i32.const 1
+ throw $exn
+ delegate 0
+ end
+ i32.const 0
+ catch $exn
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Catch target.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ try
+ throw $exn
+ catch $exn
+ try
+ throw $exn
+ delegate 0
+ end
+ i32.const 0
+ catch_all
+ i32.const 1
+ end))`
+ ).exports.f(),
+ 1
+);
+
+// Target function body.
+assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (func (export "f") (result i32)
+ try (result i32)
+ call $g
+ catch $exn
+ end)
+ (func $g (result i32)
+ try (result i32)
+ try
+ i32.const 42
+ throw $exn
+ delegate 1
+ i32.const 0
+ catch $exn
+ i32.const 1
+ i32.add
+ end))`
+ ).exports.f(),
+ 42
+);
+
+// Try-delegate from inside a loop.
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn)
+ ;; For the purpose of this test, the params below should be increasing.
+ (func (export "f") (param $depth_to_throw_exn i32)
+ (param $maximum_loop_iterations i32)
+ (result i32)
+ (local $loop_countdown i32)
+ ;; Counts how many times the loop was started.
+ (local $loop_verifier i32)
+ ;; The loop is counting down.
+ (local.get $maximum_loop_iterations)
+ (local.set $loop_countdown)
+ (try $catch_exn (result i32)
+ (do
+ (try
+ (do
+ (loop $loop
+ ;; Counts how many times the loop was started.
+ (local.set $loop_verifier
+ (i32.add (i32.const 1)
+ (local.get $loop_verifier)))
+ (if (i32.eqz (local.get $loop_countdown))
+ (then (return (i32.const 440)))
+ (else
+ (try $rethrow_label
+ (do
+ (if (i32.eq (local.get $depth_to_throw_exn)
+ (local.get $loop_countdown))
+ (then (throw $exn))
+ (else
+ (local.set $loop_countdown
+ (i32.sub (local.get $loop_countdown)
+ (i32.const 1))))))
+ (catch $exn (try
+ (do (rethrow $rethrow_label))
+ (delegate $catch_exn))))))
+ (br $loop)))
+ (catch_all unreachable))
+ (i32.const 2000))
+ (catch_all (i32.const 10000)))
+ (i32.add (local.get $loop_verifier))))`
+ ).exports.f(3, 5),
+ 10003
+);
diff --git a/js/src/jit-test/tests/wasm/exceptions/ion-loop-phi.js b/js/src/jit-test/tests/wasm/exceptions/ion-loop-phi.js
new file mode 100644
index 0000000000..bdf49ca97b
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/ion-loop-phi.js
@@ -0,0 +1,59 @@
+// Finishing a loop will try to minimize phi nodes. We must properly replace
+// phi nodes that escape a loop via catch block control flow patches.
+wasmEvalText(`(module
+ (func)
+ (func (local i32)
+ try
+ loop
+ call 0
+ i32.const 0
+ br_if 0
+ end
+ catch_all
+ end
+ )
+)`);
+
+// Same as above, but ensure that we check every enclosing try block for
+// control flow patches, as delegate can tunnel outwards.
+wasmEvalText(`(module
+ (func)
+ (func (local i32)
+ try
+ try
+ loop
+ call 0
+ i32.const 0
+ br_if 0
+ end
+ delegate 0
+ catch_all
+ end
+ )
+)`);
+
+// Ensure that we check the body block as delegate can target that.
+wasmEvalText(`(module
+ (func)
+ (func (local i32)
+ loop
+ try
+ (; catch patch to try block is added ;)
+ call 0
+ (; br_if ensures we will need a backedge ;)
+ i32.const 0
+ br_if 1
+ (; catch patches are relocated to body ;)
+ delegate 1
+ (; finishing loop backedge must fixup patches stored in body ;)
+ end
+
+ (; add another catch patch to body so that the landing pad will be a
+ join point between two edges, forcing a use of the dangling phi,
+ hitting the debug assertion
+ ;)
+ try
+ call 0
+ delegate 0
+ )
+)`);
diff --git a/js/src/jit-test/tests/wasm/exceptions/js-api.js b/js/src/jit-test/tests/wasm/exceptions/js-api.js
new file mode 100644
index 0000000000..4c2f018808
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/js-api.js
@@ -0,0 +1,490 @@
+// Tests for wasm exception proposal JS API features.
+
+load(libdir + "eqArrayHelper.js");
+
+// WebAssembly.Tag tests.
+assertErrorMessage(
+ () => WebAssembly.Tag(),
+ TypeError,
+ /calling a builtin Tag constructor without new is forbidden/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Tag(),
+ TypeError,
+ /At least 1 argument required/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Tag(3),
+ TypeError,
+ /first argument must be a tag descriptor/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Tag({ parameters: ["foobar"] }),
+ TypeError,
+ /bad value type/
+);
+
+new WebAssembly.Tag({ parameters: [] });
+new WebAssembly.Tag({ parameters: ["i32"] });
+new WebAssembly.Tag({ parameters: ["i32", "externref"] });
+
+wasmEvalText(`(module (import "m" "e" (tag)))`, {
+ m: { e: new WebAssembly.Tag({ parameters: [] }) },
+});
+
+wasmEvalText(`(module (import "m" "e" (tag (param i32))))`, {
+ m: { e: new WebAssembly.Tag({ parameters: ["i32"] }) },
+});
+
+wasmEvalText(`(module (import "m" "e" (tag (param i32 i64))))`, {
+ m: { e: new WebAssembly.Tag({ parameters: ["i32", "i64"] }) },
+});
+
+assertErrorMessage(
+ () =>
+ wasmEvalText(`(module (import "m" "e" (tag (param i32))))`, {
+ m: { e: new WebAssembly.Tag({ parameters: [] }) },
+ }),
+ WebAssembly.LinkError,
+ /imported tag 'm.e' signature mismatch/
+);
+
+assertErrorMessage(
+ () =>
+ wasmEvalText(`(module (import "m" "e" (tag (param))))`, {
+ m: { e: new WebAssembly.Tag({ parameters: ["i32"] }) },
+ }),
+ WebAssembly.LinkError,
+ /imported tag 'm.e' signature mismatch/
+);
+
+// Test WebAssembly.Tag methods.
+// TODO: add runtime detection for js-types
+// {
+// let params = [
+// [],
+// ["i32"],
+// ["i32", "i64"],
+// ["f32", "externref"],
+// ["i32", "i64", "f32", "f64"],
+// ];
+
+// for (const arg of params) {
+// const tag = new WebAssembly.Tag({ parameters: arg });
+// assertEqArray(tag.type().parameters, arg);
+// }
+// }
+
+// WebAssembly.Exception tests.
+assertErrorMessage(
+ () => WebAssembly.Exception(),
+ TypeError,
+ /calling a builtin Exception constructor without new is forbidden/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(),
+ TypeError,
+ /At least 2 arguments required/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(3, []),
+ TypeError,
+ /first argument must be a WebAssembly.Tag/
+);
+
+const { tag1, tag2, tag3, tag4, tag5, tag6, tag7, tag8, tag9 } = wasmEvalText(
+ `(module
+ (tag (export "tag1") (param))
+ (tag (export "tag2") (param i32))
+ (tag (export "tag3") (param i32 f32))
+ (tag (export "tag4") (param i32 externref i32))
+ (tag (export "tag5") (param i32 externref i32 externref))
+ (tag (export "tag6") (param funcref))
+ (tag (export "tag7") (param i64))
+ (tag (export "tag8") (param i32 f64))
+ (tag (export "tag9") (param externref funcref)))`
+).exports;
+
+new WebAssembly.Exception(tag1, []);
+new WebAssembly.Exception(tag2, [3]);
+new WebAssembly.Exception(tag3, [3, 5.5]);
+new WebAssembly.Exception(tag4, [3, "foo", 4]);
+new WebAssembly.Exception(tag5, [3, "foo", 4, "bar"]);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(tag2, []),
+ TypeError,
+ /expected 1 values but got 0/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(tag2, [3n]),
+ TypeError,
+ /can't convert BigInt to number/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(tag6, [undefined]),
+ TypeError,
+ /can only pass WebAssembly exported functions to funcref/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(tag7, [undefined]),
+ TypeError,
+ /can't convert undefined to BigInt/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(tag7, {}),
+ TypeError,
+ /\({}\) is not iterable/
+);
+
+assertErrorMessage(
+ () => new WebAssembly.Exception(tag7, 1),
+ TypeError,
+ /second argument must be an object/
+);
+
+// Test Exception methods.
+{
+ const exn1 = new WebAssembly.Exception(tag1, []);
+ assertEq(exn1.is(tag1), true);
+ assertEq(exn1.is(tag2), false);
+ assertErrorMessage(
+ () => exn1.is(),
+ TypeError,
+ /At least 1 argument required/
+ );
+ assertErrorMessage(
+ () => exn1.is(5),
+ TypeError,
+ /first argument must be a WebAssembly.Tag/
+ );
+
+ const exn2 = new WebAssembly.Exception(tag2, [3]);
+ assertEq(exn2.getArg(tag2, 0), 3);
+
+ assertEq(new WebAssembly.Exception(tag2, [undefined]).getArg(tag2, 0), 0);
+
+ const exn4 = new WebAssembly.Exception(tag4, [3, "foo", 4]);
+ assertEq(exn4.getArg(tag4, 0), 3);
+ assertEq(exn4.getArg(tag4, 1), "foo");
+ assertEq(exn4.getArg(tag4, 2), 4);
+
+ const exn5 = new WebAssembly.Exception(tag5, [3, "foo", 4, "bar"]);
+ assertEq(exn5.getArg(tag5, 3), "bar");
+
+ const { funcref } = wasmEvalText(
+ `(module (func (export "funcref")))`
+ ).exports;
+ const exn9 = new WebAssembly.Exception(tag9, ["foo", funcref]);
+ assertEq(exn9.getArg(tag9, 0), "foo");
+ assertEq(exn9.getArg(tag9, 1), funcref);
+
+ assertErrorMessage(
+ () => exn2.getArg(),
+ TypeError,
+ /At least 2 arguments required/
+ );
+ assertErrorMessage(
+ () => exn2.getArg(5, 0),
+ TypeError,
+ /first argument must be a WebAssembly.Tag/
+ );
+ assertErrorMessage(
+ () => exn2.getArg(tag2, "foo"),
+ TypeError,
+ /bad Exception getArg index/
+ );
+ assertErrorMessage(
+ () => exn2.getArg(tag2, 10),
+ RangeError,
+ /bad Exception getArg index/
+ );
+}
+
+// Test throwing a JS constructed exception to Wasm.
+assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32)
+ try (result i32)
+ call $f
+ (i32.const 0)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn: tag2,
+ f: () => {
+ throw new WebAssembly.Exception(tag2, [42]);
+ },
+ },
+ }
+ ).exports.f(),
+ 42
+);
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32 f32)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32 f32)
+ try (result i32 f32)
+ call $f
+ (i32.const 0)
+ (f32.const 0)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn: tag3,
+ f: () => {
+ throw new WebAssembly.Exception(tag3, [42, 5.5]);
+ },
+ },
+ }
+ ).exports.f(),
+ [42, 5.5]
+);
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32 f64)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32 f64)
+ try (result i32 f64)
+ call $f
+ (i32.const 0)
+ (f64.const 0)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn: tag8,
+ f: () => {
+ throw new WebAssembly.Exception(tag8, [9999, 9999]);
+ },
+ },
+ }
+ ).exports.f(),
+ [9999, 9999]
+);
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32 externref i32)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32 externref i32)
+ try (result i32 externref i32)
+ call $f
+ (i32.const 0)
+ (ref.null extern)
+ (i32.const 0)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn: tag4,
+ f: () => {
+ throw new WebAssembly.Exception(tag4, [42, "foo", 42]);
+ },
+ },
+ }
+ ).exports.f(),
+ [42, "foo", 42]
+);
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32 externref i32 externref)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32 externref i32 externref)
+ try (result i32 externref i32 externref)
+ call $f
+ (i32.const 0)
+ (ref.null extern)
+ (i32.const 0)
+ (ref.null extern)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn: tag5,
+ f: () => {
+ throw new WebAssembly.Exception(tag5, [42, "foo", 42, "bar"]);
+ },
+ },
+ }
+ ).exports.f(),
+ [42, "foo", 42, "bar"]
+);
+
+{
+ const { funcref } = wasmEvalText(
+ `(module (func (export "funcref")))`
+ ).exports;
+ assertEqArray(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param externref funcref)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result externref funcref)
+ try (result externref funcref)
+ call $f
+ (ref.null extern)
+ (ref.null func)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn: tag9,
+ f: () => {
+ throw new WebAssembly.Exception(tag9, ["foo", funcref]);
+ },
+ },
+ }
+ ).exports.f(),
+ ["foo", funcref]
+ );
+}
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32)
+ try (result i32)
+ call $f
+ (i32.const 0)
+ catch $exn
+ (i32.const 0)
+ catch_all
+ (i32.const 1)
+ end))`,
+ {
+ m: {
+ exn: tag1,
+ f: () => {
+ throw new WebAssembly.Exception(tag2, [42]);
+ },
+ },
+ }
+ ).exports.f(),
+ 1
+);
+
+{
+ const exn = new WebAssembly.Tag({ parameters: ["i32"] });
+ assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32)
+ try (result i32)
+ call $f
+ (i32.const 0)
+ catch $exn
+ end))`,
+ {
+ m: {
+ exn,
+ f: () => {
+ throw new WebAssembly.Exception(exn, [42]);
+ },
+ },
+ }
+ ).exports.f(),
+ 42
+ );
+}
+
+{
+ const exn1 = new WebAssembly.Tag({ parameters: ["i32"] });
+ const exn2 = new WebAssembly.Tag({ parameters: ["i32"] });
+ assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32)))
+ (import "m" "f" (func $f))
+ (func (export "f") (result i32)
+ try (result i32)
+ call $f
+ (i32.const 0)
+ catch $exn
+ catch_all
+ (i32.const 1)
+ end))`,
+ {
+ m: {
+ exn: exn1,
+ f: () => {
+ throw new WebAssembly.Exception(exn2, [42]);
+ },
+ },
+ }
+ ).exports.f(),
+ 1
+ );
+}
+
+// Test `getArg` on a Wasm-thrown exception.
+assertEq(
+ (() => {
+ try {
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32 f64)))
+ (func (export "f")
+ (i32.const 9999)
+ (f64.const 9999)
+ throw $exn))`,
+ { m: { exn: tag8 } }
+ ).exports.f();
+ } catch (exn) {
+ return exn.getArg(tag8, 1);
+ }
+ })(),
+ 9999
+);
+
+assertEqArray(
+ (() => {
+ try {
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn (param i32 externref i32 externref)))
+ (func (export "f") (param externref externref)
+ (i32.const 1)
+ (local.get 0)
+ (i32.const 2)
+ (local.get 1)
+ throw $exn))`,
+ { m: { exn: tag5 } }
+ ).exports.f("foo", "bar");
+ } catch (exn) {
+ return [
+ exn.getArg(tag5, 0),
+ exn.getArg(tag5, 1),
+ exn.getArg(tag5, 2),
+ exn.getArg(tag5, 3),
+ ];
+ }
+ })(),
+ [1, "foo", 2, "bar"]
+);
diff --git a/js/src/jit-test/tests/wasm/exceptions/memory.js b/js/src/jit-test/tests/wasm/exceptions/memory.js
new file mode 100644
index 0000000000..fef249ddf5
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/memory.js
@@ -0,0 +1,78 @@
+// Ensure memory operations work after a throw coming from a function call.
+// These are also testing that the InstanceReg/HeapReg are set correctly after
+// catching an exception. There are some variations to the kind of calls here;
+// we test for direct/indirect calls of local/imported functions, and the
+// indirect calls may come from a local table or an imported table.
+
+// Throw from a direct/indirect call of a local/imported function.
+{
+ let exports = wasmEvalText(
+ `(module $m
+ (memory $mem (data "bar"))
+ (tag $exn (export "exn"))
+ (tag $dummyExn (export "dummyExn"))
+ (func $throwsExn (export "throwsExn")
+ (throw $exn))
+ (func $anotherThrowsExn
+ (throw $exn))
+ (func $throwsDummyExn (export "throwsDummyExn")
+ (throw $dummyExn))
+ (table (export "tab") funcref (elem $anotherThrowsExn $throwsDummyExn)))`
+ ).exports;
+
+ function testMemoryAfterCall(callInstruction) {
+ assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn))
+ (tag $localExn (param i32))
+ (type $t (func))
+ (import "m" "tab" (table $importTable 2 funcref))
+ (import "m" "throwsExn" (func $importFuncThrowsExn))
+ (memory $mem (data "foo"))
+ (func $localFuncThrowsExn
+ (throw $exn))
+ (table $localTable funcref
+ (elem $localFuncThrowsExn $importFuncThrowsExn))
+ (func $anotherLocalFuncThrowsExn
+ (throw $exn))
+ (func $throwsLocalExn
+ (throw $localExn
+ (i32.const 9)))
+ (func (export "testFunc") (result i32)
+ (try (result i32)
+ (do
+ ${callInstruction}
+ ;; All the rest should not be reachable.
+ (call $anotherLocalFuncThrowsExn)
+ (throw $exn)
+ (call $throwsLocalExn)
+ unreachable)
+ (catch $localExn)
+ (catch $exn
+ (i32.load8_u
+ (i32.const 0)))
+ (catch $exn
+ ;; This should be ignored.
+ unreachable))))`,
+ { m: exports }
+ ).exports.testFunc(),
+ 'foo'.charCodeAt(0)
+ );
+ };
+
+ // Run test for various calls.
+ let callInstructions =
+ ["(call $anotherLocalFuncThrowsExn)",
+ "(call $importFuncThrowsExn)",
+ // Calls $localFuncThrowsExn.
+ "(call_indirect $localTable (type $t) (i32.const 0))",
+ // Calls $importFuncThrowsExn.
+ "(call_indirect $localTable (type $t) (i32.const 1))",
+ // Calls non exported function of the exports module $anotherThrowsExn.
+ "(call_indirect $importTable (type $t) (i32.const 0))"];
+
+ for (let callInstruction of callInstructions) {
+ testMemoryAfterCall(callInstruction);
+ }
+}
diff --git a/js/src/jit-test/tests/wasm/exceptions/prototypes.js b/js/src/jit-test/tests/wasm/exceptions/prototypes.js
new file mode 100644
index 0000000000..757ed2ddba
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/prototypes.js
@@ -0,0 +1,10 @@
+class _Tag extends WebAssembly.Tag {}
+class _Exception extends WebAssembly.Exception {}
+
+let tag = new _Tag({parameters: []});
+assertEq(tag instanceof _Tag, true);
+assertEq(tag instanceof WebAssembly.Tag, true);
+
+let exception = new _Exception(tag, []);
+assertEq(exception instanceof _Exception, true);
+assertEq(exception instanceof WebAssembly.Exception, true);
diff --git a/js/src/jit-test/tests/wasm/exceptions/reftypes.js b/js/src/jit-test/tests/wasm/exceptions/reftypes.js
new file mode 100644
index 0000000000..efd577dec8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/reftypes.js
@@ -0,0 +1,141 @@
+// Exception tests that use reference types.
+
+load(libdir + "eqArrayHelper.js");
+
+assertEq(
+ wasmEvalText(
+ `(module
+ (tag $exn (param externref))
+ (func (export "f") (result externref)
+ try (result externref)
+ ref.null extern
+ throw $exn
+ catch $exn
+ end))`
+ ).exports.f(),
+ null
+);
+
+{
+ let f = wasmEvalText(
+ `(module
+ (tag $exn (param funcref))
+ (func $f (export "f") (result funcref)
+ try (result funcref)
+ ref.func $f
+ throw $exn
+ catch $exn
+ end))`
+ ).exports.f;
+ assertEq(f(), f);
+}
+
+{
+ let f = wasmEvalText(
+ `(module
+ (tag $exn (param externref))
+ (func (export "f") (param externref) (result externref)
+ try (result externref)
+ local.get 0
+ throw $exn
+ catch $exn
+ end))`
+ ).exports.f;
+
+ for (v of WasmExternrefValues) {
+ assertEq(f(v), v);
+ }
+}
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (tag $exn (param externref externref))
+ (func (export "f") (param externref externref)
+ (result externref externref)
+ try (result externref externref)
+ local.get 0
+ local.get 1
+ throw $exn
+ catch $exn
+ end))`
+ ).exports.f("foo", "bar"),
+ ["foo", "bar"]
+);
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (tag $exn (param i32 i32 externref f32))
+ (func (export "f") (param externref)
+ (result i32 i32 externref f32)
+ try (result i32 i32 externref f32)
+ i32.const 0
+ i32.const 1
+ local.get 0
+ f32.const 2.0
+ throw $exn
+ catch $exn
+ end))`
+ ).exports.f("foo"),
+ [0, 1, "foo", 2.0]
+);
+
+assertEqArray(
+ wasmEvalText(
+ `(module
+ (tag $exn (param i32 i32 externref f32 externref f64))
+ (func (export "f") (param externref externref)
+ (result i32 i32 externref f32 externref f64)
+ try (result i32 i32 externref f32 externref f64)
+ i32.const 0
+ i32.const 1
+ local.get 0
+ f32.const 2.0
+ local.get 1
+ f64.const 3.0
+ throw $exn
+ catch $exn
+ end))`
+ ).exports.f("foo", "bar"),
+ [0, 1, "foo", 2.0, "bar", 3.0]
+);
+
+// Test to ensure that reference typed values are tracked correctly by
+// the GC within exception objects.
+{
+ gczeal(2, 1); // Collect on every allocation.
+
+ var thrower;
+ let exports = wasmEvalText(
+ `(module
+ (tag $exn (param externref))
+ (import "m" "f" (func $f (result externref)))
+ (func (export "thrower") (param externref)
+ (local.get 0)
+ (throw $exn))
+ (func (export "catcher") (result externref)
+ try (result externref)
+ (call $f)
+ catch $exn
+ end))`,
+ {
+ m: {
+ f: () => {
+ // The purpose of this intermediate try-catch in JS is to force
+ // some allocation to occur after Wasm's `throw`, triggering GC, and
+ // then rethrow back to Wasm to observe any errors.
+ try {
+ thrower("foo");
+ } catch (e) {
+ let v = { x: 5 };
+ throw e;
+ }
+ },
+ },
+ }
+ ).exports;
+
+ thrower = exports.thrower;
+ assertEq(exports.catcher(), "foo");
+}
diff --git a/js/src/jit-test/tests/wasm/exceptions/side-effects-in-try.js b/js/src/jit-test/tests/wasm/exceptions/side-effects-in-try.js
new file mode 100644
index 0000000000..d5d5567fca
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/side-effects-in-try.js
@@ -0,0 +1,234 @@
+// -----------------------------------------------------------------------------
+// The tests in this file assert that any side effects that happened in try code
+// before an exception was thrown, will be known to the landing pad. It checks
+// local throws and throws from direct calls (of local and imported functions).
+// Side effects checked are changes to locals, and to globals.
+// -----------------------------------------------------------------------------
+
+load(libdir + "eqArrayHelper.js");
+
+
+function testSideEffectsOnLocals() {
+ // Locals set before and after throwing instructions, either locally, or from
+ // a direct wasm function call, for locals of all Wasm numtype and a local of
+ // reftype (externref), and the thrown exception carrying a value from each
+ // Wasm numtype and one of Wasm vectype (Simd128). Testing to see if the state
+ // of the locals at the moment $exn is thrown, is known to the landing pad
+ // when $exn is caught.
+
+ let localThrow = "(throw $exn)";
+
+ // The following is taken from calls.js
+ // Some variables to be used in all tests.
+ let typesJS = ["i32", "i64", "f32", "f64"];
+ let types = typesJS.join(" ");
+ let exnTypeDef = `(type $exnType (func (param ${types})))`;
+ let correctLocalValues =
+ `;; Correct local values
+ (i32.const 2)
+ (i64.const 3)
+ (f32.const 4)
+ (f64.const 13.37)`;
+ let correctLocalValuesJS = [2, 3n, 4, 13.37];
+
+ let wrongValues =
+ `;; Wrong values.
+ (i32.const 5)
+ (i64.const 6)
+ (f32.const 0.1)
+ (f64.const 0.6437)`;
+ let wrongValuesJS = [5, 6n, 0.1, 0.6437];
+
+ // These variables are specific to the tests in this file.
+ let throwValues =
+ `;; Values to throw and catch.
+ (i32.const 7)
+ (i64.const 8)
+ (f32.const 9)
+ (f64.const 27.11)`;
+ let thrownValuesJS = [7, 8n, 9, 27.11];
+
+ let correctResultsJS = function(externref) {
+ return [].concat(thrownValuesJS,
+ correctLocalValuesJS,
+ [externref, 1]);
+ }
+
+ // Testing also locals of Wasm vectype.
+ // The following depend on whether simd is enabled or not. We write it like
+ // this so we can run this test also when SIMD is not enabled.
+ let wrongV128 = "";
+ let correctV128 = "";
+ let checkV128Value = "";
+
+ if (wasmSimdEnabled()) {
+ wrongV128 = `(v128.const i32x4 11 22 33 44)`;
+ correctV128 = `(v128.const i32x4 55 66 77 88)`;
+ checkV128Value =
+ ` ${correctV128}
+ (i32x4.eq)
+ (i32x4.all_true)`;
+ v128Type = " v128";
+ } else {
+ wrongV128 = "(i32.const 0)";
+ correctV128 = "(i32.const 1)";
+ v128Type = " i32";
+ }
+
+ let localTypes = types + " externref";
+ let resultTypes = types + " " + localTypes;
+
+ // The last i32 in the results is the v128 check.
+ let testFuncTypeInline =
+ `(param $argCorrectRef externref)
+ (param $argWrongRef externref)
+ (result ${resultTypes} i32)
+ (local $localI32 i32)
+ (local $localI64 i64)
+ (local $localF32 f32)
+ (local $localF64 f64)
+ (local $localExternref externref)
+ (local $localV128 ${v128Type})`;
+
+ let localsSet =
+ `;; Set locals.
+ (local.set $localV128)
+ (local.set $localExternref)
+ (local.set $localF64)
+ (local.set $localF32)
+ (local.set $localI64)
+ (local.set $localI32)`;
+ let localsGet =
+ `;; Get locals.
+ (local.get $localI32)
+ (local.get $localI64)
+ (local.get $localF32)
+ (local.get $localF64)
+ (local.get $localExternref)
+ (local.get $localV128)`;
+
+ // The test module parts. ----------------------------------------------------
+
+ let importsModule =
+ `(module
+ (type $exnType (func (param ${types})))
+ (tag $exn (export "exn") (type $exnType))
+ (func (export "throwif") (param $ifPredicate i32)
+ (if (local.get $ifPredicate)
+ (then
+ ${throwValues}
+ ${localThrow}))))`;
+
+ let moduleHeader = `
+ (module
+ ${exnTypeDef}
+ (import "m" "exn" (tag $exn (type $exnType)))
+ (tag $emptyExn)
+ (import "m" "throwif" (func $throwif (param $ifPredicate i32)))
+ (func $wontThrow
+ (throw $emptyExn))
+ (func $localCallThrow
+ ${throwValues}
+ ${localThrow})
+ (func (export "testFunc") ${testFuncTypeInline}
+ (try (result ${resultTypes} ${v128Type})
+ (do
+ ;; Locals not set.
+ (i32.const 0) ;; Predicate for $throwif.
+ (call $throwif) ;; So this doesn't throw.
+ ;; Set correct locals before throw to be caught.
+ ${correctLocalValues}
+ (local.get $argCorrectRef)
+ ${correctV128}
+ ${localsSet}
+ ;; Next up should be $exn being thrown locally or via a call.`;
+
+ let moduleRest = ` ;; The above throw to $exn should be caught here --------.
+ ;; Set wrong locals after throw to be caught. ;; |
+ ${wrongValues} ;; |
+ (local.get $argWrongRef) ;; The wrong externref param. ;; |
+ ${wrongV128} ;; |
+ ${localsSet} ;; |
+ (call $wontThrow) ;; |
+ ${wrongValues} ;; |
+ ${localsGet});; End of try code. ;; |
+ (catch $emptyExn ;; |
+ ${wrongValues} ;; |
+ ${localsGet}) ;; |
+ (catch $exn ;; <---------------------------------------------------'
+ ${localsGet})
+ (catch_all
+ ${wrongValues}
+ ${localsGet}))
+ ;; Check if the local has the correct v128 value.
+ ${checkV128Value}))`;
+
+ let localThrowValues = `
+ ${throwValues}
+ (throw $exn)`;
+ let directLocalCall = `
+ (call $localCallThrow)`;
+ let directImportCall = `
+ (i32.const 1)
+ (call $throwif)`;
+
+ // Run test for side effects on locals before throwing an exception locally,
+ // or from a direct call.
+
+ let callInstructions = [localThrowValues, directLocalCall, directImportCall];
+
+ for (let callThrow of callInstructions) {
+ console.log("callThrow = " + callThrow); // Uncomment for debugging.
+ moduleText = moduleHeader + callThrow + moduleRest;
+ console.log("moduleText = " + moduleText); // Uncomment for debugging.
+ assertEqArray(
+ wasmEvalText(moduleText,
+ { m : wasmEvalText(importsModule).exports }
+ ).exports.testFunc("foo", "wrongFoo"),
+ correctResultsJS("foo"));
+ }
+}
+
+// Setting globals in try code, and testing to see if the changes are known to
+// the landing pad.
+function testGlobals() {
+ let test = function (type, initialValue, resultValue, wrongValue, coercion) {
+ let exports = wasmEvalText(
+ `(module
+ (tag (export "exn"))
+ (func (export "throws")
+ (throw 0)))`
+ ).exports;
+
+ assertEq(
+ wasmEvalText(
+ `(module
+ (import "m" "exn" (tag $exn))
+ (tag $notThrownExn)
+ (import "m" "throws" (func $throws))
+ (global (mut ${type}) (${type}.const ${initialValue}))
+ (func (export "testFunc") (result ${type})
+ (try (result ${type})
+ (do
+ (global.set 0 (${type}.const ${resultValue}))
+ (call $throws)
+ (global.set 0 (${type}.const ${wrongValue}))
+ (global.get 0))
+ (catch $notThrownExn
+ (${type}.const ${wrongValue}))
+ (catch $exn
+ (global.get 0)))))`,
+ { m: exports }
+ ).exports.testFunc(), coercion(resultValue));
+ };
+
+ test("i32", 2, 7, 27, x => x);
+ test("i64", 2n, 7n, 27n, x => x);
+ test("f32", 0.3, 0.1, 0.6, Math.fround);
+ test("f64", 13.37, 0.6437244242412325666666, 4, x => x);
+};
+
+// Run all tests.
+
+testSideEffectsOnLocals();
+testGlobals();
diff --git a/js/src/jit-test/tests/wasm/exceptions/stack.js b/js/src/jit-test/tests/wasm/exceptions/stack.js
new file mode 100644
index 0000000000..337ecd54b8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/stack.js
@@ -0,0 +1,28 @@
+let tag = new WebAssembly.Tag({parameters: []});
+
+function construct(options) {
+ return new WebAssembly.Exception(tag, [], options);
+}
+function noStack(options) {
+ assertEq(construct(options).stack, undefined, 'no stack');
+}
+function hasStack(options) {
+ assertEq(typeof construct(options).stack === 'string', true, 'has stack');
+}
+
+// Test valid option constructors
+noStack(undefined);
+noStack(null);
+noStack({});
+noStack({traceStack: false});
+noStack({traceStack: 0});
+hasStack({traceStack: true});
+hasStack({traceStack: 1});
+
+// Test invalid option constructors
+assertErrorMessage(() => construct('not an object'), TypeError, /cannot be converted/);
+
+// Test that 'stack' is read-only
+let exception = construct({traceStack: true});
+exception.stack = 0;
+assertEq(typeof exception.stack === 'string', true, 'is read-only');
diff --git a/js/src/jit-test/tests/wasm/exceptions/throw-to-js.js b/js/src/jit-test/tests/wasm/exceptions/throw-to-js.js
new file mode 100644
index 0000000000..6ddc389cc1
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/throw-to-js.js
@@ -0,0 +1,343 @@
+// Tests for throwing exceptions to JS from Wasm.
+
+function assertWasmThrowsExn(thunk) {
+ let thrown = false;
+
+ try {
+ thunk();
+ } catch (exn) {
+ thrown = true;
+ assertEq(exn instanceof WebAssembly.Exception, true);
+ }
+
+ assertEq(thrown, true, "missing exception");
+}
+
+// Test that handler-less trys don't catch anything.
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f")
+ try (throw $exn) end))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func $g (throw $exn))
+ (func (export "f")
+ try (call $g) end)
+)`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f")
+ try try (throw $exn) end end))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f")
+ try
+ try
+ throw $exn
+ delegate 0
+ end))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f")
+ try
+ try
+ throw $exn
+ delegate 1
+ end))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f")
+ block
+ try
+ throw $exn
+ delegate 0
+ end))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f")
+ loop
+ try
+ throw $exn
+ delegate 0
+ end))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f")
+ (i32.const 1)
+ if
+ try
+ throw $exn
+ delegate 0
+ end))`
+ ).exports.f()
+);
+
+// Test throwing simple empty exceptions to JS.
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f")
+ (throw $exn)))`
+ ).exports.f()
+);
+
+// Test that wasm preserves the values of non-object exceptions that pass
+// through it back to JS.
+assertThrowsValue(
+ () =>
+ wasmEvalText(
+ `(module
+ (tag $exn)
+ (import "m" "import" (func $import))
+ (func (export "f")
+ try
+ (call $import)
+ catch $exn
+ ;; this block shouldn't be reached
+ end))`,
+ {
+ m: {
+ import: () => {
+ throw 42;
+ },
+ },
+ }
+ ).exports.f(),
+ 42
+);
+
+// Like previous test, but using a rethrow instruction instead.
+assertThrowsValue(
+ () =>
+ wasmEvalText(
+ `(module
+ (import "m" "import" (func $import))
+ (func (export "f")
+ try
+ (call $import)
+ catch_all
+ (rethrow 0)
+ end))`,
+ {
+ m: {
+ import: () => {
+ throw 42;
+ },
+ },
+ }
+ ).exports.f(),
+ 42
+);
+
+// Test for throwing to JS and then back to Wasm.
+{
+ var wasmThrower;
+ let exports = wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (type 0))
+ (import "m" "import" (func $import (result i32)))
+ (func (export "thrower")
+ (i32.const 42)
+ (throw $exn))
+ (func (export "catcher") (result i32)
+ try (result i32)
+ (call $import)
+ catch $exn
+ end))`,
+ {
+ m: {
+ import: () => {
+ return wasmThrower();
+ },
+ },
+ }
+ ).exports;
+
+ wasmThrower = exports.thrower;
+ assertEq(exports.catcher(), 42);
+}
+
+// Tests for checking the tags of exceptions.
+{
+ let exports = wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (tag $exn (export "exn") (type 0))
+ (func (export "thrower")
+ (i32.const 42)
+ (throw $exn)))`
+ ).exports;
+
+ let imports = {
+ store: {
+ throws: () => {
+ return exports.thrower();
+ },
+ exn: exports.exn,
+ },
+ };
+
+ // This passes the exception tag check and the exception is caught.
+ assertEq(
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (import "store" "throws" (func $thrower (result i32)))
+ (import "store" "exn" (tag $exn (type 0)))
+ (func (export "catches") (result i32)
+ try (result i32)
+ (call $thrower)
+ catch $exn
+ (i32.const 15)
+ (i32.sub)
+ end))`,
+ imports
+ ).exports.catches(),
+ 27
+ );
+
+ // This fails the exception tag check, despite the local exception having
+ // a matching signature.
+ assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (type (func (param i32)))
+ (import "store" "throws" (func $thrower (result i32)))
+ (tag $exn (type 0))
+ (func (export "catchesFail") (result i32)
+ try (result i32)
+ (call $thrower)
+ catch $exn ;; This should not recognise $exn, thus not unpack 42.
+ end))`,
+ imports
+ ).exports.catchesFail()
+ );
+}
+
+// Test that JS finally block executes after a Wasm throw.
+assertEq(
+ (() => {
+ try {
+ wasmEvalText(
+ `(module
+ (type (func (param)))
+ (tag $exn (type 0))
+ (func (export "f")
+ (throw $exn)))`
+ ).exports.f();
+ } finally {
+ return true;
+ }
+ return false;
+ })(),
+ true
+);
+
+// Test that a wasm trap that passes through JS cannot be caught in Wasm.
+{
+ let throwTrap = wasmEvalText(`(module (func (export "f") unreachable))`)
+ .exports.f;
+ let catcher = wasmEvalText(
+ `(module
+ (type (func))
+ (tag $exn (type 0))
+ (import "m" "f" (func $foreign (param) (result)))
+ (func (export "f")
+ try
+ call $foreign
+ catch $exn
+ catch_all
+ end))`,
+ {
+ m: {
+ // JS frame that goes between the two wasm frames and just rethrows.
+ f: () => {
+ try {
+ throwTrap();
+ } catch (e) {
+ throw e;
+ }
+ },
+ },
+ }
+ ).exports.f;
+
+ assertErrorMessage(
+ () => catcher(),
+ WebAssembly.RuntimeError,
+ "unreachable executed"
+ );
+}
+
+// Test delegate throwing out of function.
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ throw $exn
+ delegate 0))`
+ ).exports.f()
+);
+
+assertWasmThrowsExn(() =>
+ wasmEvalText(
+ `(module
+ (tag $exn (param))
+ (func (export "f") (result i32)
+ try (result i32)
+ i32.const 0
+ if
+ i32.const 1
+ return
+ else
+ throw $exn
+ end
+ i32.const 0
+ delegate 0))`
+ ).exports.f()
+);
diff --git a/js/src/jit-test/tests/wasm/exceptions/validation.js b/js/src/jit-test/tests/wasm/exceptions/validation.js
new file mode 100644
index 0000000000..98a37042f1
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/validation.js
@@ -0,0 +1,662 @@
+
+// Test wasm type validation for exception handling instructions.
+
+load(libdir + "wasm-binary.js");
+
+function wasmValid(mod) {
+ assertEq(WebAssembly.validate(mod), true);
+}
+
+function wasmInvalid(mod, pattern) {
+ assertEq(WebAssembly.validate(mod), false);
+ assertErrorMessage(
+ () => new WebAssembly.Module(mod),
+ WebAssembly.CompileError,
+ pattern
+ );
+}
+
+const emptyType = { args: [], ret: VoidCode };
+const i32Type = { args: [I32Code], ret: VoidCode };
+const toi32Type = { args: [], ret: I32Code };
+const i32Toi32Type = { args: [I32Code], ret: I32Code };
+const i32Toi64Type = { args: [I32Code], ret: I64Code };
+const i32i32Toi32Type = { args: [I32Code, I32Code], ret: I32Code };
+
+function testValidateDecode() {
+ // Try blocks must have a block type code.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody({
+ locals: [],
+ body: [
+ TryCode,
+ // Missing type code.
+ I32ConstCode,
+ 0x01,
+ CatchCode,
+ 0x00,
+ EndCode,
+ DropCode,
+ ReturnCode,
+ ],
+ }),
+ ]),
+ ]),
+ /bad type/
+ );
+
+ // Catch must have a tag index.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody(
+ {
+ locals: [],
+ body: [
+ TryCode,
+ I32Code,
+ I32ConstCode,
+ 0x01,
+ CatchCode,
+ // Index missing.
+ ],
+ },
+ (withEndCode = false)
+ ),
+ ]),
+ ]),
+ /expected tag index/
+ );
+
+ // Rethrow must have a depth argument.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody(
+ {
+ locals: [],
+ body: [
+ RethrowCode,
+ // Index missing.
+ ],
+ },
+ (withEndCode = false)
+ ),
+ ]),
+ ]),
+ /unable to read rethrow depth/
+ );
+
+ // Delegate must have a depth argument.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody(
+ {
+ locals: [],
+ body: [
+ TryCode,
+ I32Code,
+ I32ConstCode,
+ 0x01,
+ DelegateCode,
+ // Index missing.
+ ],
+ },
+ (withEndCode = false)
+ ),
+ ]),
+ ]),
+ /unable to read delegate depth/
+ );
+}
+
+function testValidateThrow() {
+ valid = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ i32.const 0
+ throw $exn1)
+ (tag $exn1 (type 0)))`;
+
+ validSimd = `(module
+ (tag $exn (param v128))
+ (func (export "f") (param v128) (result v128)
+ (try (result v128)
+ (do (v128.const f64x2 1 2)
+ (throw $exn))
+ (catch $exn))))`;
+
+ invalid0 = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ throw $exn1)
+ (tag $exn1 (type 0)))`;
+ error0 = /popping value from empty stack/;
+
+ invalid1 = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ i64.const 0
+ throw $exn1)
+ (tag $exn1 (type 0)))`;
+ error1 = /expression has type i64 but expected i32/;
+
+ invalid2 = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ i32.const 0
+ throw 1)
+ (tag $exn1 (type 0)))`;
+ error2 = /tag index out of range/;
+
+ wasmValidateText(valid);
+ if (wasmSimdEnabled()) {
+ wasmValidateText(validSimd);
+ }
+ wasmFailValidateText(invalid0, error0);
+ wasmFailValidateText(invalid1, error1);
+ wasmFailValidateText(invalid2, error2);
+}
+
+function testValidateTryCatch() {
+ function mod_with(fbody) {
+ return moduleWithSections([
+ sigSection([emptyType, i32Type, i32i32Toi32Type]),
+ declSection([0]),
+ tagSection([{ type: 0 }, { type: 1 }]),
+ bodySection([
+ funcBody({
+ locals: [],
+ body: fbody,
+ }),
+ ]),
+ ]);
+ }
+
+ const body1 = [
+ // try (result i32)
+ TryCode,
+ I32Code,
+ // (i32.const 1)
+ I32ConstCode,
+ varU32(1),
+ // catch 1
+ CatchCode,
+ varU32(1),
+ ];
+
+ const valid1 = mod_with(body1.concat([EndCode, DropCode, ReturnCode]));
+ const invalid1 = mod_with(
+ body1.concat([I32ConstCode, varU32(2), EndCode, DropCode, ReturnCode])
+ );
+
+ const valid2 = mod_with([
+ // (i32.const 0) (i32.const 0)
+ I32ConstCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(0),
+ // try (param i32 i32) (result i32) drop drop (i32.const 1)
+ TryCode,
+ varS32(2),
+ DropCode,
+ DropCode,
+ I32ConstCode,
+ varU32(1),
+ // catch 0 (i32.const 2) end drop return
+ CatchCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(2),
+ EndCode,
+ DropCode,
+ ReturnCode,
+ ]);
+
+ wasmValid(valid1);
+ wasmInvalid(invalid1, /unused values not explicitly dropped/);
+ wasmValid(valid2);
+
+ // Test handler-less try blocks.
+ wasmValidateText(
+ `(module (func try end))`
+ );
+
+ wasmValidateText(
+ `(module (func (result i32) try (result i32) (i32.const 1) end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (func (result i32)
+ try (result i32) (i32.const 1) (br 0) end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (func try (result i32) end))`,
+ /popping value from empty stack/
+ );
+}
+
+function testValidateCatch() {
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ bodySection([
+ funcBody({
+ locals: [],
+ body: [TryCode, VoidCode, CatchCode, varU32(0), EndCode],
+ }),
+ ]),
+ ]),
+ /tag index out of range/
+ );
+}
+
+function testValidateCatchAll() {
+ wasmValidateText(
+ `(module
+ (tag $exn)
+ (func try catch $exn catch_all end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (func (result i32)
+ try (result i32)
+ (i32.const 0)
+ catch_all
+ (i32.const 1)
+ end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func try catch_all catch 0 end))`,
+ /catch cannot follow a catch_all/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func try (result i32) (i32.const 1) catch_all end drop))`,
+ /popping value from empty stack/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param i32))
+ (func try catch $exn drop catch_all drop end))`,
+ /popping value from empty stack/
+ );
+
+ // We can't distinguish `else` and `catch_all` in error messages since they
+ // share the binary opcode.
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func try catch_all catch_all end))`,
+ /catch_all can only be used within a try/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func catch_all))`,
+ /catch_all can only be used within a try/
+ );
+}
+
+function testValidateExnPayload() {
+ valid0 = moduleWithSections([
+ sigSection([i32Type, i32Toi32Type]),
+ declSection([1]),
+ // (tag $exn (param i32))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func (param i32) (result i32) ...
+ funcBody({
+ locals: [],
+ body: [
+ // try (result i32) (local.get 0) (throw $exn) (i32.const 1)
+ TryCode,
+ I32Code,
+ LocalGetCode,
+ varU32(0),
+ ThrowCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(1),
+ // catch $exn (i32.const 1) (i32.add) end
+ CatchCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(1),
+ I32AddCode,
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ // This is to ensure the following sentence from the spec overview holds:
+ // > "the operand stack is popped back to the size the operand stack had
+ // > when the try block was entered"
+ valid1 = moduleWithSections([
+ sigSection([i32Type, toi32Type]),
+ declSection([1]),
+ // (tag $exn (param i32))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func (result i32) ...
+ funcBody({
+ locals: [],
+ body: [
+ // try (result i32) (i32.const 0) (i32.const 1) (throw $exn) drop
+ TryCode,
+ I32Code,
+ I32ConstCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(1),
+ ThrowCode,
+ varU32(0),
+ DropCode,
+ // catch $exn drop (i32.const 2) end
+ CatchCode,
+ varU32(0),
+ DropCode,
+ I32ConstCode,
+ varU32(2),
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ invalid0 = moduleWithSections([
+ sigSection([i32Type, i32Toi64Type]),
+ declSection([1]),
+ // (tag $exn (param i32))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func (param i32) (result i64) ...
+ funcBody({
+ locals: [],
+ body: [
+ // try (result i64) (local.get 0) (throw $exn) (i64.const 0)
+ TryCode,
+ I64Code,
+ LocalGetCode,
+ varU32(0),
+ ThrowCode,
+ varU32(0),
+ I64ConstCode,
+ varU32(0),
+ // catch $exn end
+ CatchCode,
+ varU32(0),
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ invalid1 = moduleWithSections([
+ // (type (func))
+ sigSection([emptyType]),
+ declSection([0]),
+ // (tag $exn (type 0))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func ...
+ funcBody({
+ locals: [],
+ body: [
+ // try catch 1 end
+ TryCode,
+ VoidCode,
+ CatchCode,
+ varU32(1),
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ wasmValid(valid0);
+ wasmValid(valid1);
+ wasmInvalid(invalid0, /has type i32 but expected i64/);
+ wasmInvalid(invalid1, /tag index out of range/);
+}
+
+function testValidateRethrow() {
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ rethrow 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch_all
+ rethrow 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (func (result i32)
+ try (result i32)
+ (i32.const 1)
+ catch_all
+ rethrow 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 0
+ end
+ end
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 2
+ end
+ end
+ end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 1
+ end
+ end
+ end))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module (func rethrow 0))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module (func try rethrow 0 end))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module (func try rethrow 0 catch_all end))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 4
+ end
+ end
+ end))`,
+ /rethrow depth exceeds current nesting level/
+ );
+}
+
+function testValidateDelegate() {
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ try
+ throw $exn
+ delegate 0
+ catch $exn
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ try
+ throw $exn
+ delegate 1
+ catch $exn
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ block
+ try
+ throw $exn
+ delegate 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ catch $exn
+ try
+ throw $exn
+ delegate 0
+ end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func (result i32)
+ try
+ throw $exn
+ delegate 0
+ (i64.const 0)
+ end))`,
+ /type mismatch: expression has type i64 but expected i32/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try (result i32)
+ (i64.const 0)
+ delegate 0))`,
+ /type mismatch: expression has type i64 but expected i32/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ try
+ throw $exn
+ delegate 2
+ catch $exn
+ end))`,
+ /delegate depth exceeds current nesting level/
+ );
+
+ wasmFailValidateText(
+ `(module (func delegate 0))`,
+ /delegate can only be used within a try/
+ );
+}
+
+testValidateDecode();
+testValidateThrow();
+testValidateTryCatch();
+testValidateCatch();
+testValidateCatchAll();
+testValidateExnPayload();
+testValidateRethrow();
+testValidateDelegate();