diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/vm/Stack.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/Stack.cpp')
-rw-r--r-- | js/src/vm/Stack.cpp | 766 |
1 files changed, 766 insertions, 0 deletions
diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp new file mode 100644 index 0000000000..574d8a9379 --- /dev/null +++ b/js/src/vm/Stack.cpp @@ -0,0 +1,766 @@ +/* -*- 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 "vm/Stack-inl.h" + +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <algorithm> // std::max +#include <iterator> // std::size +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint32_t + +#include "gc/Tracer.h" // js::TraceRoot +#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>() || IsGlobalLexicalEnvironment(env) || + 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) { + Rooted<BlockLexicalEnvironmentObject*> env( + cx, &envChain_->as<BlockLexicalEnvironmentObject>()); + BlockLexicalEnvironmentObject* fresh = + BlockLexicalEnvironmentObject::clone(cx, env); + if (!fresh) { + return false; + } + + replaceInnermostEnvironment(*fresh); + return true; +} + +bool InterpreterFrame::recreateLexicalEnvironment(JSContext* cx) { + Rooted<BlockLexicalEnvironmentObject*> env( + cx, &envChain_->as<BlockLexicalEnvironmentObject>()); + BlockLexicalEnvironmentObject* fresh = + BlockLexicalEnvironmentObject::recreate(cx, env); + if (!fresh) { + return false; + } + + 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"); + } +} + +static void TraceInterpreterActivation(JSTracer* trc, + InterpreterActivation* act) { + for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) { + InterpreterFrame* fp = frames.frame(); + fp->trace(trc, frames.sp(), frames.pc()); + } +} + +void js::TraceInterpreterActivations(JSContext* cx, JSTracer* trc) { + for (ActivationIterator iter(cx); !iter.done(); ++iter) { + Activation* act = iter.activation(); + if (act->isInterpreter()) { + TraceInterpreterActivation(trc, act->asInterpreter()); + } + } +} + +/*****************************************************************************/ + +// 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().frameType() == jit::FrameType::WasmToJSJit) { + wasm::Frame* fp = (wasm::Frame*)jsJitIter().fp(); + 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; + MOZ_ASSERT(!jsJitIter().done()); + 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_); + MOZ_DIAGNOSTIC_ASSERT(stackAddr >= endStackAddress_); + + if (isWasm()) { + Frame frame; + frame.kind = Frame_Wasm; + 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; + return mozilla::Some(frame); + } + + MOZ_ASSERT(isJSJit()); + + // 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, cx_->runtime(), + *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()) { + 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); + MOZ_ASSERT(frame.interpreterScript); + MOZ_ASSERT(frame.interpreterPC_); + } else { + frame.interpreterScript = nullptr; + frame.returnAddress_ = returnAddr; + frame.label = nullptr; + frame.realmID = 0; + } + frame.activation = activation_; + frame.endStackAddress = endStackAddress_; + 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. Assume maximum inlining depth is <64 + const char* labels[64]; + uint32_t depth = entry->callStackAtAddr(cx_->runtime(), + jsJitIter().resumePCinCurrentFrame(), + labels, std::size(labels)); + MOZ_ASSERT(depth < std::size(labels)); + for (uint32_t i = 0; i < depth; i++) { + if (offset + i >= end) { + return i; + } + frames[offset + i] = physicalFrame.value(); + frames[offset + i].label = labels[i]; + } + + 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()); +} |