diff options
Diffstat (limited to 'js/src/jit-test/tests/wasm/tail-calls/litmus16.js')
-rw-r--r-- | js/src/jit-test/tests/wasm/tail-calls/litmus16.js | 94 |
1 files changed, 94 insertions, 0 deletions
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) +} |