1960 lines
54 KiB
C++
1960 lines
54 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* 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/PodOperations.h"
|
|
|
|
#include "jscntxt.h"
|
|
|
|
#include "gc/Marking.h"
|
|
#include "jit/BaselineFrame.h"
|
|
#include "jit/JitcodeMap.h"
|
|
#include "jit/JitCompartment.h"
|
|
#include "js/GCAPI.h"
|
|
#include "vm/Debugger.h"
|
|
#include "vm/Opcodes.h"
|
|
|
|
#include "jit/JitFrameIterator-inl.h"
|
|
#include "vm/EnvironmentObject-inl.h"
|
|
#include "vm/Interpreter-inl.h"
|
|
#include "vm/Probes-inl.h"
|
|
|
|
using namespace js;
|
|
|
|
using mozilla::Maybe;
|
|
using mozilla::PodCopy;
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script,
|
|
AbstractFramePtr evalInFramePrev,
|
|
const Value& newTargetValue, HandleObject envChain)
|
|
{
|
|
flags_ = 0;
|
|
script_ = script;
|
|
|
|
// newTarget = NullValue is an initial sentinel for "please fill me in from the stack".
|
|
// It should never be passed from Ion code.
|
|
RootedValue newTarget(cx, newTargetValue);
|
|
if (script->isDirectEvalInFunction()) {
|
|
FrameIter iter(cx);
|
|
MOZ_ASSERT(!iter.isWasm());
|
|
if (newTarget.isNull() &&
|
|
iter.script()->bodyScope()->hasOnChain(ScopeKind::Function))
|
|
{
|
|
newTarget = iter.newTarget();
|
|
}
|
|
} else if (evalInFramePrev) {
|
|
if (newTarget.isNull() &&
|
|
evalInFramePrev.script()->bodyScope()->hasOnChain(ScopeKind::Function))
|
|
{
|
|
newTarget = evalInFramePrev.newTarget();
|
|
}
|
|
}
|
|
|
|
Value* dstvp = (Value*)this - 1;
|
|
dstvp[0] = newTarget;
|
|
|
|
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
|
|
}
|
|
|
|
bool
|
|
InterpreterFrame::isNonGlobalEvalFrame() const
|
|
{
|
|
return isEvalFrame() && script()->bodyScope()->as<EvalScope>().isNonGlobal();
|
|
}
|
|
|
|
JSObject*
|
|
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 ObjectGroup::newArrayObject(cx, restvp, nrest, GenericObject,
|
|
ObjectGroup::NewArrayKind::UnknownIndex);
|
|
}
|
|
|
|
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().existingScriptNonDelazifying() ==
|
|
si.scope()->as<FunctionScope>().script());
|
|
env = &env->as<CallObject>().enclosingEnvironment();
|
|
break;
|
|
|
|
case ScopeKind::FunctionBodyVar:
|
|
case ScopeKind::ParameterExpressionVar:
|
|
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:
|
|
MOZ_ASSERT(&env->as<LexicalEnvironmentObject>().scope() == si.scope());
|
|
env = &env->as<LexicalEnvironmentObject>().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:
|
|
MOZ_ASSERT(env->as<LexicalEnvironmentObject>().isGlobal());
|
|
env = &env->as<LexicalEnvironmentObject>().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().script() ==
|
|
si.scope()->as<ModuleScope>().script());
|
|
env = &env->as<ModuleEnvironmentObject>().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());
|
|
|
|
if (isEvalFrame()) {
|
|
if (!script->bodyScope()->hasEnvironment()) {
|
|
MOZ_ASSERT(!script->strict());
|
|
// Non-strict eval may introduce var bindings that conflict with
|
|
// lexical bindings in an enclosing lexical scope.
|
|
RootedObject varObjRoot(cx, &varObj());
|
|
if (!CheckEvalDeclarationConflicts(cx, script, environmentChain(), varObjRoot))
|
|
return false;
|
|
}
|
|
return probes::EnterScript(cx, script, nullptr, this);
|
|
}
|
|
|
|
if (isGlobalFrame()) {
|
|
Rooted<LexicalEnvironmentObject*> lexicalEnv(cx);
|
|
RootedObject varObjRoot(cx);
|
|
if (script->hasNonSyntacticScope()) {
|
|
lexicalEnv = &extensibleLexicalEnvironment();
|
|
varObjRoot = &varObj();
|
|
} else {
|
|
lexicalEnv = &cx->global()->lexicalEnvironment();
|
|
varObjRoot = cx->global();
|
|
}
|
|
if (!CheckGlobalDeclarationConflicts(cx, script, lexicalEnv, varObjRoot))
|
|
return false;
|
|
return probes::EnterScript(cx, script, nullptr, this);
|
|
}
|
|
|
|
if (isModuleFrame())
|
|
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());
|
|
|
|
MOZ_ASSERT(isFunctionFrame());
|
|
if (callee().needsFunctionEnvironmentObjects() && !initFunctionEnvironmentObjects(cx))
|
|
return false;
|
|
|
|
if (isConstructing()) {
|
|
if (callee().isBoundFunction()) {
|
|
thisArgument() = MagicValue(JS_UNINITIALIZED_LEXICAL);
|
|
} else if (script->isDerivedClassConstructor()) {
|
|
MOZ_ASSERT(callee().isClassConstructor());
|
|
thisArgument() = MagicValue(JS_UNINITIALIZED_LEXICAL);
|
|
} else if (thisArgument().isObject()) {
|
|
// Nothing to do. Correctly set.
|
|
} else {
|
|
MOZ_ASSERT(thisArgument().isMagic(JS_IS_CONSTRUCTING));
|
|
RootedObject callee(cx, &this->callee());
|
|
RootedObject newTarget(cx, &this->newTarget().toObject());
|
|
JSObject* obj = CreateThisForFunction(cx, callee, newTarget,
|
|
createSingleton() ? SingletonObject : GenericObject);
|
|
if (!obj)
|
|
return false;
|
|
thisArgument() = ObjectValue(*obj);
|
|
}
|
|
}
|
|
|
|
return probes::EnterScript(cx, script, script->functionNonDelazifying(), this);
|
|
}
|
|
|
|
void
|
|
InterpreterFrame::epilogue(JSContext* cx, jsbytecode* pc)
|
|
{
|
|
RootedScript script(cx, this->script());
|
|
probes::ExitScript(cx, script, script->functionNonDelazifying(), hasPushedSPSFrame());
|
|
|
|
// 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().isStarGenerator() &&
|
|
!callee().isLegacyGenerator() &&
|
|
!callee().isAsync() &&
|
|
isConstructing() &&
|
|
thisArgument().isObject() &&
|
|
returnValue().isPrimitive())
|
|
{
|
|
setReturnValue(thisArgument());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(isEvalFrame() || isGlobalFrame() || isModuleFrame());
|
|
}
|
|
|
|
bool
|
|
InterpreterFrame::checkReturn(JSContext* cx, HandleValue thisv)
|
|
{
|
|
MOZ_ASSERT(script()->isDerivedClassConstructor());
|
|
MOZ_ASSERT(isFunctionFrame());
|
|
MOZ_ASSERT(callee().isClassConstructor());
|
|
|
|
HandleValue retVal = returnValue();
|
|
if (retVal.isObject())
|
|
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, this);
|
|
|
|
setReturnValue(thisv);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
InterpreterFrame::pushVarEnvironment(JSContext* cx, HandleScope scope)
|
|
{
|
|
return js::PushVarEnvironmentObject(cx, scope, this);
|
|
}
|
|
|
|
bool
|
|
InterpreterFrame::pushLexicalEnvironment(JSContext* cx, Handle<LexicalScope*> scope)
|
|
{
|
|
LexicalEnvironmentObject* env = LexicalEnvironmentObject::create(cx, scope, this);
|
|
if (!env)
|
|
return false;
|
|
|
|
pushOnEnvironmentChain(*env);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
InterpreterFrame::freshenLexicalEnvironment(JSContext* cx)
|
|
{
|
|
Rooted<LexicalEnvironmentObject*> env(cx, &envChain_->as<LexicalEnvironmentObject>());
|
|
LexicalEnvironmentObject* fresh = LexicalEnvironmentObject::clone(cx, env);
|
|
if (!fresh)
|
|
return false;
|
|
|
|
replaceInnermostEnvironment(*fresh);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
InterpreterFrame::recreateLexicalEnvironment(JSContext* cx)
|
|
{
|
|
Rooted<LexicalEnvironmentObject*> env(cx, &envChain_->as<LexicalEnvironmentObject>());
|
|
LexicalEnvironmentObject* fresh = LexicalEnvironmentObject::recreate(cx, env);
|
|
if (!fresh)
|
|
return false;
|
|
|
|
replaceInnermostEnvironment(*fresh);
|
|
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 = Max(numActualArgs(), numFormalArgs());
|
|
TraceRootRange(trc, argc + isConstructing(), argv_, "fp argv");
|
|
} else {
|
|
// Mark newTarget.
|
|
TraceRoot(trc, ((Value*)this) - 1, "stack newTarget");
|
|
}
|
|
|
|
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 {
|
|
// Mark operand stack.
|
|
traceValues(trc, nfixed, sp - slots());
|
|
|
|
// Clear dead block-scoped locals.
|
|
while (nfixed > nlivefixed)
|
|
unaliasedLocal(--nfixed).setUndefined();
|
|
|
|
// Mark live locals.
|
|
traceValues(trc, 0, nlivefixed);
|
|
}
|
|
|
|
if (script->compartment()->debugEnvs)
|
|
script->compartment()->debugEnvs->markLiveFrame(trc, this);
|
|
|
|
if (trc->isMarkingTracer())
|
|
script->compartment()->zone()->active = true;
|
|
}
|
|
|
|
void
|
|
InterpreterFrame::traceValues(JSTracer* trc, unsigned start, unsigned end)
|
|
{
|
|
if (start < end)
|
|
TraceRootRange(trc, end - start, slots() + start, "vm_stack");
|
|
}
|
|
|
|
static void
|
|
MarkInterpreterActivation(JSTracer* trc, InterpreterActivation* act)
|
|
{
|
|
for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) {
|
|
InterpreterFrame* fp = frames.frame();
|
|
fp->trace(trc, frames.sp(), frames.pc());
|
|
}
|
|
}
|
|
|
|
void
|
|
js::MarkInterpreterActivations(JSRuntime* rt, JSTracer* trc)
|
|
{
|
|
for (ActivationIterator iter(rt); !iter.done(); ++iter) {
|
|
Activation* act = iter.activation();
|
|
if (act->isInterpreter())
|
|
MarkInterpreterActivation(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(cx, nullptr, nullptr, nullptr, *fun, script, argv, args.length(),
|
|
constructing);
|
|
return fp;
|
|
}
|
|
|
|
InterpreterFrame*
|
|
InterpreterStack::pushExecuteFrame(JSContext* cx, HandleScript script, const Value& newTargetValue,
|
|
HandleObject envChain, AbstractFramePtr evalInFrame)
|
|
{
|
|
LifoAlloc::Mark mark = allocator_.mark();
|
|
|
|
unsigned nvars = 1 /* newTarget */ + script->nslots();
|
|
uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
|
|
if (!buffer)
|
|
return nullptr;
|
|
|
|
InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 1 * sizeof(Value));
|
|
fp->mark_ = mark;
|
|
fp->initExecuteFrame(cx, script, evalInFrame, newTargetValue, envChain);
|
|
fp->initLocals();
|
|
|
|
return fp;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
FrameIter::popActivation()
|
|
{
|
|
++data_.activations_;
|
|
settleOnActivation();
|
|
}
|
|
|
|
void
|
|
FrameIter::popInterpreterFrame()
|
|
{
|
|
MOZ_ASSERT(data_.state_ == INTERP);
|
|
|
|
++data_.interpFrames_;
|
|
|
|
if (data_.interpFrames_.done())
|
|
popActivation();
|
|
else
|
|
data_.pc_ = data_.interpFrames_.pc();
|
|
}
|
|
|
|
void
|
|
FrameIter::settleOnActivation()
|
|
{
|
|
while (true) {
|
|
if (data_.activations_.done()) {
|
|
data_.state_ = DONE;
|
|
return;
|
|
}
|
|
|
|
Activation* activation = data_.activations_.activation();
|
|
|
|
// If the caller supplied principals, only show activations which are subsumed (of the same
|
|
// origin or of an origin accessible) by these principals.
|
|
if (data_.principals_) {
|
|
JSContext* cx = data_.cx_;
|
|
if (JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes) {
|
|
if (!subsumes(data_.principals_, activation->compartment()->principals())) {
|
|
++data_.activations_;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (activation->isJit()) {
|
|
data_.jitFrames_ = jit::JitFrameIterator(data_.activations_);
|
|
|
|
// Stop at the first scripted frame.
|
|
while (!data_.jitFrames_.isScripted() && !data_.jitFrames_.done())
|
|
++data_.jitFrames_;
|
|
|
|
// It's possible to have an JitActivation with no scripted frames,
|
|
// for instance if we hit an over-recursion during bailout.
|
|
if (data_.jitFrames_.done()) {
|
|
++data_.activations_;
|
|
continue;
|
|
}
|
|
|
|
nextJitFrame();
|
|
data_.state_ = JIT;
|
|
return;
|
|
}
|
|
|
|
if (activation->isWasm()) {
|
|
data_.wasmFrames_ = wasm::FrameIterator(*data_.activations_->asWasm());
|
|
|
|
if (data_.wasmFrames_.done()) {
|
|
++data_.activations_;
|
|
continue;
|
|
}
|
|
|
|
data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
|
|
data_.state_ = WASM;
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(activation->isInterpreter());
|
|
|
|
InterpreterActivation* interpAct = activation->asInterpreter();
|
|
data_.interpFrames_ = InterpreterFrameIterator(interpAct);
|
|
|
|
// If we OSR'ed into JIT code, skip the interpreter frame so that
|
|
// the same frame is not reported twice.
|
|
if (data_.interpFrames_.frame()->runningInJit()) {
|
|
++data_.interpFrames_;
|
|
if (data_.interpFrames_.done()) {
|
|
++data_.activations_;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(!data_.interpFrames_.frame()->runningInJit());
|
|
data_.pc_ = data_.interpFrames_.pc();
|
|
data_.state_ = INTERP;
|
|
return;
|
|
}
|
|
}
|
|
|
|
FrameIter::Data::Data(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
|
|
JSPrincipals* principals)
|
|
: cx_(cx),
|
|
debuggerEvalOption_(debuggerEvalOption),
|
|
principals_(principals),
|
|
state_(DONE),
|
|
pc_(nullptr),
|
|
interpFrames_(nullptr),
|
|
activations_(cx->runtime()),
|
|
jitFrames_(),
|
|
ionInlineFrameNo_(0),
|
|
wasmFrames_()
|
|
{
|
|
}
|
|
|
|
FrameIter::Data::Data(const FrameIter::Data& other)
|
|
: cx_(other.cx_),
|
|
debuggerEvalOption_(other.debuggerEvalOption_),
|
|
principals_(other.principals_),
|
|
state_(other.state_),
|
|
pc_(other.pc_),
|
|
interpFrames_(other.interpFrames_),
|
|
activations_(other.activations_),
|
|
jitFrames_(other.jitFrames_),
|
|
ionInlineFrameNo_(other.ionInlineFrameNo_),
|
|
wasmFrames_(other.wasmFrames_)
|
|
{
|
|
}
|
|
|
|
FrameIter::FrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption)
|
|
: data_(cx, debuggerEvalOption, nullptr),
|
|
ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr)
|
|
{
|
|
// settleOnActivation can only GC if principals are given.
|
|
JS::AutoSuppressGCAnalysis nogc;
|
|
settleOnActivation();
|
|
}
|
|
|
|
FrameIter::FrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
|
|
JSPrincipals* principals)
|
|
: data_(cx, debuggerEvalOption, principals),
|
|
ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr)
|
|
{
|
|
settleOnActivation();
|
|
}
|
|
|
|
FrameIter::FrameIter(const FrameIter& other)
|
|
: data_(other.data_),
|
|
ionInlineFrames_(other.data_.cx_,
|
|
data_.jitFrames_.isIonScripted() ? &other.ionInlineFrames_ : nullptr)
|
|
{
|
|
}
|
|
|
|
FrameIter::FrameIter(const Data& data)
|
|
: data_(data),
|
|
ionInlineFrames_(data.cx_, data_.jitFrames_.isIonScripted() ? &data_.jitFrames_ : nullptr)
|
|
{
|
|
MOZ_ASSERT(data.cx_);
|
|
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
while (ionInlineFrames_.frameNo() != data.ionInlineFrameNo_)
|
|
++ionInlineFrames_;
|
|
}
|
|
}
|
|
|
|
void
|
|
FrameIter::nextJitFrame()
|
|
{
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
ionInlineFrames_.resetOn(&data_.jitFrames_);
|
|
data_.pc_ = ionInlineFrames_.pc();
|
|
} else {
|
|
MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
|
|
data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
|
|
}
|
|
}
|
|
|
|
void
|
|
FrameIter::popJitFrame()
|
|
{
|
|
MOZ_ASSERT(data_.state_ == JIT);
|
|
|
|
if (data_.jitFrames_.isIonScripted() && ionInlineFrames_.more()) {
|
|
++ionInlineFrames_;
|
|
data_.pc_ = ionInlineFrames_.pc();
|
|
return;
|
|
}
|
|
|
|
++data_.jitFrames_;
|
|
while (!data_.jitFrames_.done() && !data_.jitFrames_.isScripted())
|
|
++data_.jitFrames_;
|
|
|
|
if (!data_.jitFrames_.done()) {
|
|
nextJitFrame();
|
|
return;
|
|
}
|
|
|
|
popActivation();
|
|
}
|
|
|
|
void
|
|
FrameIter::popWasmFrame()
|
|
{
|
|
MOZ_ASSERT(data_.state_ == WASM);
|
|
|
|
++data_.wasmFrames_;
|
|
data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
|
|
if (data_.wasmFrames_.done())
|
|
popActivation();
|
|
}
|
|
|
|
FrameIter&
|
|
FrameIter::operator++()
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
MOZ_CRASH("Unexpected state");
|
|
case INTERP:
|
|
if (interpFrame()->isDebuggerEvalFrame() &&
|
|
interpFrame()->evalInFramePrev() &&
|
|
data_.debuggerEvalOption_ == FOLLOW_DEBUGGER_EVAL_PREV_LINK)
|
|
{
|
|
AbstractFramePtr eifPrev = interpFrame()->evalInFramePrev();
|
|
|
|
popInterpreterFrame();
|
|
|
|
while (!hasUsableAbstractFramePtr() || abstractFramePtr() != eifPrev) {
|
|
if (data_.state_ == JIT)
|
|
popJitFrame();
|
|
else
|
|
popInterpreterFrame();
|
|
}
|
|
|
|
break;
|
|
}
|
|
popInterpreterFrame();
|
|
break;
|
|
case JIT:
|
|
popJitFrame();
|
|
break;
|
|
case WASM:
|
|
popWasmFrame();
|
|
break;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
FrameIter::Data*
|
|
FrameIter::copyData() const
|
|
{
|
|
Data* data = data_.cx_->new_<Data>(data_);
|
|
if (!data)
|
|
return nullptr;
|
|
|
|
MOZ_ASSERT(data_.state_ != WASM);
|
|
if (data && data_.jitFrames_.isIonScripted())
|
|
data->ionInlineFrameNo_ = ionInlineFrames_.frameNo();
|
|
return data;
|
|
}
|
|
|
|
AbstractFramePtr
|
|
FrameIter::copyDataAsAbstractFramePtr() const
|
|
{
|
|
AbstractFramePtr frame;
|
|
if (Data* data = copyData())
|
|
frame.ptr_ = uintptr_t(data);
|
|
return frame;
|
|
}
|
|
|
|
void*
|
|
FrameIter::rawFramePtr() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
return nullptr;
|
|
case JIT:
|
|
return data_.jitFrames_.fp();
|
|
case INTERP:
|
|
return interpFrame();
|
|
case WASM:
|
|
return data_.wasmFrames_.fp();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
JSCompartment*
|
|
FrameIter::compartment() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
case JIT:
|
|
case WASM:
|
|
return data_.activations_->compartment();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
bool
|
|
FrameIter::isEvalFrame() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
return interpFrame()->isEvalFrame();
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS())
|
|
return data_.jitFrames_.baselineFrame()->isEvalFrame();
|
|
MOZ_ASSERT(!script()->isForEval());
|
|
return false;
|
|
case WASM:
|
|
return false;
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
bool
|
|
FrameIter::isFunctionFrame() const
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
return interpFrame()->isFunctionFrame();
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS())
|
|
return data_.jitFrames_.baselineFrame()->isFunctionFrame();
|
|
return script()->functionNonDelazifying();
|
|
case WASM:
|
|
return true;
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
JSAtom*
|
|
FrameIter::functionDisplayAtom() const
|
|
{
|
|
MOZ_ASSERT(isFunctionFrame());
|
|
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
case JIT:
|
|
return calleeTemplate()->displayAtom();
|
|
case WASM:
|
|
return data_.wasmFrames_.functionDisplayAtom();
|
|
}
|
|
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
ScriptSource*
|
|
FrameIter::scriptSource() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case INTERP:
|
|
case JIT:
|
|
return script()->scriptSource();
|
|
}
|
|
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
const char*
|
|
FrameIter::filename() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
case JIT:
|
|
return script()->filename();
|
|
case WASM:
|
|
return data_.wasmFrames_.filename();
|
|
}
|
|
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
const char16_t*
|
|
FrameIter::displayURL() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
case JIT: {
|
|
ScriptSource* ss = script()->scriptSource();
|
|
return ss->hasDisplayURL() ? ss->displayURL() : nullptr;
|
|
}
|
|
case WASM:
|
|
return data_.wasmFrames_.displayURL();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
unsigned
|
|
FrameIter::computeLine(uint32_t* column) const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
case JIT:
|
|
return PCToLineNumber(script(), pc(), column);
|
|
case WASM:
|
|
if (column)
|
|
*column = 0;
|
|
return data_.wasmFrames_.lineOrBytecode();
|
|
}
|
|
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
bool
|
|
FrameIter::mutedErrors() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP:
|
|
case JIT:
|
|
return script()->mutedErrors();
|
|
case WASM:
|
|
return data_.wasmFrames_.mutedErrors();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
bool
|
|
FrameIter::isConstructing() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT:
|
|
if (data_.jitFrames_.isIonScripted())
|
|
return ionInlineFrames_.isConstructing();
|
|
MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
|
|
return data_.jitFrames_.isConstructing();
|
|
case INTERP:
|
|
return interpFrame()->isConstructing();
|
|
}
|
|
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
bool
|
|
FrameIter::ensureHasRematerializedFrame(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(isIon());
|
|
return !!activation()->asJit()->getRematerializedFrame(cx, data_.jitFrames_);
|
|
}
|
|
|
|
bool
|
|
FrameIter::hasUsableAbstractFramePtr() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
return false;
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS())
|
|
return true;
|
|
|
|
MOZ_ASSERT(data_.jitFrames_.isIonScripted());
|
|
return !!activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
|
|
ionInlineFrames_.frameNo());
|
|
break;
|
|
case INTERP:
|
|
return true;
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
AbstractFramePtr
|
|
FrameIter::abstractFramePtr() const
|
|
{
|
|
MOZ_ASSERT(hasUsableAbstractFramePtr());
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT: {
|
|
if (data_.jitFrames_.isBaselineJS())
|
|
return data_.jitFrames_.baselineFrame();
|
|
|
|
MOZ_ASSERT(data_.jitFrames_.isIonScripted());
|
|
return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
|
|
ionInlineFrames_.frameNo());
|
|
break;
|
|
}
|
|
case INTERP:
|
|
MOZ_ASSERT(interpFrame());
|
|
return AbstractFramePtr(interpFrame());
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
void
|
|
FrameIter::updatePcQuadratic()
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
break;
|
|
case INTERP: {
|
|
InterpreterFrame* frame = interpFrame();
|
|
InterpreterActivation* activation = data_.activations_->asInterpreter();
|
|
|
|
// Look for the current frame.
|
|
data_.interpFrames_ = InterpreterFrameIterator(activation);
|
|
while (data_.interpFrames_.frame() != frame)
|
|
++data_.interpFrames_;
|
|
|
|
// Update the pc.
|
|
MOZ_ASSERT(data_.interpFrames_.frame() == frame);
|
|
data_.pc_ = data_.interpFrames_.pc();
|
|
return;
|
|
}
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS()) {
|
|
jit::BaselineFrame* frame = data_.jitFrames_.baselineFrame();
|
|
jit::JitActivation* activation = data_.activations_->asJit();
|
|
|
|
// ActivationIterator::jitTop_ may be invalid, so create a new
|
|
// activation iterator.
|
|
data_.activations_ = ActivationIterator(data_.cx_->runtime());
|
|
while (data_.activations_.activation() != activation)
|
|
++data_.activations_;
|
|
|
|
// Look for the current frame.
|
|
data_.jitFrames_ = jit::JitFrameIterator(data_.activations_);
|
|
while (!data_.jitFrames_.isBaselineJS() || data_.jitFrames_.baselineFrame() != frame)
|
|
++data_.jitFrames_;
|
|
|
|
// Update the pc.
|
|
MOZ_ASSERT(data_.jitFrames_.baselineFrame() == frame);
|
|
data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
|
|
return;
|
|
}
|
|
break;
|
|
case WASM:
|
|
// Update the pc.
|
|
data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
|
|
break;
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
JSFunction*
|
|
FrameIter::calleeTemplate() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case INTERP:
|
|
MOZ_ASSERT(isFunctionFrame());
|
|
return &interpFrame()->callee();
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS())
|
|
return data_.jitFrames_.callee();
|
|
MOZ_ASSERT(data_.jitFrames_.isIonScripted());
|
|
return ionInlineFrames_.calleeTemplate();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
JSFunction*
|
|
FrameIter::callee(JSContext* cx) const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case INTERP:
|
|
return calleeTemplate();
|
|
case JIT:
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_);
|
|
return ionInlineFrames_.callee(recover);
|
|
}
|
|
MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
|
|
return calleeTemplate();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
bool
|
|
FrameIter::matchCallee(JSContext* cx, HandleFunction fun) const
|
|
{
|
|
RootedFunction currentCallee(cx, calleeTemplate());
|
|
|
|
// As we do not know if the calleeTemplate is the real function, or the
|
|
// template from which it would be cloned, we compare properties which are
|
|
// stable across the cloning of JSFunctions.
|
|
if (((currentCallee->flags() ^ fun->flags()) & JSFunction::STABLE_ACROSS_CLONES) != 0 ||
|
|
currentCallee->nargs() != fun->nargs())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Use the same condition as |js::CloneFunctionObject|, to know if we should
|
|
// expect both functions to have the same JSScript. If so, and if they are
|
|
// different, then they cannot be equal.
|
|
RootedObject global(cx, &fun->global());
|
|
bool useSameScript = CanReuseScriptForClone(fun->compartment(), currentCallee, global);
|
|
if (useSameScript &&
|
|
(currentCallee->hasScript() != fun->hasScript() ||
|
|
currentCallee->nonLazyScript() != fun->nonLazyScript()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If none of the previous filters worked, then take the risk of
|
|
// invalidating the frame to identify the JSFunction.
|
|
return callee(cx) == fun;
|
|
}
|
|
|
|
unsigned
|
|
FrameIter::numActualArgs() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case INTERP:
|
|
MOZ_ASSERT(isFunctionFrame());
|
|
return interpFrame()->numActualArgs();
|
|
case JIT:
|
|
if (data_.jitFrames_.isIonScripted())
|
|
return ionInlineFrames_.numActualArgs();
|
|
|
|
MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
|
|
return data_.jitFrames_.numActualArgs();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
unsigned
|
|
FrameIter::numFormalArgs() const
|
|
{
|
|
return script()->functionNonDelazifying()->nargs();
|
|
}
|
|
|
|
Value
|
|
FrameIter::unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing) const
|
|
{
|
|
return abstractFramePtr().unaliasedActual(i, checkAliasing);
|
|
}
|
|
|
|
JSObject*
|
|
FrameIter::environmentChain(JSContext* cx) const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT:
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_);
|
|
return ionInlineFrames_.environmentChain(recover);
|
|
}
|
|
return data_.jitFrames_.baselineFrame()->environmentChain();
|
|
case INTERP:
|
|
return interpFrame()->environmentChain();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
CallObject&
|
|
FrameIter::callObj(JSContext* cx) const
|
|
{
|
|
MOZ_ASSERT(calleeTemplate()->needsCallObject());
|
|
|
|
JSObject* pobj = environmentChain(cx);
|
|
while (!pobj->is<CallObject>())
|
|
pobj = pobj->enclosingEnvironment();
|
|
return pobj->as<CallObject>();
|
|
}
|
|
|
|
bool
|
|
FrameIter::hasArgsObj() const
|
|
{
|
|
return abstractFramePtr().hasArgsObj();
|
|
}
|
|
|
|
ArgumentsObject&
|
|
FrameIter::argsObj() const
|
|
{
|
|
MOZ_ASSERT(hasArgsObj());
|
|
return abstractFramePtr().argsObj();
|
|
}
|
|
|
|
Value
|
|
FrameIter::thisArgument(JSContext* cx) const
|
|
{
|
|
MOZ_ASSERT(isFunctionFrame());
|
|
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT:
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_);
|
|
return ionInlineFrames_.thisArgument(recover);
|
|
}
|
|
return data_.jitFrames_.baselineFrame()->thisArgument();
|
|
case INTERP:
|
|
return interpFrame()->thisArgument();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
Value
|
|
FrameIter::newTarget() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case INTERP:
|
|
return interpFrame()->newTarget();
|
|
case JIT:
|
|
MOZ_ASSERT(data_.jitFrames_.isBaselineJS());
|
|
return data_.jitFrames_.baselineFrame()->newTarget();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
Value
|
|
FrameIter::returnValue() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS())
|
|
return data_.jitFrames_.baselineFrame()->returnValue();
|
|
break;
|
|
case INTERP:
|
|
return interpFrame()->returnValue();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
void
|
|
FrameIter::setReturnValue(const Value& v)
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT:
|
|
if (data_.jitFrames_.isBaselineJS()) {
|
|
data_.jitFrames_.baselineFrame()->setReturnValue(v);
|
|
return;
|
|
}
|
|
break;
|
|
case INTERP:
|
|
interpFrame()->setReturnValue(v);
|
|
return;
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
size_t
|
|
FrameIter::numFrameSlots() const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT: {
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
return ionInlineFrames_.snapshotIterator().numAllocations() -
|
|
ionInlineFrames_.script()->nfixed();
|
|
}
|
|
jit::BaselineFrame* frame = data_.jitFrames_.baselineFrame();
|
|
return frame->numValueSlots() - data_.jitFrames_.script()->nfixed();
|
|
}
|
|
case INTERP:
|
|
MOZ_ASSERT(data_.interpFrames_.sp() >= interpFrame()->base());
|
|
return data_.interpFrames_.sp() - interpFrame()->base();
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
Value
|
|
FrameIter::frameSlotValue(size_t index) const
|
|
{
|
|
switch (data_.state_) {
|
|
case DONE:
|
|
case WASM:
|
|
break;
|
|
case JIT:
|
|
if (data_.jitFrames_.isIonScripted()) {
|
|
jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator());
|
|
index += ionInlineFrames_.script()->nfixed();
|
|
return si.maybeReadAllocByIndex(index);
|
|
}
|
|
|
|
index += data_.jitFrames_.script()->nfixed();
|
|
return *data_.jitFrames_.baselineFrame()->valueSlot(index);
|
|
case INTERP:
|
|
return interpFrame()->base()[index];
|
|
}
|
|
MOZ_CRASH("Unexpected state");
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool
|
|
js::SelfHostedFramesVisible()
|
|
{
|
|
static bool checked = false;
|
|
static bool visible = false;
|
|
if (!checked) {
|
|
checked = true;
|
|
char* env = getenv("MOZ_SHOW_ALL_JS_FRAMES");
|
|
visible = !!env;
|
|
}
|
|
return visible;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
NonBuiltinFrameIter::settle()
|
|
{
|
|
if (!SelfHostedFramesVisible()) {
|
|
while (!done() && hasScript() && script()->selfHosted())
|
|
FrameIter::operator++();
|
|
}
|
|
}
|
|
|
|
void
|
|
NonBuiltinScriptFrameIter::settle()
|
|
{
|
|
if (!SelfHostedFramesVisible()) {
|
|
while (!done() && script()->selfHosted())
|
|
ScriptFrameIter::operator++();
|
|
}
|
|
}
|
|
|
|
ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx)
|
|
: cx_(cx), entryMonitor_(cx->runtime()->entryMonitor)
|
|
{
|
|
cx->runtime()->entryMonitor = nullptr;
|
|
}
|
|
|
|
Value
|
|
ActivationEntryMonitor::asyncStack(JSContext* cx)
|
|
{
|
|
RootedValue stack(cx, ObjectOrNullValue(cx->asyncStackForNewActivations));
|
|
if (!cx->compartment()->wrap(cx, &stack)) {
|
|
cx->clearPendingException();
|
|
return UndefinedValue();
|
|
}
|
|
return stack;
|
|
}
|
|
|
|
ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame)
|
|
: ActivationEntryMonitor(cx)
|
|
{
|
|
if (entryMonitor_) {
|
|
// The InterpreterFrame is not yet part of an Activation, so it won't
|
|
// be traced if we trigger GC here. Suppress GC to avoid this.
|
|
gc::AutoSuppressGC suppressGC(cx);
|
|
RootedValue stack(cx, asyncStack(cx));
|
|
const char* asyncCause = cx->asyncCauseForNewActivations;
|
|
if (entryFrame->isFunctionFrame())
|
|
entryMonitor_->Entry(cx, &entryFrame->callee(), stack, asyncCause);
|
|
else
|
|
entryMonitor_->Entry(cx, entryFrame->script(), stack, asyncCause);
|
|
}
|
|
}
|
|
|
|
ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken)
|
|
: ActivationEntryMonitor(cx)
|
|
{
|
|
if (entryMonitor_) {
|
|
// The CalleeToken is not traced at this point and we also don't want
|
|
// a GC to discard the code we're about to enter, so we suppress GC.
|
|
gc::AutoSuppressGC suppressGC(cx);
|
|
RootedValue stack(cx, asyncStack(cx));
|
|
const char* asyncCause = cx->asyncCauseForNewActivations;
|
|
if (jit::CalleeTokenIsFunction(entryToken))
|
|
entryMonitor_->Entry(cx_, jit::CalleeTokenToFunction(entryToken), stack, asyncCause);
|
|
else
|
|
entryMonitor_->Entry(cx_, jit::CalleeTokenToScript(entryToken), stack, asyncCause);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
jit::JitActivation::JitActivation(JSContext* cx, bool active)
|
|
: Activation(cx, Jit),
|
|
prevJitTop_(cx->runtime()->jitTop),
|
|
prevJitActivation_(cx->runtime()->jitActivation),
|
|
active_(active),
|
|
rematerializedFrames_(nullptr),
|
|
ionRecovery_(cx),
|
|
bailoutData_(nullptr),
|
|
lastProfilingFrame_(nullptr),
|
|
lastProfilingCallSite_(nullptr)
|
|
{
|
|
if (active) {
|
|
cx->runtime()->jitActivation = this;
|
|
registerProfiling();
|
|
}
|
|
}
|
|
|
|
jit::JitActivation::~JitActivation()
|
|
{
|
|
if (active_) {
|
|
if (isProfiling())
|
|
unregisterProfiling();
|
|
|
|
cx_->runtime()->jitTop = prevJitTop_;
|
|
cx_->runtime()->jitActivation = prevJitActivation_;
|
|
} else {
|
|
MOZ_ASSERT(cx_->runtime()->jitTop == prevJitTop_);
|
|
MOZ_ASSERT(cx_->runtime()->jitActivation == prevJitActivation_);
|
|
}
|
|
|
|
// All reocvered value are taken from activation during the bailout.
|
|
MOZ_ASSERT(ionRecovery_.empty());
|
|
|
|
// The BailoutFrameInfo should have unregistered itself from the
|
|
// JitActivations.
|
|
MOZ_ASSERT(!bailoutData_);
|
|
|
|
clearRematerializedFrames();
|
|
js_delete(rematerializedFrames_);
|
|
}
|
|
|
|
bool
|
|
jit::JitActivation::isProfiling() const
|
|
{
|
|
// All JitActivations can be profiled.
|
|
return true;
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::setBailoutData(jit::BailoutFrameInfo* bailoutData)
|
|
{
|
|
MOZ_ASSERT(!bailoutData_);
|
|
bailoutData_ = bailoutData;
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::cleanBailoutData()
|
|
{
|
|
MOZ_ASSERT(bailoutData_);
|
|
bailoutData_ = nullptr;
|
|
}
|
|
|
|
// setActive() is inlined in GenerateJitExit() with explicit masm instructions so
|
|
// changes to the logic here need to be reflected in GenerateJitExit() in the enable
|
|
// and disable activation instruction sequences.
|
|
void
|
|
jit::JitActivation::setActive(JSContext* cx, bool active)
|
|
{
|
|
// Only allowed to deactivate/activate if activation is top.
|
|
// (Not tested and will probably fail in other situations.)
|
|
MOZ_ASSERT(cx->runtime()->activation_ == this);
|
|
MOZ_ASSERT(active != active_);
|
|
|
|
if (active) {
|
|
*((volatile bool*) active_) = true;
|
|
MOZ_ASSERT(prevJitTop_ == cx->runtime()->jitTop);
|
|
MOZ_ASSERT(prevJitActivation_ == cx->runtime()->jitActivation);
|
|
cx->runtime()->jitActivation = this;
|
|
|
|
registerProfiling();
|
|
|
|
} else {
|
|
unregisterProfiling();
|
|
|
|
cx->runtime()->jitTop = prevJitTop_;
|
|
cx->runtime()->jitActivation = prevJitActivation_;
|
|
|
|
*((volatile bool*) active_) = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::removeRematerializedFrame(uint8_t* top)
|
|
{
|
|
if (!rematerializedFrames_)
|
|
return;
|
|
|
|
if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
|
|
RematerializedFrame::FreeInVector(p->value());
|
|
rematerializedFrames_->remove(p);
|
|
}
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::clearRematerializedFrames()
|
|
{
|
|
if (!rematerializedFrames_)
|
|
return;
|
|
|
|
for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront()) {
|
|
RematerializedFrame::FreeInVector(e.front().value());
|
|
e.removeFront();
|
|
}
|
|
}
|
|
|
|
jit::RematerializedFrame*
|
|
jit::JitActivation::getRematerializedFrame(JSContext* cx, const JitFrameIterator& iter,
|
|
size_t inlineDepth)
|
|
{
|
|
MOZ_ASSERT(iter.activation() == this);
|
|
MOZ_ASSERT(iter.isIonScripted());
|
|
|
|
if (!rematerializedFrames_) {
|
|
rematerializedFrames_ = cx->new_<RematerializedFrameTable>(cx);
|
|
if (!rematerializedFrames_)
|
|
return nullptr;
|
|
if (!rematerializedFrames_->init()) {
|
|
rematerializedFrames_ = nullptr;
|
|
ReportOutOfMemory(cx);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
uint8_t* top = iter.fp();
|
|
RematerializedFrameTable::AddPtr p = rematerializedFrames_->lookupForAdd(top);
|
|
if (!p) {
|
|
RematerializedFrameVector frames(cx);
|
|
|
|
// The unit of rematerialization is an uninlined frame and its inlined
|
|
// frames. Since inlined frames do not exist outside of snapshots, it
|
|
// is impossible to synchronize their rematerialized copies to
|
|
// preserve identity. Therefore, we always rematerialize an uninlined
|
|
// frame and all its inlined frames at once.
|
|
InlineFrameIterator inlineIter(cx, &iter);
|
|
MaybeReadFallback recover(cx, this, &iter);
|
|
|
|
// Frames are often rematerialized with the cx inside a Debugger's
|
|
// compartment. To recover slots and to create CallObjects, we need to
|
|
// be in the activation's compartment.
|
|
AutoCompartment ac(cx, compartment_);
|
|
|
|
if (!RematerializedFrame::RematerializeInlineFrames(cx, top, inlineIter, recover, frames))
|
|
return nullptr;
|
|
|
|
if (!rematerializedFrames_->add(p, top, Move(frames))) {
|
|
ReportOutOfMemory(cx);
|
|
return nullptr;
|
|
}
|
|
|
|
// See comment in unsetPrevUpToDateUntil.
|
|
DebugEnvironments::unsetPrevUpToDateUntil(cx, p->value()[inlineDepth]);
|
|
}
|
|
|
|
return p->value()[inlineDepth];
|
|
}
|
|
|
|
jit::RematerializedFrame*
|
|
jit::JitActivation::lookupRematerializedFrame(uint8_t* top, size_t inlineDepth)
|
|
{
|
|
if (!rematerializedFrames_)
|
|
return nullptr;
|
|
if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top))
|
|
return inlineDepth < p->value().length() ? p->value()[inlineDepth] : nullptr;
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::removeRematerializedFramesFromDebugger(JSContext* cx, uint8_t* top)
|
|
{
|
|
// Ion bailout can fail due to overrecursion and OOM. In such cases we
|
|
// cannot honor any further Debugger hooks on the frame, and need to
|
|
// ensure that its Debugger.Frame entry is cleaned up.
|
|
if (!cx->compartment()->isDebuggee() || !rematerializedFrames_)
|
|
return;
|
|
if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
|
|
for (uint32_t i = 0; i < p->value().length(); i++)
|
|
Debugger::handleUnrecoverableIonBailoutError(cx, p->value()[i]);
|
|
}
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::markRematerializedFrames(JSTracer* trc)
|
|
{
|
|
if (!rematerializedFrames_)
|
|
return;
|
|
for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront())
|
|
e.front().value().trace(trc);
|
|
}
|
|
|
|
bool
|
|
jit::JitActivation::registerIonFrameRecovery(RInstructionResults&& results)
|
|
{
|
|
// Check that there is no entry in the vector yet.
|
|
MOZ_ASSERT(!maybeIonFrameRecovery(results.frame()));
|
|
if (!ionRecovery_.append(mozilla::Move(results)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
jit::RInstructionResults*
|
|
jit::JitActivation::maybeIonFrameRecovery(JitFrameLayout* fp)
|
|
{
|
|
for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); ) {
|
|
if (it->frame() == fp)
|
|
return it;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::removeIonFrameRecovery(JitFrameLayout* fp)
|
|
{
|
|
RInstructionResults* elem = maybeIonFrameRecovery(fp);
|
|
if (!elem)
|
|
return;
|
|
|
|
ionRecovery_.erase(elem);
|
|
}
|
|
|
|
void
|
|
jit::JitActivation::markIonRecovery(JSTracer* trc)
|
|
{
|
|
for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); it++)
|
|
it->trace(trc);
|
|
}
|
|
|
|
WasmActivation::WasmActivation(JSContext* cx)
|
|
: Activation(cx, Wasm),
|
|
entrySP_(nullptr),
|
|
resumePC_(nullptr),
|
|
fp_(nullptr),
|
|
exitReason_(wasm::ExitReason::None)
|
|
{
|
|
(void) entrySP_; // silence "unused private member" warning
|
|
|
|
prevWasm_ = cx->runtime()->wasmActivationStack_;
|
|
cx->runtime()->wasmActivationStack_ = this;
|
|
|
|
cx->compartment()->wasm.activationCount_++;
|
|
|
|
// Now that the WasmActivation is fully initialized, make it visible to
|
|
// asynchronous profiling.
|
|
registerProfiling();
|
|
}
|
|
|
|
WasmActivation::~WasmActivation()
|
|
{
|
|
// Hide this activation from the profiler before is is destroyed.
|
|
unregisterProfiling();
|
|
|
|
MOZ_ASSERT(fp_ == nullptr);
|
|
|
|
MOZ_ASSERT(cx_->runtime()->wasmActivationStack_ == this);
|
|
cx_->runtime()->wasmActivationStack_ = prevWasm_;
|
|
|
|
MOZ_ASSERT(cx_->compartment()->wasm.activationCount_ > 0);
|
|
cx_->compartment()->wasm.activationCount_--;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
Activation::registerProfiling()
|
|
{
|
|
MOZ_ASSERT(isProfiling());
|
|
cx_->runtime()->profilingActivation_ = this;
|
|
}
|
|
|
|
void
|
|
Activation::unregisterProfiling()
|
|
{
|
|
MOZ_ASSERT(isProfiling());
|
|
MOZ_ASSERT(cx_->runtime()->profilingActivation_ == this);
|
|
|
|
// There may be a non-active jit activation in the linked list. Skip past it.
|
|
Activation* prevProfiling = prevProfiling_;
|
|
while (prevProfiling && prevProfiling->isJit() && !prevProfiling->asJit()->isActive())
|
|
prevProfiling = prevProfiling->prevProfiling_;
|
|
|
|
cx_->runtime()->profilingActivation_ = prevProfiling;
|
|
}
|
|
|
|
ActivationIterator::ActivationIterator(JSRuntime* rt)
|
|
: jitTop_(rt->jitTop),
|
|
activation_(rt->activation_)
|
|
{
|
|
settle();
|
|
}
|
|
|
|
ActivationIterator&
|
|
ActivationIterator::operator++()
|
|
{
|
|
MOZ_ASSERT(activation_);
|
|
if (activation_->isJit() && activation_->asJit()->isActive())
|
|
jitTop_ = activation_->asJit()->prevJitTop();
|
|
activation_ = activation_->prev();
|
|
settle();
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
ActivationIterator::settle()
|
|
{
|
|
// Stop at the next active activation. No need to update jitTop_, since
|
|
// we don't iterate over an active jit activation.
|
|
while (!done() && activation_->isJit() && !activation_->asJit()->isActive())
|
|
activation_ = activation_->prev();
|
|
}
|
|
|
|
JS::ProfilingFrameIterator::ProfilingFrameIterator(JSContext* cx, const RegisterState& state,
|
|
uint32_t sampleBufferGen)
|
|
: rt_(cx),
|
|
sampleBufferGen_(sampleBufferGen),
|
|
activation_(nullptr),
|
|
savedPrevJitTop_(nullptr),
|
|
nogc_(cx)
|
|
{
|
|
if (!cx->spsProfiler.enabled())
|
|
MOZ_CRASH("ProfilingFrameIterator called when spsProfiler 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::JitProfilingFrameIterator) <= StorageSpace,
|
|
"Need to increase storage");
|
|
|
|
iteratorConstruct(state);
|
|
settle();
|
|
}
|
|
|
|
JS::ProfilingFrameIterator::~ProfilingFrameIterator()
|
|
{
|
|
if (!done()) {
|
|
MOZ_ASSERT(activation_->isProfiling());
|
|
iteratorDestroy();
|
|
}
|
|
}
|
|
|
|
void
|
|
JS::ProfilingFrameIterator::operator++()
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
|
|
|
|
if (activation_->isWasm()) {
|
|
++wasmIter();
|
|
settle();
|
|
return;
|
|
}
|
|
|
|
++jitIter();
|
|
settle();
|
|
}
|
|
|
|
void
|
|
JS::ProfilingFrameIterator::settle()
|
|
{
|
|
while (iteratorDone()) {
|
|
iteratorDestroy();
|
|
activation_ = activation_->prevProfiling();
|
|
|
|
// Skip past any non-active jit activations in the list.
|
|
while (activation_ && activation_->isJit() && !activation_->asJit()->isActive())
|
|
activation_ = activation_->prevProfiling();
|
|
|
|
if (!activation_)
|
|
return;
|
|
iteratorConstruct();
|
|
}
|
|
}
|
|
|
|
void
|
|
JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state)
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
|
|
|
|
if (activation_->isWasm()) {
|
|
new (storage_.addr()) wasm::ProfilingFrameIterator(*activation_->asWasm(), state);
|
|
// Set savedPrevJitTop_ to the actual jitTop_ from the runtime.
|
|
savedPrevJitTop_ = activation_->cx()->runtime()->jitTop;
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(activation_->asJit()->isActive());
|
|
new (storage_.addr()) jit::JitProfilingFrameIterator(rt_, state);
|
|
}
|
|
|
|
void
|
|
JS::ProfilingFrameIterator::iteratorConstruct()
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
|
|
|
|
if (activation_->isWasm()) {
|
|
new (storage_.addr()) wasm::ProfilingFrameIterator(*activation_->asWasm());
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(activation_->asJit()->isActive());
|
|
MOZ_ASSERT(savedPrevJitTop_ != nullptr);
|
|
new (storage_.addr()) jit::JitProfilingFrameIterator(savedPrevJitTop_);
|
|
}
|
|
|
|
void
|
|
JS::ProfilingFrameIterator::iteratorDestroy()
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
|
|
|
|
if (activation_->isWasm()) {
|
|
wasmIter().~ProfilingFrameIterator();
|
|
return;
|
|
}
|
|
|
|
// Save prevjitTop for later use
|
|
savedPrevJitTop_ = activation_->asJit()->prevJitTop();
|
|
jitIter().~JitProfilingFrameIterator();
|
|
}
|
|
|
|
bool
|
|
JS::ProfilingFrameIterator::iteratorDone()
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
|
|
|
|
if (activation_->isWasm())
|
|
return wasmIter().done();
|
|
|
|
return jitIter().done();
|
|
}
|
|
|
|
void*
|
|
JS::ProfilingFrameIterator::stackAddress() const
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
|
|
|
|
if (activation_->isWasm())
|
|
return wasmIter().stackAddress();
|
|
|
|
return jitIter().stackAddress();
|
|
}
|
|
|
|
Maybe<JS::ProfilingFrameIterator::Frame>
|
|
JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(jit::JitcodeGlobalEntry* entry) const
|
|
{
|
|
void* stackAddr = stackAddress();
|
|
|
|
if (isWasm()) {
|
|
Frame frame;
|
|
frame.kind = Frame_Wasm;
|
|
frame.stackAddress = stackAddr;
|
|
frame.returnAddress = nullptr;
|
|
frame.activation = activation_;
|
|
return mozilla::Some(mozilla::Move(frame));
|
|
}
|
|
|
|
MOZ_ASSERT(isJit());
|
|
|
|
// Look up an entry for the return address.
|
|
void* returnAddr = jitIter().returnAddressToFp();
|
|
jit::JitcodeGlobalTable* table = rt_->jitRuntime()->getJitcodeGlobalTable();
|
|
if (hasSampleBufferGen())
|
|
*entry = table->lookupForSamplerInfallible(returnAddr, rt_, sampleBufferGen_);
|
|
else
|
|
*entry = table->lookupInfallible(returnAddr);
|
|
|
|
MOZ_ASSERT(entry->isIon() || entry->isIonCache() || entry->isBaseline() || entry->isDummy());
|
|
|
|
// Dummy frames produce no stack frames.
|
|
if (entry->isDummy())
|
|
return mozilla::Nothing();
|
|
|
|
Frame frame;
|
|
frame.kind = entry->isBaseline() ? Frame_Baseline : Frame_Ion;
|
|
frame.stackAddress = stackAddr;
|
|
frame.returnAddress = returnAddr;
|
|
frame.activation = activation_;
|
|
return mozilla::Some(mozilla::Move(frame));
|
|
}
|
|
|
|
uint32_t
|
|
JS::ProfilingFrameIterator::extractStack(Frame* frames, uint32_t offset, uint32_t end) const
|
|
{
|
|
if (offset >= end)
|
|
return 0;
|
|
|
|
jit::JitcodeGlobalEntry entry;
|
|
Maybe<Frame> physicalFrame = getPhysicalFrameAndEntry(&entry);
|
|
|
|
// Dummy frames produce no stack frames.
|
|
if (physicalFrame.isNothing())
|
|
return 0;
|
|
|
|
if (isWasm()) {
|
|
frames[offset] = mozilla::Move(physicalFrame.ref());
|
|
frames[offset].label = DuplicateString(wasmIter().label());
|
|
if (!frames[offset].label)
|
|
return 0; // Drop stack frames silently on OOM.
|
|
return 1;
|
|
}
|
|
|
|
// Extract the stack for the entry. Assume maximum inlining depth is <64
|
|
const char* labels[64];
|
|
uint32_t depth = entry.callStackAtAddr(rt_, jitIter().returnAddressToFp(), labels, 64);
|
|
MOZ_ASSERT(depth < 64);
|
|
for (uint32_t i = 0; i < depth; i++) {
|
|
if (offset + i >= end)
|
|
return i;
|
|
Frame& frame = frames[offset + i];
|
|
frame = mozilla::Move(physicalFrame.ref());
|
|
frame.label = DuplicateString(labels[i]);
|
|
if (!frame.label)
|
|
return i; // Drop stack frames silently on OOM.
|
|
}
|
|
|
|
return depth;
|
|
}
|
|
|
|
Maybe<JS::ProfilingFrameIterator::Frame>
|
|
JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const
|
|
{
|
|
jit::JitcodeGlobalEntry unused;
|
|
return getPhysicalFrameAndEntry(&unused);
|
|
}
|
|
|
|
bool
|
|
JS::ProfilingFrameIterator::isWasm() const
|
|
{
|
|
MOZ_ASSERT(!done());
|
|
return activation_->isWasm();
|
|
}
|
|
|
|
bool
|
|
JS::ProfilingFrameIterator::isJit() const
|
|
{
|
|
return activation_->isJit();
|
|
}
|