Bug 1778466 part 7 - Don't use a tagged frame pointer for direct JIT => Wasm calls. r=rhunt

The tag bit is now only used for the activation's `exitFP` field.

Similar to part 2, this makes it easier for stack unwinders to unwind through Wasm frames.

Differential Revision: https://phabricator.services.mozilla.com/D151252
This commit is contained in:
Jan de Mooij 2022-07-09 17:33:57 +00:00
Родитель e431a956e1
Коммит 2ff99f68dc
5 изменённых файлов: 43 добавлений и 76 удалений

Просмотреть файл

@ -103,7 +103,7 @@ static constexpr uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitOrJitEntryFPTag),
static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitFPTag),
"FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged "
"wasm exit fp");

Просмотреть файл

@ -121,7 +121,7 @@ class JitActivation : public Activation {
bool hasExitFP() const { return !!packedExitFP_; }
uint8_t* jsOrWasmExitFP() const {
if (hasWasmExitFP()) {
return wasm::Frame::toJitEntryCaller(packedExitFP_);
return wasm::Frame::untagExitFP(packedExitFP_);
}
return packedExitFP_;
}
@ -207,21 +207,19 @@ class JitActivation : public Activation {
void setLastProfilingCallSite(void* ptr) { lastProfilingCallSite_ = ptr; }
// WebAssembly specific attributes.
bool hasWasmExitFP() const {
return wasm::Frame::isExitOrJitEntryFP(packedExitFP_);
}
bool hasWasmExitFP() const { return wasm::Frame::isExitFP(packedExitFP_); }
wasm::Frame* wasmExitFP() const {
MOZ_ASSERT(hasWasmExitFP());
return reinterpret_cast<wasm::Frame*>(
wasm::Frame::toJitEntryCaller(packedExitFP_));
wasm::Frame::untagExitFP(packedExitFP_));
}
wasm::Instance* wasmExitInstance() const {
return wasm::GetNearestEffectiveInstance(wasmExitFP());
}
void setWasmExitFP(const wasm::Frame* fp) {
if (fp) {
MOZ_ASSERT(!wasm::Frame::isExitOrJitEntryFP(fp));
packedExitFP_ = wasm::Frame::addExitOrJitEntryFPTag(fp);
MOZ_ASSERT(!wasm::Frame::isExitFP(fp));
packedExitFP_ = wasm::Frame::addExitFPTag(fp);
MOZ_ASSERT(hasWasmExitFP());
} else {
packedExitFP_ = nullptr;

Просмотреть файл

@ -272,18 +272,8 @@ namespace wasm {
class Instance;
// Bit set as the lowest bit of a frame pointer, used in two different mutually
// exclusive situations:
// - either it's a low bit tag in a FramePointer value read from the
// Frame::callerFP of an inner wasm frame. This indicates the previous call
// frame has been set up by a JIT caller that directly called into a wasm
// function's body. This is only stored in Frame::callerFP for a wasm frame
// called from JIT code, and thus it can not appear in a JitActivation's
// exitFP.
// - or it's the low big tag set when exiting wasm code in JitActivation's
// exitFP.
constexpr uintptr_t ExitOrJitEntryFPTag = 0x1;
// Bit tag set when exiting wasm code in JitActivation's exitFP.
constexpr uintptr_t ExitFPTag = 0x1;
// wasm::Frame represents the bytes pushed by the call instruction and the
// fixed prologue generated by wasm::GenerateCallablePrologue.
@ -326,36 +316,29 @@ class Frame {
uint8_t* rawCaller() const { return callerFP_; }
Frame* wasmCaller() const {
MOZ_ASSERT(!callerIsExitOrJitEntryFP());
return reinterpret_cast<Frame*>(callerFP_);
}
Frame* wasmCaller() const { return reinterpret_cast<Frame*>(callerFP_); }
bool callerIsExitOrJitEntryFP() const {
return isExitOrJitEntryFP(callerFP_);
}
uint8_t* jitEntryCaller() const { return toJitEntryCaller(callerFP_); }
uint8_t* jitEntryCaller() const { return callerFP_; }
static const Frame* fromUntaggedWasmExitFP(const void* savedFP) {
MOZ_ASSERT(!isExitOrJitEntryFP(savedFP));
MOZ_ASSERT(!isExitFP(savedFP));
return reinterpret_cast<const Frame*>(savedFP);
}
static bool isExitOrJitEntryFP(const void* fp) {
return reinterpret_cast<uintptr_t>(fp) & ExitOrJitEntryFPTag;
static bool isExitFP(const void* fp) {
return reinterpret_cast<uintptr_t>(fp) & ExitFPTag;
}
static uint8_t* toJitEntryCaller(const void* fp) {
MOZ_ASSERT(isExitOrJitEntryFP(fp));
static uint8_t* untagExitFP(const void* fp) {
MOZ_ASSERT(isExitFP(fp));
return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) &
~ExitOrJitEntryFPTag);
~ExitFPTag);
}
static uint8_t* addExitOrJitEntryFPTag(const Frame* fp) {
MOZ_ASSERT(!isExitOrJitEntryFP(fp));
static uint8_t* addExitFPTag(const Frame* fp) {
MOZ_ASSERT(!isExitFP(fp));
return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) |
ExitOrJitEntryFPTag);
ExitFPTag);
}
};

Просмотреть файл

@ -134,9 +134,6 @@ static inline void AssertDirectJitCall(const void* fp) {
// pointing in the middle of the exit frame, right before the exit
// footer; ensure the exit frame type is the expected one.
#ifdef DEBUG
if (Frame::isExitOrJitEntryFP(fp)) {
fp = Frame::toJitEntryCaller(fp);
}
auto* jitCaller = (ExitFrameLayout*)fp;
MOZ_ASSERT(jitCaller->footer()->type() ==
jit::ExitFrameType::DirectWasmJitCall);
@ -148,21 +145,19 @@ void WasmFrameIter::popFrame() {
code_ = LookupCode(returnAddress, &codeRange_);
if (!code_) {
// We run into a frame pointer which has the low bit set,
// indicating this is a direct call from the jit into the wasm
// function's body. The call stack resembles this at this point:
// This is a direct call from the jit into the wasm function's body. The
// call stack resembles this at this point:
//
// |---------------------|
// | JIT FRAME |
// | JIT FAKE EXIT FRAME | <-- tagged fp_->callerFP_
// | JIT FAKE EXIT FRAME | <-- fp_->callerFP_
// | WASM FRAME | <-- fp_
// |---------------------|
//
// fp_->callerFP_ points to the fake exit frame set up by the jit caller,
// and the return-address-to-fp is in JIT code, thus doesn't belong to any
// wasm instance's code (in particular, there's no associated CodeRange).
// Mark the frame as such and untag FP.
MOZ_ASSERT(fp_->callerIsExitOrJitEntryFP());
// Mark the frame as such.
AssertDirectJitCall(fp_->jitEntryCaller());
unwoundJitCallerFP_ = fp_->jitEntryCaller();
@ -427,10 +422,10 @@ void wasm::SetExitFP(MacroAssembler& masm, ExitReason reason,
Imm32(reason.encode()),
Address(scratch, JitActivation::offsetOfEncodedWasmExitReason()));
masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer);
masm.orPtr(Imm32(ExitFPTag), FramePointer);
masm.storePtr(FramePointer,
Address(scratch, JitActivation::offsetOfPackedExitFP()));
masm.andPtr(Imm32(int32_t(~ExitOrJitEntryFPTag)), FramePointer);
masm.andPtr(Imm32(int32_t(~ExitFPTag)), FramePointer);
}
void wasm::ClearExitFP(MacroAssembler& masm, Register scratch) {
@ -793,7 +788,7 @@ static void AssertNoWasmExitFPInJitExit(MacroAssembler& masm) {
Label ok;
masm.branchTestPtr(Assembler::Zero,
Address(scratch, JitActivation::offsetOfPackedExitFP()),
Imm32(ExitOrJitEntryFPTag), &ok);
Imm32(ExitFPTag), &ok);
masm.breakpoint();
masm.bind(&ok);
#endif
@ -978,8 +973,8 @@ void ProfilingFrameIterator::initFromExitFP(const Frame* fp) {
code_ = LookupCode(fp->returnAddress(), &codeRange_);
if (!code_) {
// This is a direct call from the JIT, the caller FP is pointing to a
// tagged JIT caller's frame.
// This is a direct call from the JIT, the caller FP is pointing to the JIT
// caller's frame.
AssertDirectJitCall(fp->jitEntryCaller());
unwoundJitCallerFP_ = fp->jitEntryCaller();
@ -1047,7 +1042,6 @@ const Instance* js::wasm::GetNearestEffectiveInstance(const Frame* fp) {
if (!code) {
// It is a direct call from JIT.
MOZ_ASSERT(fp->callerIsExitOrJitEntryFP());
AssertDirectJitCall(fp->jitEntryCaller());
return ExtractCalleeInstanceFromFrameWithInstances(fp);
}
@ -1081,12 +1075,11 @@ bool js::wasm::StartUnwinding(const RegisterState& registers,
void** const sp = (void**)registers.sp;
// The frame pointer might be:
// - in the process of tagging/untagging when calling into the JITs;
// make sure it's untagged.
// - tagged by an direct JIT call.
// - in the process of tagging/untagging when calling into C++ code (this
// happens in wasm::SetExitFP); make sure it's untagged.
// - unreliable if it's not been set yet, in prologues.
uint8_t* fp = Frame::isExitOrJitEntryFP(registers.fp)
? Frame::toJitEntryCaller(registers.fp)
uint8_t* fp = Frame::isExitFP(registers.fp)
? Frame::untagExitFP(registers.fp)
: reinterpret_cast<uint8_t*>(registers.fp);
// Get the CodeRange describing pc and the base address to which the
@ -1214,10 +1207,7 @@ bool js::wasm::StartUnwinding(const RegisterState& registers,
} else if (offsetFromEntry == PushedFP) {
// The full Frame has been pushed; fp is still the caller's fp.
const auto* frame = Frame::fromUntaggedWasmExitFP(sp);
DebugOnly<const uint8_t*> caller = frame->callerIsExitOrJitEntryFP()
? frame->jitEntryCaller()
: frame->rawCaller();
MOZ_ASSERT(caller == fp);
MOZ_ASSERT(frame->rawCaller() == fp);
fixedPC = frame->returnAddress();
fixedFP = fp;
AssertMatchesCallSite(fixedPC, fixedFP);
@ -1317,7 +1307,7 @@ bool js::wasm::StartUnwinding(const RegisterState& registers,
}
// On the error return path, FP might be set to FailFP. Ignore these
// transient frames.
if (intptr_t(fp) == (FailFP & ~ExitOrJitEntryFPTag)) {
if (intptr_t(fp) == (FailFP & ~ExitFPTag)) {
return false;
}
// Set fixedFP to the address of the JitFrameLayout on the stack.
@ -1415,13 +1405,11 @@ void ProfilingFrameIterator::operator++() {
code_ = LookupCode(callerPC_, &codeRange_);
if (!code_) {
// The parent frame is an inlined wasm call, the tagged FP points to
// the fake exit frame.
// The parent frame is an inlined wasm call, callerFP_ points to the fake
// exit frame.
MOZ_ASSERT(!codeRange_);
AssertDirectJitCall(callerFP_);
unwoundJitCallerFP_ = Frame::isExitOrJitEntryFP(callerFP_)
? Frame::toJitEntryCaller(callerFP_)
: callerFP_;
unwoundJitCallerFP_ = callerFP_;
MOZ_ASSERT(done());
return;
}

Просмотреть файл

@ -817,10 +817,10 @@ static bool GenerateInterpEntry(MacroAssembler& masm, const FuncExport& fe,
SetupABIArguments(masm, fe, argv, scratch);
// Setup wasm register state. Ensure the frame pointer passed by the C++
// caller doesn't have the ExitOrJitEntryFPTag bit set to not confuse frame
// iterators. This bit shouldn't be set if C++ code is using frame pointers,
// so this has no effect on native stack unwinders.
masm.andPtr(Imm32(int32_t(~ExitOrJitEntryFPTag)), FramePointer);
// caller doesn't have the ExitFPTag bit set to not confuse frame iterators.
// This bit shouldn't be set if C++ code is using frame pointers, so this has
// no effect on native stack unwinders.
masm.andPtr(Imm32(int32_t(~ExitFPTag)), FramePointer);
masm.loadWasmPinnedRegsFromInstance();
@ -1452,12 +1452,10 @@ void wasm::GenerateDirectCallFromJit(MacroAssembler& masm, const FuncExport& fe,
// frame pointer. We also use this to restore the frame pointer after the
// call.
*callOffset = masm.buildFakeExitFrame(scratch);
// FP := ExitFrameLayout*
masm.moveStackPtrTo(FramePointer);
masm.loadJSContext(scratch);
masm.enterFakeExitFrame(scratch, scratch, ExitFrameType::DirectWasmJitCall);
// FP := ExitFrameLayout* | ExitOrJitEntryFPTag
masm.moveStackPtrTo(FramePointer);
masm.addPtr(Imm32(ExitFooterFrame::Size()), FramePointer);
masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer);
// Move stack arguments to their final locations.
unsigned bytesNeeded = StackArgBytesForWasmABI(fe.funcType());