diff options
Diffstat (limited to 'js/src/wasm/WasmFrameIterator.cpp')
-rw-r--r-- | js/src/wasm/WasmFrameIterator.cpp | 891 |
1 files changed, 891 insertions, 0 deletions
diff --git a/js/src/wasm/WasmFrameIterator.cpp b/js/src/wasm/WasmFrameIterator.cpp new file mode 100644 index 000000000..4b616b5bc --- /dev/null +++ b/js/src/wasm/WasmFrameIterator.cpp @@ -0,0 +1,891 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * 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/WasmFrameIterator.h" + +#include "wasm/WasmInstance.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; +using namespace js::wasm; + +using mozilla::DebugOnly; +using mozilla::Swap; + +/*****************************************************************************/ +// FrameIterator implementation + +static void* +ReturnAddressFromFP(void* fp) +{ + return reinterpret_cast<Frame*>(fp)->returnAddress; +} + +static uint8_t* +CallerFPFromFP(void* fp) +{ + return reinterpret_cast<Frame*>(fp)->callerFP; +} + +FrameIterator::FrameIterator() + : activation_(nullptr), + code_(nullptr), + callsite_(nullptr), + codeRange_(nullptr), + fp_(nullptr), + pc_(nullptr), + missingFrameMessage_(false) +{ + MOZ_ASSERT(done()); +} + +FrameIterator::FrameIterator(const WasmActivation& activation) + : activation_(&activation), + code_(nullptr), + callsite_(nullptr), + codeRange_(nullptr), + fp_(activation.fp()), + pc_(nullptr), + missingFrameMessage_(false) +{ + if (fp_) { + settle(); + return; + } + + void* pc = activation.resumePC(); + if (!pc) { + MOZ_ASSERT(done()); + return; + } + pc_ = (uint8_t*)pc; + + code_ = activation_->compartment()->wasm.lookupCode(pc); + MOZ_ASSERT(code_); + + const CodeRange* codeRange = code_->lookupRange(pc); + MOZ_ASSERT(codeRange); + + if (codeRange->kind() == CodeRange::Function) + codeRange_ = codeRange; + else + missingFrameMessage_ = true; + + MOZ_ASSERT(!done()); +} + +bool +FrameIterator::done() const +{ + return !codeRange_ && !missingFrameMessage_; +} + +void +FrameIterator::operator++() +{ + MOZ_ASSERT(!done()); + if (fp_) { + DebugOnly<uint8_t*> oldfp = fp_; + fp_ += callsite_->stackDepth(); + MOZ_ASSERT_IF(code_->profilingEnabled(), fp_ == CallerFPFromFP(oldfp)); + settle(); + } else if (codeRange_) { + MOZ_ASSERT(codeRange_); + codeRange_ = nullptr; + missingFrameMessage_ = true; + } else { + MOZ_ASSERT(missingFrameMessage_); + missingFrameMessage_ = false; + } +} + +void +FrameIterator::settle() +{ + void* returnAddress = ReturnAddressFromFP(fp_); + + code_ = activation_->compartment()->wasm.lookupCode(returnAddress); + MOZ_ASSERT(code_); + + codeRange_ = code_->lookupRange(returnAddress); + MOZ_ASSERT(codeRange_); + + switch (codeRange_->kind()) { + case CodeRange::Function: + pc_ = (uint8_t*)returnAddress; + callsite_ = code_->lookupCallSite(returnAddress); + MOZ_ASSERT(callsite_); + break; + case CodeRange::Entry: + fp_ = nullptr; + pc_ = nullptr; + code_ = nullptr; + codeRange_ = nullptr; + MOZ_ASSERT(done()); + break; + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::TrapExit: + case CodeRange::Inline: + case CodeRange::FarJumpIsland: + MOZ_CRASH("Should not encounter an exit during iteration"); + } +} + +const char* +FrameIterator::filename() const +{ + MOZ_ASSERT(!done()); + return code_->metadata().filename.get(); +} + +const char16_t* +FrameIterator::displayURL() const +{ + MOZ_ASSERT(!done()); + return code_->metadata().displayURL(); +} + +bool +FrameIterator::mutedErrors() const +{ + MOZ_ASSERT(!done()); + return code_->metadata().mutedErrors(); +} + +JSAtom* +FrameIterator::functionDisplayAtom() const +{ + MOZ_ASSERT(!done()); + + JSContext* cx = activation_->cx(); + + if (missingFrameMessage_) { + const char* msg = "asm.js/wasm frames may be missing; enable the profiler before running " + "to see all frames"; + JSAtom* atom = Atomize(cx, msg, strlen(msg)); + if (!atom) { + cx->clearPendingException(); + return cx->names().empty; + } + + return atom; + } + + MOZ_ASSERT(codeRange_); + + JSAtom* atom = code_->getFuncAtom(cx, codeRange_->funcIndex()); + if (!atom) { + cx->clearPendingException(); + return cx->names().empty; + } + + return atom; +} + +unsigned +FrameIterator::lineOrBytecode() const +{ + MOZ_ASSERT(!done()); + return callsite_ ? callsite_->lineOrBytecode() + : (codeRange_ ? codeRange_->funcLineOrBytecode() : 0); +} + +/*****************************************************************************/ +// Prologue/epilogue code generation + +// These constants reflect statically-determined offsets in the profiling +// prologue/epilogue. The offsets are dynamically asserted during code +// generation. +#if defined(JS_CODEGEN_X64) +# if defined(DEBUG) +static const unsigned PushedRetAddr = 0; +static const unsigned PostStorePrePopFP = 0; +# endif +static const unsigned PushedFP = 23; +static const unsigned StoredFP = 30; +#elif defined(JS_CODEGEN_X86) +# if defined(DEBUG) +static const unsigned PushedRetAddr = 0; +static const unsigned PostStorePrePopFP = 0; +# endif +static const unsigned PushedFP = 14; +static const unsigned StoredFP = 17; +#elif defined(JS_CODEGEN_ARM) +static const unsigned PushedRetAddr = 4; +static const unsigned PushedFP = 24; +static const unsigned StoredFP = 28; +static const unsigned PostStorePrePopFP = 4; +#elif defined(JS_CODEGEN_ARM64) +static const unsigned PushedRetAddr = 0; +static const unsigned PushedFP = 0; +static const unsigned StoredFP = 0; +static const unsigned PostStorePrePopFP = 0; +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) +static const unsigned PushedRetAddr = 8; +static const unsigned PushedFP = 32; +static const unsigned StoredFP = 36; +static const unsigned PostStorePrePopFP = 4; +#elif defined(JS_CODEGEN_NONE) +# if defined(DEBUG) +static const unsigned PushedRetAddr = 0; +static const unsigned PostStorePrePopFP = 0; +# endif +static const unsigned PushedFP = 1; +static const unsigned StoredFP = 1; +#else +# error "Unknown architecture!" +#endif + +static void +PushRetAddr(MacroAssembler& masm) +{ +#if defined(JS_CODEGEN_ARM) + masm.push(lr); +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + masm.push(ra); +#else + // The x86/x64 call instruction pushes the return address. +#endif +} + +// Generate a prologue that maintains WasmActivation::fp as the virtual frame +// pointer so that ProfilingFrameIterator can walk the stack at any pc in +// generated code. +static void +GenerateProfilingPrologue(MacroAssembler& masm, unsigned framePushed, ExitReason reason, + ProfilingOffsets* offsets) +{ + Register scratch = ABINonArgReg0; + + // 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 AutoForbidPools to prevent a constant pool from being + // randomly inserted between two instructions. + { +#if defined(JS_CODEGEN_ARM) + AutoForbidPools afp(&masm, /* number of instructions in scope = */ 7); +#endif + + offsets->begin = masm.currentOffset(); + + PushRetAddr(masm); + MOZ_ASSERT_IF(!masm.oom(), PushedRetAddr == masm.currentOffset() - offsets->begin); + + masm.loadWasmActivationFromSymbolicAddress(scratch); + masm.push(Address(scratch, WasmActivation::offsetOfFP())); + MOZ_ASSERT_IF(!masm.oom(), PushedFP == masm.currentOffset() - offsets->begin); + + masm.storePtr(masm.getStackPointer(), Address(scratch, WasmActivation::offsetOfFP())); + MOZ_ASSERT_IF(!masm.oom(), StoredFP == masm.currentOffset() - offsets->begin); + } + + if (reason != ExitReason::None) + masm.store32(Imm32(int32_t(reason)), Address(scratch, WasmActivation::offsetOfExitReason())); + + if (framePushed) + masm.subFromStackPtr(Imm32(framePushed)); +} + +// Generate the inverse of GenerateProfilingPrologue. +static void +GenerateProfilingEpilogue(MacroAssembler& masm, unsigned framePushed, ExitReason reason, + ProfilingOffsets* offsets) +{ + Register scratch = ABINonArgReturnReg0; +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ + defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + Register scratch2 = ABINonArgReturnReg1; +#endif + + if (framePushed) + masm.addToStackPtr(Imm32(framePushed)); + + masm.loadWasmActivationFromSymbolicAddress(scratch); + + if (reason != ExitReason::None) { + masm.store32(Imm32(int32_t(ExitReason::None)), + Address(scratch, WasmActivation::offsetOfExitReason())); + } + + // ProfilingFrameIterator assumes fixed offsets of the last few + // instructions from profilingReturn, so AutoForbidPools to ensure that + // unintended instructions are not automatically inserted. + { +#if defined(JS_CODEGEN_ARM) + AutoForbidPools afp(&masm, /* number of instructions in scope = */ 4); +#endif + + // sp protects the stack from clobber via asynchronous signal handlers + // and the async interrupt exit. Since activation.fp can be read at any + // time and still points to the current frame, be careful to only update + // sp after activation.fp has been repointed to the caller's frame. +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ + defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + masm.loadPtr(Address(masm.getStackPointer(), 0), scratch2); + masm.storePtr(scratch2, Address(scratch, WasmActivation::offsetOfFP())); + DebugOnly<uint32_t> prePop = masm.currentOffset(); + masm.addToStackPtr(Imm32(sizeof(void *))); + MOZ_ASSERT_IF(!masm.oom(), PostStorePrePopFP == masm.currentOffset() - prePop); +#else + masm.pop(Address(scratch, WasmActivation::offsetOfFP())); + MOZ_ASSERT(PostStorePrePopFP == 0); +#endif + + offsets->profilingReturn = masm.currentOffset(); + masm.ret(); + } +} + +// In profiling mode, we need to maintain fp so that we can unwind the stack at +// any pc. In non-profiling mode, the only way to observe WasmActivation::fp is +// to call out to C++ so, as an optimization, we don't update fp. To avoid +// recompilation when the profiling mode is toggled, we generate both prologues +// a priori and switch between prologues when the profiling mode is toggled. +// Specifically, ToggleProfiling patches all callsites to either call the +// profiling or non-profiling entry point. +void +wasm::GenerateFunctionPrologue(MacroAssembler& masm, unsigned framePushed, const SigIdDesc& sigId, + FuncOffsets* offsets) +{ +#if defined(JS_CODEGEN_ARM) + // Flush pending pools so they do not get dumped between the 'begin' and + // 'entry' offsets since the difference must be less than UINT8_MAX. + masm.flushBuffer(); +#endif + + masm.haltingAlign(CodeAlignment); + + GenerateProfilingPrologue(masm, framePushed, ExitReason::None, offsets); + Label body; + masm.jump(&body); + + // Generate table entry thunk: + masm.haltingAlign(CodeAlignment); + offsets->tableEntry = masm.currentOffset(); + TrapOffset trapOffset(0); // ignored by masm.wasmEmitTrapOutOfLineCode + TrapDesc trap(trapOffset, Trap::IndirectCallBadSig, masm.framePushed()); + switch (sigId.kind()) { + case SigIdDesc::Kind::Global: { + Register scratch = WasmTableCallScratchReg; + masm.loadWasmGlobalPtr(sigId.globalDataOffset(), scratch); + masm.branchPtr(Assembler::Condition::NotEqual, WasmTableCallSigReg, scratch, trap); + break; + } + case SigIdDesc::Kind::Immediate: + masm.branch32(Assembler::Condition::NotEqual, WasmTableCallSigReg, Imm32(sigId.immediate()), trap); + break; + case SigIdDesc::Kind::None: + break; + } + offsets->tableProfilingJump = masm.nopPatchableToNearJump().offset(); + + // Generate normal prologue: + masm.nopAlign(CodeAlignment); + offsets->nonProfilingEntry = masm.currentOffset(); + PushRetAddr(masm); + masm.subFromStackPtr(Imm32(framePushed + FrameBytesAfterReturnAddress)); + + // Prologue join point, body begin: + masm.bind(&body); + masm.setFramePushed(framePushed); +} + +// Similar to GenerateFunctionPrologue (see comment), we generate both a +// profiling and non-profiling epilogue a priori. When the profiling mode is +// toggled, ToggleProfiling patches the 'profiling jump' to either be a nop +// (falling through to the normal prologue) or a jump (jumping to the profiling +// epilogue). +void +wasm::GenerateFunctionEpilogue(MacroAssembler& masm, unsigned framePushed, FuncOffsets* offsets) +{ + MOZ_ASSERT(masm.framePushed() == framePushed); + +#if defined(JS_CODEGEN_ARM) + // Flush pending pools so they do not get dumped between the profilingReturn + // and profilingJump/profilingEpilogue offsets since the difference must be + // less than UINT8_MAX. + masm.flushBuffer(); +#endif + + // Generate a nop that is overwritten by a jump to the profiling epilogue + // when profiling is enabled. + offsets->profilingJump = masm.nopPatchableToNearJump().offset(); + + // Normal epilogue: + masm.addToStackPtr(Imm32(framePushed + FrameBytesAfterReturnAddress)); + masm.ret(); + masm.setFramePushed(0); + + // Profiling epilogue: + offsets->profilingEpilogue = masm.currentOffset(); + GenerateProfilingEpilogue(masm, framePushed, ExitReason::None, offsets); +} + +void +wasm::GenerateExitPrologue(MacroAssembler& masm, unsigned framePushed, ExitReason reason, + ProfilingOffsets* offsets) +{ + masm.haltingAlign(CodeAlignment); + GenerateProfilingPrologue(masm, framePushed, reason, offsets); + masm.setFramePushed(framePushed); +} + +void +wasm::GenerateExitEpilogue(MacroAssembler& masm, unsigned framePushed, ExitReason reason, + ProfilingOffsets* offsets) +{ + // Inverse of GenerateExitPrologue: + MOZ_ASSERT(masm.framePushed() == framePushed); + GenerateProfilingEpilogue(masm, framePushed, reason, offsets); + masm.setFramePushed(0); +} + +/*****************************************************************************/ +// ProfilingFrameIterator + +ProfilingFrameIterator::ProfilingFrameIterator() + : activation_(nullptr), + code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + exitReason_(ExitReason::None) +{ + MOZ_ASSERT(done()); +} + +ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation) + : activation_(&activation), + code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + exitReason_(ExitReason::None) +{ + // If profiling hasn't been enabled for this instance, then CallerFPFromFP + // will be trash, so ignore the entire activation. In practice, this only + // happens if profiling is enabled while the instance is on the stack (in + // which case profiling will be enabled when the instance becomes inactive + // and gets called again). + if (!activation_->compartment()->wasm.profilingEnabled()) { + MOZ_ASSERT(done()); + return; + } + + initFromFP(); +} + +static inline void +AssertMatchesCallSite(const WasmActivation& activation, void* callerPC, void* callerFP, void* fp) +{ +#ifdef DEBUG + Code* code = activation.compartment()->wasm.lookupCode(callerPC); + MOZ_ASSERT(code); + + const CodeRange* callerCodeRange = code->lookupRange(callerPC); + MOZ_ASSERT(callerCodeRange); + + if (callerCodeRange->kind() == CodeRange::Entry) { + MOZ_ASSERT(callerFP == nullptr); + return; + } + + const CallSite* callsite = code->lookupCallSite(callerPC); + MOZ_ASSERT(callsite); + + MOZ_ASSERT(callerFP == (uint8_t*)fp + callsite->stackDepth()); +#endif +} + +void +ProfilingFrameIterator::initFromFP() +{ + uint8_t* fp = activation_->fp(); + stackAddress_ = fp; + + // If a signal was handled while entering an activation, the frame will + // still be null. + if (!fp) { + MOZ_ASSERT(done()); + return; + } + + void* pc = ReturnAddressFromFP(fp); + + code_ = activation_->compartment()->wasm.lookupCode(pc); + MOZ_ASSERT(code_); + + codeRange_ = code_->lookupRange(pc); + MOZ_ASSERT(codeRange_); + + // Since we don't have the pc for fp, start unwinding at the caller of fp + // (ReturnAddressFromFP(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 as well as interrupts, we note the absence + // of an exit reason and inject a fake "builtin" frame; and + // - for async interrupts, we just accept that we'll lose the innermost frame. + switch (codeRange_->kind()) { + case CodeRange::Entry: + callerPC_ = nullptr; + callerFP_ = nullptr; + break; + case CodeRange::Function: + fp = CallerFPFromFP(fp); + callerPC_ = ReturnAddressFromFP(fp); + callerFP_ = CallerFPFromFP(fp); + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, fp); + break; + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::TrapExit: + case CodeRange::Inline: + case CodeRange::FarJumpIsland: + MOZ_CRASH("Unexpected CodeRange kind"); + } + + // The iterator inserts a pretend innermost frame for non-None ExitReasons. + // This allows the variety of exit reasons to show up in the callstack. + exitReason_ = activation_->exitReason(); + + // In the case of calls to builtins or asynchronous interrupts, no exit path + // is taken so the exitReason is None. Coerce these to the Native exit + // reason so that self-time is accounted for. + if (exitReason_ == ExitReason::None) + exitReason_ = ExitReason::Native; + + MOZ_ASSERT(!done()); +} + +typedef JS::ProfilingFrameIterator::RegisterState RegisterState; + +static bool +InThunk(const CodeRange& codeRange, uint32_t offsetInModule) +{ + if (codeRange.kind() == CodeRange::FarJumpIsland) + return true; + + return codeRange.isFunction() && + offsetInModule >= codeRange.funcTableEntry() && + offsetInModule < codeRange.funcNonProfilingEntry(); +} + +ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation, + const RegisterState& state) + : activation_(&activation), + code_(nullptr), + codeRange_(nullptr), + callerFP_(nullptr), + callerPC_(nullptr), + stackAddress_(nullptr), + exitReason_(ExitReason::None) +{ + // If profiling hasn't been enabled for this instance, then CallerFPFromFP + // will be trash, so ignore the entire activation. In practice, this only + // happens if profiling is enabled while the instance is on the stack (in + // which case profiling will be enabled when the instance becomes inactive + // and gets called again). + if (!activation_->compartment()->wasm.profilingEnabled()) { + MOZ_ASSERT(done()); + return; + } + + // If pc isn't in the instance's code, we must have exited the code via an + // exit trampoline or signal handler. + code_ = activation_->compartment()->wasm.lookupCode(state.pc); + if (!code_) { + initFromFP(); + return; + } + + // Note: fp may be null while entering and leaving the activation. + uint8_t* fp = activation.fp(); + + const CodeRange* codeRange = code_->lookupRange(state.pc); + switch (codeRange->kind()) { + case CodeRange::Function: + case CodeRange::FarJumpIsland: + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::TrapExit: { + // 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 (via + // ReturnAddressFromFP(fp)), 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 offsetInModule = (uint8_t*)state.pc - code_->segment().base(); + MOZ_ASSERT(offsetInModule >= codeRange->begin()); + MOZ_ASSERT(offsetInModule < codeRange->end()); + uint32_t offsetInCodeRange = offsetInModule - codeRange->begin(); + void** sp = (void**)state.sp; +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + if (offsetInCodeRange < PushedRetAddr || InThunk(*codeRange, offsetInModule)) { + // First instruction of the ARM/MIPS function; the return address is + // still in lr and fp still holds the caller's fp. + callerPC_ = state.lr; + callerFP_ = fp; + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, sp - 2); + } else if (offsetInModule == codeRange->profilingReturn() - PostStorePrePopFP) { + // Second-to-last instruction of the ARM/MIPS function; fp points to + // the caller's fp; have not yet popped Frame. + callerPC_ = ReturnAddressFromFP(sp); + callerFP_ = CallerFPFromFP(sp); + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, sp); + } else +#endif + if (offsetInCodeRange < PushedFP || offsetInModule == codeRange->profilingReturn() || + InThunk(*codeRange, offsetInModule)) + { + // The return address has been pushed on the stack but not fp; fp + // still points to the caller's fp. + callerPC_ = *sp; + callerFP_ = fp; + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, sp - 1); + } else if (offsetInCodeRange < StoredFP) { + // The full Frame has been pushed; fp still points to the caller's + // frame. + MOZ_ASSERT(fp == CallerFPFromFP(sp)); + callerPC_ = ReturnAddressFromFP(sp); + callerFP_ = CallerFPFromFP(sp); + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, sp); + } else { + // Not in the prologue/epilogue. + callerPC_ = ReturnAddressFromFP(fp); + callerFP_ = CallerFPFromFP(fp); + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, fp); + } + break; + } + case CodeRange::Entry: { + // The entry trampoline is the final frame in an WasmActivation. The entry + // trampoline also doesn't GeneratePrologue/Epilogue so we can't use + // the general unwinding logic above. + MOZ_ASSERT(!fp); + callerPC_ = nullptr; + callerFP_ = nullptr; + break; + } + case CodeRange::Inline: { + // The throw stub clears WasmActivation::fp on it's way out. + if (!fp) { + MOZ_ASSERT(done()); + return; + } + + // Most inline code stubs execute after the prologue/epilogue have + // completed so we can simply unwind based on fp. The only exception is + // the async interrupt stub, since it can be executed at any time. + // However, the async interrupt is super rare, so we can tolerate + // skipped frames. Thus, we use simply unwind based on fp. + callerPC_ = ReturnAddressFromFP(fp); + callerFP_ = CallerFPFromFP(fp); + AssertMatchesCallSite(*activation_, callerPC_, callerFP_, fp); + break; + } + } + + codeRange_ = codeRange; + stackAddress_ = state.sp; + MOZ_ASSERT(!done()); +} + +void +ProfilingFrameIterator::operator++() +{ + if (exitReason_ != ExitReason::None) { + MOZ_ASSERT(codeRange_); + exitReason_ = ExitReason::None; + MOZ_ASSERT(!done()); + return; + } + + if (!callerPC_) { + MOZ_ASSERT(!callerFP_); + codeRange_ = nullptr; + MOZ_ASSERT(done()); + return; + } + + code_ = activation_->compartment()->wasm.lookupCode(callerPC_); + MOZ_ASSERT(code_); + + codeRange_ = code_->lookupRange(callerPC_); + MOZ_ASSERT(codeRange_); + + switch (codeRange_->kind()) { + case CodeRange::Entry: + MOZ_ASSERT(callerFP_ == nullptr); + callerPC_ = nullptr; + break; + case CodeRange::Function: + case CodeRange::ImportJitExit: + case CodeRange::ImportInterpExit: + case CodeRange::TrapExit: + case CodeRange::Inline: + case CodeRange::FarJumpIsland: + stackAddress_ = callerFP_; + callerPC_ = ReturnAddressFromFP(callerFP_); + AssertMatchesCallSite(*activation_, callerPC_, CallerFPFromFP(callerFP_), callerFP_); + callerFP_ = CallerFPFromFP(callerFP_); + break; + } + + MOZ_ASSERT(!done()); +} + +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. + // + // NB: these labels are parsed for location by + // devtools/client/performance/modules/logic/frame-utils.js + const char* importJitDescription = "fast FFI trampoline (in asm.js)"; + const char* importInterpDescription = "slow FFI trampoline (in asm.js)"; + const char* nativeDescription = "native call (in asm.js)"; + const char* trapDescription = "trap handling (in asm.js)"; + + switch (exitReason_) { + case ExitReason::None: + break; + case ExitReason::ImportJit: + return importJitDescription; + case ExitReason::ImportInterp: + return importInterpDescription; + case ExitReason::Native: + return nativeDescription; + case ExitReason::Trap: + return trapDescription; + } + + switch (codeRange_->kind()) { + case CodeRange::Function: return code_->profilingLabel(codeRange_->funcIndex()); + case CodeRange::Entry: return "entry trampoline (in asm.js)"; + case CodeRange::ImportJitExit: return importJitDescription; + case CodeRange::ImportInterpExit: return importInterpDescription; + case CodeRange::TrapExit: return trapDescription; + case CodeRange::Inline: return "inline stub (in asm.js)"; + case CodeRange::FarJumpIsland: return "interstitial (in asm.js)"; + } + + MOZ_CRASH("bad code range kind"); +} + +/*****************************************************************************/ +// Runtime patching to enable/disable profiling + +void +wasm::ToggleProfiling(const Code& code, const CallSite& callSite, bool enabled) +{ + if (callSite.kind() != CallSite::Func) + return; + + uint8_t* callerRetAddr = code.segment().base() + callSite.returnAddressOffset(); + +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) + void* callee = X86Encoding::GetRel32Target(callerRetAddr); +#elif defined(JS_CODEGEN_ARM) + uint8_t* caller = callerRetAddr - 4; + Instruction* callerInsn = reinterpret_cast<Instruction*>(caller); + BOffImm calleeOffset; + callerInsn->as<InstBLImm>()->extractImm(&calleeOffset); + void* callee = calleeOffset.getDest(callerInsn); +#elif defined(JS_CODEGEN_ARM64) + MOZ_CRASH(); + void* callee = nullptr; + (void)callerRetAddr; +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + uint8_t* caller = callerRetAddr - 2 * sizeof(uint32_t); + InstImm* callerInsn = reinterpret_cast<InstImm*>(caller); + BOffImm16 calleeOffset; + callerInsn->extractImm16(&calleeOffset); + void* callee = calleeOffset.getDest(reinterpret_cast<Instruction*>(caller)); +#elif defined(JS_CODEGEN_NONE) + MOZ_CRASH(); + void* callee = nullptr; +#else +# error "Missing architecture" +#endif + + const CodeRange* codeRange = code.lookupRange(callee); + if (!codeRange->isFunction()) + return; + + uint8_t* from = code.segment().base() + codeRange->funcNonProfilingEntry(); + uint8_t* to = code.segment().base() + codeRange->funcProfilingEntry(); + if (!enabled) + Swap(from, to); + + MOZ_ASSERT(callee == from); + +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) + X86Encoding::SetRel32(callerRetAddr, to); +#elif defined(JS_CODEGEN_ARM) + new (caller) InstBLImm(BOffImm(to - caller), Assembler::Always); +#elif defined(JS_CODEGEN_ARM64) + (void)to; + MOZ_CRASH(); +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + new (caller) InstImm(op_regimm, zero, rt_bgezal, BOffImm16(to - caller)); +#elif defined(JS_CODEGEN_NONE) + MOZ_CRASH(); +#else +# error "Missing architecture" +#endif +} + +void +wasm::ToggleProfiling(const Code& code, const CallThunk& callThunk, bool enabled) +{ + const CodeRange& cr = code.metadata().codeRanges[callThunk.u.codeRangeIndex]; + uint32_t calleeOffset = enabled ? cr.funcProfilingEntry() : cr.funcNonProfilingEntry(); + MacroAssembler::repatchFarJump(code.segment().base(), callThunk.offset, calleeOffset); +} + +void +wasm::ToggleProfiling(const Code& code, const CodeRange& codeRange, bool enabled) +{ + if (!codeRange.isFunction()) + return; + + uint8_t* codeBase = code.segment().base(); + uint8_t* profilingEntry = codeBase + codeRange.funcProfilingEntry(); + uint8_t* tableProfilingJump = codeBase + codeRange.funcTableProfilingJump(); + uint8_t* profilingJump = codeBase + codeRange.funcProfilingJump(); + uint8_t* profilingEpilogue = codeBase + codeRange.funcProfilingEpilogue(); + + if (enabled) { + MacroAssembler::patchNopToNearJump(tableProfilingJump, profilingEntry); + MacroAssembler::patchNopToNearJump(profilingJump, profilingEpilogue); + } else { + MacroAssembler::patchNearJumpToNop(tableProfilingJump); + MacroAssembler::patchNearJumpToNop(profilingJump); + } +} |