/* -*- 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); } }