From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../jit-test/tests/wasm/tail-calls/bug1851568.js | 20 ++ .../jit-test/tests/wasm/tail-calls/bug1862473.js | 24 ++ .../jit-test/tests/wasm/tail-calls/bug1865044.js | 34 +++ .../jit-test/tests/wasm/tail-calls/bug1871605.js | 30 ++ .../jit-test/tests/wasm/tail-calls/bug1871606.js | 22 ++ .../jit-test/tests/wasm/tail-calls/bug1871951.js | 75 +++++ .../jit-test/tests/wasm/tail-calls/directives.txt | 1 + .../jit-test/tests/wasm/tail-calls/exceptions.js | 54 ++++ js/src/jit-test/tests/wasm/tail-calls/gc.js | 220 +++++++++++++++ js/src/jit-test/tests/wasm/tail-calls/litmus0.js | 24 ++ js/src/jit-test/tests/wasm/tail-calls/litmus1.js | 32 +++ js/src/jit-test/tests/wasm/tail-calls/litmus10.js | 59 ++++ js/src/jit-test/tests/wasm/tail-calls/litmus11.js | 66 +++++ js/src/jit-test/tests/wasm/tail-calls/litmus12.js | 41 +++ js/src/jit-test/tests/wasm/tail-calls/litmus13.js | 79 ++++++ js/src/jit-test/tests/wasm/tail-calls/litmus15.js | 72 +++++ js/src/jit-test/tests/wasm/tail-calls/litmus16.js | 94 +++++++ js/src/jit-test/tests/wasm/tail-calls/litmus17.js | 27 ++ js/src/jit-test/tests/wasm/tail-calls/litmus2.js | 36 +++ js/src/jit-test/tests/wasm/tail-calls/litmus3.js | 36 +++ js/src/jit-test/tests/wasm/tail-calls/litmus4.js | 43 +++ js/src/jit-test/tests/wasm/tail-calls/litmus5.js | 68 +++++ js/src/jit-test/tests/wasm/tail-calls/litmus6.js | 24 ++ js/src/jit-test/tests/wasm/tail-calls/litmus7.js | 29 ++ js/src/jit-test/tests/wasm/tail-calls/litmus8.js | 38 +++ js/src/jit-test/tests/wasm/tail-calls/litmus9.js | 42 +++ .../wasm/tail-calls/return-call-indirect-syntax.js | 117 ++++++++ .../tail-calls/return-call-indirect-validate.js | 243 ++++++++++++++++ .../tests/wasm/tail-calls/return-call-profiling.js | 68 +++++ .../tests/wasm/tail-calls/return-call-validate.js | 162 +++++++++++ .../jit-test/tests/wasm/tail-calls/return_call.js | 305 +++++++++++++++++++++ .../tests/wasm/tail-calls/return_call_indirect.js | 156 +++++++++++ .../tests/wasm/tail-calls/return_call_ref.js | 94 +++++++ 33 files changed, 2435 insertions(+) create mode 100644 js/src/jit-test/tests/wasm/tail-calls/bug1851568.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/bug1862473.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/bug1865044.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/bug1871605.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/bug1871606.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/bug1871951.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/directives.txt create mode 100644 js/src/jit-test/tests/wasm/tail-calls/exceptions.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/gc.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus0.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus1.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus10.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus11.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus12.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus13.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus15.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus16.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus17.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus2.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus3.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus4.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus5.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus6.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus7.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus8.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/litmus9.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-syntax.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return-call-indirect-validate.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return-call-profiling.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return-call-validate.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return_call.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return_call_indirect.js create mode 100644 js/src/jit-test/tests/wasm/tail-calls/return_call_ref.js (limited to 'js/src/jit-test/tests/wasm/tail-calls') 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/); -- cgit v1.2.3