summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JSJitFrameIter.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit/JSJitFrameIter.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/jit/JSJitFrameIter.cpp798
1 files changed, 798 insertions, 0 deletions
diff --git a/js/src/jit/JSJitFrameIter.cpp b/js/src/jit/JSJitFrameIter.cpp
new file mode 100644
index 0000000000..5d846fbdab
--- /dev/null
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -0,0 +1,798 @@
+/* -*- 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/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/JitActivation.h"
+
+#include "vm/JSScript-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
+ : JSJitFrameIter(activation, FrameType::Exit, activation->jsExitFP()) {}
+
+JSJitFrameIter::JSJitFrameIter(const JitActivation* activation,
+ FrameType frameType, uint8_t* fp)
+ : current_(fp),
+ type_(frameType),
+ resumePCinCurrentFrame_(nullptr),
+ cachedSafepointIndex_(nullptr),
+ activation_(activation) {
+ MOZ_ASSERT(type_ == FrameType::JSJitToWasm || type_ == FrameType::Exit);
+ if (activation_->bailoutData()) {
+ current_ = activation_->bailoutData()->fp();
+ type_ = FrameType::Bailout;
+ } else {
+ 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::isUnwoundJitExit() const {
+ if (type_ != FrameType::Exit) {
+ return false;
+ }
+ return exitFrame()->isUnwoundJitExit();
+}
+
+bool JSJitFrameIter::isFunctionFrame() const {
+ return CalleeTokenIsFunction(calleeToken());
+}
+
+JSScript* JSJitFrameIter::script() const {
+ MOZ_ASSERT(isScripted());
+ 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()->actualArgs(); }
+
+uint8_t* JSJitFrameIter::prevFp() const { return current()->callerFramePtr(); }
+
+// Compute the size of a Baseline frame excluding pushed VMFunction arguments or
+// callee frame headers. This is used to calculate the number of Value slots in
+// the frame. The caller asserts this matches BaselineFrame::debugFrameSize.
+static uint32_t ComputeBaselineFrameSize(const JSJitFrameIter& frame) {
+ MOZ_ASSERT(frame.prevType() == FrameType::BaselineJS);
+
+ uint32_t frameSize = frame.current()->callerFramePtr() - frame.fp();
+
+ if (frame.isBaselineStub()) {
+ return frameSize - BaselineStubFrameLayout::Size();
+ }
+
+ // Note: an UnwoundJit exit frame is a JitFrameLayout that was turned into an
+ // ExitFrameLayout by EnsureUnwoundJitExitFrame. We have to use the original
+ // header size here because that's what we have on the stack.
+ if (frame.isScripted() || frame.isUnwoundJitExit()) {
+ return frameSize - JitFrameLayout::Size();
+ }
+
+ if (frame.isExitFrame()) {
+ frameSize -= ExitFrameLayout::Size();
+ if (frame.exitFrame()->isWrapperExit()) {
+ const VMFunctionData* data = frame.exitFrame()->footer()->function();
+ frameSize -= data->explicitStackSlots() * sizeof(void*);
+ }
+ return frameSize;
+ }
+
+ MOZ_CRASH("Unexpected frame");
+}
+
+void JSJitFrameIter::operator++() {
+ MOZ_ASSERT(!isEntry());
+
+ // Compute BaselineFrame size. 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 = ComputeBaselineFrameSize(*this);
+ baselineFrameSize_ = mozilla::Some(frameSize);
+ } else {
+ baselineFrameSize_ = mozilla::Nothing();
+ }
+
+ 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());
+
+ FloatRegisterSet fregs = reader.allFloatSpills().set().reduceSetForPush();
+ GeneralRegisterSet regs = reader.allGprSpills().set();
+
+ uintptr_t* spill = spillBase();
+ uint8_t* spillAlign =
+ alignDoubleSpill(reinterpret_cast<uint8_t*>(spill - regs.size()));
+ char* floatSpill = reinterpret_cast<char*>(spillAlign);
+
+ return MachineState::FromSafepoint(fregs, regs, floatSpill, spill);
+}
+
+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, " Caller frame ptr: %p\n", current()->callerFramePtr());
+ break;
+ case FrameType::BaselineJS:
+ dumpBaseline();
+ break;
+ case FrameType::BaselineStub:
+ fprintf(stderr, " Baseline stub frame\n");
+ fprintf(stderr, " Caller frame ptr: %p\n", current()->callerFramePtr());
+ break;
+ case FrameType::Bailout:
+ case FrameType::IonJS: {
+ InlineFrameIterator frames(TlsContext.get(), this);
+ for (;;) {
+ frames.dump();
+ if (!frames.more()) {
+ break;
+ }
+ ++frames;
+ }
+ break;
+ }
+ case FrameType::BaselineInterpreterEntry:
+ fprintf(stderr, " Baseline Interpreter Entry frame\n");
+ fprintf(stderr, " Caller frame ptr: %p\n", current()->callerFramePtr());
+ break;
+ case FrameType::Rectifier:
+ fprintf(stderr, " Rectifier frame\n");
+ fprintf(stderr, " Caller frame ptr: %p\n", current()->callerFramePtr());
+ break;
+ case FrameType::IonICCall:
+ fprintf(stderr, " Ion IC call\n");
+ fprintf(stderr, " Caller frame ptr: %p\n", current()->callerFramePtr());
+ break;
+ case FrameType::WasmToJSJit:
+ fprintf(stderr, " Fast wasm-to-JS entry frame\n");
+ fprintf(stderr, " Caller frame ptr: %p\n", current()->callerFramePtr());
+ 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());
+
+ 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, void* sp) {
+ // 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();
+
+ // Use fp_ as endStackAddress_. For cases below where we know we're currently
+ // executing JIT code, we use the current stack pointer instead.
+ endStackAddress_ = fp_;
+
+ // Profiler sampling must NOT be suppressed if we are here.
+ MOZ_ASSERT(cx->isProfilerSamplingEnabled());
+
+ // Try initializing with sampler pc
+ if (tryInitWithPC(pc)) {
+ endStackAddress_ = sp;
+ return;
+ }
+
+ // Try initializing with sampler pc using native=>bytecode table.
+ JitcodeGlobalTable* table =
+ cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
+ if (tryInitWithTable(table, pc, /* forLastCallSite = */ false)) {
+ endStackAddress_ = sp;
+ 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) {
+ return ReturnType(frame->callerFramePtr());
+}
+
+JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(
+ CommonFrameLayout* fp) {
+ endStackAddress_ = 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->isIonIC() || 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;
+ }
+
+ // For IonICEntry, use the corresponding IonEntry.
+ if (entry->isIonIC()) {
+ entry = table->lookup(entry->asIonIC().rejoinAddr());
+ MOZ_ASSERT(entry);
+ MOZ_RELEASE_ASSERT(entry->isIon());
+ }
+
+ if (entry->isIon()) {
+ // If looked-up callee doesn't match frame callee, don't accept
+ // lastProfilingCallSite
+ if (entry->asIon().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->asBaseline().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::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::moveToNextFrame(CommonFrameLayout* frame) {
+ /*
+ * fp_ points to a Baseline or Ion frame. The possible call-stacks
+ * patterns occurring between this frame and a previous Ion, Baseline or Entry
+ * frame are as follows:
+ *
+ * <Baseline-Or-Ion>
+ * ^
+ * |
+ * ^--- Ion (or Baseline JSOp::Resume)
+ * |
+ * ^--- Baseline Stub <---- Baseline
+ * |
+ * ^--- IonICCall <---- Ion
+ * |
+ * ^--- WasmToJSJit <---- (other wasm frames, not handled by this iterator)
+ * |
+ * ^--- Arguments Rectifier
+ * | ^
+ * | |
+ * | ^--- Ion
+ * | |
+ * | ^--- Baseline Stub <---- Baseline
+ * | |
+ * | ^--- WasmToJSJit <--- (other wasm frames)
+ * | |
+ * | ^--- Entry Frame (CppToJSJit)
+ * |
+ * ^--- Entry Frame (CppToJSJit)
+ * |
+ * ^--- Entry Frame (BaselineInterpreter)
+ * | ^
+ * | |
+ * | ^--- Ion
+ * | |
+ * | ^--- Baseline Stub <---- Baseline
+ * | |
+ * | ^--- WasmToJSJit <--- (other wasm frames)
+ * | |
+ * | ^--- Entry Frame (CppToJSJit)
+ * | |
+ * | ^--- Arguments Rectifier
+ *
+ * NOTE: Keep this in sync with JitRuntime::generateProfilerExitFrameTailStub!
+ */
+
+ // Unwrap baseline interpreter entry frame.
+ if (frame->prevType() == FrameType::BaselineInterpreterEntry) {
+ frame = GetPreviousRawFrame<BaselineInterpreterEntryFrameLayout*>(frame);
+ }
+
+ // Unwrap rectifier frames.
+ if (frame->prevType() == FrameType::Rectifier) {
+ frame = GetPreviousRawFrame<RectifierFrameLayout*>(frame);
+ MOZ_ASSERT(frame->prevType() == FrameType::IonJS ||
+ frame->prevType() == FrameType::BaselineStub ||
+ frame->prevType() == FrameType::WasmToJSJit ||
+ frame->prevType() == FrameType::CppToJSJit);
+ }
+
+ FrameType prevType = frame->prevType();
+ switch (prevType) {
+ case FrameType::IonJS:
+ case FrameType::BaselineJS:
+ resumePCinCurrentFrame_ = frame->returnAddress();
+ fp_ = GetPreviousRawFrame<uint8_t*>(frame);
+ type_ = prevType;
+ return;
+
+ case FrameType::BaselineStub:
+ case FrameType::IonICCall: {
+ FrameType stubPrevType = (prevType == FrameType::BaselineStub)
+ ? FrameType::BaselineJS
+ : FrameType::IonJS;
+ auto* stubFrame = GetPreviousRawFrame<CommonFrameLayout*>(frame);
+ MOZ_ASSERT(stubFrame->prevType() == stubPrevType);
+ resumePCinCurrentFrame_ = stubFrame->returnAddress();
+ fp_ = GetPreviousRawFrame<uint8_t*>(stubFrame);
+ type_ = stubPrevType;
+ return;
+ }
+
+ case FrameType::WasmToJSJit:
+ // 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());
+ return;
+
+ case FrameType::CppToJSJit:
+ // No previous frame, set to nullptr to indicate that
+ // JSJitProfilingFrameIterator is done().
+ resumePCinCurrentFrame_ = nullptr;
+ fp_ = nullptr;
+ type_ = FrameType::CppToJSJit;
+ return;
+
+ case FrameType::BaselineInterpreterEntry:
+ case FrameType::Rectifier:
+ case FrameType::Exit:
+ case FrameType::Bailout:
+ case FrameType::JSJitToWasm:
+ // Rectifier and Baseline Interpreter entry frames are handled before
+ // this switch. The other frame types can't call JS functions directly.
+ break;
+ }
+
+ MOZ_CRASH("Bad frame type.");
+}