Source code

Revision control

Copy as Markdown

Other Tools

// |jit-test| skip-if: !wasmStackSwitchingEnabled()
// Stress test for ContStackAllocator. Drives a deterministic mix of
// newborn/suspended cont allocation, mid-stack stepping, resume-to-completion
// freeing, drop-to-GC freeing, and GC purges over many iterations to exercise
// arena fill, free-list reuse, multi-arena allocation, and GC purge paths.
const ITERATIONS = 10000;
// The maximum number of live stacks.
const POOL_SIZE = 32;
const { init, makeNewbornAt, makeSuspendedAt, stepAt, finishAt, dropAt } =
wasmEvalText(`(module
(type $ft (func))
(type $ct (cont $ft))
(type $arr (array (mut (ref null $ct))))
(tag $tag)
(global $pool (mut (ref null $arr)) (ref.null $arr))
(func $g (type $ft))
(func $f (type $ft)
suspend $tag
suspend $tag
suspend $tag
)
(elem declare func $g $f)
(func (export "init") (param $size i32)
(global.set $pool (array.new_default $arr (local.get $size)))
)
(func (export "makeNewbornAt") (param $slot i32)
(array.set $arr
(global.get $pool)
(local.get $slot)
(cont.new $ct (ref.func $g)))
)
(func (export "makeSuspendedAt") (param $slot i32)
(local $captured (ref null $ct))
(block (result (ref $ct))
(cont.new $ct (ref.func $f))
resume $ct (on $tag 0)
unreachable
)
local.set $captured
(array.set $arr
(global.get $pool)
(local.get $slot)
(local.get $captured))
)
(func (export "stepAt") (param $slot i32) (result i32)
(local $k (ref null $ct))
(local $next (ref null $ct))
(local.set $k
(array.get $arr (global.get $pool) (local.get $slot)))
(if (ref.is_null (local.get $k))
(then (return (i32.const 0))))
(block (result (ref $ct))
(local.get $k)
ref.as_non_null
resume $ct (on $tag 0)
;; cont ran to completion: clear slot and return 0
(array.set $arr (global.get $pool) (local.get $slot) (ref.null $ct))
(return (i32.const 0))
)
local.set $next
(array.set $arr
(global.get $pool)
(local.get $slot)
(local.get $next))
(i32.const 1)
)
(func (export "finishAt") (param $slot i32)
(local $k (ref null $ct))
(local.set $k
(array.get $arr (global.get $pool) (local.get $slot)))
(if (ref.is_null (local.get $k))
(then (return)))
(array.set $arr (global.get $pool) (local.get $slot) (ref.null $ct))
(loop $drain
(block $caught (result (ref $ct))
(local.get $k)
ref.as_non_null
resume $ct (on $tag $caught)
return
)
local.set $k
br $drain
)
)
(func (export "dropAt") (param $slot i32)
(array.set $arr (global.get $pool) (local.get $slot) (ref.null $ct))
)
)`).exports;
init(POOL_SIZE);
// Deterministic 32-bit LCG so the operation sequence is reproducible.
let s = 0x12345678;
function rnd() {
s = (Math.imul(s, 1103515245) + 12345) | 0;
return s >>> 0;
}
let commands = [
{
chance: 20,
run: (slot) => makeNewbornAt(slot),
executions: 0,
expectedExecutions: 2048,
},
{
chance: 30,
run: (slot) => makeSuspendedAt(slot),
executions: 0,
expectedExecutions: 3009,
},
{
chance: 20,
run: (slot) => stepAt(slot),
executions: 0,
expectedExecutions: 1923,
},
{
chance: 15,
run: (slot) => finishAt(slot),
executions: 0,
expectedExecutions: 1614,
},
{
chance: 10,
run: (slot) => dropAt(slot),
executions: 0,
expectedExecutions: 1012,
},
{
chance: 3,
run: (slot) => minorgc(),
executions: 0,
expectedExecutions: 185,
},
{
chance: 2,
run: (slot) => gc(),
executions: 0,
expectedExecutions: 209,
},
];
// All commands must add up to 100%
let totalChance = 0;
for (let command of commands) {
totalChance += command.chance;
}
assertEq(totalChance, 100);
// Run a fixed amount of iterations and run random commands.
for (let i = 0; i < ITERATIONS; i++) {
let slot = rnd() % POOL_SIZE;
let r = rnd() % 100;
for (let commandIndex in commands) {
let command = commands[commandIndex];
if (r < command.chance) {
command.run(slot);
command.executions += 1;
break;
}
r -= command.chance;
}
}
// Uncomment this to get the execution counts for if they change.
// for (let command of commands) {
// print(command.executions);
// }
for (let command of commands) {
assertEq(command.executions, command.expectedExecutions);
}