/* 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 "vm/Stack-inl.h"

#include "mozilla/Maybe.h"  // mozilla::Maybe

#include <algorithm>  // std::max
#include <stddef.h>   // size_t
#include <stdint.h>   // uint8_t, uint32_t

#include "gc/Tracer.h"  // js::TraceRoot
#include "jit/InlineScriptTree.h"
#include "jit/JitcodeMap.h"
#include "jit/JitRuntime.h"
#include "js/friend/ErrorMessages.h"  // JSMSG_*
#include "js/Value.h"                 // JS::Value
#include "vm/FrameIter.h"             // js::FrameIter
#include "vm/JSContext.h"
#include "wasm/WasmProcess.h"

#include "jit/JSJitFrameIter-inl.h"
#include "vm/Probes-inl.h"

using namespace js;

using mozilla::Maybe;

using JS::Value;

/*****************************************************************************/

void InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script,
                                        AbstractFramePtr evalInFramePrev,
                                        HandleObject envChain) {
  flags_ = 0;
  script_ = script;

  envChain_ = envChain.get();
  prev_ = nullptr;
  prevpc_ = nullptr;
  prevsp_ = nullptr;

  evalInFramePrev_ = evalInFramePrev;
  MOZ_ASSERT_IF(evalInFramePrev, isDebuggerEvalFrame());

  if (script->isDebuggee()) {
    setIsDebuggee();
  }

#ifdef DEBUG
  Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
#endif
}

ArrayObject* InterpreterFrame::createRestParameter(JSContext* cx) {
  MOZ_ASSERT(script()->hasRest());
  unsigned nformal = callee().nargs() - 1, nactual = numActualArgs();
  unsigned nrest = (nactual > nformal) ? nactual - nformal : 0;
  Value* restvp = argv() + nformal;
  return NewDenseCopiedArray(cx, nrest, restvp);
}

static inline void AssertScopeMatchesEnvironment(Scope* scope,
                                                 JSObject* originalEnv) {
#ifdef DEBUG
  JSObject* env = originalEnv;
  for (ScopeIter si(scope); si; si++) {
    if (si.kind() == ScopeKind::NonSyntactic) {
      while (env->is<WithEnvironmentObject>() ||
             env->is<NonSyntacticVariablesObject>() ||
             (env->is<LexicalEnvironmentObject>() &&
              !env->as<LexicalEnvironmentObject>().isSyntactic())) {
        MOZ_ASSERT(!IsSyntacticEnvironment(env));
        env = &env->as<EnvironmentObject>().enclosingEnvironment();
      }
    } else if (si.hasSyntacticEnvironment()) {
      switch (si.kind()) {
        case ScopeKind::Function:
          MOZ_ASSERT(env->as<CallObject>()
                         .callee()
                         .maybeCanonicalFunction()
                         ->nonLazyScript() ==
                     si.scope()->as<FunctionScope>().script());
          env = &env->as<CallObject>().enclosingEnvironment();
          break;

        case ScopeKind::FunctionBodyVar:
          MOZ_ASSERT(&env->as<VarEnvironmentObject>().scope() == si.scope());
          env = &env->as<VarEnvironmentObject>().enclosingEnvironment();
          break;

        case ScopeKind::Lexical:
        case ScopeKind::SimpleCatch:
        case ScopeKind::Catch:
        case ScopeKind::NamedLambda:
        case ScopeKind::StrictNamedLambda:
        case ScopeKind::FunctionLexical:
        case ScopeKind::ClassBody:
          MOZ_ASSERT(&env->as<ScopedLexicalEnvironmentObject>().scope() ==
                     si.scope());
          env =
              &env->as<ScopedLexicalEnvironmentObject>().enclosingEnvironment();
          break;

        case ScopeKind::With:
          MOZ_ASSERT(&env->as<WithEnvironmentObject>().scope() == si.scope());
          env = &env->as<WithEnvironmentObject>().enclosingEnvironment();
          break;

        case ScopeKind::Eval:
        case ScopeKind::StrictEval:
          env = &env->as<VarEnvironmentObject>().enclosingEnvironment();
          break;

        case ScopeKind::Global:
          env =
              &env->as<GlobalLexicalEnvironmentObject>().enclosingEnvironment();
          MOZ_ASSERT(env->is<GlobalObject>());
          break;

        case ScopeKind::NonSyntactic:
          MOZ_CRASH("NonSyntactic should not have a syntactic environment");
          break;

        case ScopeKind::Module:
          MOZ_ASSERT(&env->as<ModuleEnvironmentObject>().module() ==
                     si.scope()->as<ModuleScope>().module());
          env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
          break;

        case ScopeKind::WasmInstance:
          env =
              &env->as<WasmInstanceEnvironmentObject>().enclosingEnvironment();
          break;

        case ScopeKind::WasmFunction:
          env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
          break;
      }
    }
  }

  // In the case of a non-syntactic env chain, the immediate parent of the
  // outermost non-syntactic env may be the global lexical env, or, if
  // called from Debugger, a DebugEnvironmentProxy.
  //
  // In the case of a syntactic env chain, the outermost env is always a
  // GlobalObject.
  MOZ_ASSERT(env->is<GlobalObject>() ||
             env->is<GlobalLexicalEnvironmentObject>() ||
             env->is<DebugEnvironmentProxy>());
#endif
}

static inline void AssertScopeMatchesEnvironment(InterpreterFrame* fp,
                                                 jsbytecode* pc) {
#ifdef DEBUG
  // If we OOMed before fully initializing the environment chain, the scope
  // and environment will definitely mismatch.
  if (fp->script()->initialEnvironmentShape() && fp->hasInitialEnvironment()) {
    AssertScopeMatchesEnvironment(fp->script()->innermostScope(pc),
                                  fp->environmentChain());
  }
#endif
}

bool InterpreterFrame::initFunctionEnvironmentObjects(JSContext* cx) {
  return js::InitFunctionEnvironmentObjects(cx, this);
}

bool InterpreterFrame::prologue(JSContext* cx) {
  RootedScript script(cx, this->script());

  MOZ_ASSERT(cx->interpreterRegs().pc == script->code());
  MOZ_ASSERT(cx->realm() == script->realm());

  if (!isFunctionFrame()) {
    return probes::EnterScript(cx, script, nullptr, this);
  }

  // At this point, we've yet to push any environments. Check that they
  // match the enclosing scope.
  AssertScopeMatchesEnvironment(script->enclosingScope(), environmentChain());

  if (callee().needsFunctionEnvironmentObjects() &&
      !initFunctionEnvironmentObjects(cx)) {
    return false;
  }

  MOZ_ASSERT_IF(isConstructing(),
                thisArgument().isObject() ||
                    thisArgument().isMagic(JS_UNINITIALIZED_LEXICAL));

  return probes::EnterScript(cx, script, script->function(), this);
}

void InterpreterFrame::epilogue(JSContext* cx, jsbytecode* pc) {
  RootedScript script(cx, this->script());
  MOZ_ASSERT(cx->realm() == script->realm());
  probes::ExitScript(cx, script, script->function(),
                     hasPushedGeckoProfilerFrame());

  // Check that the scope matches the environment at the point of leaving
  // the frame.
  AssertScopeMatchesEnvironment(this, pc);

  EnvironmentIter ei(cx, this, pc);
  UnwindAllEnvironmentsInFrame(cx, ei);

  if (isFunctionFrame()) {
    if (!callee().isGenerator() && !callee().isAsync() && isConstructing() &&
        thisArgument().isObject() && returnValue().isPrimitive()) {
      setReturnValue(thisArgument());
    }

    return;
  }

  MOZ_ASSERT(isEvalFrame() || isGlobalFrame() || isModuleFrame());
}

bool InterpreterFrame::checkReturn(JSContext* cx, HandleValue thisv,
                                   MutableHandleValue result) {
  MOZ_ASSERT(script()->isDerivedClassConstructor());
  MOZ_ASSERT(isFunctionFrame());
  MOZ_ASSERT(callee().isClassConstructor());

  HandleValue retVal = returnValue();
  if (retVal.isObject()) {
    result.set(retVal);
    return true;
  }

  if (!retVal.isUndefined()) {
    ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, retVal,
                     nullptr);
    return false;
  }

  if (thisv.isMagic(JS_UNINITIALIZED_LEXICAL)) {
    return ThrowUninitializedThis(cx);
  }

  result.set(thisv);
  return true;
}

bool InterpreterFrame::pushVarEnvironment(JSContext* cx, Handle<Scope*> scope) {
  return js::PushVarEnvironmentObject(cx, scope, this);
}

bool InterpreterFrame::pushLexicalEnvironment(JSContext* cx,
                                              Handle<LexicalScope*> scope) {
  BlockLexicalEnvironmentObject* env =
      BlockLexicalEnvironmentObject::createForFrame(cx, scope, this);
  if (!env) {
    return false;
  }

  pushOnEnvironmentChain(*env);
  return true;
}

bool InterpreterFrame::freshenLexicalEnvironment(JSContext* cx,
                                                 jsbytecode* pc) {
  Rooted<BlockLexicalEnvironmentObject*> env(
      cx, &envChain_->as<BlockLexicalEnvironmentObject>());
  BlockLexicalEnvironmentObject* fresh =
      BlockLexicalEnvironmentObject::clone(cx, env);
  if (!fresh) {
    return false;
  }

  if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
    Rooted<BlockLexicalEnvironmentObject*> freshRoot(cx, fresh);
    DebugEnvironments::onPopLexical(cx, this, pc);
    fresh = freshRoot;
  }

  replaceInnermostEnvironment(*fresh);
  return true;
}

bool InterpreterFrame::recreateLexicalEnvironment(JSContext* cx,
                                                  jsbytecode* pc) {
  Rooted<BlockLexicalEnvironmentObject*> env(
      cx, &envChain_->as<BlockLexicalEnvironmentObject>());
  BlockLexicalEnvironmentObject* fresh =
      BlockLexicalEnvironmentObject::recreate(cx, env);
  if (!fresh) {
    return false;
  }

  if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
    Rooted<BlockLexicalEnvironmentObject*> freshRoot(cx, fresh);
    DebugEnvironments::onPopLexical(cx, this, pc);
    fresh = freshRoot;
  }

  replaceInnermostEnvironment(*fresh);
  return true;
}

bool InterpreterFrame::pushClassBodyEnvironment(JSContext* cx,
                                                Handle<ClassBodyScope*> scope) {
  ClassBodyLexicalEnvironmentObject* env =
      ClassBodyLexicalEnvironmentObject::createForFrame(cx, scope, this);
  if (!env) {
    return false;
  }

  pushOnEnvironmentChain(*env);
  return true;
}

void InterpreterFrame::trace(JSTracer* trc, Value* sp, jsbytecode* pc) {
  TraceRoot(trc, &envChain_, "env chain");
  TraceRoot(trc, &script_, "script");

  if (flags_ & HAS_ARGS_OBJ) {
    TraceRoot(trc, &argsObj_, "arguments");
  }

  if (hasReturnValue()) {
    TraceRoot(trc, &rval_, "rval");
  }

  MOZ_ASSERT(sp >= slots());

  if (hasArgs()) {
    // Trace the callee and |this|. When we're doing a moving GC, we
    // need to fix up the callee pointer before we use it below, under
    // numFormalArgs() and script().
    TraceRootRange(trc, 2, argv_ - 2, "fp callee and this");

    // Trace arguments.
    unsigned argc = std::max(numActualArgs(), numFormalArgs());
    TraceRootRange(trc, argc + isConstructing(), argv_, "fp argv");
  }

  JSScript* script = this->script();
  size_t nfixed = script->nfixed();
  size_t nlivefixed = script->calculateLiveFixed(pc);

  if (nfixed == nlivefixed) {
    // All locals are live.
    traceValues(trc, 0, sp - slots());
  } else {
    // Trace operand stack.
    traceValues(trc, nfixed, sp - slots());

    // Clear dead block-scoped locals.
    while (nfixed > nlivefixed) {
      unaliasedLocal(--nfixed).setUndefined();
    }

    // Trace live locals.
    traceValues(trc, 0, nlivefixed);
  }

  if (auto* debugEnvs = script->realm()->debugEnvs()) {
    debugEnvs->traceLiveFrame(trc, this);
  }
}

void InterpreterFrame::traceValues(JSTracer* trc, unsigned start,
                                   unsigned end) {
  if (start < end) {
    TraceRootRange(trc, end - start, slots() + start, "vm_stack");
  }
}

void InterpreterActivation::trace(JSTracer* trc) {
  traceCommon(trc);

  for (InterpreterFrameIterator frames(this); !frames.done(); ++frames) {
    InterpreterFrame* fp = frames.frame();
    fp->trace(trc, frames.sp(), frames.pc());
  }
}

void js::TraceActivations(JSContext* cx, JSTracer* trc) {
  for (ActivationIterator iter(cx); !iter.done(); ++iter) {
    Activation* act = iter.activation();
    act->trace(trc);
  }
}

/*****************************************************************************/

// Unlike the other methods of this class, this method is defined here so that
// we don't have to #include jsautooplen.h in vm/Stack.h.
void InterpreterRegs::setToEndOfScript() { sp = fp()->base(); }

/*****************************************************************************/

InterpreterFrame* InterpreterStack::pushInvokeFrame(
    JSContext* cx, const CallArgs& args, MaybeConstruct constructing) {
  LifoAlloc::Mark mark = allocator_.mark();

  RootedFunction fun(cx, &args.callee().as<JSFunction>());
  RootedScript script(cx, fun->nonLazyScript());

  Value* argv;
  InterpreterFrame* fp = getCallFrame(cx, args, script, constructing, &argv);
  if (!fp) {
    return nullptr;
  }

  fp->mark_ = mark;
  fp->initCallFrame(nullptr, nullptr, nullptr, *fun, script, argv,
                    args.length(), constructing);
  return fp;
}

InterpreterFrame* InterpreterStack::pushExecuteFrame(
    JSContext* cx, HandleScript script, HandleObject envChain,
    AbstractFramePtr evalInFrame) {
  LifoAlloc::Mark mark = allocator_.mark();

  unsigned nvars = script->nslots();
  uint8_t* buffer =
      allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
  if (!buffer) {
    return nullptr;
  }

  InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer);
  fp->mark_ = mark;
  fp->initExecuteFrame(cx, script, evalInFrame, envChain);
  fp->initLocals();

  return fp;
}

/*****************************************************************************/

InterpreterFrameIterator& InterpreterFrameIterator::operator++() {
  MOZ_ASSERT(!done());
  if (fp_ != activation_->entryFrame_) {
    pc_ = fp_->prevpc();
    sp_ = fp_->prevsp();
    fp_ = fp_->prev();
  } else {
    pc_ = nullptr;
    sp_ = nullptr;
    fp_ = nullptr;
  }
  return *this;
}

JS::ProfilingFrameIterator::ProfilingFrameIterator(
    JSContext* cx, const RegisterState& state,
    const Maybe<uint64_t>& samplePositionInProfilerBuffer)
    : cx_(cx),
      samplePositionInProfilerBuffer_(samplePositionInProfilerBuffer),
      activation_(nullptr) {
  if (!cx->runtime()->geckoProfiler().enabled()) {
    MOZ_CRASH(
        "ProfilingFrameIterator called when geckoProfiler not enabled for "
        "runtime.");
  }

  if (!cx->profilingActivation()) {
    return;
  }

  // If profiler sampling is not enabled, skip.
  if (!cx->isProfilerSamplingEnabled()) {
    return;
  }

  activation_ = cx->profilingActivation();

  MOZ_ASSERT(activation_->isProfiling());

  static_assert(sizeof(wasm::ProfilingFrameIterator) <= StorageSpace &&
                    sizeof(jit::JSJitProfilingFrameIterator) <= StorageSpace,
                "ProfilingFrameIterator::storage_ is too small");
  static_assert(alignof(void*) >= alignof(wasm::ProfilingFrameIterator) &&
                    alignof(void*) >= alignof(jit::JSJitProfilingFrameIterator),
                "ProfilingFrameIterator::storage_ is too weakly aligned");

  iteratorConstruct(state);
  settle();
}

JS::ProfilingFrameIterator::~ProfilingFrameIterator() {
  if (!done()) {
    MOZ_ASSERT(activation_->isProfiling());
    iteratorDestroy();
  }
}

void JS::ProfilingFrameIterator::operator++() {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(activation_->isJit());
  if (isWasm()) {
    ++wasmIter();
  } else {
    ++jsJitIter();
  }
  settle();
}

void JS::ProfilingFrameIterator::settleFrames() {
  // Handle transition frames (see comment in JitFrameIter::operator++).
  if (isJSJit() && jsJitIter().done() && jsJitIter().wasmCallerFP()) {
    wasm::Frame* fp = (wasm::Frame*)jsJitIter().wasmCallerFP();
    iteratorDestroy();
    new (storage()) wasm::ProfilingFrameIterator(fp);
    kind_ = Kind::Wasm;
    MOZ_ASSERT(!wasmIter().done());
    maybeSetEndStackAddress(wasmIter().endStackAddress());
    return;
  }

  if (isWasm() && wasmIter().done() && wasmIter().unwoundJitCallerFP()) {
    uint8_t* fp = wasmIter().unwoundJitCallerFP();
    iteratorDestroy();
    // Using this ctor will skip the first jit->wasm frame, which is
    // needed because the profiling iterator doesn't know how to unwind
    // when the callee has no script.
    new (storage())
        jit::JSJitProfilingFrameIterator((jit::CommonFrameLayout*)fp);
    kind_ = Kind::JSJit;
    maybeSetEndStackAddress(jsJitIter().endStackAddress());
    return;
  }
}

void JS::ProfilingFrameIterator::settle() {
  settleFrames();
  while (iteratorDone()) {
    iteratorDestroy();
    activation_ = activation_->prevProfiling();
    endStackAddress_ = nullptr;
    if (!activation_) {
      return;
    }
    iteratorConstruct();
    settleFrames();
  }
}

void JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state) {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(activation_->isJit());

  jit::JitActivation* activation = activation_->asJit();

  // We want to know if we should start with a wasm profiling frame iterator
  // or not. To determine this, there are three possibilities:
  // - we've exited to C++ from wasm, in which case the activation
  //   exitFP low bit is tagged and we can test hasWasmExitFP().
  // - we're in wasm code, so we can do a lookup on PC.
  // - in all the other cases, we're not in wasm or we haven't exited from
  //   wasm.
  if (activation->hasWasmExitFP() || wasm::InCompiledCode(state.pc)) {
    new (storage()) wasm::ProfilingFrameIterator(*activation, state);
    kind_ = Kind::Wasm;
    maybeSetEndStackAddress(wasmIter().endStackAddress());
    return;
  }

  new (storage()) jit::JSJitProfilingFrameIterator(cx_, state.pc, state.sp);
  kind_ = Kind::JSJit;
  maybeSetEndStackAddress(jsJitIter().endStackAddress());
}

void JS::ProfilingFrameIterator::iteratorConstruct() {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(activation_->isJit());

  jit::JitActivation* activation = activation_->asJit();

  // The same reasoning as in the above iteratorConstruct variant applies
  // here, except that it's even simpler: since this activation is higher up
  // on the stack, it can only have exited to C++, through wasm or ion.
  if (activation->hasWasmExitFP()) {
    new (storage()) wasm::ProfilingFrameIterator(*activation);
    kind_ = Kind::Wasm;
    maybeSetEndStackAddress(wasmIter().endStackAddress());
    return;
  }

  auto* fp = (jit::ExitFrameLayout*)activation->jsExitFP();
  new (storage()) jit::JSJitProfilingFrameIterator(fp);
  kind_ = Kind::JSJit;
  maybeSetEndStackAddress(jsJitIter().endStackAddress());
}

void JS::ProfilingFrameIterator::iteratorDestroy() {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(activation_->isJit());

  if (isWasm()) {
    wasmIter().~ProfilingFrameIterator();
    return;
  }

  jsJitIter().~JSJitProfilingFrameIterator();
}

bool JS::ProfilingFrameIterator::iteratorDone() {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(activation_->isJit());

  if (isWasm()) {
    return wasmIter().done();
  }

  return jsJitIter().done();
}

void* JS::ProfilingFrameIterator::stackAddress() const {
  MOZ_ASSERT(!done());
  MOZ_ASSERT(activation_->isJit());

  if (isWasm()) {
    return wasmIter().stackAddress();
  }

  return jsJitIter().stackAddress();
}

Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(
    const jit::JitcodeGlobalEntry** entry) const {
  *entry = nullptr;

  void* stackAddr = stackAddress();

  MOZ_DIAGNOSTIC_ASSERT(endStackAddress_);
#ifndef ENABLE_WASM_JSPI
  // The stack addresses are monotonically increasing, except when
  // cont stacks are present (e.g. when JS PI is enabled).
  MOZ_DIAGNOSTIC_ASSERT(stackAddr >= endStackAddress_);
#endif

  if (isWasm()) {
    Frame frame;
    switch (wasmIter().category()) {
      case wasm::ProfilingFrameIterator::Category::Baseline: {
        frame.kind = FrameKind::Frame_WasmBaseline;
        break;
      }
      case wasm::ProfilingFrameIterator::Category::Ion: {
        frame.kind = FrameKind::Frame_WasmIon;
        break;
      }
      default: {
        frame.kind = FrameKind::Frame_WasmOther;
        break;
      }
    }
    frame.stackAddress = stackAddr;
    frame.returnAddress_ = nullptr;
    frame.activation = activation_;
    frame.label = nullptr;
    frame.endStackAddress = endStackAddress_;
    frame.interpreterScript = nullptr;
    // TODO: get the realm ID of wasm frames. Bug 1596235.
    frame.realmID = 0;
    frame.sourceId = 0;
    frame.line = 0;
    frame.column = 0;
    return mozilla::Some(frame);
  }

  MOZ_ASSERT(isJSJit());

  if (jit::IsPortableBaselineInterpreterEnabled()) {
    return mozilla::Nothing();
  }

  // Look up an entry for the return address.
  void* returnAddr = jsJitIter().resumePCinCurrentFrame();
  jit::JitcodeGlobalTable* table =
      cx_->runtime()->jitRuntime()->getJitcodeGlobalTable();

  // NB:
  // The following lookups should be infallible, but the ad-hoc stackwalking
  // code rots easily and corner cases where frames can't be looked up
  // occur too often (e.g. once every day).
  //
  // The calls to `lookup*` below have been changed from infallible ones to
  // fallible ones.  The proper solution to this problem is to fix all
  // the jitcode to use frame-pointers and reliably walk the stack with those.
  if (samplePositionInProfilerBuffer_) {
    *entry =
        table->lookupForSampler(returnAddr, *samplePositionInProfilerBuffer_);
  } else {
    *entry = table->lookup(returnAddr);
  }

  // Failed to look up a jitcode entry for the given address, ignore.
  if (!*entry) {
    return mozilla::Nothing();
  }

  // Dummy frames produce no stack frames.
  if ((*entry)->isDummy()) {
    return mozilla::Nothing();
  }

  Frame frame;
  if ((*entry)->isBaselineInterpreter()) {
    frame.kind = Frame_BaselineInterpreter;
  } else if ((*entry)->isBaseline() || (*entry)->isRealmIndependentShared()) {
    frame.kind = Frame_Baseline;
  } else {
    MOZ_ASSERT((*entry)->isIon() || (*entry)->isIonIC());
    frame.kind = Frame_Ion;
  }
  frame.stackAddress = stackAddr;
  if ((*entry)->isBaselineInterpreter()) {
    frame.label = jsJitIter().baselineInterpreterLabel();
    jsJitIter().baselineInterpreterScriptPC(&frame.interpreterScript,
                                            &frame.interpreterPC_,
                                            &frame.realmID, &frame.sourceId);
    MOZ_ASSERT(frame.interpreterScript);
    MOZ_ASSERT(frame.interpreterPC_);
  } else {
    frame.interpreterScript = nullptr;
    frame.returnAddress_ = returnAddr;
    frame.label = nullptr;
    frame.realmID = 0;
    frame.sourceId = 0;
  }
  frame.activation = activation_;
  frame.endStackAddress = endStackAddress_;
  // Initialize line and column info (will be populated later during
  // extractStack)
  frame.line = 0;
  frame.column = 0;
  return mozilla::Some(frame);
}

uint32_t JS::ProfilingFrameIterator::extractStack(Frame* frames,
                                                  uint32_t offset,
                                                  uint32_t end) const {
  if (offset >= end) {
    return 0;
  }

  const jit::JitcodeGlobalEntry* entry;
  Maybe<Frame> physicalFrame = getPhysicalFrameAndEntry(&entry);

  // Dummy frames produce no stack frames.
  if (physicalFrame.isNothing()) {
    return 0;
  }

  if (isWasm()) {
    frames[offset] = physicalFrame.value();
    frames[offset].label = wasmIter().label();
    return 1;
  }

  if (physicalFrame->kind == Frame_BaselineInterpreter) {
    frames[offset] = physicalFrame.value();
    return 1;
  }

  // Extract the stack for the entry.
  jit::CallStackFrameInfo frameInfos[jit::InlineScriptTree::MaxDepth];
  uint32_t depth = entry->callStackAtAddr(cx_->runtime(),
                                          jsJitIter().resumePCinCurrentFrame(),
                                          frameInfos, std::size(frameInfos));
  MOZ_ASSERT(depth <= std::size(frameInfos));
  for (uint32_t i = 0; i < depth; i++) {
    if (offset + i >= end) {
      return i;
    }
    frames[offset + i] = physicalFrame.value();
    frames[offset + i].label = frameInfos[i].label;
    frames[offset + i].sourceId = frameInfos[i].sourceId;
    frames[offset + i].line = frameInfos[i].line;
    frames[offset + i].column = frameInfos[i].column;
  }

  return depth;
}

Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const {
  const jit::JitcodeGlobalEntry* unused;
  return getPhysicalFrameAndEntry(&unused);
}

bool JS::ProfilingFrameIterator::isWasm() const {
  MOZ_ASSERT(!done());
  return kind_ == Kind::Wasm;
}

bool JS::ProfilingFrameIterator::isJSJit() const {
  return kind_ == Kind::JSJit;
}

mozilla::Maybe<JS::ProfilingFrameIterator::RegisterState>
JS::ProfilingFrameIterator::getCppEntryRegisters() const {
  if (!isJSJit()) {
    return mozilla::Nothing{};
  }
  return jit::JitRuntime::getCppEntryRegisters(jsJitIter().framePtr());
}
