diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/jit-test/tests/wasm/table-gc.js | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/table-gc.js b/js/src/jit-test/tests/wasm/table-gc.js new file mode 100644 index 0000000000..c96c70311f --- /dev/null +++ b/js/src/jit-test/tests/wasm/table-gc.js @@ -0,0 +1,221 @@ +// |jit-test| --no-baseline; --no-blinterp +// Turn off baseline and since it messes up the GC finalization assertions by +// adding spurious edges to the GC graph. + +const Module = WebAssembly.Module; +const Instance = WebAssembly.Instance; +const Table = WebAssembly.Table; +const RuntimeError = WebAssembly.RuntimeError; + +var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect (type $v2i) (local.get $i))) (export "call" (func $call))` +var callee = i => `(func $f${i} (type $v2i) (i32.const ${i}))`; + +// A table should not hold exported functions alive and exported functions +// should not hold their originating table alive. Live exported functions should +// hold instances alive and instances hold imported tables alive. Nothing +// should hold the export object alive. +resetFinalizeCount(); +var i = wasmEvalText(`(module (table 2 funcref) (export "tbl" (table 0)) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`); +var e = i.exports; +var t = e.tbl; +var f = t.get(0); +assertEq(f(), e.call(0)); +assertErrorMessage(() => e.call(1), RuntimeError, /indirect call to null/); +assertErrorMessage(() => e.call(2), RuntimeError, /index out of bounds/); +assertEq(finalizeCount(), 0); +i.edge = makeFinalizeObserver(); +t.edge = makeFinalizeObserver(); +f.edge = makeFinalizeObserver(); +gc(); +assertEq(finalizeCount(), 0); +f.x = 42; +f = null; +gc(); +assertEq(finalizeCount(), 0); +f = t.get(0); +assertEq(f.x, 42); +gc(); +assertEq(finalizeCount(), 0); +i.exports = null; +e = null; +gc(); +assertEq(finalizeCount(), 0); +t = null; +gc(); +assertEq(finalizeCount(), 0); +i = null; +gc(); +assertEq(finalizeCount(), 0); +assertEq(f(), 0); +f = null; +gc(); +assertEq(finalizeCount(), 3); + +// A table should hold the instance of any of its elements alive. +resetFinalizeCount(); +var i = wasmEvalText(`(module (table 1 funcref) (export "tbl" (table 0)) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`); +var e = i.exports; +var t = e.tbl; +var f = t.get(0); +i.edge = makeFinalizeObserver(); +t.edge = makeFinalizeObserver(); +f.edge = makeFinalizeObserver(); +gc(); +assertEq(finalizeCount(), 0); +i.exports = null; +e = null; +gc(); +assertEq(finalizeCount(), 0); +f = null; +gc(); +assertEq(finalizeCount(), 0); +i = null; +gc(); +assertEq(finalizeCount(), 0); +t = null; +gc(); +assertEq(finalizeCount(), 3); + +// Null elements shouldn't keep anything alive. +resetFinalizeCount(); +var i = wasmEvalText(`(module (table 2 funcref) (export "tbl" (table 0)) ${caller})`); +var e = i.exports; +var t = e.tbl; +i.edge = makeFinalizeObserver(); +t.edge = makeFinalizeObserver(); +gc(); +assertEq(finalizeCount(), 0); +i.exports = null; +e = null; +gc(); +assertEq(finalizeCount(), 0); +i = null; +gc(); +assertEq(finalizeCount(), 1); +t = null; +gc(); +assertEq(finalizeCount(), 2); + +// Before initialization, a table is not bound to any instance. +resetFinalizeCount(); +var i = wasmEvalText(`(module (func $f0 (result i32) (i32.const 0)) (export "f0" (func $f0)))`); +var t = new Table({initial:4, element:"anyfunc"}); +i.edge = makeFinalizeObserver(); +t.edge = makeFinalizeObserver(); +gc(); +assertEq(finalizeCount(), 0); +i = null; +gc(); +assertEq(finalizeCount(), 1); +t = null; +gc(); +assertEq(finalizeCount(), 2); + +// When a Table is created (uninitialized) and then first assigned, it keeps the +// first element's Instance alive (as above). +resetFinalizeCount(); +var i = wasmEvalText(`(module (func $f (result i32) (i32.const 42)) (export "f" (func $f)))`); +var f = i.exports.f; +var t = new Table({initial:1, element:"anyfunc"}); +i.edge = makeFinalizeObserver(); +f.edge = makeFinalizeObserver(); +t.edge = makeFinalizeObserver(); +t.set(0, f); +assertEq(t.get(0), f); +assertEq(t.get(0)(), 42); +gc(); +assertEq(finalizeCount(), 0); +f = null; +i.exports = null; +gc(); +assertEq(finalizeCount(), 0); +assertEq(t.get(0)(), 42); +i = null; +gc(); +assertEq(finalizeCount(), 0); +t.set(0, null); +assertEq(t.get(0), null); +gc(); +assertEq(finalizeCount(), 2); +t = null; +gc(); +assertEq(finalizeCount(), 3); + +// Once all of an instance's elements in a Table have been clobbered, the +// Instance should not be reachable. +resetFinalizeCount(); +var i1 = wasmEvalText(`(module (func $f1 (result i32) (i32.const 13)) (export "f1" (func $f1)))`); +var i2 = wasmEvalText(`(module (func $f2 (result i32) (i32.const 42)) (export "f2" (func $f2)))`); +var f1 = i1.exports.f1; +var f2 = i2.exports.f2; +var t = new Table({initial:2, element:"anyfunc"}); +i1.edge = makeFinalizeObserver(); +i2.edge = makeFinalizeObserver(); +f1.edge = makeFinalizeObserver(); +f2.edge = makeFinalizeObserver(); +t.edge = makeFinalizeObserver(); +t.set(0, f1); +t.set(1, f2); +gc(); +assertEq(finalizeCount(), 0); +f1 = f2 = null; +i1.exports = null; +i2.exports = null; +gc(); +assertEq(finalizeCount(), 0); +i1 = null; +i2 = null; +gc(); +assertEq(finalizeCount(), 0); +t.set(0, t.get(1)); +gc(); +assertEq(finalizeCount(), 2); +t.set(0, null); +t.set(1, null); +gc(); +assertEq(finalizeCount(), 4); +t = null; +gc(); +assertEq(finalizeCount(), 5); + +// Ensure that an instance that is only live on the stack cannot be GC even if +// there are no outstanding references. +resetFinalizeCount(); +const N = 10; +var tbl = new Table({initial:N, element:"anyfunc"}); +tbl.edge = makeFinalizeObserver(); +function runTest() { + tbl = null; + gc(); + assertEq(finalizeCount(), 0); + return 100; +} +var i = wasmEvalText( + `(module + (import "a" "b" (func $imp (result i32))) + (func $f (param i32) (result i32) (call $imp)) + (export "f" (func $f)) + )`, + {a:{b:runTest}} +); +i.edge = makeFinalizeObserver(); +tbl.set(0, i.exports.f); +var m = new Module(wasmTextToBinary(`(module + (import "a" "b" (table ${N} funcref)) + (type $i2i (func (param i32) (result i32))) + (func $f (param $i i32) (result i32) + (local.set $i (i32.sub (local.get $i) (i32.const 1))) + (i32.add + (i32.const 1) + (call_indirect (type $i2i) (local.get $i) (local.get $i)))) + (export "f" (func $f)) +)`)); +for (var i = 1; i < N; i++) { + var inst = new Instance(m, {a:{b:tbl}}); + inst.edge = makeFinalizeObserver(); + tbl.set(i, inst.exports.f); +} +inst = null; +assertEq(tbl.get(N - 1)(N - 1), 109); +gc(); +assertEq(finalizeCount(), N + 1); |