summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm')
-rw-r--r--js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js38
-rw-r--r--js/src/jit-test/tests/wasm/branch-hinting/directives.txt1
-rw-r--r--js/src/jit-test/tests/wasm/branch-hinting/parsing.js154
-rw-r--r--js/src/jit-test/tests/wasm/branch-hinting/simple_example.js41
-rw-r--r--js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js8
-rw-r--r--js/src/jit-test/tests/wasm/exnref/casting.js261
-rw-r--r--js/src/jit-test/tests/wasm/exnref/try-table.js53
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/basic.js147
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/basic2.js97
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/directives.txt1
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js69
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/gc.js67
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js270
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js406
-rw-r--r--js/src/jit-test/tests/wasm/js-promise-integration/multi.js36
-rw-r--r--js/src/jit-test/tests/wasm/regress/bug1866545.js25
-rw-r--r--js/src/jit-test/tests/wasm/regress/bug1891658.js10
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1891422.js27
-rw-r--r--js/src/jit-test/tests/wasm/testing/bug1894586.js13
19 files changed, 1634 insertions, 90 deletions
diff --git a/js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js b/js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js
new file mode 100644
index 0000000000..727a3156fb
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/branch-hinting/complex_control_flow.js
@@ -0,0 +1,38 @@
+// Test branch hinting with nested if.
+
+var imports = { "":{inc() { counter++ }} };
+counter = 0;
+
+let module = new WebAssembly.Module(wasmTextToBinary(`(module
+ (import "" "inc" (func (result i32)))
+ (func
+ (result i32)
+ (@metadata.code.branch_hint "\\00") (if (result i32)
+ (i32.const 1)
+ (then
+ (@metadata.code.branch_hint "\\00") (if (result i32)
+ (i32.const 2)
+ (then
+ (@metadata.code.branch_hint "\\00") (if (result i32)
+ (i32.const 3)
+ (then
+ (@metadata.code.branch_hint "\\00") (if (result i32)
+ (i32.const 0)
+ (then (call 0))
+ (else (i32.const 42))
+ )
+ )
+ (else (call 0))
+ )
+ )
+ (else (call 0))
+ )
+ )
+ (else (call 0))
+ )
+ )
+ (export "run" (func 1))
+)`, 42, imports));
+
+assertEq(counter, 0);
+assertEq(wasmParsedBranchHints(module), true);
diff --git a/js/src/jit-test/tests/wasm/branch-hinting/directives.txt b/js/src/jit-test/tests/wasm/branch-hinting/directives.txt
new file mode 100644
index 0000000000..8fe3d1e8e4
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/branch-hinting/directives.txt
@@ -0,0 +1 @@
+|jit-test| --setpref=wasm_branch_hinting=true; --wasm-compiler=ion; test-also=--wasm-compiler=baseline;skip-if: !wasmBranchHintingEnabled(); include:wasm.js
diff --git a/js/src/jit-test/tests/wasm/branch-hinting/parsing.js b/js/src/jit-test/tests/wasm/branch-hinting/parsing.js
new file mode 100644
index 0000000000..0ff9b0f557
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/branch-hinting/parsing.js
@@ -0,0 +1,154 @@
+// Make sure we are correctly parsing this custom section.
+var code =`
+(module
+ (func $$dummy)
+ (func $main (param i32) (result i32)
+ i32.const 0
+ local.get 0
+ i32.eq
+ ;; Only allowed on br_if and if
+ (@metadata.code.branch_hint "\\00") if
+ call $$dummy
+ i32.const 1
+ return
+ else
+ call $$dummy
+ i32.const 0
+ return
+ end
+ i32.const 3
+ return
+ )
+
+ (export "_main" (func $main))
+)`;
+
+let branchHintsModule = new WebAssembly.Module(wasmTextToBinary(code));
+assertEq(WebAssembly.Module.customSections(branchHintsModule, "metadata.code.branch_hint").length, 1);
+assertEq(wasmParsedBranchHints(branchHintsModule), true);
+
+let instance = new WebAssembly.Instance(branchHintsModule);
+assertEq(instance.exports._main(0), 1);
+
+// Testing branch hints parsing on `if` and `br_if`
+branchHintsModule = new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (func $main
+ i32.const 0
+ (@metadata.code.branch_hint "\\00")
+ if
+ i32.const 0
+ (@metadata.code.branch_hint "\\01")
+ br_if 0
+ end
+ )
+ (export "_main" (func $main))
+)`));
+assertEq(wasmParsedBranchHints(branchHintsModule), true);
+instance = new WebAssembly.Instance(branchHintsModule);
+instance.exports._main();
+
+let m = new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type (;0;) (func))
+ (type (;1;) (func (param i32) (result i32)))
+ (type (;2;) (func (result i32)))
+ (func $__wasm_nullptr (type 0)
+ unreachable)
+ (func $main (type 2) (result i32)
+ (local i32 i32 i32 i32)
+ i32.const 0
+ local.tee 2
+ local.set 3
+ loop
+ local.get 2
+ i32.const 50000
+ i32.eq
+ (@metadata.code.branch_hint "\\00") if
+ i32.const 1
+ local.set 3
+ end
+ local.get 2
+ i32.const 1
+ i32.add
+ local.tee 2
+ i32.const 100000
+ i32.ne
+ (@metadata.code.branch_hint "\\01") br_if 0 (;@1;)
+ end
+ local.get 3)
+ (table (;0;) 1 1 funcref)
+ (memory (;0;) 17 128)
+ (global (;0;) (mut i32) (i32.const 42))
+ (export "memory" (memory 0))
+ (export "_main" (func $main))
+ (elem (;0;) (i32.const 0) func $__wasm_nullptr)
+ (type (;0;) (func (param i32)))
+)`));
+
+assertEq(wasmParsedBranchHints(m), true);
+instance = new WebAssembly.Instance(m);
+assertEq(instance.exports._main(0), 1);
+
+// Testing invalid values for branch hints
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (func $main (param i32) (result i32)
+ i32.const 0
+ (@metadata.code.branch_hint "\\0000000") if
+ i32.const 1
+ return
+ end
+ i32.const 42
+ return
+ )
+ )
+`)), SyntaxError, /invalid value for branch hint/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (func $main (param i32) (result i32)
+ i32.const 0
+ (@metadata.code.branch_hint "\\02") if
+ i32.const 1
+ return
+ end
+ i32.const 42
+ return
+ )
+ )
+`)), SyntaxError, /invalid value for branch hint/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (func $main (param i32) (result i32)
+ i32.const 0
+ (@metadata.code.branch_hint "\\aaaa") if
+ i32.const 1
+ return
+ end
+ i32.const 42
+ return
+ )
+ )
+`)), SyntaxError, /wasm text error/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (func $main (param i32) (result i32)
+ i32.const 0
+ (@metadata.code.branch_hint) if
+ i32.const 1
+ return
+ end
+ i32.const 42
+ return
+ )
+ )
+`)), SyntaxError, /wasm text error/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (@metadata.code.branch_hint)
+)
+`)), SyntaxError, /wasm text error/);
diff --git a/js/src/jit-test/tests/wasm/branch-hinting/simple_example.js b/js/src/jit-test/tests/wasm/branch-hinting/simple_example.js
new file mode 100644
index 0000000000..141ee02fef
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/branch-hinting/simple_example.js
@@ -0,0 +1,41 @@
+// Branch Hinting proposal
+
+function runModule(hint) {
+ let code =`
+ (module
+ (func $$dummy)
+ (func $main (param i32) (result i32)
+ i32.const 0
+ local.get 0
+ i32.eq
+ ;; Only allowed on br_if and if
+ (@metadata.code.branch_hint "${hint}") if
+ call $$dummy
+ i32.const 1
+ return
+ else
+ call $$dummy
+ i32.const 0
+ return
+ end
+ i32.const 3
+ return
+ )
+ (export "_main" (func $main))
+ )`;
+ let branchHintsModule = new WebAssembly.Module(wasmTextToBinary(code));
+ assertEq(wasmParsedBranchHints(branchHintsModule), true);
+
+ let instance = new WebAssembly.Instance(branchHintsModule);
+ assertEq(instance.exports._main(0), 1);
+}
+
+// Ensure that we have the same result with different branch hints.
+runModule("\\00");
+runModule("\\01");
+
+let module = new WebAssembly.Module(wasmTextToBinary(`
+ (func i32.const 0 (@metadata.code.branch_hint "\\00") if end)
+`))
+
+assertEq(wasmParsedBranchHints(module), true);
diff --git a/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js b/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js
index 2c0ecb89c5..4c789dda19 100644
--- a/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js
+++ b/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js
@@ -34,14 +34,14 @@ let testModule = `(module
(func
(import "wasm:js-string" "fromCharCode")
(param i32)
- (result externref)
+ (result (ref extern))
)
(export "fromCharCode" (func 4))
(func
(import "wasm:js-string" "fromCodePoint")
(param i32)
- (result externref)
+ (result (ref extern))
)
(export "fromCodePoint" (func 5))
@@ -69,14 +69,14 @@ let testModule = `(module
(func
(import "wasm:js-string" "concat")
(param externref externref)
- (result externref)
+ (result (ref extern))
)
(export "concat" (func 9))
(func
(import "wasm:js-string" "substring")
(param externref i32 i32)
- (result externref)
+ (result (ref extern))
)
(export "substring" (func 10))
diff --git a/js/src/jit-test/tests/wasm/exnref/casting.js b/js/src/jit-test/tests/wasm/exnref/casting.js
index fa433fc152..20fbd145fe 100644
--- a/js/src/jit-test/tests/wasm/exnref/casting.js
+++ b/js/src/jit-test/tests/wasm/exnref/casting.js
@@ -1,110 +1,199 @@
// |jit-test| skip-if: !wasmGcEnabled()
-const {
- refCast,
- refTest,
- branch,
- branchFail,
- refCastNullable,
- refTestNullable,
- branchNullable,
- branchFailNullable,
-} = wasmEvalText(`(module
- (tag $a)
- (func $make (param $null i32) (result exnref)
- (if (local.get $null)
- (then
- (return (ref.null exn))
+// Test exnref
+{
+ const {
+ refCast,
+ refTest,
+ branch,
+ branchFail,
+ refCastNullable,
+ refTestNullable,
+ branchNullable,
+ branchFailNullable,
+ } = wasmEvalText(`(module
+ (tag $a)
+ (func $make (param $null i32) (result exnref)
+ (if (local.get $null)
+ (then
+ (return (ref.null exn))
+ )
)
- )
- try_table (catch_all_ref 0)
- throw $a
- end
- unreachable
- )
+ try_table (catch_all_ref 0)
+ throw $a
+ end
+ unreachable
+ )
- (func (export "refCast") (param $null i32)
- (call $make (local.get $null))
- ref.cast (ref exn)
- drop
- )
- (func (export "refTest") (param $null i32) (result i32)
- (call $make (local.get $null))
- ref.test (ref exn)
- )
- (func (export "branch") (param $null i32) (result i32)
- (block (result (ref exn))
+ (func (export "refCast") (param $null i32)
(call $make (local.get $null))
- br_on_cast 0 exnref (ref exn)
+ ref.cast (ref exn)
drop
- (return (i32.const 0))
)
- drop
- (return (i32.const 1))
- )
- (func (export "branchFail") (param $null i32) (result i32)
- (block (result exnref)
+ (func (export "refTest") (param $null i32) (result i32)
(call $make (local.get $null))
- br_on_cast_fail 0 exnref (ref exn)
+ ref.test (ref exn)
+ )
+ (func (export "branch") (param $null i32) (result i32)
+ (block (result (ref exn))
+ (call $make (local.get $null))
+ br_on_cast 0 exnref (ref exn)
+ drop
+ (return (i32.const 0))
+ )
drop
(return (i32.const 1))
)
- drop
- (return (i32.const 0))
- )
+ (func (export "branchFail") (param $null i32) (result i32)
+ (block (result exnref)
+ (call $make (local.get $null))
+ br_on_cast_fail 0 exnref (ref exn)
+ drop
+ (return (i32.const 1))
+ )
+ drop
+ (return (i32.const 0))
+ )
- (func (export "refCastNullable") (param $null i32)
- (call $make (local.get $null))
- ref.cast exnref
- drop
- )
- (func (export "refTestNullable") (param $null i32) (result i32)
- (call $make (local.get $null))
- ref.test exnref
- )
- (func (export "branchNullable") (param $null i32) (result i32)
- (block (result exnref)
+ (func (export "refCastNullable") (param $null i32)
(call $make (local.get $null))
- br_on_cast 0 exnref exnref
+ ref.cast exnref
drop
- (return (i32.const 0))
)
- drop
- (return (i32.const 1))
- )
- (func (export "branchFailNullable") (param $null i32) (result i32)
- (block (result exnref)
+ (func (export "refTestNullable") (param $null i32) (result i32)
(call $make (local.get $null))
- br_on_cast_fail 0 exnref exnref
+ ref.test exnref
+ )
+ (func (export "branchNullable") (param $null i32) (result i32)
+ (block (result exnref)
+ (call $make (local.get $null))
+ br_on_cast 0 exnref exnref
+ drop
+ (return (i32.const 0))
+ )
drop
(return (i32.const 1))
)
- drop
- (return (i32.const 0))
- )
-)`).exports;
+ (func (export "branchFailNullable") (param $null i32) (result i32)
+ (block (result exnref)
+ (call $make (local.get $null))
+ br_on_cast_fail 0 exnref exnref
+ drop
+ (return (i32.const 1))
+ )
+ drop
+ (return (i32.const 0))
+ )
+ )`).exports;
-// cast non-null exnref -> (ref exn)
-refCast(0);
-assertEq(refTest(0), 1);
-assertEq(branch(0), 1);
-assertEq(branchFail(0), 1);
+ // cast non-null exnref -> (ref exn)
+ refCast(0);
+ assertEq(refTest(0), 1);
+ assertEq(branch(0), 1);
+ assertEq(branchFail(0), 1);
-// cast non-null exnref -> exnref
-refCastNullable(0);
-assertEq(refTestNullable(0), 1);
-assertEq(branchNullable(0), 1);
-assertEq(branchFailNullable(0), 1);
+ // cast non-null exnref -> exnref
+ refCastNullable(0);
+ assertEq(refTestNullable(0), 1);
+ assertEq(branchNullable(0), 1);
+ assertEq(branchFailNullable(0), 1);
+
+ // cast null exnref -> (ref exn)
+ assertErrorMessage(() => refCast(1), WebAssembly.RuntimeError, /bad cast/);
+ assertEq(refTest(1), 0);
+ assertEq(branch(1), 0);
+ assertEq(branchFail(1), 0);
+
+ // cast null exnref -> exnref
+ refCastNullable(1);
+ assertEq(refTestNullable(1), 1);
+ assertEq(branchNullable(1), 1);
+ assertEq(branchFailNullable(1), 1);
+}
+
+
+// Test nullexnref
+{
+ const {
+ refCastNull,
+ refCastNonNull,
+ refTestNull,
+ refTestNonNull,
+ branchNull,
+ branchNonNull,
+ branchFailNull,
+ branchFailNonNull,
+ } = wasmEvalText(`(module
+ (func (export "refCastNull")
+ ref.null noexn
+ ref.cast nullexnref
+ drop
+ )
+ (func (export "refCastNonNull")
+ ref.null noexn
+ ref.cast (ref noexn)
+ drop
+ )
+ (func (export "refTestNull") (result i32)
+ ref.null noexn
+ ref.test nullexnref
+ )
+ (func (export "refTestNonNull") (result i32)
+ ref.null noexn
+ ref.test (ref noexn)
+ )
+ (func (export "branchNull") (result i32)
+ (block (result nullexnref)
+ ref.null noexn
+ br_on_cast 0 exnref nullexnref
+ drop
+ (return (i32.const 0))
+ )
+ drop
+ (return (i32.const 1))
+ )
+ (func (export "branchNonNull") (result i32)
+ (block (result (ref noexn))
+ ref.null noexn
+ br_on_cast 0 exnref (ref noexn)
+ drop
+ (return (i32.const 0))
+ )
+ drop
+ (return (i32.const 1))
+ )
+ (func (export "branchFailNull") (result i32)
+ (block (result exnref)
+ ref.null noexn
+ br_on_cast_fail 0 exnref (ref noexn)
+ drop
+ (return (i32.const 1))
+ )
+ drop
+ (return (i32.const 0))
+ )
+ (func (export "branchFailNonNull") (result i32)
+ (block (result (ref exn))
+ ref.null noexn
+ br_on_cast_fail 0 exnref (ref null noexn)
+ drop
+ (return (i32.const 1))
+ )
+ drop
+ (return (i32.const 0))
+ )
+ )`).exports;
-// cast null exnref -> (ref exn)
-assertErrorMessage(() => refCast(1), WebAssembly.RuntimeError, /bad cast/);
-assertEq(refTest(1), 0);
-assertEq(branch(1), 0);
-assertEq(branchFail(1), 0);
+ // null exceptions can be casted to nullexnref
+ refCastNull();
+ assertEq(refTestNull(), 1);
+ assertEq(branchNull(), 1);
+ assertEq(branchFailNull(), 0);
-// cast null exnref -> exnref
-refCastNullable(1);
-assertEq(refTestNullable(1), 1);
-assertEq(branchNullable(1), 1);
-assertEq(branchFailNullable(1), 1);
+ // null exceptions cannot be casted to (ref noexn)
+ assertErrorMessage(() => refCastNonNull(), WebAssembly.RuntimeError, /bad cast/);
+ assertEq(refTestNonNull(), 0);
+ assertEq(branchNonNull(), 0);
+ assertEq(branchFailNonNull(), 1);
+}
diff --git a/js/src/jit-test/tests/wasm/exnref/try-table.js b/js/src/jit-test/tests/wasm/exnref/try-table.js
index c89330917c..407d6db133 100644
--- a/js/src/jit-test/tests/wasm/exnref/try-table.js
+++ b/js/src/jit-test/tests/wasm/exnref/try-table.js
@@ -382,3 +382,56 @@
}
}
}
+
+// WebAssembly.JSTag property is read-only and enumerable
+WebAssembly.JSTag = null;
+assertEq(WebAssembly.JSTag !== null, true);
+assertEq(WebAssembly.propertyIsEnumerable('JSTag'), true);
+
+// Test try_table catching JS exceptions and unpacking them using JSTag
+{
+ let tag = WebAssembly.JSTag;
+ let values = [...WasmExternrefValues];
+ function throwJS(value) {
+ throw value;
+ }
+ let {test} = wasmEvalText(`(module
+ (import "" "tag" (tag $tag (param externref)))
+ (import "" "throwJS" (func $throwJS (param externref)))
+ (func (export "test") (param externref) (result externref)
+ try_table (catch $tag 0)
+ local.get 0
+ call $throwJS
+ end
+ unreachable
+ )
+ )`, {"": {tag, throwJS}}).exports;
+
+ for (let value of values) {
+ assertEq(value, test(value));
+ }
+}
+
+// Test try_table catching JS exceptions using JSTag and unpacking them using JSTag
+{
+ let tag = WebAssembly.JSTag;
+ let values = [...WasmExternrefValues];
+ function throwJS(value) {
+ throw new WebAssembly.Exception(tag, [value]);
+ }
+ let {test} = wasmEvalText(`(module
+ (import "" "tag" (tag $tag (param externref)))
+ (import "" "throwJS" (func $throwJS (param externref)))
+ (func (export "test") (param externref) (result externref)
+ try_table (catch $tag 0)
+ local.get 0
+ call $throwJS
+ end
+ unreachable
+ )
+ )`, {"": {tag, throwJS}}).exports;
+
+ for (let value of values) {
+ assertEq(value, test(value));
+ }
+}
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/basic.js b/js/src/jit-test/tests/wasm/js-promise-integration/basic.js
new file mode 100644
index 0000000000..8603886b07
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/basic.js
@@ -0,0 +1,147 @@
+// Example from the proposal.
+
+var compute_delta = async (i) => Promise.resolve(i / 100 || 1);
+
+var suspending_compute_delta = new WebAssembly.Function(
+ { parameters: ['externref', 'i32'], results: ['f64'] },
+ compute_delta,
+ { suspending: "first" }
+);
+
+var ins = wasmEvalText(`(module
+ (import "js" "init_state" (func $init_state (result f64)))
+ (import "js" "compute_delta"
+ (func $compute_delta_import (param externref) (param i32) (result f64)))
+
+ (global $suspender (mut externref) (ref.null extern))
+ (global $state (mut f64) (f64.const nan))
+ (func $init (global.set $state (call $init_state)))
+ (start $init)
+
+ (func $compute_delta (param i32) (result f64)
+ (local $suspender_copy externref)
+ (;return (f64.const 0.3);)
+ (;unreachable;)
+ (global.get $suspender)
+ (local.tee $suspender_copy)
+ (local.get 0)
+ (call $compute_delta_import)
+ (local.get $suspender_copy)
+ (global.set $suspender)
+ (return)
+ )
+ (func $get_state (export "get_state") (result f64) (global.get $state))
+ (func $update_state (param i32) (result f64)
+ (global.set $state (f64.add
+ (global.get $state) (call $compute_delta (local.get 0))))
+ (global.get $state)
+ )
+
+ (func (export "update_state_export")
+ (param $suspender externref) (param i32) (result f64)
+ (local.get $suspender)
+ (global.set $suspender)
+ (local.get 1)
+ (call $update_state)
+ (return)
+ )
+)`, {
+ js: {
+ init_state() { return 0; },
+ compute_delta: suspending_compute_delta,
+ },
+});
+
+var update_state = new WebAssembly.Function(
+ { parameters: ['i32'], results: ['externref'] },
+ ins.exports.update_state_export,
+ { promising: "first" }
+);
+
+var res = update_state(4);
+var tasks = res.then((r) => {
+ print(r);
+ assertEq(ins.exports.get_state(), .04);
+});
+
+assertEq(ins.exports.get_state(), 0);
+
+// Also test with exceptions/traps.
+
+async function test(c) {
+ var compute_delta = (i) => Promise.resolve(i/100 || 1);
+ if (c == 1) compute_delta = async (i) => {throw "ff"};
+ if (c == 2) compute_delta = () => {throw "ff";}
+
+ var suspending_compute_delta = new WebAssembly.Function(
+ {parameters:['externref', 'i32'], results:['f64']},
+ compute_delta,
+ {suspending:"first"}
+ );
+
+ var ins = wasmEvalText(`(module
+ (import "js" "init_state" (func $init_state (result f64)))
+ (import "js" "compute_delta"
+ (func $compute_delta_import (param externref) (param i32) (result f64)))
+
+ (global $suspender (mut externref) (ref.null extern))
+ (global $state (mut f64) (f64.const nan))
+ (func $init (global.set $state (call $init_state)))
+ (start $init)
+
+ (func $compute_delta (param i32) (result f64)
+ (local $suspender_copy externref)
+ (;return (f64.const 0.3);)
+ ${c == 3 ? "(unreachable)" : ""}
+ (global.get $suspender)
+ (local.tee $suspender_copy)
+ (local.get 0)
+ (call $compute_delta_import)
+ ${c == 4 ? "(unreachable)" : ""}
+ (local.get $suspender_copy)
+ (global.set $suspender)
+ (return)
+ )
+ (func $get_state (export "get_state") (result f64) (global.get $state))
+ (func $update_state (param i32) (result f64)
+ (global.set $state (f64.add
+ (global.get $state) (call $compute_delta (local.get 0))))
+ (global.get $state)
+ )
+
+ (func (export "update_state_export")
+ (param $suspender externref) (param i32) (result f64)
+ (local.get $suspender)
+ (global.set $suspender)
+ (local.get 1)
+ (call $update_state)
+ (return)
+ )
+ )`, {
+ js: {
+ init_state() { return 0; },
+ compute_delta: suspending_compute_delta,
+ },
+ });
+
+ var update_state = new WebAssembly.Function(
+ {parameters:['i32'], results:['externref']},
+ ins.exports.update_state_export,
+ {promising : "first"}
+ );
+
+ var res = update_state(4);
+ var p = res.then((r) => {
+ assertEq(c, 0);
+ assertEq(ins.exports.get_state(), .04);
+ }).catch(_ => {
+ assertEq(c > 0, true);
+ });
+
+ assertEq(ins.exports.get_state(), 0);
+ await p;
+}
+
+for (let c = 0; c < 5; c++) {
+ tasks = tasks.then(() => test(c));
+}
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/basic2.js b/js/src/jit-test/tests/wasm/js-promise-integration/basic2.js
new file mode 100644
index 0000000000..d381951b2c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/basic2.js
@@ -0,0 +1,97 @@
+// New API experiments.
+// Example from the proposal.
+
+var compute_delta = async (i) => Promise.resolve(i / 100 || 1);
+
+var suspending_compute_delta = new WebAssembly.Suspending(
+ compute_delta
+);
+var ins = wasmEvalText(`(module
+ (import "js" "init_state" (func $init_state (result f64)))
+ (import "js" "compute_delta"
+ (func $compute_delta (param i32) (result f64)))
+
+ (global $suspender (mut externref) (ref.null extern))
+ (global $state (mut f64) (f64.const nan))
+ (func $init (global.set $state (call $init_state)))
+ (start $init)
+
+ (func $get_state (export "get_state") (result f64) (global.get $state))
+ (func (export "update_state_export") (param i32) (result f64)
+ (global.set $state (f64.add
+ (global.get $state) (call $compute_delta (local.get 0))))
+ (global.get $state)
+ )
+)`, {
+ js: {
+ init_state() { return 0; },
+ compute_delta: suspending_compute_delta,
+ },
+});
+
+var update_state = WebAssembly.promising(
+ ins.exports.update_state_export
+);
+
+var res = update_state(4);
+var tasks = res.then((r) => {
+ print(r);
+ assertEq(ins.exports.get_state(), .04);
+});
+
+assertEq(ins.exports.get_state(), 0);
+
+// Also test with exceptions/traps.
+
+async function test(c) {
+ var compute_delta = (i) => Promise.resolve(i/100 || 1);
+ if (c == 1) compute_delta = async (i) => {throw "ff"};
+ if (c == 2) compute_delta = () => {throw "ff";}
+
+ var suspending_compute_delta = new WebAssembly.Suspending(
+ compute_delta
+ );
+ var ins = wasmEvalText(`(module
+ (import "js" "init_state" (func $init_state (result f64)))
+ (import "js" "compute_delta"
+ (func $compute_delta (param i32) (result f64)))
+
+ (global $suspender (mut externref) (ref.null extern))
+ (global $state (mut f64) (f64.const nan))
+ (func $init (global.set $state (call $init_state)))
+ (start $init)
+
+ (func $get_state (export "get_state") (result f64) (global.get $state))
+ (func (export "update_state_export") (param i32) (result f64)
+ ${c == 3 ? "(unreachable)" : ""}
+ (global.set $state (f64.add
+ (global.get $state) (call $compute_delta (local.get 0))))
+ ${c == 4 ? "(unreachable)" : ""}
+ (global.get $state)
+ )
+ )`, {
+ js: {
+ init_state() { return 0; },
+ compute_delta: suspending_compute_delta,
+ },
+ });
+
+ var update_state = WebAssembly.promising(
+ ins.exports.update_state_export
+);
+
+ var res = update_state(4);
+ var p = res.then((r) => {
+ assertEq(c, 0);
+ assertEq(ins.exports.get_state(), .04);
+ }).catch(_ => {
+ assertEq(c > 0, true);
+ });
+
+ assertEq(ins.exports.get_state(), 0);
+ await p;
+}
+
+for (let c = 0; c < 5; c++) {
+ tasks = tasks.then(() => test(c));
+}
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/directives.txt b/js/src/jit-test/tests/wasm/js-promise-integration/directives.txt
new file mode 100644
index 0000000000..b15152352a
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/directives.txt
@@ -0,0 +1 @@
+|jit-test| include:wasm.js; --setpref=wasm_js_promise_integration=true; skip-if: !wasmJSPromiseIntegrationEnabled()
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js b/js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js
new file mode 100644
index 0000000000..d17539807e
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/gc-2.js
@@ -0,0 +1,69 @@
+// Example from the proposal.
+
+gczeal(2,5);
+
+var compute_delta = (i) => Promise.resolve(i / 100 || 1);
+
+var suspending_compute_delta = new WebAssembly.Function(
+ { parameters: ['externref', 'i32'], results: ['f64'] },
+ compute_delta,
+ { suspending: "first" }
+);
+
+var ins = wasmEvalText(`(module
+ (import "js" "init_state" (func $init_state (result f64)))
+ (import "js" "compute_delta"
+ (func $compute_delta_import (param externref) (param i32) (result f64)))
+
+ (global $suspender (mut externref) (ref.null extern))
+ (global $state (mut f64) (f64.const nan))
+ (func $init (global.set $state (call $init_state)))
+ (start $init)
+
+ (func $compute_delta (param i32) (result f64)
+ (local $suspender_copy externref)
+ (;return (f64.const 0.3);)
+ (;unreachable;)
+ (global.get $suspender)
+ (local.tee $suspender_copy)
+ (local.get 0)
+ (call $compute_delta_import)
+ (local.get $suspender_copy)
+ (global.set $suspender)
+ (return)
+ )
+ (func $get_state (export "get_state") (result f64) (global.get $state))
+ (func $update_state (param i32) (result f64)
+ (global.set $state (f64.add
+ (global.get $state) (call $compute_delta (local.get 0))))
+ (global.get $state)
+ )
+
+ (func (export "update_state_export")
+ (param $suspender externref) (param i32) (result f64)
+ (local.get $suspender)
+ (global.set $suspender)
+ (local.get 1)
+ (call $update_state)
+ (return)
+ )
+)`, {
+ js: {
+ init_state() { return 0; },
+ compute_delta: suspending_compute_delta,
+ },
+});
+
+var update_state = new WebAssembly.Function(
+ { parameters: ['i32'], results: ['externref'] },
+ ins.exports.update_state_export,
+ { promising: "first" }
+);
+
+var res = update_state(4);
+var tasks = res.then((r) => {
+ print(r);
+ assertEq(ins.exports.get_state(), .04);
+});
+
+assertEq(ins.exports.get_state(), 0);
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/gc.js b/js/src/jit-test/tests/wasm/js-promise-integration/gc.js
new file mode 100644
index 0000000000..c688b99cb5
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/gc.js
@@ -0,0 +1,67 @@
+// Test if we can trace roots on the alternative stack.
+
+let i = 0;
+function js_import() {
+ return Promise.resolve({i: ++i});
+};
+let wasm_js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['externref']},
+ js_import,
+ {suspending: 'first'});
+
+let wasm_gc_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: []},
+ async () => { gc(); },
+ {suspending: 'first'});
+
+var ins = wasmEvalText(`(module
+ (import "m" "import"
+ (func (param externref) (result externref)))
+ (import "m" "gc" (func (param externref)))
+ (import "m" "conv"
+ (func (param externref) (result i32)))
+
+ (global (export "g") (mut i32) (i32.const 0))
+
+ (func (export "test") (param externref)
+ (local i32)
+ i32.const 5
+ local.set 1
+ loop
+ local.get 0
+ call 0
+ local.get 0
+ call 1
+ call 2
+ global.get 0
+ i32.add
+ global.set 0
+ local.get 1
+ i32.const 1
+ i32.sub
+ local.tee 1
+ br_if 0
+ end
+ )
+
+)`, {
+ m: {
+ import: wasm_js_import,
+ gc: wasm_gc_import,
+ conv: ({i}) => i,
+ },
+});
+
+
+let wrapped_export = new WebAssembly.Function(
+ {parameters:[], results:['externref']},
+ ins.exports.test,
+ {promising : "first"}
+);
+
+let export_promise = wrapped_export();
+assertEq(0, ins.exports.g.value);
+assertEq(true, export_promise instanceof Promise);
+export_promise.then(() =>
+ assertEq(15, ins.exports.g.value)
+);
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js
new file mode 100644
index 0000000000..d45d47b072
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.new.js
@@ -0,0 +1,270 @@
+// New version of JS promise integration API
+// Modified https://github.com/WebAssembly/js-promise-integration/tree/main/test/js-api/js-promise-integration
+
+var tests = Promise.resolve();
+function test(fn, n) {
+
+ tests = tests.then(() => {
+ let t = {res: null};
+ print("# " + n);
+ fn(t);
+ return t.res;
+ });
+}
+function promise_test(fn, n) {
+ tests = tests.then(() => {
+ print("# " + n);
+ return fn();
+ });
+}
+function assert_true(f) { assertEq(f, true); }
+function assert_equals(a, b) { assertEq(a, b); }
+function assert_array_equals(a, b) {
+ assert_equals(a.length, a.length);
+ for (let i = 0; i < a.length; i++) {
+ assert_equals(a[i], b[i]);
+ }
+}
+function assert_throws(ex, fn) {
+ try {
+ fn(); assertEq(false, true);
+ } catch(e) {
+ assertEq(e instanceof ex, true);
+ }
+}
+function promise_rejects(t, obj, p) {
+ t.res = p.then(() => {
+ assertEq(true, false);
+ }, (e) => {
+ assertEq(e instanceof obj.constructor, true);
+ });
+}
+
+function ToPromising(wasm_export) {
+ let sig = wasm_export.type();
+ assert_true(sig.parameters.length > 0);
+ assert_equals('externref', sig.parameters[0]);
+ let wrapper_sig = {
+ parameters: sig.parameters.slice(1),
+ results: ['externref']
+ };
+ return new WebAssembly.Function(
+ wrapper_sig, wasm_export, {promising: 'first'});
+}
+
+promise_test(async () => {
+ let js_import = new WebAssembly.Suspending(
+ () => Promise.resolve(42)
+ );
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (result i32)))
+ (func (export "test") (result i32)
+ call $import
+ )
+ )`, {m: {import: js_import}});
+ let wrapped_export = WebAssembly.promising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_true(export_promise instanceof Promise);
+ assert_equals(42, await export_promise);
+}, "Suspend once");
+
+promise_test(async () => {
+ let i = 0;
+ function js_import() {
+ return Promise.resolve(++i);
+ };
+ let wasm_js_import = new WebAssembly.Suspending(js_import);
+ // void test() {
+ // for (i = 0; i < 5; ++i) {
+ // g = g + await import();
+ // }
+ // }
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (global (export "g") (mut i32) (i32.const 0))
+ (func (export "test") (param externref)
+ (local i32)
+ i32.const 5
+ local.set 1
+ loop
+ local.get 0
+ call $import
+ global.get 0
+ i32.add
+ global.set 0
+ local.get 1
+ i32.const 1
+ i32.sub
+ local.tee 1
+ br_if 0
+ end
+ )
+ )`, {m: {import: wasm_js_import}});
+ let wrapped_export = WebAssembly.promising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_equals(0, instance.exports.g.value);
+ assert_true(export_promise instanceof Promise);
+ await export_promise;
+ assert_equals(15, instance.exports.g.value);
+}, "Suspend/resume in a loop");
+
+promise_test(async () => {
+ function js_import() {
+ return 42
+ };
+ let wasm_js_import = new WebAssembly.Suspending(js_import);
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (global (export "g") (mut i32) (i32.const 0))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ global.set 0
+ global.get 0
+ )
+ )`, {m: {import: wasm_js_import}});
+ let wrapped_export = WebAssembly.promising(instance.exports.test);
+ await wrapped_export();
+ assert_equals(42, instance.exports.g.value);
+}, "Do not suspend if the import's return value is not a Promise");
+
+test(t => {
+ let tag = new WebAssembly.Tag({parameters: []});
+ function js_import() {
+ return Promise.resolve();
+ };
+ let wasm_js_import = new WebAssembly.Suspending(js_import);
+ function js_throw() {
+ throw new Error();
+ }
+
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (import "m" "js_throw" (func $js_throw))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ call $js_throw
+ )
+ )`, {m: {import: wasm_js_import, js_throw}});
+ let wrapped_export = WebAssembly.promising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_true(export_promise instanceof Promise);
+ promise_rejects(t, new Error(), export_promise);
+}, "Throw after the first suspension");
+
+// TODO: Use wasm exception handling to check that the exception can be caught in wasm.
+
+test(t => {
+ let tag = new WebAssembly.Tag({parameters: ['i32']});
+ function js_import() {
+ return Promise.reject(new Error());
+ };
+ let wasm_js_import = new WebAssembly.Suspending(js_import);
+
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ )
+ )`, {m: {import: wasm_js_import, tag: tag}});
+ let wrapped_export = WebAssembly.promising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_true(export_promise instanceof Promise);
+ promise_rejects(t, new Error(), export_promise);
+}, "Rejecting promise");
+
+async function TestNestedSuspenders(suspend) {
+ // Nest two suspenders. The call chain looks like:
+ // outer (wasm) -> outer (js) -> inner (wasm) -> inner (js)
+ // If 'suspend' is true, the inner JS function returns a Promise, which
+ // suspends the inner wasm function, which returns a Promise, which suspends
+ // the outer wasm function, which returns a Promise. The inner Promise
+ // resolves first, which resumes the inner continuation. Then the outer
+ // promise resolves which resumes the outer continuation.
+ // If 'suspend' is false, the inner and outer JS functions return a regular
+ // value and no computation is suspended.
+
+ let inner = new WebAssembly.Suspending(
+ () => suspend ? Promise.resolve(42) : 43,
+ );
+
+ let export_inner;
+ let outer = new WebAssembly.Suspending(
+ () => suspend ? export_inner() : 42,
+ );
+
+ let instance = wasmEvalText(`(module
+ (import "m" "inner" (func $inner (param externref) (result i32)))
+ (import "m" "outer" (func $outer (param externref) (result i32)))
+ (func (export "outer") (param externref) (result i32)
+ local.get 0
+ call $outer
+ )
+ (func (export "inner") (param externref) (result i32)
+ local.get 0
+ call $inner
+ )
+ )`, {m: {inner, outer}});
+ export_inner = WebAssembly.promising(instance.exports.inner);
+ let export_outer = WebAssembly.promising(instance.exports.outer);
+ let result = export_outer();
+ assert_true(result instanceof Promise);
+ assert_equals(42, await result);
+}
+
+promise_test(async () => {
+ return TestNestedSuspenders(true);
+}, "Test nested suspenders with suspension");
+
+promise_test(async () => {
+ return TestNestedSuspenders(false);
+}, "Test nested suspenders with no suspension");
+
+
+test(t => {
+ let instance = wasmEvalText(`(module
+ (func (export "test") (result i32)
+ call 0
+ )
+ )`);
+ let wrapper = WebAssembly.promising(instance.exports.test);
+ promise_rejects(t, new InternalError(), wrapper());
+}, "Stack overflow");
+
+// TODO: Test suspension with funcref.
+
+test(t => {
+ // The call stack of this test looks like:
+ // export1 -> import1 -> export2 -> import2
+ // Where export1 is "promising" and import2 is "suspending". Returning a
+ // promise from import2 should trap because of the JS import in the middle.
+ let instance;
+ function import1() {
+ // import1 -> export2 (unwrapped)
+ instance.exports.export2();
+ }
+ function import2() {
+ return Promise.resolve(0);
+ }
+ import2 = new WebAssembly.Suspending(import2);
+ instance = wasmEvalText(`(module
+ (import "m" "import1" (func $import1 (result i32)))
+ (import "m" "import2" (func $import2 (result i32)))
+ (func (export "export1") (result i32)
+ ;; export1 -> import1 (unwrapped)
+ call $import1
+ )
+ (func (export "export2") (result i32)
+ ;; export2 -> import2 (suspending)
+ call $import2
+ )
+ )`,
+ {'m': {'import1': import1, 'import2': import2}});
+ // export1 (promising)
+ let wrapper = WebAssembly.promising(instance.exports.export1);
+ promise_rejects(t, new WebAssembly.RuntimeError(), wrapper());
+}, "Test that trying to suspend JS frames traps");
+
+tests.then(() => print('Done'));
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js
new file mode 100644
index 0000000000..cc743eeaaa
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/js-promise-integration.old.js
@@ -0,0 +1,406 @@
+// Old version of JS promise integration API
+// Modified https://github.com/WebAssembly/js-promise-integration/tree/main/test/js-api/js-promise-integration
+
+var tests = Promise.resolve();
+function test(fn, n) {
+
+ tests = tests.then(() => {
+ let t = {res: null};
+ print("# " + n);
+ fn(t);
+ return t.res;
+ });
+}
+function promise_test(fn, n) {
+ tests = tests.then(() => {
+ print("# " + n);
+ return fn();
+ });
+}
+function assert_true(f) { assertEq(f, true); }
+function assert_equals(a, b) { assertEq(a, b); }
+function assert_array_equals(a, b) {
+ assert_equals(a.length, a.length);
+ for (let i = 0; i < a.length; i++) {
+ assert_equals(a[i], b[i]);
+ }
+}
+function assert_throws(ex, fn) {
+ try {
+ fn(); assertEq(false, true);
+ } catch(e) {
+ assertEq(e instanceof ex, true);
+ }
+}
+function promise_rejects(t, obj, p) {
+ t.res = p.then(() => {
+ assertEq(true, false);
+ }, (e) => {
+ assertEq(e instanceof obj.constructor, true);
+ });
+}
+
+function ToPromising(wasm_export) {
+ let sig = wasm_export.type();
+ assert_true(sig.parameters.length > 0);
+ assert_equals('externref', sig.parameters[0]);
+ let wrapper_sig = {
+ parameters: sig.parameters.slice(1),
+ results: ['externref']
+ };
+ return new WebAssembly.Function(
+ wrapper_sig, wasm_export, {promising: 'first'});
+}
+
+test(() => {
+ function js_import(i) {}
+
+ let import_wrapper = new WebAssembly.Function(
+ {parameters: ['externref', 'i32'], results: []},
+ js_import,
+ {suspending: 'first'});
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func (param externref i32)))
+ (func (export "export") (param externref i32) (result i32)
+ local.get 1
+ )
+ (func (export "void_export") (param externref))
+ )`, {'m': {'import': import_wrapper}});
+ let export_wrapper = ToPromising(instance.exports.export);
+
+ // Bad flag value.
+ assert_throws(TypeError, () => new WebAssembly.Function(
+ {parameters: ['externref', 'i32'], results: []},
+ js_import,
+ {suspending: 'foo'}));
+
+ assert_throws(TypeError, () => new WebAssembly.Function(
+ {parameters: ['i32'], results: ['externref']},
+ instance.exports.export,
+ {promising: 'foo'}));
+
+ // Signature mismatch.
+ assert_throws(Error /*TypeError*/, () => new WebAssembly.Function(
+ {parameters: ['externref'], results: []},
+ new WebAssembly.Function(
+ {parameters: [], results: ['i32']}, js_import),
+ {suspending: 'first'}));
+
+ assert_throws(TypeError, () => new WebAssembly.Function(
+ {parameters: ['externref', 'i32'], results: ['i32']},
+ instance.exports.export,
+ {promising: 'first'}));
+
+ // Check the wrapper signatures.
+ // let export_sig = export_wrapper.type();
+ // assert_array_equals(['i32'], export_sig.parameters);
+ // assert_array_equals(['externref'], export_sig.results);
+
+ let import_sig = import_wrapper.type();
+ assert_array_equals(['externref', 'i32'], import_sig.parameters);
+ assert_array_equals([], import_sig.results);
+ // let void_export_wrapper = ToPromising(instance.exports.void_export);
+ // let void_export_sig = void_export_wrapper.type();
+ // assert_array_equals([], void_export_sig.parameters);
+ // assert_array_equals(['externref'], void_export_sig.results);
+}, "Test import and export type checking");
+
+promise_test(async () => {
+ let js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ () => Promise.resolve(42),
+ {suspending: 'first'});
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ )
+ )`, {m: {import: js_import}});
+ let wrapped_export = ToPromising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_true(export_promise instanceof Promise);
+ assert_equals(42, await export_promise);
+}, "Suspend once");
+
+promise_test(async () => {
+ let i = 0;
+ function js_import() {
+ return Promise.resolve(++i);
+ };
+ let wasm_js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ js_import,
+ {suspending: 'first'});
+ // void test() {
+ // for (i = 0; i < 5; ++i) {
+ // g = g + await import();
+ // }
+ // }
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (global (export "g") (mut i32) (i32.const 0))
+ (func (export "test") (param externref)
+ (local i32)
+ i32.const 5
+ local.set 1
+ loop
+ local.get 0
+ call $import
+ global.get 0
+ i32.add
+ global.set 0
+ local.get 1
+ i32.const 1
+ i32.sub
+ local.tee 1
+ br_if 0
+ end
+ )
+ )`, {m: {import: wasm_js_import}});
+ let wrapped_export = ToPromising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_equals(0, instance.exports.g.value);
+ assert_true(export_promise instanceof Promise);
+ await export_promise;
+ assert_equals(15, instance.exports.g.value);
+}, "Suspend/resume in a loop");
+
+promise_test(async () => {
+ function js_import() {
+ return 42
+ };
+ let wasm_js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ js_import,
+ {suspending: 'first'});
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (global (export "g") (mut i32) (i32.const 0))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ global.set 0
+ global.get 0
+ )
+ )`, {m: {import: wasm_js_import}});
+ let wrapped_export = ToPromising(instance.exports.test);
+ await wrapped_export();
+ assert_equals(42, instance.exports.g.value);
+}, "Do not suspend if the import's return value is not a Promise");
+
+test(t => {
+ let tag = new WebAssembly.Tag({parameters: []});
+ function js_import() {
+ return Promise.resolve();
+ };
+ let wasm_js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ js_import,
+ {suspending: 'first'});
+ function js_throw() {
+ throw new Error();
+ }
+
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (import "m" "js_throw" (func $js_throw))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ call $js_throw
+ )
+ )`, {m: {import: wasm_js_import, js_throw}});
+ let wrapped_export = ToPromising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_true(export_promise instanceof Promise);
+ promise_rejects(t, new Error(), export_promise);
+}, "Throw after the first suspension");
+
+// TODO: Use wasm exception handling to check that the exception can be caught in wasm.
+
+test(t => {
+ let tag = new WebAssembly.Tag({parameters: ['i32']});
+ function js_import() {
+ return Promise.reject(new Error());
+ };
+ let wasm_js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ js_import,
+ {suspending: 'first'});
+
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ )
+ )`, {m: {import: wasm_js_import, tag: tag}});
+ let wrapped_export = ToPromising(instance.exports.test);
+ let export_promise = wrapped_export();
+ assert_true(export_promise instanceof Promise);
+ promise_rejects(t, new Error(), export_promise);
+}, "Rejecting promise");
+
+async function TestNestedSuspenders(suspend) {
+ // Nest two suspenders. The call chain looks like:
+ // outer (wasm) -> outer (js) -> inner (wasm) -> inner (js)
+ // If 'suspend' is true, the inner JS function returns a Promise, which
+ // suspends the inner wasm function, which returns a Promise, which suspends
+ // the outer wasm function, which returns a Promise. The inner Promise
+ // resolves first, which resumes the inner continuation. Then the outer
+ // promise resolves which resumes the outer continuation.
+ // If 'suspend' is false, the inner and outer JS functions return a regular
+ // value and no computation is suspended.
+
+ let inner = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ () => suspend ? Promise.resolve(42) : 43,
+ {suspending: 'first'});
+
+ let export_inner;
+ let outer = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ () => suspend ? export_inner() : 42,
+ {suspending: 'first'});
+
+ let instance = wasmEvalText(`(module
+ (import "m" "inner" (func $inner (param externref) (result i32)))
+ (import "m" "outer" (func $outer (param externref) (result i32)))
+ (func (export "outer") (param externref) (result i32)
+ local.get 0
+ call $outer
+ )
+ (func (export "inner") (param externref) (result i32)
+ local.get 0
+ call $inner
+ )
+ )`, {m: {inner, outer}});
+ export_inner = ToPromising(instance.exports.inner);
+ let export_outer = ToPromising(instance.exports.outer);
+ let result = export_outer();
+ assert_true(result instanceof Promise);
+ assert_equals(42, await result);
+}
+
+promise_test(async () => {
+ return TestNestedSuspenders(true);
+}, "Test nested suspenders with suspension");
+
+promise_test(async () => {
+ return TestNestedSuspenders(false);
+}, "Test nested suspenders with no suspension");
+
+test(() => {
+ let js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ () => Promise.resolve(42),
+ {suspending: 'first'});
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ )
+ (func (export "return_suspender") (param externref) (result externref)
+ local.get 0
+ )
+ )`, {m: {import: js_import}});
+ let suspender = ToPromising(instance.exports.return_suspender)();
+ for (s of [suspender, null, undefined, {}]) {
+ assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s));
+ }
+}, "Call import with an invalid suspender");
+
+test(t => {
+ let instance = wasmEvalText(`(module
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call 0
+ )
+ )`);
+ let wrapper = ToPromising(instance.exports.test);
+ promise_rejects(t, new InternalError(), wrapper());
+}, "Stack overflow");
+
+test (() => {
+ let js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ () => Promise.resolve(42),
+ {suspending: 'first'});
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ )
+ (func (export "return_suspender") (param externref) (result externref)
+ local.get 0
+ )
+ )`, {m: {import: js_import}});
+ let suspender = ToPromising(instance.exports.return_suspender)();
+ for (s of [suspender, null, undefined, {}]) {
+ assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s));
+ }
+}, "Pass an invalid suspender");
+
+// TODO: Test suspension with funcref.
+
+test(t => {
+ // The call stack of this test looks like:
+ // export1 -> import1 -> export2 -> import2
+ // Where export1 is "promising" and import2 is "suspending". Returning a
+ // promise from import2 should trap because of the JS import in the middle.
+ let instance;
+ function import1() {
+ // import1 -> export2 (unwrapped)
+ instance.exports.export2();
+ }
+ function import2() {
+ return Promise.resolve(0);
+ }
+ import2 = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ import2,
+ {suspending: 'first'});
+ instance = wasmEvalText(`(module
+ (import "m" "import1" (func $import1 (result i32)))
+ (import "m" "import2" (func $import2 (param externref) (result i32)))
+ (global (mut externref) (ref.null extern))
+ (func (export "export1") (param externref) (result i32)
+ ;; export1 -> import1 (unwrapped)
+ local.get 0
+ global.set 0
+ call $import1
+ )
+ (func (export "export2") (result i32)
+ ;; export2 -> import2 (suspending)
+ global.get 0
+ call $import2
+ )
+ )`,
+ {'m': {'import1': import1, 'import2': import2}});
+ // export1 (promising)
+ let wrapper = new WebAssembly.Function(
+ {parameters: [], results: ['externref']},
+ instance.exports.export1,
+ {promising: 'first'});
+ promise_rejects(t, new WebAssembly.RuntimeError(), wrapper());
+}, "Test that trying to suspend JS frames traps");
+
+"Invalid test. Skipping..." || test(() => {
+ let js_import = new WebAssembly.Function(
+ {parameters: ['externref'], results: ['i32']},
+ () => 42,
+ {suspending: 'first'});
+ let instance = wasmEvalText(`(module
+ (import "m" "import" (func $import (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $import
+ )
+ )`, {m: {import: js_import}});
+ assert_equals(42, instance.exports.test(null));
+}, "Pass an invalid suspender to the import and return a non-promise");
+
+tests.then(() => print('Done'));
diff --git a/js/src/jit-test/tests/wasm/js-promise-integration/multi.js b/js/src/jit-test/tests/wasm/js-promise-integration/multi.js
new file mode 100644
index 0000000000..7713637642
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/js-promise-integration/multi.js
@@ -0,0 +1,36 @@
+// Multiple promises at the same time.
+
+function js_import() {
+ return Promise.resolve(42);
+}
+var wasm_js_import = new WebAssembly.Function(
+ { parameters: ['externref'], results: ['i32'] },
+ js_import,
+ { suspending: 'first' });
+
+var ins = wasmEvalText(`(module
+ (import "m" "import" (func $f (param externref) (result i32)))
+ (func (export "test") (param externref) (result i32)
+ local.get 0
+ call $f
+ )
+)`, {"m": {import: wasm_js_import}});
+
+let wrapped_export = new WebAssembly.Function(
+ {
+ parameters: [],
+ results: ['externref']
+ },
+ ins.exports.test, { promising: 'first' });
+
+Promise.resolve().then(() => {
+ wrapped_export().then(i => {
+ assertEq(42, i)
+ });
+});
+
+Promise.resolve().then(() => {
+ wrapped_export().then(i => {
+ assertEq(42, i)
+ });
+});
diff --git a/js/src/jit-test/tests/wasm/regress/bug1866545.js b/js/src/jit-test/tests/wasm/regress/bug1866545.js
new file mode 100644
index 0000000000..6c6a92c0ab
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/bug1866545.js
@@ -0,0 +1,25 @@
+// Tests stack alignment during tail calls (in Ion).
+
+var ins = wasmEvalText(`
+ (module
+ (func $trap
+ (param $i i32) (param $arr i32)
+ unreachable
+ )
+ (func $second
+ (return_call $trap
+ (i32.const 33)
+ (i32.const 66)
+ )
+ )
+ (func (export "test")
+ (call $second)
+ )
+ )
+`);
+
+assertErrorMessage(
+ () => ins.exports.test(),
+ WebAssembly.RuntimeError,
+ "unreachable executed",
+);
diff --git a/js/src/jit-test/tests/wasm/regress/bug1891658.js b/js/src/jit-test/tests/wasm/regress/bug1891658.js
new file mode 100644
index 0000000000..304ea0f5ea
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/bug1891658.js
@@ -0,0 +1,10 @@
+// Tests OOM during wasmLosslessInvoke.
+
+var ins = wasmEvalText('(module (func (export "f")(result i32) i32.const 1))');
+
+oomAtAllocation(1);
+try {
+ wasmLosslessInvoke(ins.exports.f);
+} catch (e) {
+ assertEq(e, "out of memory");
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1891422.js b/js/src/jit-test/tests/wasm/tail-calls/bug1891422.js
new file mode 100644
index 0000000000..c5c43d006f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1891422.js
@@ -0,0 +1,27 @@
+// |jit-test| --more-compartments; skip-variant-if: --setpref=wasm_test_serialization=true, true; skip-variant-if: --wasm-compiler=ion, true
+
+a = newGlobal({ newCompartment: true });
+a.b = this;
+a.eval(`Debugger(b).onExceptionUnwind = function () {};`);
+
+var ins0 = wasmEvalText(`(module
+ (func $fac-acc (export "e") (param i64 i64)
+ unreachable
+ )
+)`);
+var ins = wasmEvalText(`(module
+ (import "" "e" (func $fac-acc (param i64 i64)))
+ (type $tz (func (param i64)))
+ (table $t 1 1 funcref)
+ (func $f (export "fac") (param i64)
+ local.get 0
+ i32.const 0
+ return_call_indirect $t (type $tz)
+ )
+ (elem $t (i32.const 0) $fac-acc)
+)`, {"": {e: ins0.exports.e}});
+
+
+assertErrorMessage(() => {
+ ins.exports.fac(5n);
+}, WebAssembly.RuntimeError, /indirect call signature mismatch/);
diff --git a/js/src/jit-test/tests/wasm/testing/bug1894586.js b/js/src/jit-test/tests/wasm/testing/bug1894586.js
new file mode 100644
index 0000000000..eb19cc0a69
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/testing/bug1894586.js
@@ -0,0 +1,13 @@
+var it = 100;
+function f() {
+ if (--it < 0) {
+ return;
+ }
+ wasmDumpIon(
+ wasmTextToBinary(
+ "(type $x (struct))(global $g (ref null $x) ref.null $x)(func $h)"
+ )
+ );
+ oomTest(f);
+}
+f();