Source code
Revision control
Copy as Markdown
Other Tools
// |jit-test| skip-if: !wasmJSPromiseIntegrationEnabled()
function testSingle() {
var g = newGlobal({newCompartment: true});
var dbg = new Debugger(g);
var savedEnv = null;
var saving = false;
var wasmEnters = 0;
dbg.onEnterFrame = function(frame) {
if (frame.type !== "wasmcall" || !saving) return;
// The second wasm frame is $inner, the one that suspends.
if (++wasmEnters === 2 && !savedEnv) {
savedEnv = frame.environment;
}
};
g.eval(`
var resolveHold;
function hold() { return new Promise(r => { resolveHold = r; }); }
var fill = [];
for (var k = 0; k < 96; k++) {
fill.push('(local.set ' + k + ' (i64.const 0x4141414141414141))');
}
var bin = wasmTextToBinary(\`(module
(import "" "hold" (func $hold))
(func $inner (param i32) (result i32) (local i32 externref)
(local.set 1 (local.get 0))
(call $hold)
(local.get 1))
(func (export "testA") (param i32) (result i32)
(call $inner (local.get 0)))
(func (export "testB") (result i32) (local \${'i64 '.repeat(96)})
\${fill.join(' ')}
(call $hold)
(i32.const 0))
)\`);
var inst = new WebAssembly.Instance(new WebAssembly.Module(bin),
{"": {hold: new WebAssembly.Suspending(hold)}});
var pA = WebAssembly.promising(inst.exports.testA);
var pB = WebAssembly.promising(inst.exports.testB);
`);
// Suspend inside $inner and keep its environment alive.
saving = true;
g.eval("var qA = pA(7);");
saving = false;
drainJobQueue();
assertEq(savedEnv !== null, true);
assertEq(savedEnv.getVariable("var0"), 7);
// Drop the suspended continuation so the ContObject is finalized and its
// stack is returned to the arena.
g.eval("qA = null; resolveHold = null;");
gc(); gc();
drainJobQueue();
assertEq(savedEnv.getVariable("var0").optimizedOut, true);
// Run a second continuation that recycles the freed slot and fills it with
// 0x4141414141414141. Reading through a surviving entry here would walk a
// forged frame.
g.eval("var qB = pB();");
drainJobQueue();
assertEq(savedEnv.getVariable("var0").optimizedOut, true);
}
// Nested variant
function testNested() {
var g = newGlobal({newCompartment: true});
var dbg = new Debugger(g);
var envs = [];
var saving = false;
dbg.onEnterFrame = function(frame) {
if (frame.type !== "wasmcall" || !saving) return;
envs.push(frame.environment);
};
g.eval(`
var resolveHold;
function hold() { return new Promise(r => { resolveHold = r; }); }
var bin = wasmTextToBinary(\`(module
(import "" "hold" (func $hold))
(import "" "callInner" (func $callInner))
(func $deep (param i32) (result i32) (local i32)
(local.set 1 (local.get 0))
(call $hold)
(local.get 1))
(func (export "inner") (param i32) (result i32)
(call $deep (local.get 0)))
(func (export "outer") (result i32)
(call $callInner)
(i32.const 1))
)\`);
var pInner;
function callInner() { return pInner(11); }
var inst = new WebAssembly.Instance(new WebAssembly.Module(bin), {"": {
hold: new WebAssembly.Suspending(hold),
callInner: new WebAssembly.Suspending(callInner),
}});
pInner = WebAssembly.promising(inst.exports.inner);
var pOuter = WebAssembly.promising(inst.exports.outer);
`);
// outer suspends on inner, which suspends on hold: two nested stacks.
saving = true;
g.eval("var q = pOuter();");
saving = false;
drainJobQueue();
assertEq(envs.length, 3);
// $deep is the innermost frame; its param is still readable while suspended.
assertEq(envs[2].getVariable("var0"), 11);
g.eval("q = null; resolveHold = null;");
gc(); gc();
drainJobQueue();
gc();
for (var env of envs) {
for (var name of env.names()) {
var v = env.getVariable(name);
// Every wasm local on the discarded chain must now read as optimized out
// rather than off a freed stack.
assertEq(typeof v === "object" && v !== null && v.optimizedOut === true,
true);
}
}
}
testSingle();
testNested();