Bug 1471169 - Implement realm switching for Wasm calls. r=luke

This commit is contained in:
Jan de Mooij 2018-06-28 12:08:59 +02:00
parent e62f5044db
commit 62bb224959
15 changed files with 150 additions and 20 deletions

View File

@ -726,3 +726,44 @@ assertEq(e.call(), 1090);
// Test the error path in the arguments rectifier.
assertErrorMessage(() => i.missTwo(1337), Error, "a FFI to believe in");
})();
(function testCrossRealmImport() {
var g = newGlobal({sameCompartmentAs: this});
g.evaluate("function f1() { assertCorrectRealm(); return 123; }");
g.mem = new Memory({initial:8});
// The current_memory builtin asserts cx->realm matches instance->realm so
// we call it here.
var i1 = new Instance(new Module(wasmTextToBinary(`
(module
(import $imp1 "a" "f1" (result i32))
(import $imp2 "a" "f2" (result i32))
(import "a" "m" (memory 1))
(func $test (result i32)
(i32.add
(i32.add
(i32.add (current_memory) (call $imp1))
(current_memory))
(call $imp2)))
(export "impstub" $imp1)
(export "test" $test))
`)), {a:{m:g.mem, f1:g.f1, f2:g.Math.abs}});
for (var i = 0; i < 20; i++) {
assertEq(i1.exports.impstub(), 123);
assertEq(i1.exports.test(), 139);
}
// Inter-module/inter-realm wasm => wasm calls.
var src = `
(module
(import $imp "a" "othertest" (result i32))
(import "a" "m" (memory 1))
(func (result i32) (i32.add (call $imp) (current_memory)))
(export "test" 1))
`;
g.i1 = i1;
g.evaluate("i2 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`" + src + "`)), {a:{m:mem,othertest:i1.exports.test}})");
for (var i = 0; i < 20; i++)
assertEq(g.i2.exports.test(), 147);
})();

View File

@ -201,3 +201,32 @@ var tbl = wasmEvalText(`(module (table (export "tbl") anyfunc (elem $f)) (func $
tbl.get(0).foo = 42;
gc();
assertEq(tbl.get(0).foo, 42);
(function testCrossRealmCall() {
var g = newGlobal({sameCompartmentAs: this});
// The current_memory builtin asserts cx->realm matches instance->realm so
// we call it here.
var src = `
(module
(import "a" "t" (table 3 anyfunc))
(import "a" "m" (memory 1))
(func $f (result i32) (i32.add (i32.const 3) (current_memory)))
(elem (i32.const 0) $f))
`;
g.mem = new Memory({initial:4});
g.tbl = new Table({initial:3, element:"anyfunc"});
var i1 = g.evaluate("new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`" + src + "`)), {a:{t:tbl,m:mem}})");
var call = new Instance(new Module(wasmTextToBinary(`
(module
(import "a" "t" (table 3 anyfunc))
(import "a" "m" (memory 1))
(type $v2i (func (result i32)))
(func $call (param $i i32) (result i32) (i32.add (call_indirect $v2i (get_local $i)) (current_memory)))
(export "call" $call))
`)), {a:{t:g.tbl,m:g.mem}}).exports.call;
for (var i = 0; i < 10; i++)
assertEq(call(0), 11);
})();

View File

@ -7123,6 +7123,7 @@ CodeGenerator::emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck)
// TLS and pinned regs. The only case where where we don't have to reload
// the TLS and pinned regs is when the callee preserves them.
bool reloadRegs = true;
bool switchRealm = true;
const wasm::CallSiteDesc& desc = mir->desc();
const wasm::CalleeDesc& callee = mir->callee();
@ -7130,6 +7131,7 @@ CodeGenerator::emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck)
case wasm::CalleeDesc::Func:
masm.call(desc, callee.funcIndex());
reloadRegs = false;
switchRealm = false;
break;
case wasm::CalleeDesc::Import:
masm.wasmCallImport(desc, callee);
@ -7137,22 +7139,30 @@ CodeGenerator::emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck)
case wasm::CalleeDesc::AsmJSTable:
case wasm::CalleeDesc::WasmTable:
masm.wasmCallIndirect(desc, callee, needsBoundsCheck);
reloadRegs = callee.which() == wasm::CalleeDesc::WasmTable && callee.wasmTableIsExternal();
reloadRegs = switchRealm =
(callee.which() == wasm::CalleeDesc::WasmTable && callee.wasmTableIsExternal());
break;
case wasm::CalleeDesc::Builtin:
masm.call(desc, callee.builtin());
reloadRegs = false;
switchRealm = false;
break;
case wasm::CalleeDesc::BuiltinInstanceMethod:
masm.wasmCallBuiltinInstanceMethod(desc, mir->instanceArg(), callee.builtin());
switchRealm = false;
break;
}
if (reloadRegs) {
masm.loadWasmTlsRegFromFrame();
masm.loadWasmPinnedRegsFromTls();
if (switchRealm)
masm.switchToWasmTlsRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
} else {
MOZ_ASSERT(!switchRealm);
}
if (mir->spIncrement())
masm.reserveStack(mir->spIncrement());
}

View File

@ -1825,6 +1825,14 @@ MacroAssembler::switchToBaselineFrameRealm(Register scratch)
switchToObjectRealm(scratch, scratch);
}
void
MacroAssembler::switchToWasmTlsRealm(Register scratch1, Register scratch2)
{
loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), scratch1);
loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, realm)), scratch2);
storePtr(scratch2, Address(scratch1, JSContext::offsetOfRealm()));
}
void
MacroAssembler::debugAssertContextRealm(const void* realm, Register scratch)
{
@ -3530,6 +3538,11 @@ MacroAssembler::wasmCallImport(const wasm::CallSiteDesc& desc, const wasm::Calle
static_assert(ABINonArgReg0 != WasmTlsReg, "by constraint");
#endif
// Switch to the callee's realm.
loadWasmGlobalPtr(globalDataOffset + offsetof(wasm::FuncImportTls, realm), ABINonArgReg1);
loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), ABINonArgReg2);
storePtr(ABINonArgReg1, Address(ABINonArgReg2, JSContext::offsetOfRealm()));
// Switch to the callee's TLS and pinned registers and make the call.
loadWasmGlobalPtr(globalDataOffset + offsetof(wasm::FuncImportTls, tls), WasmTlsReg);
loadWasmPinnedRegsFromTls();
@ -3562,7 +3575,7 @@ void
MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
bool needsBoundsCheck)
{
Register scratch = WasmTableCallScratchReg;
Register scratch = WasmTableCallScratchReg0;
Register index = WasmTableCallIndexReg;
if (callee.which() == wasm::CalleeDesc::AsmJSTable) {
@ -3623,6 +3636,7 @@ MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::Cal
bind(&nonNull);
loadWasmPinnedRegsFromTls();
switchToWasmTlsRealm(index, WasmTableCallScratchReg1);
loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, code)), scratch);
} else {

View File

@ -2017,6 +2017,7 @@ class MacroAssembler : public MacroAssemblerSpecific
void switchToRealm(const void* realm, Register scratch);
void switchToObjectRealm(Register obj, Register scratch);
void switchToBaselineFrameRealm(Register scratch);
void switchToWasmTlsRealm(Register scratch1, Register scratch2);
void debugAssertContextRealm(const void* realm, Register scratch);
void loadJitActivation(Register dest) {

View File

@ -119,6 +119,7 @@ bool IsUnaligned(const wasm::MemoryAccessDesc& access);
static constexpr Register ABINonArgReg0 = r4;
static constexpr Register ABINonArgReg1 = r5;
static constexpr Register ABINonArgReg2 = r6;
static constexpr Register ABINonArgReg3 = r7;
// This register may be volatile or nonvolatile. Avoid d15 which is the
// ScratchDoubleReg.
@ -142,9 +143,10 @@ static constexpr Register WasmTlsReg = r9;
// Registers used for wasm table calls. These registers must be disjoint
// from the ABI argument registers, WasmTlsReg and each other.
static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
static constexpr Register WasmTableCallSigReg = ABINonArgReg1;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg2;
static constexpr Register WasmTableCallScratchReg0 = ABINonArgReg0;
static constexpr Register WasmTableCallScratchReg1 = ABINonArgReg1;
static constexpr Register WasmTableCallSigReg = ABINonArgReg2;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg3;
static constexpr Register PreBarrierReg = r1;

View File

@ -438,6 +438,7 @@ class ABIArgGenerator
static constexpr Register ABINonArgReg0 = r8;
static constexpr Register ABINonArgReg1 = r9;
static constexpr Register ABINonArgReg2 = r10;
static constexpr Register ABINonArgReg3 = r11;
// This register may be volatile or nonvolatile. Avoid d31 which is the
// ScratchDoubleReg.
@ -461,9 +462,10 @@ static constexpr Register WasmTlsReg { Registers::x23 };
// Registers used for wasm table calls. These registers must be disjoint
// from the ABI argument registers, WasmTlsReg and each other.
static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
static constexpr Register WasmTableCallSigReg = ABINonArgReg1;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg2;
static constexpr Register WasmTableCallScratchReg0 = ABINonArgReg0;
static constexpr Register WasmTableCallScratchReg1 = ABINonArgReg1;
static constexpr Register WasmTableCallSigReg = ABINonArgReg2;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg3;
static inline bool
GetIntArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register* out)

View File

@ -68,6 +68,7 @@ static constexpr Register64 ReturnReg64(InvalidReg);
static constexpr Register ABINonArgReg0 { Registers::invalid_reg };
static constexpr Register ABINonArgReg1 { Registers::invalid_reg };
static constexpr Register ABINonArgReg2 { Registers::invalid_reg };
static constexpr Register ABINonArgReg3 { Registers::invalid_reg };
static constexpr Register ABINonArgReturnReg0 { Registers::invalid_reg };
static constexpr Register ABINonArgReturnReg1 { Registers::invalid_reg };
static constexpr Register ABINonVolatileReg { Registers::invalid_reg };
@ -75,7 +76,8 @@ static constexpr Register ABINonArgReturnVolatileReg { Registers::invalid_reg };
static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::invalid_reg };
static constexpr Register WasmTableCallScratchReg { Registers::invalid_reg };
static constexpr Register WasmTableCallScratchReg0 { Registers::invalid_reg };
static constexpr Register WasmTableCallScratchReg1 { Registers::invalid_reg };
static constexpr Register WasmTableCallSigReg { Registers::invalid_reg };
static constexpr Register WasmTableCallIndexReg { Registers::invalid_reg };
static constexpr Register WasmTlsReg { Registers::invalid_reg };

View File

@ -181,6 +181,7 @@ class ABIArgGenerator
static constexpr Register ABINonArgReg0 = rax;
static constexpr Register ABINonArgReg1 = rbx;
static constexpr Register ABINonArgReg2 = r10;
static constexpr Register ABINonArgReg3 = r12;
// This register may be volatile or nonvolatile. Avoid xmm15 which is the
// ScratchDoubleReg.
@ -204,9 +205,10 @@ static constexpr Register WasmTlsReg = r14;
// Registers used for asm.js/wasm table calls. These registers must be disjoint
// from the ABI argument registers, WasmTlsReg and each other.
static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
static constexpr Register WasmTableCallSigReg = ABINonArgReg1;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg2;
static constexpr Register WasmTableCallScratchReg0 = ABINonArgReg0;
static constexpr Register WasmTableCallScratchReg1 = ABINonArgReg1;
static constexpr Register WasmTableCallSigReg = ABINonArgReg2;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg3;
static constexpr Register OsrFrameReg = IntArgReg3;

View File

@ -83,6 +83,7 @@ class ABIArgGenerator
static constexpr Register ABINonArgReg0 = eax;
static constexpr Register ABINonArgReg1 = ebx;
static constexpr Register ABINonArgReg2 = ecx;
static constexpr Register ABINonArgReg3 = edx;
// This register may be volatile or nonvolatile. Avoid xmm7 which is the
// ScratchDoubleReg.
@ -106,9 +107,10 @@ static constexpr Register WasmTlsReg = esi;
// Registers used for asm.js/wasm table calls. These registers must be disjoint
// from the ABI argument registers, WasmTlsReg and each other.
static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
static constexpr Register WasmTableCallSigReg = ABINonArgReg1;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg2;
static constexpr Register WasmTableCallScratchReg0 = ABINonArgReg0;
static constexpr Register WasmTableCallScratchReg1 = ABINonArgReg1;
static constexpr Register WasmTableCallSigReg = ABINonArgReg2;
static constexpr Register WasmTableCallIndexReg = ABINonArgReg3;
static constexpr Register OsrFrameReg = edx;
static constexpr Register PreBarrierReg = edx;

View File

@ -3522,7 +3522,7 @@ class BaseCompiler final : public BaseCompilerInterface
{
explicit FunctionCall(uint32_t lineOrBytecode)
: lineOrBytecode(lineOrBytecode),
reloadMachineStateAfter(false),
isInterModule(false),
usesSystemAbi(false),
#ifdef JS_CODEGEN_ARM
hardFP(true),
@ -3533,7 +3533,7 @@ class BaseCompiler final : public BaseCompilerInterface
uint32_t lineOrBytecode;
ABIArgGenerator abi;
bool reloadMachineStateAfter;
bool isInterModule;
bool usesSystemAbi;
#ifdef JS_CODEGEN_ARM
bool hardFP;
@ -3544,7 +3544,7 @@ class BaseCompiler final : public BaseCompilerInterface
void beginCall(FunctionCall& call, UseABI useABI, InterModule interModule)
{
call.reloadMachineStateAfter = interModule == InterModule::True || useABI == UseABI::System;
call.isInterModule = interModule == InterModule::True;
call.usesSystemAbi = useABI == UseABI::System;
if (call.usesSystemAbi) {
@ -3575,7 +3575,11 @@ class BaseCompiler final : public BaseCompilerInterface
size_t adjustment = call.stackArgAreaSize + call.frameAlignAdjustment;
fr.freeArgAreaAndPopBytes(adjustment, stackSpace);
if (call.reloadMachineStateAfter) {
if (call.isInterModule) {
masm.loadWasmTlsRegFromFrame();
masm.loadWasmPinnedRegsFromTls();
masm.switchToWasmTlsRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
} else if (call.usesSystemAbi) {
// On x86 there are no pinned registers, so don't waste time
// reloading the Tls.
#ifndef JS_CODEGEN_X86

View File

@ -530,7 +530,7 @@ wasm::GenerateFunctionPrologue(MacroAssembler& masm, const FuncTypeIdDesc& funcT
offsets->begin = masm.currentOffset();
switch (funcTypeId.kind()) {
case FuncTypeIdDesc::Kind::Global: {
Register scratch = WasmTableCallScratchReg;
Register scratch = WasmTableCallScratchReg0;
masm.loadWasmGlobalPtr(funcTypeId.globalDataOffset(), scratch);
masm.branchPtr(Assembler::Condition::Equal, WasmTableCallSigReg, scratch,
&normalEntry);

View File

@ -108,6 +108,8 @@ bool
Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, const uint64_t* argv,
MutableHandleValue rval)
{
AssertRealmUnchanged aru(cx);
Tier tier = code().bestTier();
const FuncImport& fi = metadata(tier).funcImports[funcImportIndex];
@ -151,6 +153,8 @@ Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, con
FuncImportTls& import = funcImportTls(fi);
RootedFunction importFun(cx, &import.obj->as<JSFunction>());
MOZ_ASSERT(cx->realm() == importFun->realm());
RootedValue fval(cx, ObjectValue(*import.obj));
RootedValue thisv(cx, UndefinedValue());
if (!Call(cx, fval, thisv, args, rval))
@ -319,6 +323,10 @@ Instance::growMemory_i32(Instance* instance, uint32_t delta)
/* static */ uint32_t
Instance::currentMemory_i32(Instance* instance)
{
// This invariant must hold when running Wasm code. Assert it here so we can
// write tests for cross-realm calls.
MOZ_ASSERT(TlsContext.get()->realm() == instance->realm());
uint32_t byteLength = instance->memory()->volatileMemoryLength();
MOZ_ASSERT(byteLength % wasm::PageSize == 0);
return byteLength / wasm::PageSize;
@ -504,6 +512,7 @@ Instance::Instance(JSContext* cx,
tlsData()->boundsCheckLimit = memory ? memory->buffer().wasmBoundsCheckLimit() : 0;
#endif
tlsData()->instance = this;
tlsData()->realm = realm_;
tlsData()->cx = cx;
tlsData()->resetInterrupt(cx);
tlsData()->jumpTable = code_->tieringJumpTable();
@ -520,16 +529,19 @@ Instance::Instance(JSContext* cx,
Tier calleeTier = calleeInstance.code().bestTier();
const CodeRange& codeRange = calleeInstanceObj->getExportedFunctionCodeRange(f, calleeTier);
import.tls = calleeInstance.tlsData();
import.realm = f->realm();
import.code = calleeInstance.codeBase(calleeTier) + codeRange.funcNormalEntry();
import.baselineScript = nullptr;
import.obj = calleeInstanceObj;
} else if (void* thunk = MaybeGetBuiltinThunk(f, fi.funcType())) {
import.tls = tlsData();
import.realm = f->realm();
import.code = thunk;
import.baselineScript = nullptr;
import.obj = f;
} else {
import.tls = tlsData();
import.realm = f->realm();
import.code = codeBase(callerTier) + fi.interpExitCodeOffset();
import.baselineScript = nullptr;
import.obj = f;

View File

@ -1069,6 +1069,9 @@ GenerateImportFunction(jit::MacroAssembler& masm, const FuncImport& fi, FuncType
masm.loadWasmTlsRegFromFrame();
masm.loadWasmPinnedRegsFromTls();
// Restore cx->realm.
masm.switchToWasmTlsRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
GenerateFunctionEpilogue(masm, framePushed, offsets);
return FinishOffsets(masm, offsets);
}

View File

@ -1858,6 +1858,9 @@ struct TlsData
// Pointer to the Instance that contains this TLS data.
Instance* instance;
// Equal to instance->realm_.
JS::Realm* realm;
// The containing JSContext.
JSContext* cx;
@ -1927,6 +1930,9 @@ struct FuncImportTls
// with any pinned registers) before calling 'code'.
TlsData* tls;
// The callee function's realm.
JS::Realm* realm;
// If 'code' points into a JIT code thunk, the BaselineScript of the callee,
// for bidirectional registration purposes.
jit::BaselineScript* baselineScript;