800 lines
24 KiB
C++
800 lines
24 KiB
C++
/* -*- 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>() ||
|
|
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");
|
|
}
|
|
}
|
|
|
|
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().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
|
|
// suspendable 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;
|
|
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, 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() || (*entry)->isSelfHostedShared()) {
|
|
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());
|
|
}
|