litmus11.js (1943B)
1 // Once we exhaust the register arguments this will alternately grow and then 2 // shrink the stack frame across tail call boundaries because the increment of 3 // stack allocation is 16 bytes and our variability exceeds that. 4 // 5 // (This is not redundant with eg litmus0, because in that case all the 6 // functions have the same ballast. Here we have different ballast, so we get growing and 7 // shrinking.) 8 // 9 // See litmus13 for the same-module call_indirect case. 10 // See litmus16 for the cross-module call_indirect case. 11 12 function ntimes(n, v) { 13 if (typeof v == "function") 14 return iota(n).map(v).join(' '); 15 return iota(n).map(_ => v).join(' '); 16 } 17 18 function get_local(n) { 19 return `(local.get ${n})` 20 } 21 22 function compute(ballast) { 23 return iota(ballast).reduce((p,g,n) => `(i32.or ${p} (local.get ${n}))`, 24 `(i32.const ${1 << ballast})`) 25 } 26 27 function build(n, ballast) { 28 switch (n) { 29 case 0: 30 return ` 31 (func $f0 (export "f") (result i32) 32 (return_call $f1 (i32.const ${1 << n}))) 33 `; 34 case ballast: 35 return ` 36 (func $f${ballast} (param ${ntimes(ballast, 'i32')}) (result i32) 37 (if (result i32) (i32.eqz (global.get $glob)) 38 (then (return ${compute(ballast)})) 39 (else (block (result i32) 40 (global.set $glob (i32.sub (global.get $glob) (i32.const 1))) 41 (return_call $f0))))) 42 `; 43 default: 44 return ` 45 (func $f${n} (param ${ntimes(n, 'i32')}) (result i32) 46 (return_call $f${n+1} (i32.const ${1 << n}) ${ntimes(n, get_local)})) 47 ` 48 } 49 } 50 51 for ( let ballast=1; ballast < TailCallBallast; ballast++ ) { 52 53 let vals = iota(ballast+1).map(v => 1 << v); 54 let sumv = vals.reduce((p,c) => p|c); 55 let text = ` 56 (module 57 (global $glob (mut i32) (i32.const ${TailCallIterations})) 58 ${ntimes(ballast, n => build(n, ballast))} 59 ${build(ballast, ballast)}) 60 `; 61 62 let ins = wasmEvalText(text); 63 assertEq(ins.exports.f(), sumv); 64 }