Bug 1284155 - Baldr: allow cross-element Table elements (r=bbouvier)

MozReview-Commit-ID: 3xalhzMAeJp
This commit is contained in:
Luke Wagner 2016-08-05 15:39:57 -05:00
parent 555b22b818
commit 9ba6432be7
8 changed files with 208 additions and 21 deletions

View File

@ -1085,9 +1085,7 @@ WasmTableObject::setImpl(JSContext* cx, const CallArgs& args)
const FuncExport& funcExport = instance.metadata().lookupFuncExport(funcIndex);
const CodeRange& codeRange = instance.metadata().codeRanges[funcExport.codeRangeIndex()];
void* code = instance.codeSegment().base() + codeRange.funcTableEntry();
if (!table.set(cx, index, code, instance))
return false;
table.set(index, code, instance);
} else {
table.setNull(index);
}

View File

@ -467,9 +467,7 @@ Module::initElems(JSContext* cx, HandleWasmInstanceObject instanceObj,
void* code = codeBase + seg.elems[i];
if (useProfilingEntry)
code = codeBase + instance.code().lookupRange(code)->funcProfilingEntry();
if (!table.set(cx, offset + i, code, instance))
return false;
table.set(offset + i, code, instance);
}
prevEnd = offset + seg.elems.length();

View File

@ -99,22 +99,16 @@ Table::externalArray() const
return (ExternalTableElem*)array_.get();
}
bool
Table::set(JSContext* cx, uint32_t index, void* code, Instance& instance)
void
Table::set(uint32_t index, void* code, Instance& instance)
{
if (external_) {
ExternalTableElem& elem = externalArray()[index];
if (elem.tls->instance != &instance) {
JS_ReportError(cx, "cross-module Table import NYI");
return false;
}
elem.code = code;
elem.tls = &instance.tlsData();
} else {
internalArray()[index] = code;
}
return true;
}
void

View File

@ -58,7 +58,7 @@ class Table : public ShareableBase<Table>
void** internalArray() const;
ExternalTableElem* externalArray() const;
bool set(JSContext* cx, uint32_t index, void* code, Instance& instance);
void set(uint32_t index, void* code, Instance& instance);
void setNull(uint32_t index);
// about:memory reporting:

View File

@ -261,7 +261,7 @@ assertEq(e.tbl1.get(1), null);
e.tbl1.set(3, e.f1);
assertEq(e.tbl1.get(0), e.tbl1.get(3));
// Re-exports:
// Re-exports and Identity:
var code = textToBinary('(module (import "a" "b" (memory 1 1)) (export "foo" memory) (export "bar" memory))');
var mem = new Memory({initial:1});
@ -275,6 +275,18 @@ var e = new Instance(new Module(code), {a:{b:tbl}}).exports;
assertEq(tbl, e.foo);
assertEq(tbl, e.bar);
var code = textToBinary('(module (import "a" "b" (table 2 2)) (func $foo) (elem (i32.const 0) $foo) (export "foo" $foo))');
var tbl = new Table({initial:2, element:"anyfunc"});
var e1 = new Instance(new Module(code), {a:{b:tbl}}).exports;
assertEq(e1.foo, tbl.get(0));
tbl.set(1, e1.foo);
assertEq(e1.foo, tbl.get(1));
var e2 = new Instance(new Module(code), {a:{b:tbl}}).exports;
assertEq(e2.foo, tbl.get(0));
assertEq(e1.foo, tbl.get(1));
assertEq(tbl.get(0) === e1.foo, false);
assertEq(e1.foo === e2.foo, false);
// Non-existent export errors
assertErrorMessage(() => new Module(textToBinary('(module (export "a" 0))')), TypeError, /exported function index out of bounds/);

View File

@ -117,10 +117,10 @@ Error);
(func $foo (result i32) (i32.const 42))
(export "foo" $foo)
(func $bar (result i32) (i32.const 13))
(table $foo $bar)
(table (resizable 10))
(elem (i32.const 0) $foo $bar)
(export "tbl" table)
)
`))).exports;
)`))).exports;
assertEq(e.foo(), 42);
assertEq(e.tbl.get(0)(), 42);
assertEq(e.tbl.get(1)(), 13);
@ -151,6 +151,35 @@ Error);
assertEq(e.tbl.get(1)(), 13);
assertEqStacks(disableSingleStepProfiling(), ["", ">", "0,>", ">", "", ">", "1,>", ">", ""]);
disableSPSProfiling();
var e2 = new Instance(new Module(textToBinary(`
(module
(type $v2i (func (result i32)))
(import "a" "b" (table 10))
(elem (i32.const 2) $bar)
(func $bar (result i32) (i32.const 99))
(func $baz (param $i i32) (result i32) (call_indirect $v2i (get_local $i)))
(export "baz" $baz)
)`)),
{a:{b:e.tbl}}).exports;
enableSPSProfiling();
enableSingleStepProfiling();
assertEq(e2.baz(0), 42);
assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]);
disableSPSProfiling();
enableSPSProfiling();
enableSingleStepProfiling();
assertEq(e2.baz(1), 13);
assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "1,1,>", "1,>", ">", ""]);
disableSPSProfiling();
enableSPSProfiling();
enableSingleStepProfiling();
assertEq(e2.baz(2), 99);
assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]);
disableSPSProfiling();
})();
(function() {

View File

@ -152,3 +152,79 @@ assertEq(finalizeCount(), 2);
t = null;
gc();
assertEq(finalizeCount(), 4);
// Once all of an instance's elements in a Table (including the
// bad-indirect-call stub) have been clobbered, the Instance should not be
// rooted.
resetFinalizeCount();
var i1 = evalText(`(module (func $f1 (result i32) (i32.const 13)) (export "f1" $f1))`);
var i2 = evalText(`(module (func $f2 (result i32) (i32.const 42)) (export "f2" $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(), 2);
i1 = null;
i2 = null;
gc();
assertEq(finalizeCount(), 2);
t.set(0, t.get(1));
gc();
assertEq(finalizeCount(), 3);
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(), 1);
return 100;
}
var i = evalText(
`(module
(import "a" "b" (result i32))
(func $f (param i32) (result i32) (call_import 0))
(export "f" $f)
)`,
{a:{b:runTest}}
);
i.edge = makeFinalizeObserver();
tbl.set(0, i.exports.f);
var m = new Module(textToBinary(`(module
(import "a" "b" (table ${N}))
(type $i2i (func (param i32) (result i32)))
(func $f (param $i i32) (result i32)
(set_local $i (i32.sub (get_local $i) (i32.const 1)))
(i32.add
(i32.const 1)
(call_indirect $i2i (get_local $i) (get_local $i))))
(export "f" $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);

View File

@ -5,6 +5,7 @@ load(libdir + 'asserts.js');
const Module = WebAssembly.Module;
const Instance = WebAssembly.Instance;
const Table = WebAssembly.Table;
const Memory = WebAssembly.Memory;
// Explicitly opt into the new binary format for imports and exports until it
// is used by default everywhere.
@ -65,11 +66,90 @@ assertErrorMessage(() => call(6), Error, /bad wasm indirect call/);
assertErrorMessage(() => call(10), Error, /out-of-range/);
var tbl = new Table({initial:3, element:"anyfunc"});
var call = evalText(`(module (import "a" "b" (table 2)) (export "tbl" table) (elem (i32.const 0) $f0 $f1) ${callee(0)} ${callee(1)} ${caller})`, {a:{b:tbl}}).exports.call;
var call = evalText(`(module (import "a" "b" (table 3)) (export "tbl" table) (elem (i32.const 0) $f0 $f1) ${callee(0)} ${callee(1)} ${caller})`, {a:{b:tbl}}).exports.call;
assertEq(call(0), 0);
assertEq(call(1), 1);
assertEq(tbl.get(0)(), 0);
assertEq(tbl.get(1)(), 1);
assertErrorMessage(() => call(2), Error, /bad wasm indirect call/);
assertEq(tbl.get(2), null);
var exp = evalText(`(module (import "a" "b" (table 3)) (export "tbl" table) (elem (i32.const 2) $f2) ${callee(2)} ${caller})`, {a:{b:tbl}}).exports;
assertEq(exp.tbl, tbl);
assertEq(exp.call(0), 0);
assertEq(exp.call(1), 1);
assertEq(exp.call(2), 2);
assertEq(call(0), 0);
assertEq(call(1), 1);
assertEq(call(2), 2);
assertEq(tbl.get(0)(), 0);
assertEq(tbl.get(1)(), 1);
assertEq(tbl.get(2)(), 2);
var exp1 = evalText(`(module (table (resizable 10)) (export "tbl" table) (elem (i32.const 0) $f0 $f0) ${callee(0)} (export "f0" $f0) ${caller})`).exports
assertEq(exp1.tbl.get(0), exp1.f0);
assertEq(exp1.tbl.get(1), exp1.f0);
assertEq(exp1.tbl.get(2), null);
assertEq(exp1.call(0), 0);
assertEq(exp1.call(1), 0);
assertErrorMessage(() => exp1.call(2), Error, /bad wasm indirect call/);
var exp2 = evalText(`(module (import "a" "b" (table 10)) (export "tbl" table) (elem (i32.const 1) $f1 $f1) ${callee(1)} (export "f1" $f1) ${caller})`, {a:{b:exp1.tbl}}).exports
assertEq(exp1.tbl, exp2.tbl);
assertEq(exp2.tbl.get(0), exp1.f0);
assertEq(exp2.tbl.get(1), exp2.f1);
assertEq(exp2.tbl.get(2), exp2.f1);
assertEq(exp1.call(0), 0);
assertEq(exp1.call(1), 1);
assertEq(exp1.call(2), 1);
assertEq(exp2.call(0), 0);
assertEq(exp2.call(1), 1);
assertEq(exp2.call(2), 1);
var tbl = new Table({initial:3, element:"anyfunc"});
var e1 = evalText(`(module (func $f (result i32) (i32.const 42)) (export "f" $f))`).exports;
var e2 = evalText(`(module (func $g (result f32) (f32.const 10)) (export "g" $g))`).exports;
var e3 = evalText(`(module (func $h (result i32) (i32.const 13)) (export "h" $h))`).exports;
tbl.set(0, e1.f);
tbl.set(1, e2.g);
tbl.set(2, e3.h);
var e4 = evalText(`(module (import "a" "b" (table 3)) ${caller})`, {a:{b:tbl}}).exports;
assertEq(e4.call(0), 42);
assertErrorMessage(() => e4.call(1), Error, /bad wasm indirect call/);
assertEq(e4.call(2), 13);
var m = new Module(textToBinary(`(module
(type $i2i (func (param i32) (result i32)))
(import "a" "mem" (memory 1))
(import "a" "tbl" (table 10))
(import $imp "a" "imp" (result i32))
(func $call (param $i i32) (result i32)
(i32.add
(call_import $imp)
(i32.add
(i32.load (i32.const 0))
(if (i32.eqz (get_local $i))
(then (i32.const 0))
(else
(set_local $i (i32.sub (get_local $i) (i32.const 1)))
(call_indirect $i2i (get_local $i) (get_local $i)))))))
(export "call" $call)
)`));
var failTime = false;
var tbl = new Table({initial:10, element:"anyfunc"});
var mem1 = new Memory({initial:1});
var e1 = new Instance(m, {a:{mem:mem1, tbl, imp() {if (failTime) throw new Error("ohai"); return 1}}}).exports;
tbl.set(0, e1.call);
var mem2 = new Memory({initial:1});
var e2 = new Instance(m, {a:{mem:mem2, tbl, imp() {return 10} }}).exports;
tbl.set(1, e2.call);
var mem3 = new Memory({initial:1});
var e3 = new Instance(m, {a:{mem:mem3, tbl, imp() {return 100} }}).exports;
new Int32Array(mem1.buffer)[0] = 1000;
new Int32Array(mem2.buffer)[0] = 10000;
new Int32Array(mem3.buffer)[0] = 100000;
assertEq(e3.call(2), 111111);
failTime = true;
assertErrorMessage(() => e3.call(2), Error, "ohai");
// Call signatures are matched structurally: