// |jit-test| skip-if: !wasmStackSwitchingEnabled() // Tests for cont.new/resume with non-empty cont types (params and results) and // suspend with tag results. These cover the cases needed to run Kotlin // stack-switching Wasm output. // Section 1: cont.new + resume with cont params and results (no suspend) // Simplest case: cont takes one i32 param and returns i32 { let { run } = wasmEvalText(`(module (type $ft (func (param i32) (result i32))) (type $ct (cont $ft)) (func $f (type $ft) local.get 0 i32.const 1 i32.add ) (elem declare func $f) (func (export "run") (param i32) (result i32) local.get 0 ref.func $f cont.new $ct resume $ct ) )`).exports; assertEq(run(5), 6); assertEq(run(41), 42); } // f64 param and result (no suspend): tests base frame stub f64 loading/storing { let { run } = wasmEvalText(`(module (type $ft (func (param f64) (result f64))) (type $ct (cont $ft)) (func $f (type $ft) local.get 0 f64.const 1.0 f64.add ) (elem declare func $f) (func (export "run") (param f64) (result f64) local.get 0 ref.func $f cont.new $ct resume $ct ) )`).exports; assertEq(run(0), 1); assertEq(run(2), 3); } // Two i32 params, returns their sum { let { run } = wasmEvalText(`(module (type $ft (func (param i32 i32) (result i32))) (type $ct (cont $ft)) (func $f (type $ft) local.get 0 local.get 1 i32.add ) (elem declare func $f) (func (export "run") (param i32 i32) (result i32) local.get 0 local.get 1 ref.func $f cont.new $ct resume $ct ) )`).exports; assertEq(run(3, 4), 7); assertEq(run(10, 32), 42); } // Section 2: suspend + resume with tag that has both params and results // Tag with (param i32) (result i32): the suspended function delivers a value // and receives a different value back when the continuation is resumed. // Flow: run(x) -> $f suspends with (x+10) -> handler resumes with (x+10)*2 // -> $f returns (x+10)*2 as its result. { let { run } = wasmEvalText(`(module (type $ft (func (param i32) (result i32))) (type $ct (cont $ft)) (tag $tag (param i32) (result i32)) (func $f (type $ft) local.get 0 i32.const 10 i32.add suspend $tag ;; delivers (arg+10), receives i32 back ) (elem declare func $f) (func (export "run") (param $x i32) (result i32) (local $v i32) (local $k (ref null $ct)) (block $on_tag (result i32 (ref $ct)) local.get $x ref.func $f cont.new $ct resume $ct (on $tag 0) return ;; cont returned normally (not expected here) ) local.set $k local.set $v local.get $v i32.const 2 i32.mul local.get $k resume $ct ;; resume delivers (v*2) as tag result; cont returns it ) )`).exports; assertEq(run(0), 20); // (0+10)*2 = 20 assertEq(run(5), 30); // (5+10)*2 = 30 assertEq(run(1), 22); // (1+10)*2 = 22 } // Tag with (param i32) (result i32), cont with matching (param i32) -- // the cont receives the tag result as its re-entry param. // $f suspends delivering its arg, receives a new value back, stores it in a // global. { let { run, getG } = wasmEvalText(`(module (type $ft (func (param i32))) (type $ct (cont $ft)) (tag $tag (param i32) (result i32)) (global $g (mut i32) (i32.const 0)) (func $f (type $ft) local.get 0 suspend $tag ;; delivers arg, gets i32 back global.set $g ;; store the returned value ) (elem declare func $f) (func (export "run") (param $init i32) (param $reply i32) (local $v i32) (local $k (ref null $ct)) (block $on_tag (result i32 (ref $ct)) local.get $init ref.func $f cont.new $ct resume $ct (on $tag 0) return ) local.set $k local.set $v local.get $reply local.get $k resume $ct ;; resume: pass $reply as the tag result; $f stores it ) (func (export "getG") (result i32) global.get $g) )`).exports; run(10, 99); assertEq(getG(), 99); run(5, 7); assertEq(getG(), 7); } // f64 tag param only (no result): tests handlersResultArea stores f64 correctly. // $f suspends with f64 tag param; handler stores it in a global. { let { run, getG } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag (param f64)) (global $g (mut f64) (f64.const 0)) (func $f (type $ft) f64.const 3.141592653589793 suspend $tag ) (elem declare func $f) (func (export "run") (param $reply f64) (local $v f64) (local $k (ref null $ct)) (block $on_tag (result f64 (ref $ct)) ref.func $f cont.new $ct resume $ct (on $tag 0) return ) local.set $k local.set $v local.get $v global.set $g local.get $k resume $ct ) (func (export "getG") (result f64) global.get $g) )`).exports; run(0); assertEq(getG(), Math.PI); } // f64 tag result only (no tag params): isolates the EmitSuspend copy for f64. // $f drops its initial f64 arg, suspends delivering nothing, receives f64 back. { let { run } = wasmEvalText(`(module (type $ft (func (param f64) (result f64))) (type $ct (cont $ft)) (tag $tag (result f64)) (func $f (type $ft) local.get 0 drop suspend $tag ) (elem declare func $f) (func (export "run") (result f64) (local $k (ref null $ct)) (block $on_tag (result (ref $ct)) f64.const 0 ref.func $f cont.new $ct resume $ct (on $tag 0) return ) local.set $k f64.const 3.141592653589793 local.get $k resume $ct ) )`).exports; assertEq(run(), Math.PI); } // Section 3: suspend tag results with non-pointer-sized types (i64, f32, f64) // These exercise the word-count copy loop in EmitSuspend for types larger or // differently-sized than a pointer. // i64 tag result: suspend delivers i64, receives i64 back { let { run } = wasmEvalText(`(module (type $ft (func (param i64) (result i64))) (type $ct (cont $ft)) (tag $tag (param i64) (result i64)) (func $f (type $ft) local.get 0 i64.const 0x100000001 i64.add suspend $tag ) (elem declare func $f) (func (export "run") (param $x i64) (result i64) (local $v i64) (local $k (ref null $ct)) (block $on_tag (result i64 (ref $ct)) local.get $x ref.func $f cont.new $ct resume $ct (on $tag 0) return ) local.set $k local.set $v local.get $v i64.const 2 i64.mul local.get $k resume $ct ) )`).exports; assertEq(run(0n), (0n + 0x100000001n) * 2n); assertEq(run(5n), (5n + 0x100000001n) * 2n); } // f32 tag result { let { run } = wasmEvalText(`(module (type $ft (func (param f32) (result f32))) (type $ct (cont $ft)) (tag $tag (param f32) (result f32)) (func $f (type $ft) local.get 0 f32.const 1.5 f32.add suspend $tag ) (elem declare func $f) (func (export "run") (param $x f32) (result f32) (local $v f32) (local $k (ref null $ct)) (block $on_tag (result f32 (ref $ct)) local.get $x ref.func $f cont.new $ct resume $ct (on $tag 0) return ) local.set $k local.set $v local.get $v f32.const 2.0 f32.mul local.get $k resume $ct ) )`).exports; assertEq(run(0), (0 + 1.5) * 2); // 3.0 assertEq(run(1), (1 + 1.5) * 2); // 5.0 } // f64 tag result { let { run } = wasmEvalText(`(module (type $ft (func (param f64) (result f64))) (type $ct (cont $ft)) (tag $tag (param f64) (result f64)) (func $f (type $ft) local.get 0 f64.const 3.141592653589793 f64.add suspend $tag ) (elem declare func $f) (func (export "run") (param $x f64) (result f64) (local $v f64) (local $k (ref null $ct)) (block $on_tag (result f64 (ref $ct)) local.get $x ref.func $f cont.new $ct resume $ct (on $tag 0) return ) local.set $k local.set $v local.get $v f64.const 2.0 f64.mul local.get $k resume $ct ) )`).exports; assertEq(run(0), (0 + Math.PI) * 2); assertEq(run(1), (1 + Math.PI) * 2); } // v128 tag result (two machine words on 64-bit, four on 32-bit). // v128 cannot cross the JS/wasm boundary, so the test lives entirely in wasm // and returns an i32 extracted from the final v128. The cont type has a v128 // param so that resume always takes v128 (both initial call and tag resumption). if (wasmSimdEnabled()) { let { run } = wasmEvalText(`(module (type $ft (func (param v128) (result i32))) (type $ct (cont $ft)) (tag $tag (param v128) (result v128)) ;; $f: takes v128 param, suspends delivering it, receives v128 tag result ;; back, adds [10,20,30,40] and extracts lane 0 as i32. (func $f (type $ft) local.get 0 suspend $tag v128.const i32x4 10 20 30 40 i32x4.add i32x4.extract_lane 0 ) (elem declare func $f) (func (export "run") (result i32) (local $v v128) (local $k (ref null $ct)) (block $on_tag (result v128 (ref $ct)) v128.const i32x4 1 2 3 4 ;; initial cont param ref.func $f cont.new $ct resume $ct (on $tag 0) ;; takes (v128, cont_ref) return ) local.set $k local.set $v ;; $v = [1,2,3,4]; resume with [100,200,300,400] as tag result v128.const i32x4 100 200 300 400 local.get $k resume $ct ) )`).exports; // $f receives tag result [100,200,300,400], adds [10,20,30,40] -> lane 0 = 110 assertEq(run(), 110); } // Three continuation types used in a single function: empty, three params, one result. { let { run } = wasmEvalText(`(module (type $ft0 (func)) (type $ct0 (cont $ft0)) (type $ft3p (func (param i32 f64 i64))) (type $ct3p (cont $ft3p)) (type $ft1r (func (result i32))) (type $ct1r (cont $ft1r)) ;; Empty cont: does nothing. (func $empty (type $ft0)) ;; Three-param cont: stores params into memory. (func $store3 (type $ft3p) i32.const 0 local.get 0 i32.store offset=0 i32.const 0 local.get 1 f64.store offset=8 i32.const 0 local.get 2 i64.store offset=16 ) ;; One-result cont: loads i32 result from memory. (func $load1 (type $ft1r) i32.const 0 i32.load offset=0 ) (memory 1) (elem declare func $empty $store3 $load1) (func (export "run") (param i32 f64 i64) (result i32) ;; Resume empty cont. ref.func $empty cont.new $ct0 resume $ct0 ;; Resume three-param cont: writes params to memory. local.get 0 local.get 1 local.get 2 ref.func $store3 cont.new $ct3p resume $ct3p ;; Resume one-result cont: reads i32 from memory. ref.func $load1 cont.new $ct1r resume $ct1r ) )`).exports; assertEq(run(42, Math.PI, 0x100000001n), 42); } // Two coroutines with cont args, cont results, tag params, and tag results — // exercises all four cont params/results data paths in one test. // // gen1(seed): yields seed, seed+1, seed+2 via $yield1 (param i32, no result); // gen1's cont type changes after suspend ($ct → $ct1b) because the tag has // no result. Returns the final seed value. // // gen2(seed): yields seed, seed+delta1, seed+delta1+delta2 via $yield2 // (param i32, result i32); the driver feeds each delta back through the // tag result (suspendResultsArea path). Returns the final seed value. // // gen1(1) → yields 1, 2, 3, returns 3 // gen2(10) → yields 10, 12, 8 (deltas +2, -4), returns 8 // // Driver: (1+10)*(2+12)*(3+8) + gen1_ret + gen2_ret = 11*14*11 + 3 + 8 = 1705 // // $startGen2 only does cont.new and returns the ref; the initial resume with // seed happens in the driver, showing cont.new and first resume can be in // different functions. { let { run } = wasmEvalText(`(module (type $ft (func (param i32) (result i32))) (type $ct (cont $ft)) (type $ft1b (func (result i32))) (type $ct1b (cont $ft1b)) (tag $yield1 (param i32)) (tag $yield2 (param i32) (result i32)) (func $gen1 (type $ft) (local $s i32) local.get 0 local.tee $s suspend $yield1 local.get $s i32.const 1 i32.add local.tee $s suspend $yield1 local.get $s i32.const 1 i32.add local.tee $s suspend $yield1 local.get $s ) (func $gen2 (type $ft) (local $s i32) local.get 0 local.tee $s suspend $yield2 local.get $s i32.add local.tee $s suspend $yield2 local.get $s i32.add local.tee $s suspend $yield2 drop local.get $s ) (elem declare func $gen1 $gen2) (func $startGen2 (result (ref $ct)) ref.func $gen2 cont.new $ct ) (func $next2 (param $reply i32) (param $k (ref $ct)) (result i32 (ref $ct)) (block $on_yield (result i32 (ref $ct)) local.get $reply local.get $k resume $ct (on $yield2 0) unreachable ) ) (func $finish1 (param $k (ref $ct1b)) (result i32) (block $on_yield (result i32 (ref $ct1b)) local.get $k resume $ct1b (on $yield1 0) return ) unreachable ) (func (export "run") (result i32) (local $k1 (ref null $ct1b)) (local $k2 (ref null $ct)) (local $v1 i32) (local $v2 i32) (local $prod i32) (local $r1 i32) (local $r2 i32) (block $init1 (result i32 (ref $ct1b)) i32.const 1 ref.func $gen1 cont.new $ct resume $ct (on $yield1 0) unreachable ) local.set $k1 local.set $v1 (block $init2 (result i32 (ref $ct)) i32.const 10 call $startGen2 resume $ct (on $yield2 0) unreachable ) local.set $k2 local.set $v2 local.get $v1 local.get $v2 i32.add local.set $prod (block $next1_a (result i32 (ref $ct1b)) local.get $k1 ref.as_non_null resume $ct1b (on $yield1 0) unreachable ) local.set $k1 local.set $v1 i32.const 2 local.get $k2 ref.as_non_null call $next2 local.set $k2 local.set $v2 local.get $prod local.get $v1 local.get $v2 i32.add i32.mul local.set $prod (block $next1_b (result i32 (ref $ct1b)) local.get $k1 ref.as_non_null resume $ct1b (on $yield1 0) unreachable ) local.set $k1 local.set $v1 i32.const -4 local.get $k2 ref.as_non_null call $next2 local.set $k2 local.set $v2 local.get $prod local.get $v1 local.get $v2 i32.add i32.mul local.set $prod local.get $k1 ref.as_non_null call $finish1 local.set $r1 (block $finish2_done (result i32) (block $finish2_on_yield (result i32 (ref $ct)) i32.const 0 local.get $k2 ref.as_non_null resume $ct (on $yield2 0) br $finish2_done ) unreachable ) local.set $r2 local.get $prod local.get $r1 i32.add local.get $r2 i32.add ) )`).exports; assertEq(run(), 11 * 14 * 11 + 3 + 8); // 1705 } // Cont type with three results: two go via the stack-results hidden-arg ABI, // one in a register. Verify all three values survive the cont base frame stub. { let { run } = wasmEvalText(`(module (type $ft3r (func (result i32 i64 f64))) (type $ct3r (cont $ft3r)) (func $f (type $ft3r) i32.const 42 i64.const 0x100000001 f64.const 2.718281828 ) (elem declare func $f) (func (export "run") (result i32 i64 f64) ref.func $f cont.new $ct3r resume $ct3r ) )`).exports; let [a, b, c] = run(); assertEq(a, 42); assertEq(b, 0x100000001n); assertEq(Math.abs(c - 2.718281828) < 1e-9, true); } // Section 4: pathological cases with many params and results. These force the // base frame stub to spill params/results beyond the ABI registers onto the // stack areas. // Many i32 cont params: the cont sums them (sum 1..N == N*(N+1)/2). { const N = 24; const ptypes = Array(N).fill('i32').join(' '); let sum = 'local.get 0\n'; for (let i = 1; i < N; i++) sum += `local.get ${i}\ni32.add\n`; let args = ''; for (let i = 0; i < N; i++) args += `i32.const ${i + 1}\n`; let { run } = wasmEvalText(`(module (type $ft (func (param ${ptypes}) (result i32))) (type $ct (cont $ft)) (func $f (type $ft) ${sum}) (elem declare func $f) (func (export "run") (result i32) ${args} ref.func $f cont.new $ct resume $ct) )`).exports; assertEq(run(), (N * (N + 1)) / 2); } // Many i32 cont results: the cont produces N values, the caller sums them. { const N = 24; const rtypes = Array(N).fill('i32').join(' '); let produce = ''; for (let i = 0; i < N; i++) produce += `i32.const ${i + 1}\n`; let { run } = wasmEvalText(`(module (type $ft (func (result ${rtypes}))) (type $ct (cont $ft)) (func $f (type $ft) ${produce}) (elem declare func $f) (func (export "run") (result i32) ref.func $f cont.new $ct resume $ct ${'i32.add\n'.repeat(N - 1)}) )`).exports; assertEq(run(), (N * (N + 1)) / 2); } // Many mixed-type params AND results echoed back: stresses the base frame stub // loading params and storing results across the register/stack ABI split for // all three numeric classes at once. { const K = 6; // K groups of (i32 i64 f64) -> 3*K params and results const group = 'i32 i64 f64'; const types = Array(K).fill(group).join(' '); let echo = ''; for (let i = 0; i < 3 * K; i++) echo += `local.get ${i}\n`; let args = ''; for (let i = 0; i < K; i++) { args += `i32.const ${i}\ni64.const ${i + 100}\nf64.const ${i + 0.5}\n`; } let { run } = wasmEvalText(`(module (type $ft (func (param ${types}) (result ${types}))) (type $ct (cont $ft)) (func $f (type $ft) ${echo}) (elem declare func $f) (func (export "run") (result ${types}) ${args} ref.func $f cont.new $ct resume $ct) )`).exports; let r = run(); for (let i = 0; i < K; i++) { assertEq(r[3 * i], i); assertEq(r[3 * i + 1], BigInt(i + 100)); assertEq(Math.abs(r[3 * i + 2] - (i + 0.5)) < 1e-9, true); } }