litmus16.js (3361B)
1 // This alternately grows and then shrinks the stack frame across tail call boundaries. 2 // Here we go cross-module as well. There is enough ballast that the stack frame will 3 // alternately have to grow and shrink across some of the calls. 4 // 5 // We generate one module+instance per function so that every call is cross-instance. 6 // Each module has an "up" function (calling the next higher index) and a "down" function 7 // (calling the next lower index). The last "up" function decrements the global counter 8 // and optionally returns a result $down0 just calls $up0. 9 // 10 // TODO: Test that the proper instance is being restored? Or have we done that elsewhere? 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,_,n) => `(i32.or ${p} (local.get ${n}))`, 24 `(i32.const ${1 << ballast})`) 25 } 26 27 function code(n, ballast) { 28 switch (n) { 29 case 0: 30 return ` 31 (func $up0 (export "f") (result i32) 32 (return_call_indirect (type $ty1) (i32.const ${1 << n}) (i32.const 1))) 33 (func $down0 (result i32) 34 (return_call $up0))`; 35 case ballast: 36 return ` 37 (func $up${ballast} (param ${ntimes(ballast, 'i32')}) (result i32) 38 (if (result i32) (i32.eqz (global.get $glob)) 39 (then (return ${compute(ballast)})) 40 (else 41 (block (result i32) 42 (global.set $glob (i32.sub (global.get $glob) (i32.const 1))) 43 (return_call_indirect (type $ty${ballast-1}) ${ntimes(ballast-1,get_local)} (i32.const ${ballast+1}))))))`; 44 default: 45 return ` 46 (func $up${n} (param ${ntimes(n, 'i32')}) (result i32) 47 (return_call_indirect (type $ty${n+1}) (i32.const ${1 << n}) ${ntimes(n, get_local)} (i32.const ${n+1}))) 48 (func $down${n} (param ${ntimes(n, 'i32')}) (result i32) 49 (return_call_indirect (type $ty${n-1}) ${ntimes(n-1, get_local)} (i32.const ${2*ballast-n+1})))`; 50 } 51 } 52 53 function types(n, ballast) { 54 var tys = ''; 55 if (n > 0) 56 tys += ` 57 (type $ty${n-1} (func (param ${ntimes(n-1, 'i32')}) (result i32)))`; 58 if (n < ballast) 59 tys += ` 60 (type $ty${n+1} (func (param ${ntimes(n+1, 'i32')}) (result i32)))` 61 return tys; 62 } 63 64 function inits(n, ballast) { 65 var inits = ` 66 (elem (i32.const ${n}) $up${n})` 67 if (n < ballast) 68 inits += ` 69 (elem (i32.const ${2*ballast-n}) $down${n})`; 70 return inits 71 } 72 73 for (let ballast = 1; ballast < TailCallBallast; ballast++) { 74 let counter = new WebAssembly.Global({ mutable: true, value: "i32" }, TailCallIterations/10); 75 let table = new WebAssembly.Table({ initial: ballast * 2 + 1, maximum: ballast * 2 + 1, element: "anyfunc" }); 76 let tys = ntimes(ballast + 1, n => types(n)); 77 let vals = iota(ballast + 1).map(v => 1 << v); 78 let sumv = vals.reduce((p, c) => p | c); 79 let ins = []; 80 let imp = { "": { table, counter } }; 81 for (let i = 0; i <= ballast; i++) { 82 let txt = ` 83 (module 84 ${types(i, ballast)} 85 (import "" "table" (table $t ${ballast * 2 - 1} funcref)) 86 (import "" "counter" (global $glob (mut i32))) 87 ${inits(i, ballast)} 88 ${code(i, ballast)})`; 89 ins[i] = wasmEvalText(txt, imp); 90 } 91 assertEq(ins[0].exports.f(), sumv) 92 }