summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/tail-calls
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jit-test/tests/wasm/tail-calls
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/wasm/tail-calls')
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1851568.js20
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1862473.js24
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1865044.js34
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1871605.js30
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1871606.js22
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/bug1871951.js75
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/directives.txt1
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/exceptions.js54
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/gc.js220
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus0.js24
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus1.js32
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus10.js59
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus11.js66
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus12.js41
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus13.js79
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus15.js72
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus16.js94
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus17.js27
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus2.js36
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus3.js36
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus4.js43
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus5.js68
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus6.js24
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus7.js29
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus8.js38
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/litmus9.js42
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-syntax.js117
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-validate.js243
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return-call-profiling.js68
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return-call-validate.js162
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return_call.js305
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return_call_indirect.js156
-rw-r--r--js/src/jit-test/tests/wasm/tail-calls/return_call_ref.js94
33 files changed, 2435 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1851568.js b/js/src/jit-test/tests/wasm/tail-calls/bug1851568.js
new file mode 100644
index 0000000000..b9622042d8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1851568.js
@@ -0,0 +1,20 @@
+wasmFailValidateText(`(module
+ (func (result i32 f64)
+ i32.const 1
+ f64.const 2.0
+ )
+ (func (export "f") (result f64)
+ return_call 0
+ )
+)`, /type mismatch/);
+
+wasmFailValidateText(`(module
+ (func (result i32 f64)
+ i32.const 1
+ f64.const 2.0
+ )
+ (func (export "f") (result f32 i32 f64)
+ f32.const 3.14
+ return_call 0
+ )
+)`, /type mismatch/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1862473.js b/js/src/jit-test/tests/wasm/tail-calls/bug1862473.js
new file mode 100644
index 0000000000..57e5ea2118
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1862473.js
@@ -0,0 +1,24 @@
+// |jit-test| --wasm-gc; skip-if: !wasmGcEnabled()
+
+var ins = wasmEvalText(`(module
+ (func $func1)
+ (func $func2 (param i32 arrayref arrayref i32 i32 i32 i32)
+ return_call $func1
+ )
+ (func (export "test")
+ i32.const 1
+ ref.null array
+ ref.null array
+ i32.const -2
+ i32.const -3
+ i32.const -4
+ i32.const -5
+ call $func2
+
+ ref.null array
+ ref.cast (ref array)
+ drop
+ )
+)`);
+
+assertErrorMessage(() => ins.exports.test(), WebAssembly.RuntimeError, /bad cast/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1865044.js b/js/src/jit-test/tests/wasm/tail-calls/bug1865044.js
new file mode 100644
index 0000000000..042e641f32
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1865044.js
@@ -0,0 +1,34 @@
+// |jit-test| --more-compartments; skip-variant-if: --wasm-test-serialization, true; skip-variant-if: --wasm-compiler=ion, true; skip-if: !wasmGcEnabled() || !('Function' in WebAssembly)
+
+a = newGlobal();
+a.b = this;
+a.eval("(" + function () {
+ Debugger(b).onExceptionUnwind = function () {}
+} + ")()");
+function c(d) {
+ binary = wasmTextToBinary(d)
+ e = new WebAssembly.Module(binary)
+ return new WebAssembly.Instance(e)
+}
+f = c(`
+(type $g
+ (func (param i64 i64 funcref) (result i64))
+)
+(func (export "vis") (param i64 i64 funcref) (result i64)
+ local.get 0
+ local.get 1
+ local.get 2
+ local.get 2
+ ref.cast (ref $g)
+ return_call_ref $g
+)
+`);
+h = f.exports["vis"];
+i = new WebAssembly.Function({
+ parameters: [ "i64", "i64", "funcref" ], results: ["i64"]
+}, function () {});
+
+assertErrorMessage(
+ () => h(3n, 1n, i),
+ TypeError, /can't convert undefined to BigInt/
+);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1871605.js b/js/src/jit-test/tests/wasm/tail-calls/bug1871605.js
new file mode 100644
index 0000000000..84f37a3e42
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1871605.js
@@ -0,0 +1,30 @@
+// |jit-test| --more-compartments; skip-variant-if: --wasm-test-serialization, true; skip-variant-if: --wasm-compiler=ion, true; skip-if: !wasmGcEnabled()
+
+var dbg = newGlobal()
+dbg.parent = this
+dbg.eval(`
+ Debugger(parent).onEnterFrame = function() {}
+`)
+
+var wasm = `(module
+ (type $t1 (array (mut i32)))
+ (type $t2 (array (mut (ref null $t1))))
+ (import "" "c"
+ (func $c
+ (param i32 (ref $t2))
+ (result (ref $t2))))
+ (func $f (result (ref $t2))
+ (return_call $c
+ (i32.const 0)
+ (array.new $t2 (ref.null $t1) (i32.const 1500)))
+ )
+ (func (export "test")
+ (drop (call $f))
+ )
+)`;
+
+var ins = wasmEvalText(wasm, {"": { c() {},}});
+assertErrorMessage(
+ () => ins.exports.test(),
+ TypeError, /bad type/
+);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1871606.js b/js/src/jit-test/tests/wasm/tail-calls/bug1871606.js
new file mode 100644
index 0000000000..aa520d1f53
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1871606.js
@@ -0,0 +1,22 @@
+// |jit-test| --more-compartments; skip-variant-if: --wasm-test-serialization, true; skip-variant-if: --wasm-compiler=ion, true
+dbg = newGlobal();
+dbg.b = this;
+dbg.eval("(" + function() {
+ Debugger(b)
+} + ")()");
+var ins = wasmEvalText(`
+ (table 1 funcref)
+ (type $d (func (param f64)))
+ (func $e
+ (export "test")
+ f64.const 0.0
+ i32.const 0
+ return_call_indirect (type $d)
+ )
+ (elem (i32.const 0) $e)
+`);
+
+assertErrorMessage(
+ () => ins.exports.test(),
+ WebAssembly.RuntimeError, /indirect call signature mismatch/
+);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/bug1871951.js b/js/src/jit-test/tests/wasm/tail-calls/bug1871951.js
new file mode 100644
index 0000000000..6b3e481d59
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/bug1871951.js
@@ -0,0 +1,75 @@
+// |jit-test| skip-variant-if: --wasm-test-serialization, true; skip-if: !wasmGcEnabled()
+
+gczeal(18)
+function a(str, imports) {
+ b = wasmTextToBinary(str)
+ try {
+ c = new WebAssembly.Module(b)
+ } catch {}
+ return new WebAssembly.Instance(c, imports)
+}
+gczeal(10)
+base = a(`(module
+ (global (export "rngState")
+ (mut i32) i32.const 1
+ )
+ (global $ttl (mut i32) (i32.const 100000))
+
+ (type $d (array (mut i32)))
+ (type $e (array (mut (ref null $d))))
+
+ (func $f
+ (param $arr (ref $d))
+ (result (ref $d))
+
+ (global.set $ttl (i32.sub (global.get $ttl) (i32.const 1)))
+ (if (i32.eqz (global.get $ttl))
+ (then
+ unreachable
+ )
+ )
+
+ local.get $arr
+ )
+ (func $createSecondaryArray (result (ref $d))
+ (return_call $f
+ (array.new $d (i32.const 0) (i32.const 1))
+ )
+ )
+ (func $g (export "createPrimaryArrayLoop")
+ (param i32) (param $arrarr (ref $e))
+ (result (ref $e))
+
+ (call $createSecondaryArray (local.get $arrarr))
+ (return_call $g
+ (i32.const 1)
+ local.get $arrarr
+ )
+ )
+)`)
+t58 = `(module
+ (type $d (array (mut i32)))
+ (type $e (array (mut (ref null $d))))
+
+ (import "" "rngState" (global $h (mut i32)))
+ (import "" "createPrimaryArrayLoop" (func $g (param i32 (ref $e)) (result (ref $e))))
+
+ (func $createPrimaryArray (result (ref $e))
+ (return_call $g
+ i32.const 0
+ (array.new $e (ref.null $d) (i32.const 500))
+ )
+ )
+ (func (export "churn") (result i32)
+ (local $arrarr (ref $e))
+ (local.set $arrarr (call $createPrimaryArray))
+
+ (global.get $h)
+ )
+)`
+j = a(t58, {"": base.exports})
+fns = j.exports;
+
+assertErrorMessage(() => {
+ fns.churn();
+}, WebAssembly.RuntimeError, /unreachable executed/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/directives.txt b/js/src/jit-test/tests/wasm/tail-calls/directives.txt
new file mode 100644
index 0000000000..66e8161b9d
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/directives.txt
@@ -0,0 +1 @@
+|jit-test| --wasm-tail-calls; test-also=--wasm-compiler=baseline; test-also=--wasm-compiler=ion; test-also=--wasm-test-serialization; skip-if: !wasmTailCallsEnabled(); include:wasm.js
diff --git a/js/src/jit-test/tests/wasm/tail-calls/exceptions.js b/js/src/jit-test/tests/wasm/tail-calls/exceptions.js
new file mode 100644
index 0000000000..fd05f37e53
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/exceptions.js
@@ -0,0 +1,54 @@
+// |jit-test| --wasm-exceptions; skip-if: !wasmExceptionsEnabled()
+
+// Simple test with return_call.
+var ins = wasmEvalText(`(module
+ (tag $exn)
+ (func $f0 (result i32)
+ throw $exn
+ )
+ (func $f1 (result i32)
+ try
+ return_call $f0
+ catch $exn
+ i32.const 3
+ return
+ end
+ i32.const 4
+ )
+ (func (export "main") (result i32)
+ call $f1
+ )
+)`);
+assertErrorMessage(() => ins.exports.main(), WebAssembly.Exception, /.*/);
+
+// Test if return stub, created by introducing the slow path,
+// not interfering with exception propagation.
+var ins0 = wasmEvalText(`(module
+ (tag $exn)
+ (func $f0 (result i32)
+ throw $exn
+ )
+ (func (export "f") (result i32)
+ call $f0
+ )
+ (export "t" (tag $exn))
+)`);
+var ins = wasmEvalText(`(module
+ (import "" "t" (tag $exn))
+ (import "" "f" (func $f0 (result i32)))
+ (func $f1 (result i32)
+ try
+ return_call $f0
+ catch $exn
+ i32.const 3
+ return
+ end
+ i32.const 4
+ )
+ (func (export "main") (result i32)
+ call $f1
+ )
+ )`, {"":ins0.exports,});
+
+assertErrorMessage(() => ins.exports.main(), WebAssembly.Exception, /.*/);
+ \ No newline at end of file
diff --git a/js/src/jit-test/tests/wasm/tail-calls/gc.js b/js/src/jit-test/tests/wasm/tail-calls/gc.js
new file mode 100644
index 0000000000..10e5971e6d
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/gc.js
@@ -0,0 +1,220 @@
+// |jit-test| --wasm-function-references; --wasm-gc; skip-if: !wasmGcEnabled() || getBuildConfiguration("simulator")
+
+// Tests GC references passed as arguments during return calls.
+// Similar to js/src/jit-test/tests/wasm/gc/trailers-gc-stress.js
+
+let base = wasmEvalText(`(module
+ ;; A simple pseudo-random number generator.
+ ;; Produces numbers in the range 0 .. 2^16-1.
+ (global $rngState (export "rngState")
+ (mut i32) (i32.const 1)
+ )
+ (func $rand (export "rand") (result i32)
+ (local $t i32)
+ ;; update $rngState
+ (local.set $t (global.get $rngState))
+ (local.set $t (i32.mul (local.get $t) (i32.const 1103515245)))
+ (local.set $t (i32.add (local.get $t) (i32.const 12345)))
+ (global.set $rngState (local.get $t))
+ ;; pull 16 random bits out of it
+ (local.set $t (i32.shr_u (local.get $t) (i32.const 15)))
+ (local.set $t (i32.and (local.get $t) (i32.const 0xFFFF)))
+ (local.get $t)
+ )
+
+ ;; Array types
+ (type $tArrayI32 (array (mut i32))) ;; "secondary array" above
+ (type $tArrayArrayI32 (array (mut (ref null $tArrayI32)))) ;; "primary array"
+
+ (func $createSecondaryArrayLoop (export "createSecondaryArrayLoop")
+ (param $i i32) (param $arr (ref $tArrayI32))
+ (result (ref $tArrayI32))
+ (block $cont
+ (br_if $cont (i32.ge_u (local.get $i) (array.len (local.get $arr))))
+ (array.set $tArrayI32 (local.get $arr) (local.get $i) (call $rand))
+ (return_call $createSecondaryArrayLoop
+ (i32.add (local.get $i) (i32.const 1))
+ (local.get $arr))
+ )
+ (local.get $arr)
+ )
+ ;; Create an array ("secondary array") containing random numbers, with a
+ ;; size between 1 and 50, also randomly chosen.
+ (func $createSecondaryArray (export "createSecondaryArray")
+ (result (ref $tArrayI32))
+ (return_call $createSecondaryArrayLoop
+ (i32.const 0)
+ (array.new $tArrayI32
+ (i32.const 0)
+ (i32.add (i32.rem_u (call $rand) (i32.const 50)) (i32.const 1)))
+ )
+ )
+
+ (func $createPrimaryArrayLoop (export "createPrimaryArrayLoop")
+ (param $i i32) (param $arrarr (ref $tArrayArrayI32))
+ (result (ref $tArrayArrayI32))
+ (block $cont
+ (br_if $cont (i32.ge_u (local.get $i) (array.len (local.get $arrarr))))
+ (array.set $tArrayArrayI32 (local.get $arrarr)
+ (local.get $i) (call $createSecondaryArray))
+ (return_call $createPrimaryArrayLoop
+ (i32.add (local.get $i) (i32.const 1))
+ (local.get $arrarr))
+ )
+ (local.get $arrarr)
+ )
+)`);
+let t =
+`(module
+ ;; Array types (the same as in the base)
+ (type $tArrayI32 (array (mut i32))) ;; "secondary array" above
+ (type $tArrayArrayI32 (array (mut (ref null $tArrayI32)))) ;; "primary array"
+
+ (import "" "rngState" (global $rngState (mut i32)))
+ (import "" "rand" (func $rand (result i32)))
+ (import "" "createSecondaryArrayLoop"
+ (func $createSecondaryArrayLoop
+ (param $i i32) (param $arr (ref $tArrayI32))
+ (result (ref $tArrayI32))))
+ (import "" "createPrimaryArrayLoop"
+ (func $createPrimaryArrayLoop
+ (param $i i32) (param $arrarr (ref $tArrayArrayI32))
+ (result (ref $tArrayArrayI32))))
+
+ ;; Create an array ("secondary array") containing random numbers, with a
+ ;; size between 1 and 50, also randomly chosen.
+ ;; (Copy of the base one to create trampoline)
+ (func $createSecondaryArray (export "createSecondaryArray")
+ (result (ref $tArrayI32))
+ (return_call $createSecondaryArrayLoop
+ (i32.const 0)
+ (array.new $tArrayI32
+ (i32.const 0)
+ (i32.add (i32.rem_u (call $rand) (i32.const 50)) (i32.const 1)))
+ )
+ )
+
+ ;; Create an array (the "primary array") of 1500 elements of
+ ;; type ref-of-tArrayI32.
+ (func $createPrimaryArray (export "createPrimaryArray")
+ (result (ref $tArrayArrayI32))
+ (return_call $createPrimaryArrayLoop
+ (i32.const 0)
+ (array.new $tArrayArrayI32 (ref.null $tArrayI32) (i32.const 1500)))
+ )
+
+ ;; Use $createPrimaryArray to create an initial array. Then randomly replace
+ ;; elements for a while.
+ (func $churn (export "churn") (param $thresh i32) (result i32)
+ (local $i i32)
+ (local $j i32)
+ (local $finalSum i32)
+ (local $arrarr (ref $tArrayArrayI32))
+ (local $arr (ref null $tArrayI32))
+ (local $arrLen i32)
+ (local.set $arrarr (call $createPrimaryArray))
+ ;; This loop iterates 500,000 times. Each iteration, it chooses
+ ;; a randomly element in $arrarr and replaces it with a new
+ ;; random array of 32-bit ints.
+ (loop $cont
+ ;; make $j be a random number in 0 .. $thresh-1.
+ ;; Then replace that index in $arrarr with a new random arrayI32.
+ (local.set $j (i32.rem_u (call $rand) (local.get $thresh)))
+ (array.set $tArrayArrayI32 (local.get $arrarr)
+ (local.get $j) (call $createSecondaryArray))
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $cont (i32.lt_u (local.get $i) (i32.const 500000)))
+ )
+
+ ;; Finally, compute a checksum by summing all the numbers
+ ;; in all secondary arrays. This simply assumes that all of the refs to
+ ;; secondary arrays are non-null, which isn't per-se guaranteed by the
+ ;; previous loop, but it works in this case because the RNG
+ ;; produces each index value to overwrite at least once.
+ (local.set $finalSum (i32.const 0))
+ (local.set $i (i32.const 0)) ;; loop var for the outer loop
+ (loop $outer
+ ;; body of outer loop
+ ;; $arr = $arrarr[i]
+ (local.set $arr (array.get $tArrayArrayI32 (local.get $arrarr)
+ (local.get $i)))
+ ;; iterate over $arr
+ (local.set $arrLen (array.len (local.get $arr)))
+ (local.set $j (i32.const 0)) ;; loop var for the inner loop
+ (loop $inner
+ ;; body of inner loop
+ (local.set $finalSum
+ (i32.rotl (local.get $finalSum) (i32.const 1)))
+ (local.set $finalSum
+ (i32.xor (local.get $finalSum)
+ (array.get $tArrayI32 (local.get $arr)
+ (local.get $j))))
+ ;; loop control for the inner loop
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))
+ (br_if $inner (i32.lt_u (local.get $j) (local.get $arrLen)))
+ )
+ ;; loop control for the outer loop
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $outer (i32.lt_u (local.get $i) (i32.const 1500)))
+ )
+
+ ;; finally, roll in the final value of the RNG state
+ (i32.xor (local.get $finalSum) (global.get $rngState))
+ )
+)`;
+
+let i = wasmEvalText(t, {"": base.exports,});
+let fns = i.exports;
+
+assertEq(fns.churn(800), -575895114);
+assertEq(fns.churn(1200), -1164697516);
+
+wasmValidateText(`(module
+ (rec
+ (type $s1 (sub (struct (field i32))))
+ (type $s2 (sub $s1 (struct (field i32 f32))))
+ )
+ (func (result (ref $s2))
+ struct.new_default $s2
+ )
+ (func (export "f") (result (ref $s1))
+ return_call 0
+ )
+)`);
+
+wasmFailValidateText(`(module
+ (rec
+ (type $s1 (sub (struct (field i32))))
+ (type $s2 (sub $s1 (struct (field i32 f32))))
+ )
+ (func (result (ref $s1))
+ struct.new_default $s1
+ )
+ (func (export "f") (result (ref $s2))
+ return_call 0
+ )
+)`, /type mismatch/);
+
+wasmValidateText(`(module
+ (rec
+ (type $s1 (sub (struct (field i32))))
+ (type $s2 (sub $s1 (struct (field i32 f32))))
+ )
+ (type $t (func (result (ref $s2))))
+ (func (export "f") (param (ref $t)) (result (ref $s1))
+ local.get 0
+ return_call_ref $t
+ )
+)`);
+
+wasmFailValidateText(`(module
+ (rec
+ (type $s1 (sub (struct (field i32))))
+ (type $s2 (sub $s1 (struct (field i32 f32))))
+ )
+ (type $t (func (result (ref $s1))))
+ (func (export "f") (param (ref $t)) (result (ref $s2))
+ local.get 0
+ return_call_ref $t
+ )
+)`, /type mismatch/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus0.js b/js/src/jit-test/tests/wasm/tail-calls/litmus0.js
new file mode 100644
index 0000000000..89e978c2e7
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus0.js
@@ -0,0 +1,24 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Loop implemented using tail calls - the call passes as many arguments as
+// the function receives, of the same types.
+//
+// The variable ballast is intended to test that we handle various combinations
+// of stack and register arguments properly.
+
+for ( let ballast=1; ballast < TailCallBallast; ballast++ ) {
+ let vals = iota(ballast,1);
+ let ps = vals.map(_ => 'i32').join(' ')
+ let es = vals.map(i => `(local.get ${1+i})`).join(' ')
+ let sum = vals.reduceRight((p,c) => `(i32.add (local.get ${c+1}) ${p})`, `(i32.const 0)`)
+ let sumv = vals.reduce((p,c) => p+c);
+ let text = `
+(module
+ (func $loop (export "loop") (param $n i32) (param $q i32) (param ${ps}) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.add (local.get $q) ${sum})))
+ (else (return_call $loop (i32.sub (local.get $n) (i32.const 1)) (i32.add (local.get $q) (i32.const 1)) ${es})))))
+`;
+ let ins = wasmEvalText(text);
+ assertEq(ins.exports.loop(TailCallIterations, ...vals), TailCallIterations + sumv);
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus1.js b/js/src/jit-test/tests/wasm/tail-calls/litmus1.js
new file mode 100644
index 0000000000..7a86e3ce52
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus1.js
@@ -0,0 +1,32 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Mutually recursive functions implement a multi-entry loop using tail calls.
+// The functions do not have the same signatures, so if all arguments are stack
+// arguments then these use different amounts of stack space.
+//
+// The variable ballast is intended to test that we handle various combinations
+// of stack and register arguments properly.
+
+for ( let ballast=1; ballast < TailCallBallast; ballast++ ) {
+ let vals = iota(ballast,1);
+ let ps = vals.map(_ => 'i32').join(' ')
+ let es = vals.map(i => `(local.get ${i})`).join(' ')
+ let sum = vals.reduceRight((p,c) => `(i32.add (local.get ${c}) ${p})`, `(i32.const 0)`)
+ let sumv = vals.reduce((p,c) => p+c);
+ let text = `
+(module
+ (func $odd (export "odd") (param $n i32) (param ${ps}) (param $dummy i32) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.or (i32.shl ${sum} (i32.const 1)) (i32.const 0))))
+ (else (return_call $even (i32.sub (local.get $n) (i32.const 1)) ${es}))))
+
+ (func $even (export "even") (param $n i32) (param ${ps}) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.or (i32.shl ${sum} (i32.const 1)) (i32.const 1))))
+ (else (return_call $odd (i32.sub (local.get $n) (i32.const 1)) ${es} (i32.const 33))))))`;
+
+ let ins = wasmEvalText(text);
+ assertEq(ins.exports.even(TailCallIterations, ...vals), (sumv*2) + 1);
+ assertEq(ins.exports.odd(TailCallIterations, ...vals, 33), (sumv*2) + 0);
+}
+
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus10.js b/js/src/jit-test/tests/wasm/tail-calls/litmus10.js
new file mode 100644
index 0000000000..8ef83f1336
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus10.js
@@ -0,0 +1,59 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Tail-call litmus test with multiple results
+//
+// Mutually recursive functions implement a multi-entry loop using indirect
+// cross-module tail calls. The functions do not have the same signatures, so
+// if all arguments are stack arguments then these use different amounts of
+// stack space.
+//
+// Cross-module mutual recursion must be set up by JS, which is a bit of hair.
+// But this should not destroy the ability to tail-call.
+//
+// The mutable globals accessed after the call are there to attempt to ensure
+// that the correct instance is restored after the chain of tail calls.
+
+var table = new WebAssembly.Table({initial:2, maximum:2, element:"funcref"})
+
+var odd_cookie = 24680246;
+var oddins = wasmEvalText(`
+(module
+ (table (import "" "table") 2 2 funcref)
+ (type $even_t (func (param i32) (result i32 i32 i32)))
+ (global $glob (export "g") (mut i32) (i32.const ${odd_cookie}))
+
+ (func $odd_entry (export "odd_entry") (param $n i32) (result i32 i32 i32 i32)
+ (call $odd (local.get $n) (i32.const 86))
+ (global.get $glob))
+
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0) (i32.const 32769) (i32.const -37)))
+ (else (return_call_indirect (type $even_t) (i32.sub (local.get $n) (i32.const 1)) (i32.const 0))))))`,
+ {"":{table}});
+
+var even_cookie = 12345678;
+var evenins = wasmEvalText(`
+(module
+ (table (import "" "table") 2 2 funcref)
+ (type $odd_t (func (param i32 i32) (result i32 i32 i32)))
+ (global $glob (export "g") (mut i32) (i32.const ${even_cookie}))
+
+ (func $even_entry (export "even_entry") (param $n i32) (result i32 i32 i32 i32)
+ (call $even (local.get $n))
+ (global.get $glob))
+
+ (func $even (export "even") (param $n i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 1) (i32.const -17) (i32.const 44021)))
+ (else (return_call_indirect (type $odd_t) (i32.sub (local.get $n) (i32.const 1)) (i32.const 33) (i32.const 1))))))`,
+ {"":{table}});
+
+
+table.set(0, evenins.exports.even);
+table.set(1, oddins.exports.odd);
+
+assertSame(evenins.exports.even_entry(TailCallIterations), [1, -17, 44021, even_cookie]);
+assertSame(oddins.exports.odd_entry(TailCallIterations+1, 33), [1, -17, 44021, odd_cookie]);
+assertSame(evenins.exports.even_entry(TailCallIterations+1), [0, 32769, -37, even_cookie]);
+assertSame(oddins.exports.odd_entry(TailCallIterations, 33), [0, 32769, -37, odd_cookie]);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus11.js b/js/src/jit-test/tests/wasm/tail-calls/litmus11.js
new file mode 100644
index 0000000000..78c7cd4c49
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus11.js
@@ -0,0 +1,66 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Once we exhaust the register arguments this will alternately grow and then
+// shrink the stack frame across tail call boundaries because the increment of
+// stack allocation is 16 bytes and our variability exceeds that.
+//
+// (This is not redundant with eg litmus0, because in that case all the
+// functions have the same ballast. Here we have different ballast, so we get growing and
+// shrinking.)
+//
+// See litmus13 for the same-module call_indirect case.
+// See litmus16 for the cross-module call_indirect case.
+
+function ntimes(n, v) {
+ if (typeof v == "function")
+ return iota(n).map(v).join(' ');
+ return iota(n).map(_ => v).join(' ');
+}
+
+function get_local(n) {
+ return `(local.get ${n})`
+}
+
+function compute(ballast) {
+ return iota(ballast).reduce((p,g,n) => `(i32.or ${p} (local.get ${n}))`,
+ `(i32.const ${1 << ballast})`)
+}
+
+function build(n, ballast) {
+ switch (n) {
+ case 0:
+ return `
+(func $f0 (export "f") (result i32)
+ (return_call $f1 (i32.const ${1 << n})))
+`;
+ case ballast:
+ return `
+(func $f${ballast} (param ${ntimes(ballast, 'i32')}) (result i32)
+ (if (result i32) (i32.eqz (global.get $glob))
+ (then (return ${compute(ballast)}))
+ (else (block (result i32)
+ (global.set $glob (i32.sub (global.get $glob) (i32.const 1)))
+ (return_call $f0)))))
+`;
+ default:
+ return `
+(func $f${n} (param ${ntimes(n, 'i32')}) (result i32)
+ (return_call $f${n+1} (i32.const ${1 << n}) ${ntimes(n, get_local)}))
+`
+ }
+}
+
+for ( let ballast=1; ballast < TailCallBallast; ballast++ ) {
+
+ let vals = iota(ballast+1).map(v => 1 << v);
+ let sumv = vals.reduce((p,c) => p|c);
+ let text = `
+(module
+ (global $glob (mut i32) (i32.const ${TailCallIterations}))
+ ${ntimes(ballast, n => build(n, ballast))}
+ ${build(ballast, ballast)})
+`;
+
+ let ins = wasmEvalText(text);
+ assertEq(ins.exports.f(), sumv);
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus12.js b/js/src/jit-test/tests/wasm/tail-calls/litmus12.js
new file mode 100644
index 0000000000..8e488d19e2
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus12.js
@@ -0,0 +1,41 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Basically try to assert that the instance is restored properly when
+// performing an import call. We do this by accessing the instance once we get
+// out of the tail call chain.
+
+// In this pure form, this test can't test for stack overflow as we can't do a loop of
+// imported functions without return_call_indirect and a table.
+
+// TODO: Probably add a loop here with an indirect call
+// TODO: More ballast will make it more likely that stack smashing is detected
+
+var insh = wasmEvalText(`
+(module
+ (global $glob (export "glob") (mut i32) (i32.const 12345678))
+ (func $h (export "h") (param i32) (result i32)
+ (local.get 0)))`);
+
+var insg = wasmEvalText(`
+(module
+ (import "insh" "h" (func $h (param i32) (result i32)))
+ (global $glob (export "glob") (mut i32) (i32.const 24680246))
+ (func $g (export "g") (param i32) (result i32)
+ (return_call $h (local.get 0))))`, {insh:insh.exports});
+
+var insf = wasmEvalText(`
+(module
+ (import "insg" "g" (func $g (param i32) (result i32)))
+ (global $glob (export "glob") (mut i32) (i32.const 36903690))
+ (func $f (export "f") (param i32) (result i32)
+ (return_call $g (local.get 0))))`, {insg:insg.exports});
+
+var start = wasmEvalText(`
+(module
+ (import "insf" "f" (func $f (param i32) (result i32)))
+ (global $glob (export "glob") (mut i32) (i32.const 480480480))
+ (func (export "run") (param i32) (result i32)
+ (i32.add (call $f (local.get 0)) (global.get $glob))))`, {insf:insf.exports});
+
+assertEq(start.exports.run(37), 480480480 + 37);
+
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus13.js b/js/src/jit-test/tests/wasm/tail-calls/litmus13.js
new file mode 100644
index 0000000000..238bf09392
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus13.js
@@ -0,0 +1,79 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Once we exhaust the register arguments this will alternately grow and then
+// shrink the stack frame across tail call boundaries because the increment of
+// stack allocation is 16 bytes and our variability exceeds that.
+//
+// (This is not redundant with eg litmus2, because in that case all the
+// functions have the same ballast. Here we have different ballast, so we get
+// growing and shrinking.)
+//
+// See litmus11 for the direct-call case.
+// See litmus16 for the cross-module call_indirect case.
+
+function ntimes(n, v) {
+ if (typeof v == "function")
+ return iota(n).map(v).join(' ');
+ return iota(n).map(_ => v).join(' ');
+}
+
+function get_local(n) {
+ return `(local.get ${n})`
+}
+
+function compute(ballast) {
+ return iota(ballast).reduce((p,g,n) => `(i32.or ${p} (local.get ${n}))`,
+ `(i32.const ${1 << ballast})`)
+}
+
+function build(n, ballast) {
+ switch (n) {
+ case 0:
+ return `
+(func $f0 (export "f") (result i32)
+ (return_call_indirect (type $ty1) (i32.const ${1 << n}) (i32.const 1)))
+`;
+ case ballast:
+ return `
+(func $f${ballast} (param ${ntimes(ballast, 'i32')}) (result i32)
+ (if (result i32) (i32.eqz (global.get $glob))
+ (then (return ${compute(ballast)}))
+ (else (block (result i32)
+ (global.set $glob (i32.sub (global.get $glob) (i32.const 1)))
+ (return_call_indirect (type $ty0) (i32.const 0))))))
+`;
+ default:
+ return `
+(func $f${n} (param ${ntimes(n, 'i32')}) (result i32)
+ (return_call_indirect (type $ty${n+1}) (i32.const ${1 << n}) ${ntimes(n, get_local)} (i32.const ${n+1})))
+`
+ }
+}
+
+function types(n) {
+ var ps = n == 0 ? '' : `(param ${ntimes(n, 'i32')})`;
+ return `
+(type $ty${n} (func ${ps} (result i32)))`;
+}
+
+function funcnames(n) {
+ return ntimes(n, n => `$f${n}`)
+}
+
+for ( let ballast=1; ballast < TailCallBallast; ballast++ ) {
+
+ let vals = iota(ballast+1).map(v => 1 << v);
+ let sumv = vals.reduce((p,c) => p|c);
+ let text = `
+(module
+ ${ntimes(ballast+1, n => types(n))}
+ (table $t ${ballast+1} ${ballast+1} funcref)
+ (elem (table $t) (i32.const 0) func ${funcnames(ballast+1)})
+ (global $glob (mut i32) (i32.const ${TailCallIterations}))
+ ${ntimes(ballast, n => build(n, ballast))}
+ ${build(ballast, ballast)})
+`;
+
+ let ins = wasmEvalText(text);
+ assertEq(ins.exports.f(), sumv);
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus15.js b/js/src/jit-test/tests/wasm/tail-calls/litmus15.js
new file mode 100644
index 0000000000..38596a6b43
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus15.js
@@ -0,0 +1,72 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// On systems with up to four register arguments, this alternately grows and then shrinks
+// the stack frame across tail call boundaries. All functions are in the same
+// module.
+
+var ins = wasmEvalText(`
+(module
+ (type $ty0 (func (result i32)))
+ (type $ty1 (func (param i32) (result i32)))
+ (type $ty2 (func (param i32 i32) (result i32)))
+ (type $ty3 (func (param i32 i32 i32) (result i32)))
+ (type $ty4 (func (param i32 i32 i32 i32) (result i32)))
+ (type $ty5 (func (param i32 i32 i32 i32 i32) (result i32)))
+ (type $ty6 (func (param i32 i32 i32 i32 i32 i32) (result i32)))
+ (type $ty7 (func (param i32 i32 i32 i32 i32 i32 i32) (result i32)))
+ (type $ty8 (func (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
+ (type $ty9 (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
+
+ (table $t 10 10 funcref)
+ (elem (table $t) (i32.const 0) func $f $g $h $i $j $k $l $m $n $o)
+
+ (global $glob (mut i32) (i32.const ${TailCallIterations}))
+
+ (func $o (param i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
+ (if (result i32) (i32.eqz (global.get $glob))
+ (then
+ (i32.or (i32.const 512)
+ (i32.or (local.get 0)
+ (i32.or (local.get 1)
+ (i32.or (local.get 2)
+ (i32.or (local.get 3)
+ (i32.or (local.get 4)
+ (i32.or (local.get 5)
+ (i32.or (local.get 6)
+ (i32.or (local.get 7) (local.get 8)))))))))))
+ (else
+ (block (result i32)
+ (global.set $glob (i32.sub (global.get $glob) (i32.const 1)))
+ (return_call_indirect (type $ty0) (i32.const 0))))))
+
+ (func $n (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
+ (return_call_indirect (type $ty9) (i32.const 256) (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4) (local.get 5) (local.get 6) (local.get 7) (i32.const 9)))
+
+ (func $m (param i32 i32 i32 i32 i32 i32 i32) (result i32)
+ (return_call_indirect (type $ty8) (i32.const 128) (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4) (local.get 5) (local.get 6) (i32.const 8)))
+
+ (func $l (param i32 i32 i32 i32 i32 i32) (result i32)
+ (return_call_indirect (type $ty7) (i32.const 64) (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4) (local.get 5) (i32.const 7)))
+
+ (func $k (param i32 i32 i32 i32 i32) (result i32)
+ (return_call_indirect (type $ty6) (i32.const 32) (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4) (i32.const 6)))
+
+ (func $j (param i32 i32 i32 i32) (result i32)
+ (return_call_indirect (type $ty5) (i32.const 16) (local.get 0) (local.get 1) (local.get 2) (local.get 3) (i32.const 5)))
+
+ (func $i (param i32 i32 i32) (result i32)
+ (return_call_indirect (type $ty4) (i32.const 8) (local.get 0) (local.get 1) (local.get 2) (i32.const 4)))
+
+ (func $h (param i32 i32) (result i32)
+ (return_call_indirect (type $ty3) (i32.const 4) (local.get 0) (local.get 1) (i32.const 3)))
+
+ (func $g (param i32) (result i32)
+ (return_call_indirect (type $ty2) (i32.const 2) (local.get 0) (i32.const 2)))
+
+ (func $f (export "f") (result i32)
+ (return_call_indirect (type $ty1) (i32.const 1) (i32.const 1))))`);
+
+assertEq(ins.exports.f(), 1023);
+print("OK")
+
+
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus16.js b/js/src/jit-test/tests/wasm/tail-calls/litmus16.js
new file mode 100644
index 0000000000..716b0520d2
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus16.js
@@ -0,0 +1,94 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// This alternately grows and then shrinks the stack frame across tail call boundaries.
+// Here we go cross-module as well. There is enough ballast that the stack frame will
+// alternately have to grow and shrink across some of the calls.
+//
+// We generate one module+instance per function so that every call is cross-instance.
+// Each module has an "up" function (calling the next higher index) and a "down" function
+// (calling the next lower index). The last "up" function decrements the global counter
+// and optionally returns a result $down0 just calls $up0.
+//
+// TODO: Test that the proper instance is being restored? Or have we done that elsewhere?
+
+function ntimes(n, v) {
+ if (typeof v == "function")
+ return iota(n).map(v).join(' ');
+ return iota(n).map(_ => v).join(' ');
+}
+
+function get_local(n) {
+ return `(local.get ${n})`
+}
+
+function compute(ballast) {
+ return iota(ballast).reduce((p,_,n) => `(i32.or ${p} (local.get ${n}))`,
+ `(i32.const ${1 << ballast})`)
+}
+
+function code(n, ballast) {
+ switch (n) {
+ case 0:
+ return `
+(func $up0 (export "f") (result i32)
+ (return_call_indirect (type $ty1) (i32.const ${1 << n}) (i32.const 1)))
+(func $down0 (result i32)
+ (return_call $up0))`;
+ case ballast:
+ return `
+(func $up${ballast} (param ${ntimes(ballast, 'i32')}) (result i32)
+ (if (result i32) (i32.eqz (global.get $glob))
+ (then (return ${compute(ballast)}))
+ (else
+ (block (result i32)
+ (global.set $glob (i32.sub (global.get $glob) (i32.const 1)))
+ (return_call_indirect (type $ty${ballast-1}) ${ntimes(ballast-1,get_local)} (i32.const ${ballast+1}))))))`;
+ default:
+ return `
+(func $up${n} (param ${ntimes(n, 'i32')}) (result i32)
+ (return_call_indirect (type $ty${n+1}) (i32.const ${1 << n}) ${ntimes(n, get_local)} (i32.const ${n+1})))
+(func $down${n} (param ${ntimes(n, 'i32')}) (result i32)
+ (return_call_indirect (type $ty${n-1}) ${ntimes(n-1, get_local)} (i32.const ${2*ballast-n+1})))`;
+ }
+}
+
+function types(n, ballast) {
+ var tys = '';
+ if (n > 0)
+ tys += `
+(type $ty${n-1} (func (param ${ntimes(n-1, 'i32')}) (result i32)))`;
+ if (n < ballast)
+ tys += `
+(type $ty${n+1} (func (param ${ntimes(n+1, 'i32')}) (result i32)))`
+ return tys;
+}
+
+function inits(n, ballast) {
+ var inits = `
+(elem (i32.const ${n}) $up${n})`
+ if (n < ballast)
+ inits += `
+(elem (i32.const ${2*ballast-n}) $down${n})`;
+ return inits
+}
+
+for (let ballast = 1; ballast < TailCallBallast; ballast++) {
+ let counter = new WebAssembly.Global({ mutable: true, value: "i32" }, TailCallIterations/10);
+ let table = new WebAssembly.Table({ initial: ballast * 2 + 1, maximum: ballast * 2 + 1, element: "anyfunc" });
+ let tys = ntimes(ballast + 1, n => types(n));
+ let vals = iota(ballast + 1).map(v => 1 << v);
+ let sumv = vals.reduce((p, c) => p | c);
+ let ins = [];
+ let imp = { "": { table, counter } };
+ for (let i = 0; i <= ballast; i++) {
+ let txt = `
+(module
+ ${types(i, ballast)}
+ (import "" "table" (table $t ${ballast * 2 - 1} funcref))
+ (import "" "counter" (global $glob (mut i32)))
+ ${inits(i, ballast)}
+ ${code(i, ballast)})`;
+ ins[i] = wasmEvalText(txt, imp);
+ }
+ assertEq(ins[0].exports.f(), sumv)
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus17.js b/js/src/jit-test/tests/wasm/tail-calls/litmus17.js
new file mode 100644
index 0000000000..025c025305
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus17.js
@@ -0,0 +1,27 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Test that unwinding will find the right instance in the middle of a tail call
+// chain. This test is mostly useful on the simulators, as they will walk the
+// stack, looking for the innermost instance, on every memory reference. The
+// logic of this test is that the setup intra-module tail call in mod2 will need
+// to be recorded as possibly-inter-module because the subsequent inter-module
+// call replaces the activation with one that is actually inter-module.
+
+let ins1 = wasmEvalText(`
+(module
+ (memory (export "mem") 1 1)
+ (func (export "memref") (result i32)
+ (i32.load (i32.const 0))))`);
+
+let ins2 = wasmEvalText(`
+(module
+ (import "mod1" "memref" (func $memref (result i32)))
+
+ (func (export "run") (result i32)
+ (return_call $g))
+
+ (func $g (result i32)
+ (return_call $memref)))`, {mod1: ins1.exports});
+
+(new Int32Array(ins1.exports.mem.buffer))[0] = 1337;
+assertEq(ins2.exports.run(), 1337);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus2.js b/js/src/jit-test/tests/wasm/tail-calls/litmus2.js
new file mode 100644
index 0000000000..867bf6d677
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus2.js
@@ -0,0 +1,36 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Mutually recursive functions implement a multi-entry loop using indirect tail
+// calls. The functions do not have the same signatures, so if all arguments
+// are stack arguments then these use different amounts of stack space.
+//
+// The variable ballast is intended to test that we handle various combinations
+// of stack and register arguments properly.
+
+for ( let ballast=1; ballast < TailCallBallast; ballast++ ) {
+ let vals = iota(ballast,1);
+ let ps = vals.map(_ => 'i32').join(' ')
+ let es = vals.map(i => `(local.get ${i})`).join(' ')
+ let sum = vals.reduceRight((p,c) => `(i32.add (local.get ${c}) ${p})`, `(i32.const 0)`)
+ let sumv = vals.reduce((p,c) => p+c);
+ let text = `
+(module
+ (table 2 2 funcref)
+ (elem (i32.const 0) $even $odd)
+ (type $t (func (param i32 ${ps}) (result i32)))
+ (type $q (func (param i32 ${ps} i32) (result i32)))
+
+ (func $odd (export "odd") (param $n i32) (param ${ps}) (param $dummy i32) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.or (i32.shl ${sum} (i32.const 1)) (i32.const 0))))
+ (else (return_call_indirect (type $t) (i32.sub (local.get $n) (i32.const 1)) ${es} (i32.const 0)))))
+
+ (func $even (export "even") (param $n i32) (param ${ps}) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.or (i32.shl ${sum} (i32.const 1)) (i32.const 1))))
+ (else (return_call_indirect (type $q) (i32.sub (local.get $n) (i32.const 1)) ${es} (i32.const 33) (i32.const 1))))))`
+
+ let ins = wasmEvalText(text);
+ assertEq(ins.exports.even(TailCallIterations, ...vals), (sumv*2) + 1);
+ assertEq(ins.exports.odd(TailCallIterations, ...vals, 33), (sumv*2) + 0);
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus3.js b/js/src/jit-test/tests/wasm/tail-calls/litmus3.js
new file mode 100644
index 0000000000..bdd0918717
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus3.js
@@ -0,0 +1,36 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled() || !wasmExceptionsEnabled()
+
+// Mutually recursive functions implement a multi-entry loop using tail calls,
+// with exception handling.
+//
+// The functions do not have the same signatures, so if all arguments are stack
+// arguments then these use different amounts of stack space.
+//
+// The "even" function will throw when the input is zero, so when an exception
+// bubbles out of return_call it must not be caught by the exception handler in
+// "odd", but must instead be delegated to the caller, which is JS code.
+
+var ins = wasmEvalText(`
+(module
+ (tag $t)
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32)
+ try (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0)))
+ (else (return_call $even (i32.sub (local.get $n) (i32.const 1)))))
+ catch_all
+ unreachable
+ end)
+
+ (func $even (export "even") (param $n i32) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (throw $t))
+ (else (return_call $odd (i32.sub (local.get $n) (i32.const 1)) (i32.const 33)))))
+)
+`);
+
+assertErrorMessage(() => ins.exports.even(TailCallIterations), WebAssembly.Exception, /.*/);
+assertEq(ins.exports.odd(TailCallIterations, 33), 0);
+
+assertEq(ins.exports.even(TailCallIterations+1), 0);
+assertErrorMessage(() => ins.exports.odd(TailCallIterations+1, 33), WebAssembly.Exception, /.*/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus4.js b/js/src/jit-test/tests/wasm/tail-calls/litmus4.js
new file mode 100644
index 0000000000..c16f712aac
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus4.js
@@ -0,0 +1,43 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled() || !wasmExceptionsEnabled()
+
+// Mutually recursive functions implement a multi-entry loop using indirect tail
+// calls, with exception handling.
+//
+// The functions do not have the same signatures, so if all arguments are stack
+// arguments then these use different amounts of stack space.
+//
+// The "even" function will throw when the input is zero, so when an exception
+// bubbles out of return_call it must not be caught by the exception handler in
+// "odd", but must instead be delegated to the caller, which is JS code.
+
+var ins = wasmEvalText(`
+(module
+ (table 2 2 funcref)
+ (elem (i32.const 0) $even $odd)
+ (type $even_t (func (param i32) (result i32)))
+ (type $odd_t (func (param i32 i32) (result i32)))
+ (tag $t)
+
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32)
+ try (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0)))
+ (else (return_call_indirect (type $even_t) (i32.sub (local.get $n) (i32.const 1))
+ (i32.const 0))))
+ catch_all
+ unreachable
+ end)
+
+ (func $even (export "even") (param $n i32) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (throw $t))
+ (else (return_call_indirect (type $odd_t) (i32.sub (local.get $n) (i32.const 1)) (i32.const 33)
+ (i32.const 1)))))
+)
+`);
+
+assertErrorMessage(() => ins.exports.even(TailCallIterations), WebAssembly.Exception, /.*/);
+assertEq(ins.exports.odd(TailCallIterations, 33), 0);
+
+assertEq(ins.exports.even(TailCallIterations+1), 0);
+assertErrorMessage(() => ins.exports.odd(TailCallIterations+1, 33), WebAssembly.Exception, /.*/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus5.js b/js/src/jit-test/tests/wasm/tail-calls/litmus5.js
new file mode 100644
index 0000000000..d7b82c082b
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus5.js
@@ -0,0 +1,68 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Mutually recursive functions implement a multi-entry loop using indirect
+// cross-module tail calls. The functions do not have the same signatures, so
+// if all arguments are stack arguments then these use different amounts of
+// stack space.
+//
+// Cross-module mutual recursion must be set up by JS, which is a bit of hair.
+// But this should not destroy the ability to tail-call.
+//
+// The variable ballast is intended to test that we handle various combinations
+// of stack and register arguments properly.
+//
+// The mutable globals accessed after the call are there to attempt to ensure
+// that the correct instance is restored after the chain of tail calls.
+
+for ( let ballast=1; ballast < TailCallBallast; ballast++ ) {
+ let vals = iota(ballast,1);
+ let ps = vals.map(_ => 'i32').join(' ')
+ let es = vals.map(i => `(local.get ${i})`).join(' ')
+ let sum = vals.reduceRight((p,c) => `(i32.add (local.get ${c}) ${p})`, `(i32.const 0)`)
+ let sumv = vals.reduce((p,c) => p+c);
+
+ let odd_cookie = 24680246;
+ let oddtext = `
+(module
+ (table (import "" "table") 2 2 funcref)
+ (type $even_t (func (param i32 ${ps}) (result i32)))
+ (global $glob (export "g") (mut i32) (i32.const ${odd_cookie}))
+
+ (func $odd_entry (export "odd_entry") (param $n i32) (param ${ps}) (result i32)
+ (call $odd (local.get $n) ${es} (i32.const 86))
+ (global.get $glob)
+ i32.add)
+
+ (func $odd (export "odd") (param $n i32) (param ${ps}) (param $dummy i32) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.or (i32.shl ${sum} (i32.const 1)) (i32.const 0))))
+ (else (return_call_indirect (type $even_t) (i32.sub (local.get $n) (i32.const 1)) ${es} (i32.const 0))))))`
+
+ let even_cookie = 12345678;
+ let eventext = `
+(module
+ (table (import "" "table") 2 2 funcref)
+ (type $odd_t (func (param i32 ${ps} i32) (result i32)))
+ (global $glob (export "g") (mut i32) (i32.const ${even_cookie}))
+
+ (func $even_entry (export "even_entry") (param $n i32) (param ${ps}) (result i32)
+ (call $even (local.get $n) ${es})
+ (global.get $glob)
+ i32.add)
+
+ (func $even (export "even") (param $n i32) (param ${ps}) (result i32)
+ (if (result i32) (i32.eqz (local.get $n))
+ (then (return (i32.or (i32.shl ${sum} (i32.const 1)) (i32.const 1))))
+ (else (return_call_indirect (type $odd_t) (i32.sub (local.get $n) (i32.const 1)) ${es} (i32.const 33) (i32.const 1))))))`
+
+ let table = new WebAssembly.Table({initial:2, maximum:2, element:"funcref"})
+
+ let oddins = wasmEvalText(oddtext, {"":{table}});
+ let evenins = wasmEvalText(eventext, {"":{table}});
+
+ table.set(0, evenins.exports.even);
+ table.set(1, oddins.exports.odd);
+
+ assertEq(evenins.exports.even_entry(TailCallIterations, ...vals), ((sumv*2) + 1) + even_cookie);
+ assertEq(oddins.exports.odd_entry(TailCallIterations, ...vals, 33), ((sumv*2) + 0) + odd_cookie);
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus6.js b/js/src/jit-test/tests/wasm/tail-calls/litmus6.js
new file mode 100644
index 0000000000..6d9e8b9be6
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus6.js
@@ -0,0 +1,24 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Tail-call litmus test with multiple results
+//
+// Mutually recursive functions implement a multi-entry loop using tail calls.
+// The functions do not have the same signatures, so if all arguments are stack
+// arguments then these use different amounts of stack space.
+
+var ins = wasmEvalText(`
+(module
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0) (i32.const 32769) (i32.const -37)))
+ (else (return_call $even (i32.sub (local.get $n) (i32.const 1))))))
+
+ (func $even (export "even") (param $n i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 1) (i32.const -17) (i32.const 44021)))
+ (else (return_call $odd (i32.sub (local.get $n) (i32.const 1)) (i32.const 33)))))
+)
+`);
+
+assertSame(ins.exports.even(TailCallIterations), [1, -17, 44021]);
+assertSame(ins.exports.odd(TailCallIterations, 33), [0, 32769, -37]);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus7.js b/js/src/jit-test/tests/wasm/tail-calls/litmus7.js
new file mode 100644
index 0000000000..43cfc8d3ed
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus7.js
@@ -0,0 +1,29 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+// Tail-call litmus test with multiple results
+//
+// Mutually recursive functions implement a multi-entry loop using indirect tail
+// calls. The functions do not have the same signatures, so if all arguments
+// are stack arguments then these use different amounts of stack space.
+
+var ins = wasmEvalText(`
+(module
+ (table 2 2 funcref)
+ (elem (i32.const 0) $even $odd)
+ (type $t (func (param i32) (result i32 i32 i32)))
+ (type $q (func (param i32 i32) (result i32 i32 i32)))
+
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0) (i32.const 32769) (i32.const -37)))
+ (else (return_call_indirect (type $t) (i32.sub (local.get $n) (i32.const 1)) (i32.const 0)))))
+
+ (func $even (export "even") (param $n i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 1) (i32.const -17) (i32.const 44021)))
+ (else (return_call_indirect (type $q) (i32.sub (local.get $n) (i32.const 1)) (i32.const 33) (i32.const 1)))))
+)
+`);
+
+assertSame(ins.exports.even(TailCallIterations), [1, -17, 44021]);
+assertSame(ins.exports.odd(TailCallIterations, 33), [0, 32769, -37]);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus8.js b/js/src/jit-test/tests/wasm/tail-calls/litmus8.js
new file mode 100644
index 0000000000..4602664488
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus8.js
@@ -0,0 +1,38 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled() || !wasmExceptionsEnabled()
+
+// Tail-call litmus test with multiple results
+//
+// Mutually recursive functions implement a multi-entry loop using tail calls,
+// with exception handling.
+//
+// The functions do not have the same signatures, so if all arguments are stack
+// arguments then these use different amounts of stack space.
+//
+// The "even" function will throw when the input is zero, so when an exception
+// bubbles out of return_call it must not be caught by the exception handler in
+// "odd", but must instead be delegated to the caller, which is JS code.
+
+var ins = wasmEvalText(`
+(module
+ (tag $t)
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32 i32 i32)
+ try (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0) (i32.const 32769) (i32.const -37)))
+ (else (return_call $even (i32.sub (local.get $n) (i32.const 1)))))
+ catch_all
+ unreachable
+ end)
+
+ (func $even (export "even") (param $n i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (throw $t))
+ (else (return_call $odd (i32.sub (local.get $n) (i32.const 1)) (i32.const 33)))))
+)
+`);
+
+assertErrorMessage(() => ins.exports.even(TailCallIterations), WebAssembly.Exception, /.*/);
+assertSame(ins.exports.odd(TailCallIterations, 33), [0, 32769, -37]);
+
+assertErrorMessage(() => ins.exports.odd(TailCallIterations+1, 33), WebAssembly.Exception, /.*/);
+assertSame(ins.exports.even(TailCallIterations+1), [0, 32769, -37]);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/litmus9.js b/js/src/jit-test/tests/wasm/tail-calls/litmus9.js
new file mode 100644
index 0000000000..3bbde27111
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/litmus9.js
@@ -0,0 +1,42 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled() || !wasmExceptionsEnabled()
+
+// Tail-call litmus test with multiple results
+//
+// Mutually recursive functions implement a multi-entry loop using indirect tail
+// calls, with exception handling.
+//
+// The functions do not have the same signatures, so if all arguments are stack
+// arguments then these use different amounts of stack space.
+//
+// The "even" function will throw when the input is zero, so when an exception
+// bubbles out of return_call it must not be caught by the exception handler in
+// "odd", but must instead be delegated to the caller, which is JS code.
+
+var ins = wasmEvalText(`
+(module
+ (table 2 2 funcref)
+ (elem (i32.const 0) $even $odd)
+ (type $even_t (func (param i32) (result i32 i32 i32)))
+ (type $odd_t (func (param i32 i32) (result i32 i32 i32)))
+ (tag $t)
+
+ (func $odd (export "odd") (param $n i32) (param $dummy i32) (result i32 i32 i32)
+ try (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (return (i32.const 0) (i32.const 32769) (i32.const -37)))
+ (else (return_call_indirect (type $even_t) (i32.sub (local.get $n) (i32.const 1))
+ (i32.const 0))))
+ catch_all
+ unreachable
+ end)
+
+ (func $even (export "even") (param $n i32) (result i32 i32 i32)
+ (if (result i32 i32 i32) (i32.eqz (local.get $n))
+ (then (throw $t))
+ (else (return_call_indirect (type $odd_t) (i32.sub (local.get $n) (i32.const 1)) (i32.const 33)
+ (i32.const 1)))))
+)
+`);
+
+assertErrorMessage(() => ins.exports.even(TailCallIterations), WebAssembly.Exception, /.*/);
+assertSame(ins.exports.odd(TailCallIterations, 33), [0, 32769, -37]);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-syntax.js b/js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-syntax.js
new file mode 100644
index 0000000000..aa0ab5eebf
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-syntax.js
@@ -0,0 +1,117 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+function assertInvalidSyntax(module) {
+ assertErrorMessage(() => wasmTextToBinary(module), SyntaxError,
+ /wasm text error/);
+}
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32) (result i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (type $sig) (result i32) (param i32)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32 i32) (result i32 i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (param i32) (type $sig) (result i32)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32) (result i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (param i32) (result i32) (type $sig)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32) (result i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (result i32) (type $sig) (param i32)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32) (result i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (result i32) (param i32) (type $sig)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (result i32) (param i32)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (table 0 anyfunc)
+ (func (return_call_indirect (param $x i32) (i32.const 0) (i32.const 0)))
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (type $sig) (result i32) (i32.const 0))
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32) (result i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (type $sig) (result i32) (i32.const 0))
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32) (result i32)))
+ (table 0 anyfunc)
+ (func
+ (return_call_indirect (type $sig) (param i32)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
+
+assertInvalidSyntax(`
+ (module
+ (type $sig (func (param i32 i32) (result i32)))
+ (table 0 anyfunc)
+ (func (result i32)
+ (return_call_indirect (type $sig) (param i32) (result i32)
+ (i32.const 0) (i32.const 0)
+ )
+ )
+ )`);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-validate.js b/js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-validate.js
new file mode 100644
index 0000000000..15c2142a55
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-validate.js
@@ -0,0 +1,243 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+wasmValidateText(
+ `(module
+ (type (func))
+ (table 0 funcref)
+ (func $arity-1-vs-0
+ (return_call_indirect (type 0)
+ (i32.const 1) (i32.const 0))))`);
+
+wasmValidateText(
+ `(module
+ (type (func))
+ (table 0 funcref)
+ (func $arity-1-vs-0
+ (return_call_indirect (type 0)
+ (f64.const 2) (i32.const 1) (i32.const 0))))`);
+
+const constants = [{'type': 'i32', 'value': '0x132'},
+ {'type': 'i64', 'value': '0x164'},
+ {'type': 'f32', 'value': '0xf32'},
+ {'type': 'f64', 'value': '0xf64'}];
+
+function validateConst(type, value) {
+ wasmValidateText(
+ `(module
+ (type $out-${type} (func (result ${type})))
+ (func $type-${type} (result ${type})
+ (return_call_indirect (type $out-${type}) (i32.const 0)))
+ (func $const-${type} (result ${type})
+ (${type}.const ${value}))
+ (func $t (result ${type})
+ (return_call $type-${type}))
+ (table funcref (elem $const-${type})))`);
+}
+for (let {type, value} of constants) {
+ validateConst(type, value);
+}
+
+function validateOneArg(type, value) {
+ wasmValidateText(
+ `(module
+ (type $f-${type} (func (param ${type}) (result ${type})))
+ (func $id-${type} (param ${type}) (result ${type})
+ (local.get 0))
+ (func $t (result ${type})
+ (return_call_indirect (type $f-${type}) (${type}.const ${value})
+ (i32.const 0)))
+ (table funcref (elem $id-${type})))`);
+}
+for (let {type, value} of constants) {
+ validateOneArg(type, value);
+}
+
+function validateTwoArgs(t0, v0, t1, v1) {
+ wasmValidateText(
+ `(module
+ (type $f (func (param ${t0} ${t1}) (result ${t1})))
+ (func $second-${t0}-${t1} (param ${t0} ${t1}) (result ${t1})
+ (local.get 1))
+ (func $t (result ${t1})
+ (return_call_indirect (type $f)
+ (${t0}.const ${v0}) (${t1}.const ${v1})
+ (i32.const 0)))
+ (table funcref (elem $second-${t0}-${t1})))`);
+}
+for (let {type: t0, value: v0} of constants) {
+ for (let {type: t1, value: v1} of constants) {
+ validateTwoArgs(t0, v0, t1, v1);
+ }
+}
+
+wasmValidateText(
+ `(module
+ (type $f (func (param i64) (result i64)))
+ (func $id-i64 (param i64) (result i64)
+ (local.get 0))
+ (func $dispatch1 (param i32 i64) (result i64)
+ (return_call_indirect (type $f) (local.get 1) (local.get 0)))
+ (func $dispatch2 (param i32) (result i64)
+ (return_call_indirect (type $f) (i64.const 42) (local.get 0)))
+ (func $t1 (result i64)
+ (return_call $dispatch1 (i32.const 0) (i64.const 1)))
+ (func $t2 (result i64)
+ (return_call $dispatch2 (i32.const 100)))
+ (table funcref (elem $id-i64)))`);
+
+wasmValidateText(
+ `(module
+ (type $i64-i64 (func (param i64 i64) (result i64)))
+ (func $fac (type $i64-i64)
+ (return_call_indirect (type $i64-i64)
+ (local.get 0) (i64.const 1) (i32.const 0)))
+
+ (func $fac-acc (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call_indirect (type $i64-i64)
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ (i32.const 0)))))
+
+ (table funcref (elem $fac-acc)))`);
+
+wasmValidateText(
+ `(module
+ (type $t (func (param i32) (result i32)))
+ (func $even (param i32) (result i32)
+ (if (result i32) (i32.eqz (local.get 0))
+ (then (i32.const 44))
+ (else
+ (return_call_indirect (type $t)
+ (i32.sub (local.get 0) (i32.const 1))
+ (i32.const 1)))))
+ (func $odd (param i32) (result i32)
+ (if (result i32) (i32.eqz (local.get 0))
+ (then (i32.const 99))
+ (else
+ (return_call_indirect (type $t)
+ (i32.sub (local.get 0) (i32.const 1))
+ (i32.const 0)))))
+ (table funcref (elem $even $odd)))`);
+
+wasmFailValidateText(
+ `(module
+ (type (func))
+ (func $no-table
+ (return_call_indirect (type 0) (i32.const 0))))`,
+ /call_indirect without a table/);
+
+wasmFailValidateText(
+ `(module
+ (type (func))
+ (table 0 funcref)
+ (func $type-void-vs-num
+ (i32.eqz (return_call_indirect (type 0) (i32.const 0)))))`,
+ /unused values not explicitly dropped/);
+
+wasmFailValidateText(
+ `(module
+ (type (func))
+ (table 0 funcref)
+ (func $type-void-vs-num (result i32)
+ (i32.eqz (return_call_indirect (type 0) (i32.const 0)))))`,
+ /type mismatch: expected 1 values, got 0 values/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (result i64)))
+ (table 0 funcref)
+ (func $type-num-vs-num
+ (i32.eqz (return_call_indirect (type 0) (i32.const 0)))))`,
+ /type mismatch: expected 0 values, got 1 values/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (result i64)))
+ (table 0 funcref)
+ (func $type-num-vs-num (result i32)
+ (i32.eqz (return_call_indirect (type 0) (i32.const 0)))))`,
+ /expression has type i64 but expected i32/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param i32)))
+ (table 0 funcref)
+ (func $arity-0-vs-1
+ (return_call_indirect (type 0) (i32.const 0))))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param f64 i32)))
+ (table 0 funcref)
+ (func $arity-0-vs-2
+ (return_call_indirect (type 0) (i32.const 0))))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param i32)))
+ (table 0 funcref)
+ (func $type-func-void-vs-i32
+ (return_call_indirect (type 0) (i32.const 1) (nop))))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param i32)))
+ (table 0 funcref)
+ (func $type-func-num-vs-i32
+ (return_call_indirect (type 0) (i32.const 0) (i64.const 1))))`,
+ /expression has type i64 but expected i32/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param i32 i32)))
+ (table 0 funcref)
+ (func $type-first-void-vs-num
+ (return_call_indirect (type 0) (nop) (i32.const 1) (i32.const 0))
+ ))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param i32 i32)))
+ (table 0 funcref)
+ (func $type-second-void-vs-num
+ (return_call_indirect (type 0) (i32.const 1) (nop) (i32.const 0))))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param i32 f64)))
+ (table 0 funcref)
+ (func $type-first-num-vs-num
+ (return_call_indirect (type 0)
+ (f64.const 1) (i32.const 1) (i32.const 0))))`,
+ /expression has type i32 but expected f64/);
+
+wasmFailValidateText(
+ `(module
+ (type (func (param f64 i32)))
+ (table 0 funcref)
+ (func $type-second-num-vs-num
+ (return_call_indirect (type 0)
+ (i32.const 1) (f64.const 1) (i32.const 0))))`,
+ /expression has type f64 but expected i32/);
+
+wasmFailValidateText(
+ `(module
+ (table 0 funcref)
+ (func $unbound-type
+ (return_call_indirect (type 1) (i32.const 0))))`,
+ /signature index out of range/);
+
+wasmFailValidateText(
+ `(module
+ (table 0 funcref)
+ (func $large-type
+ (return_call_indirect (type 1012321300) (i32.const 0))))`,
+ /signature index out of range/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return-call-profiling.js b/js/src/jit-test/tests/wasm/tail-calls/return-call-profiling.js
new file mode 100644
index 0000000000..2946a16d71
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return-call-profiling.js
@@ -0,0 +1,68 @@
+// Tests if the profiler (frame iterator) can unwind in the middle
+// of collapse frame instructions.
+
+enableGeckoProfiling();
+try {
+ enableSingleStepProfiling();
+} catch (e) {
+ // continue anyway if single step profiling is not supported
+}
+
+var ins = wasmEvalText(`
+(module
+ (func $f (param i64 i64 i64 i64 i64 i64 i64 i64 i64)
+ local.get 0
+ i64.eqz
+ br_if 0
+ local.get 0
+ return_call $g
+ )
+ (func $g (param i64)
+ local.get 0
+ i64.const 1
+ i64.sub
+ i64.const 2
+ i64.const 6
+ i64.const 3
+ i64.const 4
+ i64.const 1
+ i64.const 2
+ i64.const 6
+ i64.const 3
+ return_call $f
+ )
+ (func (export "run") (param i64)
+ local.get 0
+ call $g
+ )
+)`);
+
+for (var i = 0; i < 10; i++) {
+ ins.exports.run(100n);
+}
+
+// Also when trampoline is used.
+var ins0 = wasmEvalText(`(module (func (export "t")))`);
+var ins = wasmEvalText(`
+(module
+ (import "" "t" (func $g))
+ (func $f (return_call_indirect $t (i32.const 0)))
+ (table $t 1 1 funcref)
+
+ (func (export "run") (param i64)
+ loop
+ local.get 0
+ i64.eqz
+ br_if 1
+ call $f
+ local.get 0
+ i64.const 1
+ i64.sub
+ local.set 0
+ br 0
+ end
+ )
+ (elem (i32.const 0) $g)
+)`, {"": {t: ins0.exports.t},});
+
+ins.exports.run(10n);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return-call-validate.js b/js/src/jit-test/tests/wasm/tail-calls/return-call-validate.js
new file mode 100644
index 0000000000..344549f58f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return-call-validate.js
@@ -0,0 +1,162 @@
+// |jit-test| skip-if: !wasmTailCallsEnabled()
+
+wasmValidateText(
+ `(module
+ (func $arity-1-vs-0
+ (i32.const 1) (return_call 1))
+ (func))`);
+
+wasmValidateText(
+ `(module
+ (func $arity-2-vs-0
+ (f64.const 2) (i32.const 1) (return_call 1))
+ (func))`);
+
+const constants = [{'type': 'i32', 'value': '0x132'},
+ {'type': 'i64', 'value': '0x164'},
+ {'type': 'f32', 'value': '0xf32'},
+ {'type': 'f64', 'value': '0xf64'}];
+
+function validateConst(type, value) {
+ wasmValidateText(
+ `(module
+ (func $const-${type} (result ${type})
+ (${type}.const ${value}))
+ (func $trampoline-${type} (result ${type})
+ (return_call $const-${type}))
+ (func $t (result ${type})
+ (return_call $trampoline-${type})))`);
+}
+for (let {type, value} of constants) {
+ validateConst(type, value);
+}
+
+function validateOneArg(type, value) {
+ wasmValidateText(
+ `(module
+ (func $id-${type} (param ${type}) (result ${type})
+ (local.get 0))
+ (func $t (result ${type})
+ (return_call $id-${type} (${type}.const ${value}))))`);
+}
+for (let {type, value} of constants) {
+ validateOneArg(type, value);
+}
+
+function validateTwoArgs(t0, v0, t1, v1) {
+ wasmValidateText(
+ `(module
+ (func $second-${t0}-${t1} (param ${t0} ${t1}) (result ${t1})
+ (local.get 1))
+ (func $t (result ${t1})
+ (return_call $second-${t0}-${t1}
+ (${t0}.const ${v0}) (${t1}.const ${v1}))))`);
+}
+for (let {type: t0, value: v0} of constants) {
+ for (let {type: t1, value: v1} of constants) {
+ validateTwoArgs(t0, v0, t1, v1);
+ }
+}
+
+wasmValidateText(
+ `(module
+ (func $fac-acc (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))))))
+ (func $t (result i64)
+ (return_call $fac-acc (i64.const 0) (i64.const 1))))`);
+
+wasmValidateText(
+ `(module
+ (func $count (param i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 0))
+ (else (return_call $count (i64.sub (local.get 0) (i64.const 1))))))
+ (func $t (result i64)
+ (return_call $count (i64.const 0))))`);
+
+wasmValidateText(
+ `(module
+ (func $even (param i64) (result i32)
+ (if (result i32) (i64.eqz (local.get 0))
+ (then (i32.const 44))
+ (else (return_call $odd (i64.sub (local.get 0) (i64.const 1))))))
+ (func $odd (param i64) (result i32)
+ (if (result i32) (i64.eqz (local.get 0))
+ (then (i32.const 99))
+ (else (return_call $even (i64.sub (local.get 0) (i64.const 1))))))
+ (func $t (result i32)
+ (return_call $even (i64.const 0))))`);
+
+wasmFailValidateText(
+ `(module
+ (func $type-void-vs-num (result i32)
+ (return_call 1) (i32.const 0))
+ (func))`,
+ /type mismatch: expected 1 values, got 0 values/);
+
+wasmFailValidateText(
+ `(module
+ (func $type-num-vs-num (result i32)
+ (return_call 1) (i32.const 0))
+ (func (result i64)
+ (i64.const 1)))`,
+ /expression has type i64 but expected i32/);
+
+wasmFailValidateText(
+ `(module
+ (func $arity-0-vs-1
+ (return_call 1))
+ (func (param i32)))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (func $arity-0-vs-2
+ (return_call 1))
+ (func (param f64 i32)))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (func $type-first-void-vs-num
+ (return_call 1 (nop) (i32.const 1)))
+ (func (param i32 i32)))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (func $type-second-void-vs-num
+ (return_call 1 (i32.const 1) (nop)))
+ (func (param i32 i32)))`,
+ /popping value from empty stack/);
+
+wasmFailValidateText(
+ `(module
+ (func $type-first-num-vs-num
+ (return_call 1 (f64.const 1) (i32.const 1)))
+ (func (param i32 f64)))`,
+ /expression has type i32 but expected f64/);
+
+wasmFailValidateText(
+ `(module
+ (func $type-second-num-vs-num
+ (return_call 1 (i32.const 1) (f64.const 1)))
+ (func (param f64 i32)))`,
+ /expression has type f64 but expected i32/);
+
+wasmFailValidateText(
+ `(module
+ (func $unbound-func
+ (return_call 1)))`,
+ /callee index out of range/);
+
+wasmFailValidateText(
+ `(module
+ (func $large-func
+ (return_call 1012321300)))`,
+ /callee index out of range/);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return_call.js b/js/src/jit-test/tests/wasm/tail-calls/return_call.js
new file mode 100644
index 0000000000..ddaa933aaf
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return_call.js
@@ -0,0 +1,305 @@
+var ins = wasmEvalText(`(module
+ (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ )
+ )
+ )
+ )
+ (func (export "main") (param i64) (result i64)
+ (call $fac-acc (local.get 0) (i64.const 1))
+ )
+)`);
+
+// Check return call via wasm function
+assertEq(ins.exports.main(5n), 120n);
+
+// Check return call directly via interpreter stub
+const fac = ins.exports["fac-acc"];
+assertEq(fac(4n, 1n), 24n);
+
+// Check return call directly via jit stub
+check_stub1: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub1;
+ const check = function() {
+ fac(4n, 1n);
+ };
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check();
+}
+
+// Return call of an import
+var ins0 = wasmEvalText(`(module
+ (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ )
+ )
+ )
+ )
+)`);
+
+var ins = wasmEvalText(`(module
+ (import "" "fac-acc" (func $fac-acc (param i64 i64) (result i64)))
+ (func (export "fac") (param i64) (result i64)
+ local.get 0
+ i64.const 1
+ return_call $fac-acc
+ )
+ (func (export "main") (result i64)
+ i64.const 4
+ call 1
+ )
+)`, {"": {"fac-acc": ins0.exports["fac-acc"],}});
+
+assertEq(ins.exports.main(), 24n);
+assertEq(ins.exports.fac(3n, 1n), 6n);
+check_stub2: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub2;
+ const check = function() {
+ ins.exports.fac(3n, 1n)
+ };
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check();
+}
+
+// Check with parameters area growth
+var ins0 = wasmEvalText(`(module
+ (func $fac-acc (export "fac-acc") (param i64 i64 i64 i64 i64 i64 i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ i64.const 1 i64.const 2 i64.const 3 i64.const 4 i64.const 5 i64.const 6
+ )
+ )
+ )
+ )
+)`);
+
+var ins = wasmEvalText(`(module
+ (import "" "fac-acc" (func $fac-acc (param i64 i64 i64 i64 i64 i64 i64 i64) (result i64)))
+ (func (export "fac") (param i64) (result i64)
+ local.get 0
+ i64.const 1
+ i64.const 1 i64.const 2 i64.const 3 i64.const 4 i64.const 5 i64.const 6
+ return_call $fac-acc
+ )
+ (func (export "main") (result i64)
+ i64.const 5
+ call 1
+ )
+)`, {"": {"fac-acc": ins0.exports["fac-acc"],}});
+
+assertEq(ins.exports.main(), 120n);
+assertEq(ins.exports.fac(3n, 1n), 6n);
+check_stub3: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub3;
+ const check = function() {
+ ins.exports.fac(4n, 1n)
+ };
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check();
+}
+
+// Test multi-value returns.
+var ins = wasmEvalText(`(module
+ (memory (export "memory") 1 1)
+ (func $rec (export "rec") (param i32 i32 i32 i32 i32 i32 i32) (result i32 i32 f32 f32)
+ (local f32 i32)
+ (if (result i32 i32 f32 f32) (i32.ge_u (local.get 0) (local.get 1))
+ (then
+ (local.get 5)
+ (local.get 6)
+ (local.tee 7 (f32.div (f32.convert_i32_u (local.get 3)) (f32.convert_i32_u (local.get 2))))
+ (f32.sqrt
+ (f32.sub
+ (f32.div (f32.convert_i32_u (local.get 4)) (f32.convert_i32_u (local.get 2)))
+ (f32.mul (local.get 7) (local.get 7))
+ )
+ )
+ )
+ (else
+ (return_call $rec
+ (i32.add (local.get 0) (i32.const 1))
+ (local.get 1)
+ (i32.add (local.get 2) (i32.const 1))
+ (i32.add (local.get 3) (local.tee 8 (i32.load8_u (local.get 0))))
+ (i32.add (local.get 4) (i32.mul (local.get 8) (local.get 8)))
+ (if (result i32) (i32.gt_s (local.get 5) (local.get 8))
+ (then (local.get 8)) (else (local.get 5))
+ )
+ (if (result i32) (i32.lt_s (local.get 6) (local.get 8))
+ (then (local.get 8)) (else (local.get 6))
+ )
+ )
+ )
+ )
+ )
+ (func $main (export "main") (result i32 i32 f32 f32)
+ (call $rec
+ (i32.const 0)
+ (i32.const 6)
+ (i32.const 0)
+ (i32.const 0)
+ (i32.const 0)
+ (i32.const 1000)
+ (i32.const -1000)
+ )
+ )
+ (data (i32.const 0) "\\02\\13\\22\\04\\08\\30")
+)`);
+
+const main = ins.exports["main"];
+assertEq(""+ main(), "2,48,19.16666603088379,16.836633682250977");
+assertEq("" + ins.exports.rec(1, 5, 0, 0, 0, 1000, -1000), "4,34,16.25,11.627016067504883");
+check_stub3: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub3;
+ const check = function() {
+ ins.exports.rec(1, 5, 0, 0, 0, 1000, -1000);
+ };
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check();
+}
+
+// Handling trap.
+var ins = wasmEvalText(`(module
+ (func $fac-acc (export "fac") (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (unreachable))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ )
+ )
+ )
+ )
+ (func (export "main") (param i64) (result i64)
+ (call $fac-acc (local.get 0) (i64.const 1))
+ )
+)`);
+
+assertErrorMessage(() => ins.exports.main(4n), WebAssembly.RuntimeError, /unreachable executed/);
+assertErrorMessage(() => ins.exports.fac(3n, 1n), WebAssembly.RuntimeError, /unreachable executed/);
+
+// Performance and stack growth: calculating sum of numbers 1..40000000
+var ins = wasmEvalText(`(module
+ (func $sum (param i32 i64) (result i64)
+ local.get 0
+ i32.eqz
+ if
+ local.get 1
+ return
+ else
+ local.get 0
+ i32.const 1
+ i32.sub
+ local.get 1
+ local.get 0
+ i64.extend_i32_s
+ i64.add
+ return_call $sum
+ end
+ unreachable
+ )
+
+ (func (export "main") (param i32) (result i64)
+ local.get 0
+ i64.const 0
+ call $sum
+ )
+)`);
+
+if (getBuildConfiguration("simulator")) {
+ assertEq(ins.exports.main(400000), 80000200000n);
+} else {
+ assertEq(ins.exports.main(40000000), 800000020000000n);
+}
+
+// GC/externref shall not cling to the trampoline frame.
+// The `return_call` caller will create a trampoline because the callee is
+// an import. The caller will create a GC object and will hold in its frame
+// and a WeakMap.
+// Test if the created object is in the WeakMap even after gc().
+var wm = new WeakMap();
+var ins = wasmEvalText(`(module
+ (import "" "test" (func $test))
+ (func $sum (param i32 i64) (result i64)
+ local.get 0
+ i32.eqz
+ if
+ call $test
+ local.get 1
+ return
+ else
+ local.get 0
+ i32.const 1
+ i32.sub
+ local.get 1
+ local.get 0
+ i64.extend_i32_s
+ i64.add
+ return_call $sum
+ end
+ unreachable
+ )
+ (export "sum" (func $sum))
+)`, {"": {
+ test() {
+ gc();
+ assertEq(nondeterministicGetWeakMapKeys(wm).length, 0);
+ }
+}});
+
+var ins2 = wasmEvalText(`(module
+ (import "" "add_ref" (func $add_ref (result externref)))
+ (import "" "use_ref" (func $use_ref (param externref)))
+ (import "" "sum" (func $sum (param i32 i64) (result i64)))
+ (global $g1 (mut i32) (i32.const 0))
+ (func (export "main_gc") (param i32) (result i64)
+ (local $ref externref)
+ call $add_ref
+ local.set $ref
+
+ local.get $ref
+ call $use_ref
+
+ block
+ global.get $g1
+ br_if 0
+ local.get 0
+ i64.const 0
+ return_call $sum
+ end
+
+ local.get $ref
+ call $use_ref
+ i64.const -1
+ )
+)`, {"": {
+ sum: ins.exports.sum,
+ add_ref() {
+ const obj = {}; wm.set(obj, 'foo'); return obj;
+ },
+ use_ref(obj) {
+ assertEq(nondeterministicGetWeakMapKeys(wm).length, 1);
+ },
+}});
+
+assertEq(ins2.exports.main_gc(400000), 80000200000n);
+assertEq(nondeterministicGetWeakMapKeys(wm).length, 0);
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return_call_indirect.js b/js/src/jit-test/tests/wasm/tail-calls/return_call_indirect.js
new file mode 100644
index 0000000000..f9639902fe
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return_call_indirect.js
@@ -0,0 +1,156 @@
+var ins0 = wasmEvalText(`(module
+ (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ )
+ )
+ )
+ )
+)`);
+
+var ins = wasmEvalText(`(module
+ (import "" "fac-acc" (func $fac-acc (param i64 i64) (result i64)))
+ (type $ty (func (param i64 i64) (result i64)))
+ (table $t 1 1 funcref)
+ (func $f (export "fac") (param i64) (result i64)
+ local.get 0
+ i64.const 1
+ i32.const 0
+ return_call_indirect $t (type $ty)
+ )
+ (elem $t (i32.const 0) $fac-acc)
+
+ (func (export "main") (result i64)
+ i64.const 5
+ call $f
+ )
+)`, {"": {"fac-acc": ins0.exports["fac-acc"]}});
+
+// Check return call via wasm function
+assertEq(ins.exports.main(5n), 120n);
+
+// Check return call directly via interpreter stub
+const fac = ins.exports["fac"];
+assertEq(fac(4n, 1n), 24n);
+
+// Check return call directly via jit stub
+check_stub1: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub1;
+ const check = function() {
+ fac(4n, 1n);
+ };
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check();
+}
+
+// Invalid func type
+var ins = wasmEvalText(`(module
+ (import "" "fac-acc" (func $fac-acc (param i64 i64) (result i64)))
+ (type $ty (func (param i64 i64) (result i64)))
+ (type $tz (func (param i64) (result i64)))
+ (table $t 1 1 funcref)
+ (func $f (export "fac") (param i64) (result i64)
+ local.get 0
+ i64.const 1
+ i32.const 0
+ return_call_indirect $t (type $tz)
+ )
+ (elem $t (i32.const 0) $fac-acc)
+
+ (func (export "main") (result i64)
+ i64.const 5
+ call $f
+ )
+)`, {"": {"fac-acc": ins0.exports["fac-acc"]}});
+
+assertErrorMessage(() => ins.exports.main(), WebAssembly.RuntimeError, /indirect call signature mismatch/);
+assertErrorMessage(() => ins.exports.fac(6n), WebAssembly.RuntimeError, /indirect call signature mismatch/);
+
+// Invalid func type, but testing when entry directly does invalid return_call_indirect.
+var wasm = wasmTextToBinary(`(module
+ (global $g (export "g") (mut i32) (i32.const 0))
+ (table 1 1 funcref)
+ (type $ft (func (param f64)))
+ (func $f (export "f")
+ unreachable
+ )
+ (func $test (export "test")
+ global.get $g
+ br_if 0
+ f64.const 0.0
+ i32.const 0
+ return_call_indirect (type $ft)
+ )
+ (elem (i32.const 0) $f)
+)`);
+
+var ins = new WebAssembly.Instance(new WebAssembly.Module(wasm));
+function check_err() { ins.exports.test(); }
+assertErrorMessage(check_err, WebAssembly.RuntimeError, /indirect call signature mismatch/);
+
+var ins = new WebAssembly.Instance(new WebAssembly.Module(wasm));
+check_stub2: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub2;
+ ins.exports.g.value = 1;
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check_err();
+ ins.exports.g.value = 0;
+ assertErrorMessage(check_err, WebAssembly.RuntimeError, /indirect call signature mismatch/);
+}
+
+var ins = new WebAssembly.Instance(new WebAssembly.Module(wasm));
+check_stub3: {
+ let options = getJitCompilerOptions();
+ if (!options["ion.enable"]) break check_stub3;
+ ins.exports.g.value = 1;
+ var check_err2 = function() { for (var i = 0; i < 5; i++) ins.exports.test(); };
+ for (let i = options["ion.warmup.trigger"]+2; i--;)
+ check_err2();
+ ins.exports.g.value = 0;
+ assertErrorMessage(() => check_err2(), WebAssembly.RuntimeError, /indirect call signature mismatch/);
+}
+
+// Performance and stack growth: calculating sum of numbers 1..40000000
+var ins = wasmEvalText(`(module
+ (table 1 1 funcref)
+ (type $rec (func (param i32 i64) (result i64)))
+ (func $sum (param i32 i64) (result i64)
+ local.get 0
+ i32.eqz
+ if
+ local.get 1
+ return
+ else
+ local.get 0
+ i32.const 1
+ i32.sub
+ local.get 1
+ local.get 0
+ i64.extend_i32_s
+ i64.add
+ i32.const 0
+ return_call_indirect (type $rec)
+ end
+ unreachable
+ )
+ (elem (i32.const 0) $sum)
+
+ (func (export "main") (param i32) (result i64)
+ local.get 0
+ i64.const 0
+ i32.const 0
+ return_call_indirect (type $rec)
+ )
+)`);
+
+if (getBuildConfiguration("simulator")) {
+ assertEq(ins.exports.main(400000), 80000200000n);
+} else {
+ assertEq(ins.exports.main(40000000), 800000020000000n);
+}
diff --git a/js/src/jit-test/tests/wasm/tail-calls/return_call_ref.js b/js/src/jit-test/tests/wasm/tail-calls/return_call_ref.js
new file mode 100644
index 0000000000..c5f250a41c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/tail-calls/return_call_ref.js
@@ -0,0 +1,94 @@
+// |jit-test| --wasm-gc; skip-if: !wasmGcEnabled()
+var ins = wasmEvalText(`(module
+ (type $t (func (param i64 i64 funcref) (result i64)))
+ (elem declare func $fac-acc $fac-acc-broken)
+ (func $fac-acc (export "fac-acc") (param i64 i64 funcref) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $vis
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ (local.get 2)
+ )
+ )
+ )
+ )
+ ;; same as $fac-acc but fails on i == 6
+ (func $fac-acc-broken (param i64 i64 funcref) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $vis
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ (select (result funcref)
+ (ref.null func) (local.get 2)
+ (i64.eq (local.get 0) (i64.const 6)))
+ )
+ )
+ )
+ )
+ (func $vis (export "vis") (param i64 i64 funcref) (result i64)
+ local.get 0
+ local.get 1
+ local.get 2
+ local.get 2
+ ref.cast (ref null $t)
+ return_call_ref $t
+ )
+ (func $trap (export "trap") (param i64 i64 funcref) (result i64)
+ unreachable
+ )
+ (func (export "main") (param i64) (result i64)
+ (call $vis (local.get 0) (i64.const 1) (ref.func $fac-acc))
+ )
+ (func (export "main_null") (param i64) (result i64)
+ (return_call $vis (local.get 0) (i64.const 1) (ref.null $t))
+ )
+ (func (export "main_broken") (param i64) (result i64)
+ (return_call $vis (local.get 0) (i64.const 1) (ref.func $fac-acc-broken))
+ )
+)`);
+
+// Check return call via wasm function
+assertEq(ins.exports.main(5n), 120n);
+
+// Check return call directly via interpreter stub
+const fac = ins.exports["fac-acc"];
+const vis = ins.exports["vis"];
+assertEq(vis(4n, 1n, fac), 24n);
+
+// Calling into JavaScript (and back).
+if ("Function" in WebAssembly) {
+ const visFn = new WebAssembly.Function({
+ parameters: ["i64", "i64", "funcref"],
+ results: ["i64"]
+ }, function (i, n, fn) {
+ if (i <= 0n) {
+ return n;
+ }
+ return vis(i - 1n, i * n, fn);
+ });
+
+ assertEq(vis(3n, 1n, visFn), 6n);
+}
+
+// Check return call directly via jit stub
+check_stub1: {
+ let options = getJitCompilerOptions();
+ if (!options["baseline.enable"]) break check_stub1;
+ const check = function() {
+ vis(4n, 1n, fac);
+ };
+ for (let i = options["baseline.warmup.trigger"] + 1; i--;)
+ check();
+}
+
+// Handling traps.
+const trap = ins.exports["trap"];
+assertErrorMessage(() => vis(4n, 1n, trap), WebAssembly.RuntimeError, /unreachable executed/);
+const main_broken = ins.exports["main_broken"];
+assertErrorMessage(() => main_broken(8n), WebAssembly.RuntimeError, /dereferencing null pointer/);
+const main_null = ins.exports["main_null"];
+assertErrorMessage(() => main_null(5n), WebAssembly.RuntimeError, /dereferencing null pointer/);