summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JSJitFrameIter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/JSJitFrameIter.cpp')
-rw-r--r--js/src/jit/JSJitFrameIter.cpp817
1 files changed, 817 insertions, 0 deletions
diff --git a/js/src/jit/JSJitFrameIter.cpp b/js/src/jit/JSJitFrameIter.cpp
new file mode 100644
index 0000000000..907eef5adb
--- /dev/null
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -0,0 +1,817 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/JSJitFrameIter-inl.h"
+
+#include "jit/BaselineDebugModeOSR.h"
+#include "jit/BaselineIC.h"
+#include "jit/CalleeToken.h"
+#include "jit/IonScript.h"
+#include "jit/JitcodeMap.h"
+#include "jit/JitFrames.h"
+#include "jit/JitRuntime.h"
+#include "jit/JitScript.h"
+#include "jit/MacroAssembler.h" // js::jit::Assembler::GetPointer
+#include "jit/SafepointIndex.h"
+#include "jit/Safepoints.h"
+#include "jit/ScriptFromCalleeToken.h"
+#include "jit/VMFunctions.h"
+#include "js/friend/DumpFunctions.h" // js::DumpObject, js::DumpValue
+
+#include "vm/JSScript-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
+ : current_(activation->jsExitFP()),
+ type_(FrameType::Exit),
+ resumePCinCurrentFrame_(nullptr),
+ frameSize_(0),
+ cachedSafepointIndex_(nullptr),
+ activation_(activation) {
+ if (activation_->bailoutData()) {
+ current_ = activation_->bailoutData()->fp();
+ frameSize_ = activation_->bailoutData()->topFrameSize();
+ type_ = FrameType::Bailout;
+ } else {
+ MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
+ }
+}
+
+JSJitFrameIter::JSJitFrameIter(const JitActivation* activation,
+ FrameType frameType, uint8_t* fp)
+ : current_(fp),
+ type_(frameType),
+ resumePCinCurrentFrame_(nullptr),
+ frameSize_(0),
+ cachedSafepointIndex_(nullptr),
+ activation_(activation) {
+ MOZ_ASSERT(type_ == FrameType::JSJitToWasm || type_ == FrameType::Exit);
+ MOZ_ASSERT(!activation_->bailoutData());
+ MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
+}
+
+bool JSJitFrameIter::checkInvalidation() const {
+ IonScript* dummy;
+ return checkInvalidation(&dummy);
+}
+
+bool JSJitFrameIter::checkInvalidation(IonScript** ionScriptOut) const {
+ JSScript* script = this->script();
+ if (isBailoutJS()) {
+ *ionScriptOut = activation_->bailoutData()->ionScript();
+ return !script->hasIonScript() || script->ionScript() != *ionScriptOut;
+ }
+
+ uint8_t* returnAddr = resumePCinCurrentFrame();
+ // N.B. the current IonScript is not the same as the frame's
+ // IonScript if the frame has since been invalidated.
+ bool invalidated = !script->hasIonScript() ||
+ !script->ionScript()->containsReturnAddress(returnAddr);
+ if (!invalidated) {
+ return false;
+ }
+
+ int32_t invalidationDataOffset = ((int32_t*)returnAddr)[-1];
+ uint8_t* ionScriptDataOffset = returnAddr + invalidationDataOffset;
+ IonScript* ionScript = (IonScript*)Assembler::GetPointer(ionScriptDataOffset);
+ MOZ_ASSERT(ionScript->containsReturnAddress(returnAddr));
+ *ionScriptOut = ionScript;
+ return true;
+}
+
+CalleeToken JSJitFrameIter::calleeToken() const {
+ return ((JitFrameLayout*)current_)->calleeToken();
+}
+
+JSFunction* JSJitFrameIter::callee() const {
+ MOZ_ASSERT(isScripted());
+ MOZ_ASSERT(isFunctionFrame());
+ return CalleeTokenToFunction(calleeToken());
+}
+
+JSFunction* JSJitFrameIter::maybeCallee() const {
+ if (isScripted() && isFunctionFrame()) {
+ return callee();
+ }
+ return nullptr;
+}
+
+bool JSJitFrameIter::isBareExit() const {
+ if (type_ != FrameType::Exit) {
+ return false;
+ }
+ return exitFrame()->isBareExit();
+}
+
+bool JSJitFrameIter::isFunctionFrame() const {
+ return CalleeTokenIsFunction(calleeToken());
+}
+
+JSScript* JSJitFrameIter::script() const {
+ MOZ_ASSERT(isScripted());
+ if (isBaselineJS()) {
+ return baselineFrame()->script();
+ }
+ JSScript* script = ScriptFromCalleeToken(calleeToken());
+ MOZ_ASSERT(script);
+ return script;
+}
+
+JSScript* JSJitFrameIter::maybeForwardedScript() const {
+ MOZ_ASSERT(isScripted());
+ if (isBaselineJS()) {
+ return MaybeForwardedScriptFromCalleeToken(baselineFrame()->calleeToken());
+ }
+ JSScript* script = MaybeForwardedScriptFromCalleeToken(calleeToken());
+ MOZ_ASSERT(script);
+ return script;
+}
+
+void JSJitFrameIter::baselineScriptAndPc(JSScript** scriptRes,
+ jsbytecode** pcRes) const {
+ MOZ_ASSERT(isBaselineJS());
+ JSScript* script = this->script();
+ if (scriptRes) {
+ *scriptRes = script;
+ }
+
+ MOZ_ASSERT(pcRes);
+
+ // The Baseline Interpreter stores the bytecode pc in the frame.
+ if (baselineFrame()->runningInInterpreter()) {
+ MOZ_ASSERT(baselineFrame()->interpreterScript() == script);
+ *pcRes = baselineFrame()->interpreterPC();
+ return;
+ }
+
+ // There must be a BaselineScript with a RetAddrEntry for the current return
+ // address.
+ uint8_t* retAddr = resumePCinCurrentFrame();
+ const RetAddrEntry& entry =
+ script->baselineScript()->retAddrEntryFromReturnAddress(retAddr);
+ *pcRes = entry.pc(script);
+}
+
+Value* JSJitFrameIter::actualArgs() const { return jsFrame()->argv() + 1; }
+
+uint8_t* JSJitFrameIter::prevFp() const {
+ return current_ + current()->prevFrameLocalSize() + current()->headerSize();
+}
+
+void JSJitFrameIter::operator++() {
+ MOZ_ASSERT(!isEntry());
+
+ // Compute BaselineFrame size, the size stored in the descriptor excluding
+ // VMFunction arguments pushed for VM calls.
+ //
+ // In debug builds this is equivalent to BaselineFrame::debugFrameSize_. This
+ // is asserted at the end of this method.
+ if (current()->prevType() == FrameType::BaselineJS) {
+ uint32_t frameSize = prevFrameLocalSize();
+ if (isExitFrame() && exitFrame()->isWrapperExit()) {
+ const VMFunctionData* data = exitFrame()->footer()->function();
+ frameSize -= data->explicitStackSlots() * sizeof(void*);
+ }
+ baselineFrameSize_ = mozilla::Some(frameSize);
+ } else {
+ baselineFrameSize_ = mozilla::Nothing();
+ }
+
+ frameSize_ = prevFrameLocalSize();
+ cachedSafepointIndex_ = nullptr;
+
+ // If the next frame is the entry frame, just exit. Don't update current_,
+ // since the entry and first frames overlap.
+ if (isEntry(current()->prevType())) {
+ type_ = current()->prevType();
+ return;
+ }
+
+ type_ = current()->prevType();
+ resumePCinCurrentFrame_ = current()->returnAddress();
+ current_ = prevFp();
+
+ MOZ_ASSERT_IF(isBaselineJS(),
+ baselineFrame()->debugFrameSize() == *baselineFrameSize_);
+}
+
+uintptr_t* JSJitFrameIter::spillBase() const {
+ MOZ_ASSERT(isIonJS());
+
+ // Get the base address to where safepoint registers are spilled.
+ // Out-of-line calls do not unwind the extra padding space used to
+ // aggregate bailout tables, so we use frameSize instead of frameLocals,
+ // which would only account for local stack slots.
+ return reinterpret_cast<uintptr_t*>(fp() - ionScript()->frameSize());
+}
+
+MachineState JSJitFrameIter::machineState() const {
+ MOZ_ASSERT(isIonScripted());
+
+ // The MachineState is used by GCs for tracing call-sites.
+ if (MOZ_UNLIKELY(isBailoutJS())) {
+ return *activation_->bailoutData()->machineState();
+ }
+
+ SafepointReader reader(ionScript(), safepoint());
+ uintptr_t* spill = spillBase();
+ MachineState machine;
+
+ for (GeneralRegisterBackwardIterator iter(reader.allGprSpills()); iter.more();
+ ++iter) {
+ machine.setRegisterLocation(*iter, --spill);
+ }
+
+ uint8_t* spillAlign = alignDoubleSpill(reinterpret_cast<uint8_t*>(spill));
+
+ char* floatSpill = reinterpret_cast<char*>(spillAlign);
+ FloatRegisterSet fregs = reader.allFloatSpills().set();
+ fregs = fregs.reduceSetForPush();
+ for (FloatRegisterBackwardIterator iter(fregs); iter.more(); ++iter) {
+ floatSpill -= (*iter).size();
+ for (uint32_t a = 0; a < (*iter).numAlignedAliased(); a++) {
+ // Only say that registers that actually start here start here.
+ // e.g. d0 should not start at s1, only at s0.
+ FloatRegister ftmp = (*iter).alignedAliased(a);
+ machine.setRegisterLocation(ftmp, (double*)floatSpill);
+ }
+ }
+
+ return machine;
+}
+
+JitFrameLayout* JSJitFrameIter::jsFrame() const {
+ MOZ_ASSERT(isScripted());
+ if (isBailoutJS()) {
+ return (JitFrameLayout*)activation_->bailoutData()->fp();
+ }
+
+ return (JitFrameLayout*)fp();
+}
+
+IonScript* JSJitFrameIter::ionScript() const {
+ MOZ_ASSERT(isIonScripted());
+ if (isBailoutJS()) {
+ return activation_->bailoutData()->ionScript();
+ }
+
+ IonScript* ionScript = nullptr;
+ if (checkInvalidation(&ionScript)) {
+ return ionScript;
+ }
+ return ionScriptFromCalleeToken();
+}
+
+IonScript* JSJitFrameIter::ionScriptFromCalleeToken() const {
+ MOZ_ASSERT(isIonJS());
+ MOZ_ASSERT(!checkInvalidation());
+ return script()->ionScript();
+}
+
+const SafepointIndex* JSJitFrameIter::safepoint() const {
+ MOZ_ASSERT(isIonJS());
+ if (!cachedSafepointIndex_) {
+ cachedSafepointIndex_ =
+ ionScript()->getSafepointIndex(resumePCinCurrentFrame());
+ }
+ return cachedSafepointIndex_;
+}
+
+SnapshotOffset JSJitFrameIter::snapshotOffset() const {
+ MOZ_ASSERT(isIonScripted());
+ if (isBailoutJS()) {
+ return activation_->bailoutData()->snapshotOffset();
+ }
+ return osiIndex()->snapshotOffset();
+}
+
+const OsiIndex* JSJitFrameIter::osiIndex() const {
+ MOZ_ASSERT(isIonJS());
+ SafepointReader reader(ionScript(), safepoint());
+ return ionScript()->getOsiIndex(reader.osiReturnPointOffset());
+}
+
+bool JSJitFrameIter::isConstructing() const {
+ return CalleeTokenIsConstructing(calleeToken());
+}
+
+unsigned JSJitFrameIter::numActualArgs() const {
+ if (isScripted()) {
+ return jsFrame()->numActualArgs();
+ }
+
+ MOZ_ASSERT(isExitFrameLayout<NativeExitFrameLayout>());
+ return exitFrame()->as<NativeExitFrameLayout>()->argc();
+}
+
+void JSJitFrameIter::dumpBaseline() const {
+ MOZ_ASSERT(isBaselineJS());
+
+ fprintf(stderr, " JS Baseline frame\n");
+ if (isFunctionFrame()) {
+ fprintf(stderr, " callee fun: ");
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ DumpObject(callee());
+#else
+ fprintf(stderr, "?\n");
+#endif
+ } else {
+ fprintf(stderr, " global frame, no callee\n");
+ }
+
+ fprintf(stderr, " file %s line %u\n", script()->filename(),
+ script()->lineno());
+
+ JSContext* cx = TlsContext.get();
+ RootedScript script(cx);
+ jsbytecode* pc;
+ baselineScriptAndPc(script.address(), &pc);
+
+ fprintf(stderr, " script = %p, pc = %p (offset %u)\n", (void*)script, pc,
+ uint32_t(script->pcToOffset(pc)));
+ fprintf(stderr, " current op: %s\n", CodeName(JSOp(*pc)));
+
+ fprintf(stderr, " actual args: %u\n", numActualArgs());
+
+ for (unsigned i = 0; i < baselineFrameNumValueSlots(); i++) {
+ fprintf(stderr, " slot %u: ", i);
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ Value* v = baselineFrame()->valueSlot(i);
+ DumpValue(*v);
+#else
+ fprintf(stderr, "?\n");
+#endif
+ }
+}
+
+void JSJitFrameIter::dump() const {
+ switch (type_) {
+ case FrameType::CppToJSJit:
+ fprintf(stderr, " Entry frame\n");
+ fprintf(stderr, " Frame size: %u\n",
+ unsigned(current()->prevFrameLocalSize()));
+ break;
+ case FrameType::BaselineJS:
+ dumpBaseline();
+ break;
+ case FrameType::BaselineStub:
+ fprintf(stderr, " Baseline stub frame\n");
+ fprintf(stderr, " Frame size: %u\n",
+ unsigned(current()->prevFrameLocalSize()));
+ break;
+ case FrameType::Bailout:
+ case FrameType::IonJS: {
+ InlineFrameIterator frames(TlsContext.get(), this);
+ for (;;) {
+ frames.dump();
+ if (!frames.more()) {
+ break;
+ }
+ ++frames;
+ }
+ break;
+ }
+ case FrameType::Rectifier:
+ fprintf(stderr, " Rectifier frame\n");
+ fprintf(stderr, " Frame size: %u\n",
+ unsigned(current()->prevFrameLocalSize()));
+ break;
+ case FrameType::IonICCall:
+ fprintf(stderr, " Ion IC call\n");
+ fprintf(stderr, " Frame size: %u\n",
+ unsigned(current()->prevFrameLocalSize()));
+ break;
+ case FrameType::WasmToJSJit:
+ fprintf(stderr, " Fast wasm-to-JS entry frame\n");
+ fprintf(stderr, " Frame size: %u\n",
+ unsigned(current()->prevFrameLocalSize()));
+ break;
+ case FrameType::Exit:
+ fprintf(stderr, " Exit frame\n");
+ break;
+ case FrameType::JSJitToWasm:
+ fprintf(stderr, " Wasm exit frame\n");
+ break;
+ };
+ fputc('\n', stderr);
+}
+
+#ifdef DEBUG
+bool JSJitFrameIter::verifyReturnAddressUsingNativeToBytecodeMap() {
+ MOZ_ASSERT(resumePCinCurrentFrame_ != nullptr);
+
+ // Only handle Ion frames for now.
+ if (type_ != FrameType::IonJS && type_ != FrameType::BaselineJS) {
+ return true;
+ }
+
+ JSRuntime* rt = TlsContext.get()->runtime();
+
+ // Don't verify while off thread.
+ if (!CurrentThreadCanAccessRuntime(rt)) {
+ return true;
+ }
+
+ // Don't verify if sampling is being suppressed.
+ if (!TlsContext.get()->isProfilerSamplingEnabled()) {
+ return true;
+ }
+
+ if (JS::RuntimeHeapIsMinorCollecting()) {
+ return true;
+ }
+
+ JitRuntime* jitrt = rt->jitRuntime();
+
+ // Look up and print bytecode info for the native address.
+ const JitcodeGlobalEntry* entry =
+ jitrt->getJitcodeGlobalTable()->lookup(resumePCinCurrentFrame_);
+ if (!entry) {
+ return true;
+ }
+
+ JitSpew(JitSpew_Profiling, "Found nativeToBytecode entry for %p: %p - %p",
+ resumePCinCurrentFrame_, entry->nativeStartAddr(),
+ entry->nativeEndAddr());
+
+ JitcodeGlobalEntry::BytecodeLocationVector location;
+ uint32_t depth = UINT32_MAX;
+ if (!entry->callStackAtAddr(rt, resumePCinCurrentFrame_, location, &depth)) {
+ return false;
+ }
+ MOZ_ASSERT(depth > 0 && depth != UINT32_MAX);
+ MOZ_ASSERT(location.length() == depth);
+
+ JitSpew(JitSpew_Profiling, "Found bytecode location of depth %u:", depth);
+ for (size_t i = 0; i < location.length(); i++) {
+ JitSpew(JitSpew_Profiling, " %s:%u - %zu",
+ location[i].getDebugOnlyScript()->filename(),
+ location[i].getDebugOnlyScript()->lineno(),
+ size_t(location[i].toRawBytecode() -
+ location[i].getDebugOnlyScript()->code()));
+ }
+
+ if (type_ == FrameType::IonJS) {
+ // Create an InlineFrameIterator here and verify the mapped info against the
+ // iterator info.
+ InlineFrameIterator inlineFrames(TlsContext.get(), this);
+ for (size_t idx = 0; idx < location.length(); idx++) {
+ MOZ_ASSERT(idx < location.length());
+ MOZ_ASSERT_IF(idx < location.length() - 1, inlineFrames.more());
+
+ JitSpew(JitSpew_Profiling, "Match %d: ION %s:%u(%zu) vs N2B %s:%u(%zu)",
+ (int)idx, inlineFrames.script()->filename(),
+ inlineFrames.script()->lineno(),
+ size_t(inlineFrames.pc() - inlineFrames.script()->code()),
+ location[idx].getDebugOnlyScript()->filename(),
+ location[idx].getDebugOnlyScript()->lineno(),
+ size_t(location[idx].toRawBytecode() -
+ location[idx].getDebugOnlyScript()->code()));
+
+ MOZ_ASSERT(inlineFrames.script() == location[idx].getDebugOnlyScript());
+
+ if (inlineFrames.more()) {
+ ++inlineFrames;
+ }
+ }
+ }
+
+ return true;
+}
+#endif // DEBUG
+
+JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(JSContext* cx,
+ void* pc) {
+ // If no profilingActivation is live, initialize directly to
+ // end-of-iteration state.
+ if (!cx->profilingActivation()) {
+ type_ = FrameType::CppToJSJit;
+ fp_ = nullptr;
+ resumePCinCurrentFrame_ = nullptr;
+ return;
+ }
+
+ MOZ_ASSERT(cx->profilingActivation()->isJit());
+
+ JitActivation* act = cx->profilingActivation()->asJit();
+
+ // If the top JitActivation has a null lastProfilingFrame, assume that
+ // it's a trivially empty activation, and initialize directly
+ // to end-of-iteration state.
+ if (!act->lastProfilingFrame()) {
+ type_ = FrameType::CppToJSJit;
+ fp_ = nullptr;
+ resumePCinCurrentFrame_ = nullptr;
+ return;
+ }
+
+ // Get the fp from the current profilingActivation
+ fp_ = (uint8_t*)act->lastProfilingFrame();
+
+ // Profiler sampling must NOT be suppressed if we are here.
+ MOZ_ASSERT(cx->isProfilerSamplingEnabled());
+
+ // Try initializing with sampler pc
+ if (tryInitWithPC(pc)) {
+ return;
+ }
+
+ // Try initializing with sampler pc using native=>bytecode table.
+ JitcodeGlobalTable* table =
+ cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
+ if (tryInitWithTable(table, pc, /* forLastCallSite = */ false)) {
+ return;
+ }
+
+ // Try initializing with lastProfilingCallSite pc
+ void* lastCallSite = act->lastProfilingCallSite();
+ if (lastCallSite) {
+ if (tryInitWithPC(lastCallSite)) {
+ return;
+ }
+
+ // Try initializing with lastProfilingCallSite pc using native=>bytecode
+ // table.
+ if (tryInitWithTable(table, lastCallSite, /* forLastCallSite = */ true)) {
+ return;
+ }
+ }
+
+ // If nothing matches, for now just assume we are at the start of the last
+ // frame's baseline jit code or interpreter code.
+ type_ = FrameType::BaselineJS;
+ if (frameScript()->hasBaselineScript()) {
+ resumePCinCurrentFrame_ = frameScript()->baselineScript()->method()->raw();
+ } else {
+ MOZ_ASSERT(IsBaselineInterpreterEnabled());
+ resumePCinCurrentFrame_ =
+ cx->runtime()->jitRuntime()->baselineInterpreter().codeRaw();
+ }
+}
+
+template <typename ReturnType = CommonFrameLayout*>
+static inline ReturnType GetPreviousRawFrame(CommonFrameLayout* frame) {
+ size_t prevSize = frame->prevFrameLocalSize() + frame->headerSize();
+ return ReturnType((uint8_t*)frame + prevSize);
+}
+
+JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(
+ CommonFrameLayout* fp) {
+ moveToNextFrame(fp);
+}
+
+bool JSJitProfilingFrameIterator::tryInitWithPC(void* pc) {
+ JSScript* callee = frameScript();
+
+ // Check for Ion first, since it's more likely for hot code.
+ if (callee->hasIonScript() &&
+ callee->ionScript()->method()->containsNativePC(pc)) {
+ type_ = FrameType::IonJS;
+ resumePCinCurrentFrame_ = pc;
+ return true;
+ }
+
+ // Check for containment in Baseline jitcode second.
+ if (callee->hasBaselineScript() &&
+ callee->baselineScript()->method()->containsNativePC(pc)) {
+ type_ = FrameType::BaselineJS;
+ resumePCinCurrentFrame_ = pc;
+ return true;
+ }
+
+ return false;
+}
+
+bool JSJitProfilingFrameIterator::tryInitWithTable(JitcodeGlobalTable* table,
+ void* pc,
+ bool forLastCallSite) {
+ if (!pc) {
+ return false;
+ }
+
+ const JitcodeGlobalEntry* entry = table->lookup(pc);
+ if (!entry) {
+ return false;
+ }
+
+ JSScript* callee = frameScript();
+
+ MOZ_ASSERT(entry->isIon() || entry->isBaseline() ||
+ entry->isBaselineInterpreter() || entry->isDummy());
+
+ // Treat dummy lookups as an empty frame sequence.
+ if (entry->isDummy()) {
+ type_ = FrameType::CppToJSJit;
+ fp_ = nullptr;
+ resumePCinCurrentFrame_ = nullptr;
+ return true;
+ }
+
+ if (entry->isIon()) {
+ // If looked-up callee doesn't match frame callee, don't accept
+ // lastProfilingCallSite
+ if (entry->ionEntry().getScript(0) != callee) {
+ return false;
+ }
+
+ type_ = FrameType::IonJS;
+ resumePCinCurrentFrame_ = pc;
+ return true;
+ }
+
+ if (entry->isBaseline()) {
+ // If looked-up callee doesn't match frame callee, don't accept
+ // lastProfilingCallSite
+ if (forLastCallSite && entry->baselineEntry().script() != callee) {
+ return false;
+ }
+
+ type_ = FrameType::BaselineJS;
+ resumePCinCurrentFrame_ = pc;
+ return true;
+ }
+
+ if (entry->isBaselineInterpreter()) {
+ type_ = FrameType::BaselineJS;
+ resumePCinCurrentFrame_ = pc;
+ return true;
+ }
+
+ return false;
+}
+
+const char* JSJitProfilingFrameIterator::baselineInterpreterLabel() const {
+ MOZ_ASSERT(type_ == FrameType::BaselineJS);
+ return frameScript()->jitScript()->profileString();
+}
+
+void JSJitProfilingFrameIterator::baselineInterpreterScriptPC(
+ JSScript** script, jsbytecode** pc, uint64_t* realmID) const {
+ MOZ_ASSERT(type_ == FrameType::BaselineJS);
+ BaselineFrame* blFrame =
+ (BaselineFrame*)(fp_ - BaselineFrame::FramePointerOffset -
+ BaselineFrame::Size());
+ *script = frameScript();
+ *pc = (*script)->code();
+
+ if (blFrame->runningInInterpreter() &&
+ blFrame->interpreterScript() == *script) {
+ jsbytecode* interpPC = blFrame->interpreterPC();
+ if ((*script)->containsPC(interpPC)) {
+ *pc = interpPC;
+ }
+
+ *realmID = (*script)->realm()->creationOptions().profilerRealmID();
+ }
+}
+
+void JSJitProfilingFrameIterator::operator++() {
+ JitFrameLayout* frame = framePtr();
+ moveToNextFrame(frame);
+}
+
+void JSJitProfilingFrameIterator::moveToWasmFrame(CommonFrameLayout* frame) {
+ // No previous js jit frame, this is a transition frame, used to
+ // pass a wasm iterator the correct value of FP.
+ resumePCinCurrentFrame_ = nullptr;
+ fp_ = GetPreviousRawFrame<uint8_t*>(frame);
+ type_ = FrameType::WasmToJSJit;
+ MOZ_ASSERT(!done());
+}
+
+void JSJitProfilingFrameIterator::moveToCppEntryFrame() {
+ // No previous frame, set to nullptr to indicate that
+ // JSJitProfilingFrameIterator is done().
+ resumePCinCurrentFrame_ = nullptr;
+ fp_ = nullptr;
+ type_ = FrameType::CppToJSJit;
+}
+
+void JSJitProfilingFrameIterator::moveToNextFrame(CommonFrameLayout* frame) {
+ /*
+ * fp_ points to a Baseline or Ion frame. The possible call-stacks
+ * patterns occurring between this frame and a previous Ion or Baseline
+ * frame are as follows:
+ *
+ * <Baseline-Or-Ion>
+ * ^
+ * |
+ * ^--- Ion
+ * |
+ * ^--- Baseline Stub <---- Baseline
+ * |
+ * ^--- WasmToJSJit <---- (other wasm frames, not handled by this iterator)
+ * |
+ * ^--- Argument Rectifier
+ * | ^
+ * | |
+ * | ^--- Ion
+ * | |
+ * | ^--- Baseline Stub <---- Baseline
+ * | |
+ * | ^--- WasmToJSJit <--- (other wasm frames)
+ * | |
+ * | ^--- CppToJSJit
+ * |
+ * ^--- Entry Frame (From C++)
+ * Exit Frame (From previous JitActivation)
+ * ^
+ * |
+ * ^--- Ion
+ * |
+ * ^--- Baseline
+ * |
+ * ^--- Baseline Stub <---- Baseline
+ */
+ FrameType prevType = frame->prevType();
+
+ if (prevType == FrameType::IonJS) {
+ resumePCinCurrentFrame_ = frame->returnAddress();
+ fp_ = GetPreviousRawFrame<uint8_t*>(frame);
+ type_ = FrameType::IonJS;
+ return;
+ }
+
+ if (prevType == FrameType::BaselineJS) {
+ resumePCinCurrentFrame_ = frame->returnAddress();
+ fp_ = GetPreviousRawFrame<uint8_t*>(frame);
+ type_ = FrameType::BaselineJS;
+ return;
+ }
+
+ if (prevType == FrameType::BaselineStub) {
+ BaselineStubFrameLayout* stubFrame =
+ GetPreviousRawFrame<BaselineStubFrameLayout*>(frame);
+ MOZ_ASSERT(stubFrame->prevType() == FrameType::BaselineJS);
+
+ resumePCinCurrentFrame_ = stubFrame->returnAddress();
+ fp_ = ((uint8_t*)stubFrame->reverseSavedFramePtr()) +
+ jit::BaselineFrame::FramePointerOffset;
+ type_ = FrameType::BaselineJS;
+ return;
+ }
+
+ if (prevType == FrameType::Rectifier) {
+ RectifierFrameLayout* rectFrame =
+ GetPreviousRawFrame<RectifierFrameLayout*>(frame);
+ FrameType rectPrevType = rectFrame->prevType();
+
+ if (rectPrevType == FrameType::IonJS) {
+ resumePCinCurrentFrame_ = rectFrame->returnAddress();
+ fp_ = GetPreviousRawFrame<uint8_t*>(rectFrame);
+ type_ = FrameType::IonJS;
+ return;
+ }
+
+ if (rectPrevType == FrameType::BaselineStub) {
+ BaselineStubFrameLayout* stubFrame =
+ GetPreviousRawFrame<BaselineStubFrameLayout*>(rectFrame);
+ resumePCinCurrentFrame_ = stubFrame->returnAddress();
+ fp_ = ((uint8_t*)stubFrame->reverseSavedFramePtr()) +
+ jit::BaselineFrame::FramePointerOffset;
+ type_ = FrameType::BaselineJS;
+ return;
+ }
+
+ if (rectPrevType == FrameType::WasmToJSJit) {
+ moveToWasmFrame(rectFrame);
+ return;
+ }
+
+ if (rectPrevType == FrameType::CppToJSJit) {
+ moveToCppEntryFrame();
+ return;
+ }
+
+ MOZ_CRASH("Bad frame type prior to rectifier frame.");
+ }
+
+ if (prevType == FrameType::IonICCall) {
+ IonICCallFrameLayout* callFrame =
+ GetPreviousRawFrame<IonICCallFrameLayout*>(frame);
+
+ MOZ_ASSERT(callFrame->prevType() == FrameType::IonJS);
+
+ resumePCinCurrentFrame_ = callFrame->returnAddress();
+ fp_ = GetPreviousRawFrame<uint8_t*>(callFrame);
+ type_ = FrameType::IonJS;
+ return;
+ }
+
+ if (prevType == FrameType::WasmToJSJit) {
+ moveToWasmFrame(frame);
+ return;
+ }
+
+ if (prevType == FrameType::CppToJSJit) {
+ moveToCppEntryFrame();
+ return;
+ }
+
+ MOZ_CRASH("Bad frame type.");
+}