// |jit-test| skip-if: !wasmStackSwitchingEnabled() const TYPES = [ ['i32', 'i32.const 42', 42], ['i64', 'i64.const 42', 42n], ['f32', 'f32.const 13.5', 13.5], ['f64', 'f64.const 13.5', 13.5], ]; // Section 1: Basic cont.new + resume (no suspend) // Simplest case: (func) cont, resume returns nothing { let { run } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (func $f (type $ft)) (elem declare func $f) (func (export "run") ref.func $f cont.new $ct resume $ct ) )`).exports; run(); } // Section 2: Suspend and resume // Single suspend, tag with no params, handler catches { let { run } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag) (func $f (type $ft) suspend $tag ) (elem declare func $f) (func (export "run") (result i32) (block (result (ref $ct)) ref.func $f cont.new $ct resume $ct (on $tag 0) unreachable ) drop i32.const 1 ) )`).exports; assertEq(run(), 1); } // Single suspend with typed tag param, handler reads correct value for (let [type, wasmConst, jsVal] of TYPES) { let { run } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag (param ${type})) (func $f (type $ft) ${wasmConst} suspend $tag ) (elem declare func $f) (func (export "run") (result ${type}) (block (result ${type} (ref $ct)) ref.func $f cont.new $ct resume $ct (on $tag 0) unreachable ) drop ) )`).exports; assertEq(run(), jsVal); } // Multiple tag params: (tag (param f64 i32)), handler reads both { let { run, getF, getI } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag (param f64 i32)) (global $gf (mut f64) (f64.const 0)) (global $gi (mut i32) (i32.const 0)) (func $f (type $ft) f64.const 1.5 i32.const 42 suspend $tag ) (elem declare func $f) (func (export "run") (block (result f64 i32 (ref $ct)) ref.func $f cont.new $ct resume $ct (on $tag 0) unreachable ) drop global.set $gi global.set $gf ) (func (export "getF") (result f64) global.get $gf) (func (export "getI") (result i32) global.get $gi) )`).exports; run(); assertEq(getF(), 1.5); assertEq(getI(), 42); } // Two suspensions from same function, each delivers a distinct value { let { init, step } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag (param i32)) (global $k (mut (ref null $ct)) (ref.null $ct)) (func $f (type $ft) i32.const 10 suspend $tag i32.const 20 suspend $tag ) (elem declare func $f) (func (export "init") ref.func $f cont.new $ct global.set $k ) (func (export "step") (result i32) (block (result i32 (ref $ct)) global.get $k resume $ct (on $tag 0) i32.const 0 return ) global.set $k ) )`).exports; init(); assertEq(step(), 10); assertEq(step(), 20); assertEq(step(), 0); } // Many suspensions: function suspends N times, JS-side loop resumes each time { let { init, step } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag (param i32)) (global $k (mut (ref null $ct)) (ref.null $ct)) (func $f (type $ft) i32.const 1 suspend $tag i32.const 2 suspend $tag i32.const 3 suspend $tag i32.const 4 suspend $tag i32.const 5 suspend $tag ) (elem declare func $f) (func (export "init") ref.func $f cont.new $ct global.set $k ) (func (export "step") (result i32) (block (result i32 (ref $ct)) global.get $k resume $ct (on $tag 0) i32.const 0 return ) global.set $k ) )`).exports; init(); for (let i = 1; i <= 5; i++) { assertEq(step(), i); } assertEq(step(), 0); } // Two suspensions handled by chaining: the cont ref from the first handler is // used directly on the stack to drive the second resume { let { run } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $tag (param f64 i32)) (func $f (export "f") (type $ft) f64.const 20 i32.const 0 suspend $tag f64.const 40 i32.const 1 suspend $tag ) (func (export "run") (result i32) (block (result f64 i32 (ref $ct)) (block (result f64 i32 (ref $ct)) ref.func $f cont.new $ct resume $ct (on $tag 0) unreachable ) resume $ct (on $tag 0) unreachable ) drop br 0 ) )`).exports; assertEq(run(), 1); } // Section 3: Handler dispatch { let { init, step, getTagId, getVal } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $t1 (param i32)) (tag $t2 (param i32)) (global $k (mut (ref null $ct)) (ref.null $ct)) (global $lastTag (mut i32) (i32.const 0)) (global $lastVal (mut i32) (i32.const 0)) (func $f (type $ft) i32.const 1 suspend $t1 i32.const 2 suspend $t2 ) (elem declare func $f) (func (export "init") ref.func $f cont.new $ct global.set $k ) (func (export "step") (block $b1 (result i32 (ref $ct)) (block $b2 (result i32 (ref $ct)) global.get $k resume $ct (on $t1 1) (on $t2 0) return ) ;; t2 handler (label 0) global.set $k i32.const 2 global.set $lastTag global.set $lastVal return ) ;; t1 handler (label 1) global.set $k i32.const 1 global.set $lastTag global.set $lastVal ) (func (export "getTagId") (result i32) global.get $lastTag) (func (export "getVal") (result i32) global.get $lastVal) )`).exports; init(); step(); assertEq(getTagId(), 1); assertEq(getVal(), 1); step(); assertEq(getTagId(), 2); assertEq(getVal(), 2); } // Two tags with separate per-tag step functions; each fires correctly { let { init, step1, step2, getTagId, getVal } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (tag $t1 (param i32)) (tag $t2 (param i32)) (global $k (mut (ref null $ct)) (ref.null $ct)) (global $lastTag (mut i32) (i32.const 0)) (global $lastVal (mut i32) (i32.const 0)) (func $f (type $ft) i32.const 1 suspend $t1 i32.const 2 suspend $t2 ) (elem declare func $f) (func (export "init") ref.func $f cont.new $ct global.set $k ) (func (export "step1") (block (result i32 (ref $ct)) global.get $k resume $ct (on $t1 0) return ) global.set $k i32.const 1 global.set $lastTag global.set $lastVal ) (func (export "step2") (block (result i32 (ref $ct)) global.get $k resume $ct (on $t2 0) return ) global.set $k i32.const 2 global.set $lastTag global.set $lastVal ) (func (export "getTagId") (result i32) global.get $lastTag) (func (export "getVal") (result i32) global.get $lastVal) )`).exports; init(); step1(); assertEq(getTagId(), 1); assertEq(getVal(), 1); step2(); assertEq(getTagId(), 2); assertEq(getVal(), 2); } // Section 4: Nested continuations // Continuation A resumes B; B completes, A continues (verified via global) { let { run, getG } = wasmEvalText(`(module (type $ft (func)) (type $ct (cont $ft)) (global $g (mut i32) (i32.const 0)) (func $inner (type $ft) i32.const 42 global.set $g ) (func $outer (type $ft) ref.func $inner cont.new $ct resume $ct ) (elem declare func $inner $outer) (func (export "run") ref.func $outer cont.new $ct resume $ct ) (func (export "getG") (result i32) global.get $g) )`).exports; run(); assertEq(getG(), 42); } // Section 5: Imports and side effects // Call JS import from within continuation; import has a side effect { let sideEffect = 0; let { run } = wasmEvalText(`(module (import "env" "fn" (func $fn)) (type $ft (func)) (type $ct (cont $ft)) (func $f (type $ft) call $fn ) (elem declare func $f) (func (export "run") ref.func $f cont.new $ct resume $ct ) )`, { env: { fn: () => { sideEffect = 1; } } }).exports; run(); assertEq(sideEffect, 1); } // Section 6: Re-entrant wasm through JS // Continuation calls JS import which calls back into wasm creating a new continuation { let callCount = 0; let inst = wasmEvalText(`(module (import "env" "doWork" (func $doWork)) (type $ft (func)) (type $ct (cont $ft)) (func $inner (type $ft)) (func $f (type $ft) call $doWork ) (elem declare func $inner $f) (func (export "run") ref.func $f cont.new $ct resume $ct ) (func (export "runInner") ref.func $inner cont.new $ct resume $ct ) )`, { env: { doWork: () => { callCount++; inst.exports.runInner(); } } }); inst.exports.run(); assertEq(callCount, 1); }