diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/wasm/WasmFrameIter.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm/WasmFrameIter.cpp')
-rw-r--r-- | js/src/wasm/WasmFrameIter.cpp | 1539 |
1 files changed, 1539 insertions, 0 deletions
diff --git a/js/src/wasm/WasmFrameIter.cpp b/js/src/wasm/WasmFrameIter.cpp new file mode 100644 index 0000000000..e7dd88ccad --- /dev/null +++ b/js/src/wasm/WasmFrameIter.cpp @@ -0,0 +1,1539 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm/WasmFrameIter.h" + +#include "jit/JitFrames.h" +#include "vm/JitActivation.h" // js::jit::JitActivation +#include "vm/JSContext.h" +#include "wasm/WasmInstance.h" +#include "wasm/WasmStubs.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; +using namespace js::wasm; + +using mozilla::DebugOnly; +using mozilla::Maybe; + +/*****************************************************************************/ +// WasmFrameIter implementation + +WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp) + : activation_(activation), + code_(nullptr), + codeRange_(nullptr), + lineOrBytecode_(0), + fp_(fp ? fp : activation->wasmExitFP()), + tls_(nullptr), + unwoundIonCallerFP_(nullptr), + unwoundIonFrameType_(jit::FrameType(-1)), + unwind_(Unwind::False), + unwoundAddressOfReturnAddress_(nullptr), + resumePCinCurrentFrame_(nullptr) { + MOZ_ASSERT(fp_); + tls_ = GetNearestEffectiveTls(fp_); + + // When the stack is captured during a trap (viz., to create the .stack + // for an Error object), use the pc/bytecode information captured by the + // signal handler in the runtime. Take care not to use this trap unwind + // state for wasm frames in the middle of a JitActivation, i.e., wasm frames + // that called into JIT frames before the trap. + + if (activation->isWasmTrapping() && fp_ == activation->wasmExitFP()) { + const TrapData& trapData = activation->wasmTrapData(); + void* unwoundPC = trapData.unwoundPC; + + code_ = &tls_->instance->code(); + MOZ_ASSERT(code_ == LookupCode(unwoundPC)); + + codeRange_ = code_->lookupFuncRange(unwoundPC); + MOZ_ASSERT(codeRange_); + + lineOrBytecode_ = trapData.bytecodeOffset; + + MOZ_ASSERT(!done()); + return; + } + + // Otherwise, execution exits wasm code via an exit stub which sets exitFP + // to the exit stub's frame. Thus, in this case, we want to start iteration + // at the caller of the exit frame, whose Code, CodeRange and CallSite are + // indicated by the returnAddress of the exit stub's frame. If the caller + // was Ion, we can just skip the wasm frames. + + popFrame(); + MOZ_ASSERT(!done() || unwoundIonCallerFP_); +} + +bool WasmFrameIter::done() const { + MOZ_ASSERT(!!fp_ == !!code_); + MOZ_ASSERT(!!fp_ == !!codeRange_); + return !fp_; +} + +void WasmFrameIter::operator++() { + MOZ_ASSERT(!done()); + + // When the iterator is set to unwind, each time the iterator pops a frame, + // the JitActivation is updated so that the just-popped frame is no longer + // visible. This is necessary since Debugger::onLeaveFrame is called before + // popping each frame and, once onLeaveFrame is called for a given frame, + // that frame must not be visible to subsequent stack iteration (or it + // could be added as a "new" frame just as it becomes garbage). When the + // frame is trapping, then exitFP is included in the callstack (otherwise, + // it is skipped, as explained above). So to unwind the innermost frame, we + // just clear the trapping state. + + if (unwind_ == Unwind::True) { + if (activation_->isWasmTrapping()) { + activation_->finishWasmTrap(); + } + activation_->setWasmExitFP(fp_); + } + + popFrame(); +} + +void WasmFrameIter::popFrame() { + if (fp_->callerIsExitOrJitEntryFP()) { + // 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: + // + // |---------------------| + // | JIT FRAME | + // | JIT FAKE EXIT FRAME | <-- tagged 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(!LookupCode(fp_->returnAddress())); + + unwoundIonCallerFP_ = fp_->jitEntryCaller(); + unwoundIonFrameType_ = FrameType::Exit; + + if (unwind_ == Unwind::True) { + activation_->setJSExitFP(unwoundIonCallerFP()); + unwoundAddressOfReturnAddress_ = fp_->addressOfReturnAddress(); + } + + fp_ = nullptr; + code_ = nullptr; + codeRange_ = nullptr; + + MOZ_ASSERT(done()); + return; + } + + Frame* prevFP = fp_; + fp_ = fp_->wasmCaller(); + resumePCinCurrentFrame_ = prevFP->returnAddress(); + + if (!fp_) { + code_ = nullptr; + codeRange_ = nullptr; + + if (unwind_ == Unwind::True) { + // We're exiting via the interpreter entry; we can safely reset + // exitFP. + activation_->setWasmExitFP(nullptr); + unwoundAddressOfReturnAddress_ = prevFP->addressOfReturnAddress(); + } + + MOZ_ASSERT(done()); + return; + } + + void* returnAddress = prevFP->returnAddress(); + code_ = LookupCode(returnAddress, &codeRange_); + MOZ_ASSERT(codeRange_); + + if (codeRange_->isJitEntry()) { + // This wasm function has been called through the generic JIT entry by + // a JIT caller, so the call stack resembles this: + // + // |---------------------| + // | JIT FRAME | + // | JSJIT TO WASM EXIT | <-- fp_ + // | WASM JIT ENTRY | <-- prevFP (already unwound) + // | WASM FRAME | (already unwound) + // |---------------------| + // + // The next value of FP is just a regular jit frame used as a marker to + // know that we should transition to a JSJit frame iterator. + unwoundIonCallerFP_ = reinterpret_cast<uint8_t*>(fp_); + unwoundIonFrameType_ = FrameType::JSJitToWasm; + + fp_ = nullptr; + code_ = nullptr; + codeRange_ = nullptr; + + if (unwind_ == Unwind::True) { + activation_->setJSExitFP(unwoundIonCallerFP()); + unwoundAddressOfReturnAddress_ = prevFP->addressOfReturnAddress(); + } + + MOZ_ASSERT(done()); + return; + } + + MOZ_ASSERT(codeRange_->kind() == CodeRange::Function); + + const CallSite* callsite = code_->lookupCallSite(returnAddress); + MOZ_ASSERT(callsite); + + if (callsite->mightBeCrossInstance()) { + tls_ = ExtractCallerTlsFromFrameWithTls(prevFP); + } + + MOZ_ASSERT(code_ == &tls()->instance->code()); + lineOrBytecode_ = callsite->lineOrBytecode(); + + MOZ_ASSERT(!done()); +} + +const char* WasmFrameIter::filename() const { + MOZ_ASSERT(!done()); + return code_->metadata().filename.get(); +} + +const char16_t* WasmFrameIter::displayURL() const { + MOZ_ASSERT(!done()); + return code_->metadata().displayURL(); +} + +bool WasmFrameIter::mutedErrors() const { + MOZ_ASSERT(!done()); + return code_->metadata().mutedErrors(); +} + +JSAtom* WasmFrameIter::functionDisplayAtom() const { + MOZ_ASSERT(!done()); + + JSContext* cx = activation_->cx(); + JSAtom* atom = instance()->getFuncDisplayAtom(cx, codeRange_->funcIndex()); + if (!atom) { + cx->clearPendingException(); + return cx->names().empty; + } + + return atom; +} + +unsigned WasmFrameIter::lineOrBytecode() const { + MOZ_ASSERT(!done()); + return lineOrBytecode_; +} + +uint32_t WasmFrameIter::funcIndex() const { + MOZ_ASSERT(!done()); + return codeRange_->funcIndex(); +} + +unsigned WasmFrameIter::computeLine(uint32_t* column) const { + if (instance()->isAsmJS()) { + if (column) { + *column = 1; + } + return lineOrBytecode_; + } + + // As a terrible hack to avoid changing the tons of places that pass around + // (url, line, column) tuples to instead passing around a Variant that + // stores a (url, func-index, bytecode-offset) tuple for wasm frames, + // wasm stuffs its tuple into the existing (url, line, column) tuple, + // tagging the high bit of the column to indicate "this is a wasm frame". + // When knowing clients see this bit, they shall render the tuple + // (url, line, column|bit) as "url:wasm-function[column]:0xline" according + // to the WebAssembly Web API's Developer-Facing Display Conventions. + // https://webassembly.github.io/spec/web-api/index.html#conventions + // The wasm bytecode offset continues to be passed as the JS line to avoid + // breaking existing devtools code written when this used to be the case. + + MOZ_ASSERT(!(codeRange_->funcIndex() & ColumnBit)); + if (column) { + *column = codeRange_->funcIndex() | ColumnBit; + } + return lineOrBytecode_; +} + +Instance* WasmFrameIter::instance() const { + MOZ_ASSERT(!done()); + return tls_->instance; +} + +void** WasmFrameIter::unwoundAddressOfReturnAddress() const { + MOZ_ASSERT(done()); + MOZ_ASSERT(unwind_ == Unwind::True); + MOZ_ASSERT(unwoundAddressOfReturnAddress_); + return unwoundAddressOfReturnAddress_; +} + +bool WasmFrameIter::debugEnabled() const { + MOZ_ASSERT(!done()); + + // Only non-imported functions can have debug frames. + // + // Metadata::debugEnabled is only set if debugging is actually enabled (both + // requested, and available via baseline compilation), and Tier::Debug code + // will be available. + return code_->metadata().debugEnabled && + codeRange_->funcIndex() >= + code_->metadata(Tier::Debug).funcImports.length(); +} + +DebugFrame* WasmFrameIter::debugFrame() const { + MOZ_ASSERT(!done()); + return DebugFrame::from(fp_); +} + +jit::FrameType WasmFrameIter::unwoundIonFrameType() const { + MOZ_ASSERT(unwoundIonCallerFP_); + MOZ_ASSERT(unwoundIonFrameType_ != jit::FrameType(-1)); + return unwoundIonFrameType_; +} + +uint8_t* WasmFrameIter::resumePCinCurrentFrame() const { + if (resumePCinCurrentFrame_) { + return resumePCinCurrentFrame_; + } + MOZ_ASSERT(activation_->isWasmTrapping()); + // The next instruction is the instruction following the trap instruction. + return (uint8_t*)activation_->wasmTrapData().resumePC; +} + +/*****************************************************************************/ +// Prologue/epilogue code generation + +// These constants reflect statically-determined offsets in the +// prologue/epilogue. The offsets are dynamically asserted during code +// generation. +#if defined(JS_CODEGEN_X64) +static const unsigned PushedRetAddr = 0; +static const unsigned PushedFP = 1; +static const unsigned SetFP = 4; +static const unsigned PoppedFP = 0; +#elif defined(JS_CODEGEN_X86) +static const unsigned PushedRetAddr = 0; +static const unsigned PushedFP = 1; +static const unsigned SetFP = 3; +static const unsigned PoppedFP = 0; +#elif defined(JS_CODEGEN_ARM) +static const unsigned BeforePushRetAddr = 0; +static const unsigned PushedRetAddr = 4; +static const unsigned PushedFP = 8; +static const unsigned SetFP = 12; +static const unsigned PoppedFP = 0; +#elif defined(JS_CODEGEN_ARM64) +// On ARM64 we do not use push or pop; the prologues and epilogues are +// structured differently due to restrictions on SP alignment. Even so, +// PushedRetAddr and PushedFP are used in some restricted contexts +// and must be superficially meaningful. +static const unsigned BeforePushRetAddr = 0; +static const unsigned PushedRetAddr = 8; +static const unsigned PushedFP = 12; +static const unsigned SetFP = 16; +static const unsigned PoppedFP = 4; +static_assert(BeforePushRetAddr == 0, "Required by StartUnwinding"); +static_assert(PushedFP > PushedRetAddr, "Required by StartUnwinding"); +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) +static const unsigned PushedRetAddr = 8; +static const unsigned PushedFP = 12; +static const unsigned SetFP = 16; +static const unsigned PoppedFP = 4; +#elif defined(JS_CODEGEN_NONE) +// Synthetic values to satisfy asserts and avoid compiler warnings. +static const unsigned PushedRetAddr = 0; +static const unsigned PushedFP = 1; +static const unsigned SetFP = 2; +static const unsigned PoppedFP = 3; +#else +# error "Unknown architecture!" +#endif +static constexpr unsigned SetJitEntryFP = PushedRetAddr + SetFP - PushedFP; + +static void LoadActivation(MacroAssembler& masm, const Register& dest) { + // WasmCall pushes a JitActivation. + masm.loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), dest); + masm.loadPtr(Address(dest, JSContext::offsetOfActivation()), dest); +} + +void wasm::SetExitFP(MacroAssembler& masm, ExitReason reason, + Register scratch) { + MOZ_ASSERT(!reason.isNone()); + + LoadActivation(masm, scratch); + + masm.store32( + Imm32(reason.encode()), + Address(scratch, JitActivation::offsetOfEncodedWasmExitReason())); + + masm.orPtr(Imm32(ExitOrJitEntryFPTag), FramePointer); + masm.storePtr(FramePointer, + Address(scratch, JitActivation::offsetOfPackedExitFP())); + masm.andPtr(Imm32(int32_t(~ExitOrJitEntryFPTag)), FramePointer); +} + +void wasm::ClearExitFP(MacroAssembler& masm, Register scratch) { + LoadActivation(masm, scratch); + masm.storePtr(ImmWord(0x0), + Address(scratch, JitActivation::offsetOfPackedExitFP())); + masm.store32( + Imm32(0x0), + Address(scratch, JitActivation::offsetOfEncodedWasmExitReason())); +} + +static void GenerateCallablePrologue(MacroAssembler& masm, uint32_t* entry) { + masm.setFramePushed(0); + + // ProfilingFrameIterator needs to know the offsets of several key + // instructions from entry. To save space, we make these offsets static + // constants and assert that they match the actual codegen below. On ARM, + // this requires AutoForbidPoolsAndNops to prevent a constant pool from being + // randomly inserted between two instructions. +#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + { + *entry = masm.currentOffset(); + + masm.subFromStackPtr(Imm32(sizeof(Frame))); + masm.storePtr(ra, Address(StackPointer, Frame::returnAddressOffset())); + MOZ_ASSERT_IF(!masm.oom(), PushedRetAddr == masm.currentOffset() - *entry); + masm.storePtr(FramePointer, Address(StackPointer, Frame::callerFPOffset())); + MOZ_ASSERT_IF(!masm.oom(), PushedFP == masm.currentOffset() - *entry); + masm.moveStackPtrTo(FramePointer); + MOZ_ASSERT_IF(!masm.oom(), SetFP == masm.currentOffset() - *entry); + } +#elif defined(JS_CODEGEN_ARM64) + { + // We do not use the PseudoStackPointer. + MOZ_ASSERT(masm.GetStackPointer64().code() == sp.code()); + + AutoForbidPoolsAndNops afp(&masm, + /* number of instructions in scope = */ 4); + + *entry = masm.currentOffset(); + + masm.Sub(sp, sp, sizeof(Frame)); + masm.Str(ARMRegister(lr, 64), MemOperand(sp, Frame::returnAddressOffset())); + MOZ_ASSERT_IF(!masm.oom(), PushedRetAddr == masm.currentOffset() - *entry); + masm.Str(ARMRegister(FramePointer, 64), + MemOperand(sp, Frame::callerFPOffset())); + MOZ_ASSERT_IF(!masm.oom(), PushedFP == masm.currentOffset() - *entry); + masm.Mov(ARMRegister(FramePointer, 64), sp); + MOZ_ASSERT_IF(!masm.oom(), SetFP == masm.currentOffset() - *entry); + } +#else + { +# if defined(JS_CODEGEN_ARM) + AutoForbidPoolsAndNops afp(&masm, + /* number of instructions in scope = */ 6); + + *entry = masm.currentOffset(); + + static_assert(BeforePushRetAddr == 0); + masm.push(lr); +# else + *entry = masm.currentOffset(); + // The x86/x64 call instruction pushes the return address. +# endif + + MOZ_ASSERT_IF(!masm.oom(), PushedRetAddr == masm.currentOffset() - *entry); + masm.push(FramePointer); + MOZ_ASSERT_IF(!masm.oom(), PushedFP == masm.currentOffset() - *entry); + masm.moveStackPtrTo(FramePointer); + MOZ_ASSERT_IF(!masm.oom(), SetFP == masm.currentOffset() - *entry); + } +#endif +} + +static void GenerateCallableEpilogue(MacroAssembler& masm, unsigned framePushed, + ExitReason reason, uint32_t* ret) { + if (framePushed) { + masm.freeStack(framePushed); + } + + if (!reason.isNone()) { + ClearExitFP(masm, ABINonArgReturnVolatileReg); + } + + DebugOnly<uint32_t> poppedFP; + +#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + + masm.loadPtr(Address(StackPointer, Frame::callerFPOffset()), FramePointer); + poppedFP = masm.currentOffset(); + masm.loadPtr(Address(StackPointer, Frame::returnAddressOffset()), ra); + + *ret = masm.currentOffset(); + masm.as_jr(ra); + masm.addToStackPtr(Imm32(sizeof(Frame))); + +#elif defined(JS_CODEGEN_ARM64) + + // We do not use the PseudoStackPointer. + MOZ_ASSERT(masm.GetStackPointer64().code() == sp.code()); + + AutoForbidPoolsAndNops afp(&masm, /* number of instructions in scope = */ 4); + + masm.Ldr(ARMRegister(FramePointer, 64), + MemOperand(sp, Frame::callerFPOffset())); + poppedFP = masm.currentOffset(); + + masm.Ldr(ARMRegister(lr, 64), MemOperand(sp, Frame::returnAddressOffset())); + *ret = masm.currentOffset(); + + masm.Add(sp, sp, sizeof(Frame)); + masm.Ret(ARMRegister(lr, 64)); + +#else + // Forbid pools for the same reason as described in GenerateCallablePrologue. +# if defined(JS_CODEGEN_ARM) + AutoForbidPoolsAndNops afp(&masm, /* number of instructions in scope = */ 6); +# endif + + // There is an important ordering constraint here: fp must be repointed to + // the caller's frame before any field of the frame currently pointed to by + // fp is popped: asynchronous signal handlers (which use stack space + // starting at sp) could otherwise clobber these fields while they are still + // accessible via fp (fp fields are read during frame iteration which is + // *also* done asynchronously). + + masm.pop(FramePointer); + poppedFP = masm.currentOffset(); + + *ret = masm.currentOffset(); + masm.ret(); + +#endif + + MOZ_ASSERT_IF(!masm.oom(), PoppedFP == *ret - poppedFP); +} + +static void EnsureOffset(MacroAssembler& masm, uint32_t base, + uint32_t targetOffset) { + MOZ_ASSERT(targetOffset % CodeAlignment == 0); + MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() - base <= targetOffset); + + while (masm.currentOffset() - base < targetOffset) { + masm.nopAlign(CodeAlignment); + if (masm.currentOffset() - base < targetOffset) { + masm.nop(); + } + } + + MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() - base == targetOffset); +} + +void wasm::GenerateFunctionPrologue(MacroAssembler& masm, + const TypeIdDesc& funcTypeId, + const Maybe<uint32_t>& tier1FuncIndex, + FuncOffsets* offsets) { + // These constants reflect statically-determined offsets + // between a function's checked call entry and a tail's entry. + static_assert(WasmCheckedCallEntryOffset % CodeAlignment == 0, + "code aligned"); + static_assert(WasmCheckedTailEntryOffset % CodeAlignment == 0, + "code aligned"); + + // Flush pending pools so they do not get dumped between the 'begin' and + // 'uncheckedCallEntry' offsets since the difference must be less than + // UINT8_MAX to be stored in CodeRange::funcbeginToUncheckedCallEntry_. + masm.flushBuffer(); + masm.haltingAlign(CodeAlignment); + + // We are going to generate the next code layout: + // --------------------------------------------- + // checked call entry: callable prologue + // checked tail entry: check signature + // jump functionBody + // unchecked call entry: callable prologue + // functionBody + // ----------------------------------------------- + // checked call entry - used for call_indirect when we have to check the + // signature. + // checked tail entry - used by trampolines which already had pushed Frame + // on the calleeās behalf. + // unchecked call entry - used for regular direct same-instance calls. + + Label functionBody; + + // Generate checked call entry. The BytecodeOffset of the trap is fixed up to + // be the bytecode offset of the callsite by JitActivation::startWasmTrap. + offsets->begin = masm.currentOffset(); + MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() - offsets->begin == + WasmCheckedCallEntryOffset); + uint32_t dummy; + GenerateCallablePrologue(masm, &dummy); + + EnsureOffset(masm, offsets->begin, WasmCheckedTailEntryOffset); + switch (funcTypeId.kind()) { + case TypeIdDescKind::Global: { + Register scratch = WasmTableCallScratchReg0; + masm.loadWasmGlobalPtr(funcTypeId.globalDataOffset(), scratch); + masm.branchPtr(Assembler::Condition::Equal, WasmTableCallSigReg, scratch, + &functionBody); + masm.wasmTrap(Trap::IndirectCallBadSig, BytecodeOffset(0)); + break; + } + case TypeIdDescKind::Immediate: { + masm.branch32(Assembler::Condition::Equal, WasmTableCallSigReg, + Imm32(funcTypeId.immediate()), &functionBody); + masm.wasmTrap(Trap::IndirectCallBadSig, BytecodeOffset(0)); + break; + } + case TypeIdDescKind::None: + masm.jump(&functionBody); + break; + } + + // The checked entries might have generated a small constant pool in case of + // immediate comparison. + masm.flushBuffer(); + + // Generate unchecked call entry: + masm.nopAlign(CodeAlignment); + GenerateCallablePrologue(masm, &offsets->uncheckedCallEntry); + masm.bind(&functionBody); + + // Tiering works as follows. The Code owns a jumpTable, which has one + // pointer-sized element for each function up to the largest funcIndex in + // the module. Each table element is an address into the Tier-1 or the + // Tier-2 function at that index; the elements are updated when Tier-2 code + // becomes available. The Tier-1 function will unconditionally jump to this + // address. The table elements are written racily but without tearing when + // Tier-2 compilation is finished. + // + // The address in the table is either to the instruction following the jump + // in Tier-1 code, or into the function prologue after the standard setup in + // Tier-2 code. Effectively, Tier-1 code performs standard frame setup on + // behalf of whatever code it jumps to, and the target code allocates its + // own frame in whatever way it wants. + if (tier1FuncIndex) { + Register scratch = ABINonArgReg0; + masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, jumpTable)), scratch); + masm.jump(Address(scratch, *tier1FuncIndex * sizeof(uintptr_t))); + } + + offsets->tierEntry = masm.currentOffset(); + + MOZ_ASSERT(masm.framePushed() == 0); +} + +void wasm::GenerateFunctionEpilogue(MacroAssembler& masm, unsigned framePushed, + FuncOffsets* offsets) { + // Inverse of GenerateFunctionPrologue: + MOZ_ASSERT(masm.framePushed() == framePushed); + GenerateCallableEpilogue(masm, framePushed, ExitReason::None(), + &offsets->ret); + MOZ_ASSERT(masm.framePushed() == 0); +} + +void wasm::GenerateExitPrologue(MacroAssembler& masm, unsigned framePushed, + ExitReason reason, CallableOffsets* offsets) { + masm.haltingAlign(CodeAlignment); + + GenerateCallablePrologue(masm, &offsets->begin); + + // This frame will be exiting compiled code to C++ so record the fp and + // reason in the JitActivation so the frame iterators can unwind. + SetExitFP(masm, reason, ABINonArgReturnVolatileReg); + + MOZ_ASSERT(masm.framePushed() == 0); + masm.reserveStack(framePushed); +} + +void wasm::GenerateExitEpilogue(MacroAssembler& masm, unsigned framePushed, + ExitReason reason, CallableOffsets* offsets) { + // Inverse of GenerateExitPrologue: + MOZ_ASSERT(masm.framePushed() == framePushed); + GenerateCallableEpilogue(masm, framePushed, reason, &offsets->ret); + MOZ_ASSERT(masm.framePushed() == 0); +} + +static void AssertNoWasmExitFPInJitExit(MacroAssembler& masm) { + // As a general stack invariant, if Activation::packedExitFP is tagged as + // wasm, it must point to a valid wasm::Frame. The JIT exit stub calls into + // JIT code and thus does not really exit, thus, when entering/leaving the + // JIT exit stub from/to normal wasm code, packedExitFP is not tagged wasm. +#ifdef DEBUG + Register scratch = ABINonArgReturnReg0; + LoadActivation(masm, scratch); + + Label ok; + masm.branchTestPtr(Assembler::Zero, + Address(scratch, JitActivation::offsetOfPackedExitFP()), + Imm32(ExitOrJitEntryFPTag), &ok); + masm.breakpoint(); + masm.bind(&ok); +#endif +} + +void wasm::GenerateJitExitPrologue(MacroAssembler& masm, unsigned framePushed, + CallableOffsets* offsets) { + masm.haltingAlign(CodeAlignment); + + GenerateCallablePrologue(masm, &offsets->begin); + AssertNoWasmExitFPInJitExit(masm); + + MOZ_ASSERT(masm.framePushed() == 0); + masm.reserveStack(framePushed); +} + +void wasm::GenerateJitExitEpilogue(MacroAssembler& masm, unsigned framePushed, + CallableOffsets* offsets) { + // Inverse of GenerateJitExitPrologue: + MOZ_ASSERT(masm.framePushed() == framePushed); + AssertNoWasmExitFPInJitExit(masm); + GenerateCallableEpilogue(masm, framePushed, ExitReason::None(), + &offsets->ret); + MOZ_ASSERT(masm.framePushed() == 0); +} + +void wasm::GenerateJitEntryPrologue(MacroAssembler& masm, Offsets* offsets) { + masm.haltingAlign(CodeAlignment); + + { +#if defined(JS_CODEGEN_ARM) + AutoForbidPoolsAndNops afp(&masm, + /* number of instructions in scope = */ 2); + offsets->begin = masm.currentOffset(); + static_assert(BeforePushRetAddr == 0); + masm.push(lr); +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + offsets->begin = masm.currentOffset(); + masm.push(ra); +#elif defined(JS_CODEGEN_ARM64) + AutoForbidPoolsAndNops afp(&masm, + /* number of instructions in scope = */ 3); + offsets->begin = masm.currentOffset(); + static_assert(BeforePushRetAddr == 0); + // Subtract from SP first as SP must be aligned before offsetting. + masm.Sub(sp, sp, 8); + masm.storePtr(lr, Address(masm.getStackPointer(), 0)); + masm.adjustFrame(8); +#else + // The x86/x64 call instruction pushes the return address. + offsets->begin = masm.currentOffset(); +#endif + MOZ_ASSERT_IF(!masm.oom(), + PushedRetAddr == masm.currentOffset() - offsets->begin); + + // Save jit frame pointer, so unwinding from wasm to jit frames is trivial. + masm.moveStackPtrTo(FramePointer); + MOZ_ASSERT_IF(!masm.oom(), + SetJitEntryFP == masm.currentOffset() - offsets->begin); + } + + masm.setFramePushed(0); +} + +/*****************************************************************************/ +// ProfilingFrameIterator + +ProfilingFrameIterator::ProfilingFrameIterator() + : code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), + exitReason_(ExitReason::Fixed::None) { + MOZ_ASSERT(done()); +} + +ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation) + : code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), + exitReason_(activation.wasmExitReason()) { + initFromExitFP(activation.wasmExitFP()); +} + +ProfilingFrameIterator::ProfilingFrameIterator(const Frame* fp) + : code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), + exitReason_(ExitReason::Fixed::ImportJit) { + MOZ_ASSERT(fp); + initFromExitFP(fp); +} + +static inline void AssertDirectJitCall(const void* fp) { + // Called via an inlined fast JIT to wasm call: in this case, FP is + // 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); +#endif +} + +static inline void AssertMatchesCallSite(void* callerPC, uint8_t* callerFP) { +#ifdef DEBUG + const CodeRange* callerCodeRange; + const Code* code = LookupCode(callerPC, &callerCodeRange); + + if (!code) { + AssertDirectJitCall(callerFP); + return; + } + + MOZ_ASSERT(callerCodeRange); + + if (callerCodeRange->isInterpEntry()) { + MOZ_ASSERT(callerFP == nullptr); + return; + } + + if (callerCodeRange->isJitEntry()) { + MOZ_ASSERT(callerFP != nullptr); + return; + } + + const CallSite* callsite = code->lookupCallSite(callerPC); + MOZ_ASSERT(callsite); +#endif +} + +void ProfilingFrameIterator::initFromExitFP(const Frame* fp) { + MOZ_ASSERT(fp); + stackAddress_ = (void*)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. + AssertDirectJitCall(fp->jitEntryCaller()); + + unwoundIonCallerFP_ = fp->jitEntryCaller(); + MOZ_ASSERT(done()); + return; + } + + MOZ_ASSERT(codeRange_); + + // Since we don't have the pc for fp, start unwinding at the caller of fp. + // This means that the innermost frame is skipped. This is fine because: + // - for import exit calls, the innermost frame is a thunk, so the first + // frame that shows up is the function calling the import; + // - for Math and other builtin calls, we note the absence of an exit + // reason and inject a fake "builtin" frame; and + switch (codeRange_->kind()) { + case CodeRange::InterpEntry: + callerPC_ = nullptr; + callerFP_ = nullptr; + codeRange_ = nullptr; + exitReason_ = ExitReason(ExitReason::Fixed::FakeInterpEntry); + break; + case CodeRange::JitEntry: + callerPC_ = nullptr; + callerFP_ = nullptr; + unwoundIonCallerFP_ = fp->rawCaller(); + break; + case CodeRange::Function: + fp = fp->wasmCaller(); + callerPC_ = fp->returnAddress(); + callerFP_ = fp->rawCaller(); + AssertMatchesCallSite(callerPC_, callerFP_); + break; + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::BuiltinThunk: + case CodeRange::TrapExit: + case CodeRange::DebugTrap: + case CodeRange::Throw: + case CodeRange::FarJumpIsland: + MOZ_CRASH("Unexpected CodeRange kind"); + } + + MOZ_ASSERT(!done()); +} + +static bool isSignatureCheckFail(uint32_t offsetInCode, + const CodeRange* codeRange) { + if (!codeRange->isFunction()) { + return false; + } + // checked call entry: 1. push Frame + // 2. set FP + // 3. signature check <--- check if we are here. + // 4. jump 7 + // unchecked call entry: 5. push Frame + // 6. set FP + // 7. function's code + return offsetInCode < codeRange->funcUncheckedCallEntry() && + (offsetInCode - codeRange->funcCheckedCallEntry()) > SetFP; +} + +const TlsData* js::wasm::GetNearestEffectiveTls(const Frame* fp) { + while (true) { + if (fp->callerIsExitOrJitEntryFP()) { + // It is a direct call from JIT. + MOZ_ASSERT(!LookupCode(fp->returnAddress())); + return ExtractCalleeTlsFromFrameWithTls(fp); + } + + uint8_t* returnAddress = fp->returnAddress(); + const CodeRange* codeRange = nullptr; + const Code* code = LookupCode(returnAddress, &codeRange); + MOZ_ASSERT(codeRange); + + if (codeRange->isEntry()) { + return ExtractCalleeTlsFromFrameWithTls(fp); + } + + MOZ_ASSERT(codeRange->kind() == CodeRange::Function); + MOZ_ASSERT(code); + const CallSite* callsite = code->lookupCallSite(returnAddress); + if (callsite->mightBeCrossInstance()) { + return ExtractCalleeTlsFromFrameWithTls(fp); + } + + fp = fp->wasmCaller(); + } +} + +TlsData* js::wasm::GetNearestEffectiveTls(Frame* fp) { + return const_cast<TlsData*>( + GetNearestEffectiveTls(const_cast<const Frame*>(fp))); +} + +bool js::wasm::StartUnwinding(const RegisterState& registers, + UnwindState* unwindState, bool* unwoundCaller) { + // Shorthands. + uint8_t* const pc = (uint8_t*)registers.pc; + 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. + // - unreliable if it's not been set yet, in prologues. + uint8_t* fp = Frame::isExitOrJitEntryFP(registers.fp) + ? Frame::toJitEntryCaller(registers.fp) + : reinterpret_cast<uint8_t*>(registers.fp); + + // Get the CodeRange describing pc and the base address to which the + // CodeRange is relative. If the pc is not in a wasm module or a builtin + // thunk, then execution must be entering from or leaving to the C++ caller + // that pushed the JitActivation. + const CodeRange* codeRange; + uint8_t* codeBase; + const Code* code = nullptr; + + const CodeSegment* codeSegment = LookupCodeSegment(pc, &codeRange); + if (codeSegment) { + code = &codeSegment->code(); + codeBase = codeSegment->base(); + MOZ_ASSERT(codeRange); + } else if (!LookupBuiltinThunk(pc, &codeRange, &codeBase)) { + return false; + } + + // When the pc is inside the prologue/epilogue, the innermost call's Frame + // is not complete and thus fp points to the second-to-innermost call's + // Frame. Since fp can only tell you about its caller, naively unwinding + // while pc is in the prologue/epilogue would skip the second-to-innermost + // call. To avoid this problem, we use the static structure of the code in + // the prologue and epilogue to do the Right Thing. + uint32_t offsetInCode = pc - codeBase; + MOZ_ASSERT(offsetInCode >= codeRange->begin()); + MOZ_ASSERT(offsetInCode < codeRange->end()); + + // Compute the offset of the pc from the (unchecked call) entry of the code + // range. The checked call entry and the unchecked call entry have common + // prefix, so pc before signature check in the checked call entry is + // equivalent to the pc of the unchecked-call-entry. Thus, we can simplify the + // below case analysis by redirecting all pc-in-checked-call-entry before + // signature check cases to the pc-at-unchecked-call-entry case. + uint32_t offsetFromEntry; + if (codeRange->isFunction()) { + if (offsetInCode < codeRange->funcUncheckedCallEntry()) { + offsetFromEntry = offsetInCode - codeRange->funcCheckedCallEntry(); + } else { + offsetFromEntry = offsetInCode - codeRange->funcUncheckedCallEntry(); + } + } else { + offsetFromEntry = offsetInCode - codeRange->begin(); + } + + // Most cases end up unwinding to the caller state; not unwinding is the + // exception here. + *unwoundCaller = true; + + uint8_t* fixedFP = nullptr; + void* fixedPC = nullptr; + switch (codeRange->kind()) { + case CodeRange::Function: + case CodeRange::FarJumpIsland: + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::BuiltinThunk: + case CodeRange::DebugTrap: +#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + if (codeRange->isThunk()) { + // The FarJumpIsland sequence temporary scrambles ra. + // Don't unwind to caller. + fixedPC = pc; + fixedFP = fp; + *unwoundCaller = false; + AssertMatchesCallSite( + Frame::fromUntaggedWasmExitFP(fp)->returnAddress(), + Frame::fromUntaggedWasmExitFP(fp)->rawCaller()); + } else if (offsetFromEntry < PushedFP) { + // On MIPS we rely on register state instead of state saved on + // stack until the wasm::Frame is completely built. + // On entry the return address is in ra (registers.lr) and + // fp holds the caller's fp. + fixedPC = (uint8_t*)registers.lr; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); + } else +#elif defined(JS_CODEGEN_ARM64) + if (offsetFromEntry < PushedFP || codeRange->isThunk()) { + // Constraints above ensure that this covers BeforePushRetAddr and + // PushedRetAddr. + // + // On ARM64 we subtract the size of the Frame from SP and then store + // values into the stack. Execution can be interrupted at various + // places in that sequence. We rely on the register state for our + // values. + fixedPC = (uint8_t*)registers.lr; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); + } else +#elif defined(JS_CODEGEN_ARM) + if (offsetFromEntry == BeforePushRetAddr || codeRange->isThunk()) { + // The return address is still in lr and fp holds the caller's fp. + fixedPC = (uint8_t*)registers.lr; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); + } else +#endif + if (offsetFromEntry == PushedRetAddr || codeRange->isThunk()) { + // The return address has been pushed on the stack but fp still + // points to the caller's fp. + fixedPC = sp[0]; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); + } 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); + fixedPC = frame->returnAddress(); + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); +#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + } else if (offsetInCode >= codeRange->ret() - PoppedFP && + offsetInCode <= codeRange->ret()) { + // The fixedFP field of the Frame has been loaded into fp. + // The ra and TLS might also be loaded, but the Frame structure is + // still on stack, so we can acess the ra form there. + MOZ_ASSERT(*sp == fp); + fixedPC = Frame::fromUntaggedWasmExitFP(sp)->returnAddress(); + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); +#elif defined(JS_CODEGEN_ARM64) + // The stack pointer does not move until all values have + // been restored so several cases can be coalesced here. + } else if (offsetInCode >= codeRange->ret() - PoppedFP && + offsetInCode <= codeRange->ret()) { + fixedPC = Frame::fromUntaggedWasmExitFP(sp)->returnAddress(); + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); +#else + } else if (offsetInCode >= codeRange->ret() - PoppedFP && + offsetInCode < codeRange->ret()) { + // The fixedFP field of the Frame has been popped into fp. + fixedPC = sp[1]; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); + } else if (offsetInCode == codeRange->ret()) { + // Both the TLS and fixedFP fields have been popped and fp now + // points to the caller's frame. + fixedPC = sp[0]; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); +#endif + } else { + if (codeRange->kind() == CodeRange::ImportJitExit) { + // The jit exit contains a range where the value of FP can't be + // trusted. Technically, we could recover fp from sp, but since + // the range is so short, for now just drop the stack. + if (offsetInCode >= codeRange->jitExitUntrustedFPStart() && + offsetInCode < codeRange->jitExitUntrustedFPEnd()) { + return false; + } + } + + if (isSignatureCheckFail(offsetInCode, codeRange)) { + // Frame have been pushed and FP has been set. + const auto* frame = Frame::fromUntaggedWasmExitFP(fp); + fixedFP = frame->rawCaller(); + fixedPC = frame->returnAddress(); + AssertMatchesCallSite(fixedPC, fixedFP); + break; + } + + // Not in the prologue/epilogue. + fixedPC = pc; + fixedFP = fp; + *unwoundCaller = false; + AssertMatchesCallSite( + Frame::fromUntaggedWasmExitFP(fp)->returnAddress(), + Frame::fromUntaggedWasmExitFP(fp)->rawCaller()); + break; + } + break; + case CodeRange::TrapExit: + // These code stubs execute after the prologue/epilogue have completed + // so pc/fp contains the right values here. + fixedPC = pc; + fixedFP = fp; + *unwoundCaller = false; + AssertMatchesCallSite(Frame::fromUntaggedWasmExitFP(fp)->returnAddress(), + Frame::fromUntaggedWasmExitFP(fp)->rawCaller()); + break; + case CodeRange::InterpEntry: + // The entry trampoline is the final frame in an wasm JitActivation. The + // entry trampoline also doesn't GeneratePrologue/Epilogue so we can't + // use the general unwinding logic above. + break; + case CodeRange::JitEntry: + // There's a jit frame above the current one; we don't care about pc + // since the Jit entry frame is a jit frame which can be considered as + // an exit frame. +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ + defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + if (offsetFromEntry < PushedRetAddr) { + // We haven't pushed the jit return address yet, thus the jit + // frame is incomplete. During profiling frame iteration, it means + // that the jit profiling frame iterator won't be able to unwind + // this frame; drop it. + return false; + } +#endif + fixedFP = + offsetFromEntry < SetJitEntryFP ? reinterpret_cast<uint8_t*>(sp) : fp; + fixedPC = nullptr; + + // On the error return path, FP might be set to FailFP. Ignore these + // transient frames. + if (intptr_t(fixedFP) == (FailFP & ~ExitOrJitEntryFPTag)) { + return false; + } + break; + case CodeRange::Throw: + // The throw stub executes a small number of instructions before popping + // the entire activation. To simplify testing, we simply pretend throw + // stubs have already popped the entire stack. + return false; + } + + unwindState->code = code; + unwindState->codeRange = codeRange; + unwindState->fp = fixedFP; + unwindState->pc = fixedPC; + return true; +} + +ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation, + const RegisterState& state) + : code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + unwoundIonCallerFP_(nullptr), + exitReason_(ExitReason::Fixed::None) { + // Let wasmExitFP take precedence to StartUnwinding when it is set since + // during the body of an exit stub, the register state may not be valid + // causing StartUnwinding() to abandon unwinding this activation. + if (activation.hasWasmExitFP()) { + exitReason_ = activation.wasmExitReason(); + initFromExitFP(activation.wasmExitFP()); + return; + } + + bool unwoundCaller; + UnwindState unwindState; + if (!StartUnwinding(state, &unwindState, &unwoundCaller)) { + MOZ_ASSERT(done()); + return; + } + + MOZ_ASSERT(unwindState.codeRange); + + if (unwoundCaller) { + callerFP_ = unwindState.fp; + callerPC_ = unwindState.pc; + // In the case of a function call, if the original FP value is tagged, + // then we're being called through a direct JIT call (the interpreter + // and the jit entry don't set FP's low bit). We can't observe + // transient tagged values of FP (during wasm::SetExitFP) here because + // StartUnwinding would not have unwound then. + if (unwindState.codeRange->isFunction() && + Frame::isExitOrJitEntryFP(reinterpret_cast<uint8_t*>(state.fp))) { + unwoundIonCallerFP_ = callerFP_; + } + } else { + callerFP_ = Frame::fromUntaggedWasmExitFP(unwindState.fp)->rawCaller(); + callerPC_ = Frame::fromUntaggedWasmExitFP(unwindState.fp)->returnAddress(); + // See comment above. The only way to get a tagged FP here means that + // the caller is a fast JIT caller which called into a wasm function. + if (Frame::isExitOrJitEntryFP(callerFP_)) { + MOZ_ASSERT(unwindState.codeRange->isFunction()); + unwoundIonCallerFP_ = Frame::toJitEntryCaller(callerFP_); + } + } + + if (unwindState.codeRange->isJitEntry()) { + MOZ_ASSERT(!unwoundIonCallerFP_); + unwoundIonCallerFP_ = callerFP_; + } + + if (unwindState.codeRange->isInterpEntry()) { + unwindState.codeRange = nullptr; + exitReason_ = ExitReason(ExitReason::Fixed::FakeInterpEntry); + } + + code_ = unwindState.code; + codeRange_ = unwindState.codeRange; + stackAddress_ = state.sp; + MOZ_ASSERT(!done()); +} + +void ProfilingFrameIterator::operator++() { + if (!exitReason_.isNone()) { + DebugOnly<bool> wasInterpEntry = exitReason_.isInterpEntry(); + exitReason_ = ExitReason::None(); + MOZ_ASSERT((!codeRange_) == wasInterpEntry); + MOZ_ASSERT(done() == wasInterpEntry); + return; + } + + if (unwoundIonCallerFP_) { + MOZ_ASSERT(codeRange_->isFunction() || codeRange_->isJitEntry()); + callerPC_ = nullptr; + callerFP_ = nullptr; + codeRange_ = nullptr; + MOZ_ASSERT(done()); + return; + } + + if (!callerPC_) { + MOZ_ASSERT(!callerFP_); + codeRange_ = nullptr; + MOZ_ASSERT(done()); + return; + } + + if (!callerFP_) { + MOZ_ASSERT(LookupCode(callerPC_, &codeRange_) == code_); + MOZ_ASSERT(codeRange_->kind() == CodeRange::InterpEntry); + exitReason_ = ExitReason(ExitReason::Fixed::FakeInterpEntry); + codeRange_ = nullptr; + callerPC_ = nullptr; + MOZ_ASSERT(!done()); + return; + } + + code_ = LookupCode(callerPC_, &codeRange_); + + if (!code_ && Frame::isExitOrJitEntryFP(callerFP_)) { + // The parent frame is an inlined wasm call, the tagged FP points to + // the fake exit frame. + MOZ_ASSERT(!codeRange_); + AssertDirectJitCall(callerFP_); + unwoundIonCallerFP_ = Frame::toJitEntryCaller(callerFP_); + MOZ_ASSERT(done()); + return; + } + + MOZ_ASSERT(codeRange_); + + if (codeRange_->isJitEntry()) { + unwoundIonCallerFP_ = callerFP_; + MOZ_ASSERT(!done()); + return; + } + + MOZ_ASSERT(code_ == + &GetNearestEffectiveTls(Frame::fromUntaggedWasmExitFP(callerFP_)) + ->instance->code()); + + switch (codeRange_->kind()) { + case CodeRange::Function: + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::BuiltinThunk: + case CodeRange::TrapExit: + case CodeRange::DebugTrap: + case CodeRange::FarJumpIsland: { + stackAddress_ = callerFP_; + const auto* frame = Frame::fromUntaggedWasmExitFP(callerFP_); + callerPC_ = frame->returnAddress(); + AssertMatchesCallSite(callerPC_, frame->rawCaller()); + callerFP_ = frame->rawCaller(); + break; + } + case CodeRange::InterpEntry: + MOZ_CRASH("should have had null caller fp"); + case CodeRange::JitEntry: + MOZ_CRASH("should have been guarded above"); + case CodeRange::Throw: + MOZ_CRASH("code range doesn't have frame"); + } + + MOZ_ASSERT(!done()); +} + +static const char* ThunkedNativeToDescription(SymbolicAddress func) { + MOZ_ASSERT(NeedsBuiltinThunk(func)); + switch (func) { + case SymbolicAddress::HandleDebugTrap: + case SymbolicAddress::HandleThrow: + case SymbolicAddress::HandleTrap: + case SymbolicAddress::CallImport_General: + case SymbolicAddress::CoerceInPlace_ToInt32: + case SymbolicAddress::CoerceInPlace_ToNumber: + case SymbolicAddress::CoerceInPlace_ToBigInt: + case SymbolicAddress::BoxValue_Anyref: + MOZ_ASSERT(!NeedsBuiltinThunk(func), + "not in sync with NeedsBuiltinThunk"); + break; + case SymbolicAddress::ToInt32: + return "call to asm.js native ToInt32 coercion (in wasm)"; + case SymbolicAddress::DivI64: + return "call to native i64.div_s (in wasm)"; + case SymbolicAddress::UDivI64: + return "call to native i64.div_u (in wasm)"; + case SymbolicAddress::ModI64: + return "call to native i64.rem_s (in wasm)"; + case SymbolicAddress::UModI64: + return "call to native i64.rem_u (in wasm)"; + case SymbolicAddress::TruncateDoubleToUint64: + return "call to native i64.trunc_u/f64 (in wasm)"; + case SymbolicAddress::TruncateDoubleToInt64: + return "call to native i64.trunc_s/f64 (in wasm)"; + case SymbolicAddress::SaturatingTruncateDoubleToUint64: + return "call to native i64.trunc_u:sat/f64 (in wasm)"; + case SymbolicAddress::SaturatingTruncateDoubleToInt64: + return "call to native i64.trunc_s:sat/f64 (in wasm)"; + case SymbolicAddress::Uint64ToDouble: + return "call to native f64.convert_u/i64 (in wasm)"; + case SymbolicAddress::Uint64ToFloat32: + return "call to native f32.convert_u/i64 (in wasm)"; + case SymbolicAddress::Int64ToDouble: + return "call to native f64.convert_s/i64 (in wasm)"; + case SymbolicAddress::Int64ToFloat32: + return "call to native f32.convert_s/i64 (in wasm)"; +#if defined(JS_CODEGEN_ARM) + case SymbolicAddress::aeabi_idivmod: + return "call to native i32.div_s (in wasm)"; + case SymbolicAddress::aeabi_uidivmod: + return "call to native i32.div_u (in wasm)"; +#endif + case SymbolicAddress::AllocateBigInt: + return "call to native Allocate<BigInt, NoGC> (in wasm)"; + case SymbolicAddress::ModD: + return "call to asm.js native f64 % (mod)"; + case SymbolicAddress::SinD: + return "call to asm.js native f64 Math.sin"; + case SymbolicAddress::CosD: + return "call to asm.js native f64 Math.cos"; + case SymbolicAddress::TanD: + return "call to asm.js native f64 Math.tan"; + case SymbolicAddress::ASinD: + return "call to asm.js native f64 Math.asin"; + case SymbolicAddress::ACosD: + return "call to asm.js native f64 Math.acos"; + case SymbolicAddress::ATanD: + return "call to asm.js native f64 Math.atan"; + case SymbolicAddress::CeilD: + return "call to native f64.ceil (in wasm)"; + case SymbolicAddress::CeilF: + return "call to native f32.ceil (in wasm)"; + case SymbolicAddress::FloorD: + return "call to native f64.floor (in wasm)"; + case SymbolicAddress::FloorF: + return "call to native f32.floor (in wasm)"; + case SymbolicAddress::TruncD: + return "call to native f64.trunc (in wasm)"; + case SymbolicAddress::TruncF: + return "call to native f32.trunc (in wasm)"; + case SymbolicAddress::NearbyIntD: + return "call to native f64.nearest (in wasm)"; + case SymbolicAddress::NearbyIntF: + return "call to native f32.nearest (in wasm)"; + case SymbolicAddress::ExpD: + return "call to asm.js native f64 Math.exp"; + case SymbolicAddress::LogD: + return "call to asm.js native f64 Math.log"; + case SymbolicAddress::PowD: + return "call to asm.js native f64 Math.pow"; + case SymbolicAddress::ATan2D: + return "call to asm.js native f64 Math.atan2"; + case SymbolicAddress::MemoryGrow: + return "call to native memory.grow (in wasm)"; + case SymbolicAddress::MemorySize: + return "call to native memory.size (in wasm)"; + case SymbolicAddress::WaitI32: + return "call to native i32.wait (in wasm)"; + case SymbolicAddress::WaitI64: + return "call to native i64.wait (in wasm)"; + case SymbolicAddress::Wake: + return "call to native wake (in wasm)"; + case SymbolicAddress::CoerceInPlace_JitEntry: + return "out-of-line coercion for jit entry arguments (in wasm)"; + case SymbolicAddress::ReportV128JSCall: + return "jit call to v128 wasm function"; + case SymbolicAddress::MemCopy: + case SymbolicAddress::MemCopyShared: + return "call to native memory.copy function"; + case SymbolicAddress::DataDrop: + return "call to native data.drop function"; + case SymbolicAddress::MemFill: + case SymbolicAddress::MemFillShared: + return "call to native memory.fill function"; + case SymbolicAddress::MemInit: + return "call to native memory.init function"; + case SymbolicAddress::TableCopy: + return "call to native table.copy function"; + case SymbolicAddress::TableFill: + return "call to native table.fill function"; + case SymbolicAddress::ElemDrop: + return "call to native elem.drop function"; + case SymbolicAddress::TableGet: + return "call to native table.get function"; + case SymbolicAddress::TableGrow: + return "call to native table.grow function"; + case SymbolicAddress::TableInit: + return "call to native table.init function"; + case SymbolicAddress::TableSet: + return "call to native table.set function"; + case SymbolicAddress::TableSize: + return "call to native table.size function"; + case SymbolicAddress::RefFunc: + return "call to native ref.func function"; + case SymbolicAddress::PreBarrierFiltering: + return "call to native filtering GC prebarrier (in wasm)"; + case SymbolicAddress::PostBarrier: + return "call to native GC postbarrier (in wasm)"; + case SymbolicAddress::PostBarrierFiltering: + return "call to native filtering GC postbarrier (in wasm)"; + case SymbolicAddress::StructNew: + return "call to native struct.new (in wasm)"; + case SymbolicAddress::StructNarrow: + return "call to native struct.narrow (in wasm)"; +#if defined(JS_CODEGEN_MIPS32) + case SymbolicAddress::js_jit_gAtomic64Lock: + MOZ_CRASH(); +#endif +#ifdef WASM_CODEGEN_DEBUG + case SymbolicAddress::PrintI32: + case SymbolicAddress::PrintPtr: + case SymbolicAddress::PrintF32: + case SymbolicAddress::PrintF64: + case SymbolicAddress::PrintText: +#endif + case SymbolicAddress::Limit: + break; + } + return "?"; +} + +const char* ProfilingFrameIterator::label() const { + MOZ_ASSERT(!done()); + + // Use the same string for both time inside and under so that the two + // entries will be coalesced by the profiler. + // Must be kept in sync with /tools/profiler/tests/test_asm.js + static const char importJitDescription[] = "fast exit trampoline (in wasm)"; + static const char importInterpDescription[] = + "slow exit trampoline (in wasm)"; + static const char builtinNativeDescription[] = + "fast exit trampoline to native (in wasm)"; + static const char trapDescription[] = "trap handling (in wasm)"; + static const char debugTrapDescription[] = "debug trap handling (in wasm)"; + + if (!exitReason_.isFixed()) { + return ThunkedNativeToDescription(exitReason_.symbolic()); + } + + switch (exitReason_.fixed()) { + case ExitReason::Fixed::None: + break; + case ExitReason::Fixed::ImportJit: + return importJitDescription; + case ExitReason::Fixed::ImportInterp: + return importInterpDescription; + case ExitReason::Fixed::BuiltinNative: + return builtinNativeDescription; + case ExitReason::Fixed::Trap: + return trapDescription; + case ExitReason::Fixed::DebugTrap: + return debugTrapDescription; + case ExitReason::Fixed::FakeInterpEntry: + return "slow entry trampoline (in wasm)"; + } + + switch (codeRange_->kind()) { + case CodeRange::Function: + return code_->profilingLabel(codeRange_->funcIndex()); + case CodeRange::InterpEntry: + MOZ_CRASH("should be an ExitReason"); + case CodeRange::JitEntry: + return "fast entry trampoline (in wasm)"; + case CodeRange::ImportJitExit: + return importJitDescription; + case CodeRange::BuiltinThunk: + return builtinNativeDescription; + case CodeRange::ImportInterpExit: + return importInterpDescription; + case CodeRange::TrapExit: + return trapDescription; + case CodeRange::DebugTrap: + return debugTrapDescription; + case CodeRange::FarJumpIsland: + return "interstitial (in wasm)"; + case CodeRange::Throw: + MOZ_CRASH("does not have a frame"); + } + + MOZ_CRASH("bad code range kind"); +} |