// |jit-test| skip-if: !wasmStackSwitchingEnabled()
const TYPES = [
['i32' , 'i32.const 42' , 42 ],
['i64' , 'i64.const 42' , 42 n],
['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 );
}
Messung V0.5 in Prozent C=91 H=93 G=91
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland