blob: b755b72942729f0f035914524f9aad2516d50698 [file] [log] [blame]
/* -*- 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 "asmjs/AsmJSFrameIterator.h"
#include "asmjs/AsmJSModule.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/Interpreter-inl.h"
#include "vm/Probes-inl.h"
#include "vm/ScopeObject-inl.h"
using namespace js;
using mozilla::Maybe;
using mozilla::PodCopy;
/*****************************************************************************/
void
InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr evalInFramePrev,
const Value& newTargetValue, HandleObject scopeChain,
ExecuteType type)
{
/*
* See encoding of ExecuteType. When GLOBAL isn't set, we are executing a
* script in the context of another frame and the frame type is determined
* by the context.
*/
flags_ = type | HAS_SCOPECHAIN;
JSObject* callee = nullptr;
// 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 (!(flags_ & (GLOBAL | MODULE))) {
if (evalInFramePrev) {
MOZ_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame());
if (evalInFramePrev.isFunctionFrame()) {
callee = evalInFramePrev.callee();
if (newTarget.isNull())
newTarget = evalInFramePrev.newTarget();
flags_ |= FUNCTION;
} else {
flags_ |= GLOBAL;
}
} else {
FrameIter iter(cx);
MOZ_ASSERT(iter.isFunctionFrame() || iter.isGlobalFrame());
MOZ_ASSERT(!iter.isAsmJS());
if (iter.isFunctionFrame()) {
if (newTarget.isNull())
newTarget = iter.newTarget();
callee = iter.callee(cx);
flags_ |= FUNCTION;
} else {
flags_ |= GLOBAL;
}
}
}
Value* dstvp = (Value*)this - 2;
if (isFunctionFrame()) {
dstvp[1] = ObjectValue(*callee);
exec.fun = &callee->as<JSFunction>();
u.evalScript = script;
} else {
MOZ_ASSERT(isGlobalFrame() || isModuleFrame());
dstvp[1] = NullValue();
exec.script = script;
#ifdef DEBUG
u.evalScript = (JSScript*)0xbad;
#endif
}
dstvp[0] = newTarget;
scopeChain_ = scopeChain.get();
prev_ = nullptr;
prevpc_ = nullptr;
prevsp_ = nullptr;
MOZ_ASSERT_IF(evalInFramePrev, isDebuggerEvalFrame());
evalInFramePrev_ = evalInFramePrev;
if (script->isDebuggee())
setIsDebuggee();
#ifdef DEBUG
Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
#endif
}
bool
InterpreterFrame::isNonGlobalEvalFrame() const
{
return isEvalFrame() &&
script()->enclosingStaticScope()->as<StaticEvalObject>().isNonGlobal();
}
bool
InterpreterFrame::copyRawFrameSlots(AutoValueVector* vec)
{
if (!vec->resize(numFormalArgs() + script()->nfixed()))
return false;
PodCopy(vec->begin(), argv(), numFormalArgs());
PodCopy(vec->begin() + numFormalArgs(), slots(), script()->nfixed());
return true;
}
JSObject*
InterpreterFrame::createRestParameter(JSContext* cx)
{
MOZ_ASSERT(fun()->hasRest());
unsigned nformal = fun()->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
AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* scope)
{
#ifdef DEBUG
RootedObject originalScope(cx, scope);
RootedObject enclosingScope(cx, script->enclosingStaticScope());
for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) {
if (i.type() == StaticScopeIter<NoGC>::NonSyntactic) {
while (scope->is<DynamicWithObject>() ||
scope->is<NonSyntacticVariablesObject>() ||
(scope->is<ClonedBlockObject>() &&
!scope->as<ClonedBlockObject>().isSyntactic()))
{
MOZ_ASSERT(!IsSyntacticScope(scope));
scope = &scope->as<ScopeObject>().enclosingScope();
}
} else if (i.hasSyntacticDynamicScopeObject()) {
switch (i.type()) {
case StaticScopeIter<NoGC>::Module:
MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().module().script() == i.moduleScript());
scope = &scope->as<ModuleEnvironmentObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::Function:
MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript());
scope = &scope->as<CallObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::Block:
MOZ_ASSERT(&i.block() == scope->as<ClonedBlockObject>().staticScope());
scope = &scope->as<ClonedBlockObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::With:
MOZ_ASSERT(&i.staticWith() == scope->as<DynamicWithObject>().staticScope());
scope = &scope->as<DynamicWithObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::NamedLambda:
scope = &scope->as<DeclEnvObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::Eval:
scope = &scope->as<CallObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::NonSyntactic:
MOZ_CRASH("NonSyntactic should not have a syntactic scope");
break;
}
}
}
// In the case of a non-syntactic scope chain, the immediate parent of the
// outermost non-syntactic scope may be the global lexical scope, or, if
// called from Debugger, a DebugScopeObject.
//
// In the case of a syntactic scope chain, the outermost scope is always a
// GlobalObject.
MOZ_ASSERT(scope->is<GlobalObject>() || IsGlobalLexicalScope(scope) ||
scope->is<DebugScopeObject>());
#endif
}
bool
InterpreterFrame::initFunctionScopeObjects(JSContext* cx)
{
CallObject* callobj = CallObject::createForFunction(cx, this);
if (!callobj)
return false;
pushOnScopeChain(*callobj);
flags_ |= HAS_CALL_OBJ;
return true;
}
bool
InterpreterFrame::prologue(JSContext* cx)
{
RootedScript script(cx, this->script());
MOZ_ASSERT(isModuleFrame() == !!script->module());
MOZ_ASSERT(cx->interpreterRegs().pc == script->code());
if (isEvalFrame()) {
if (script->strict()) {
CallObject* callobj = CallObject::createForStrictEval(cx, this);
if (!callobj)
return false;
pushOnScopeChain(*callobj);
flags_ |= HAS_CALL_OBJ;
} else {
// 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, scopeChain(), varObjRoot))
return false;
}
return probes::EnterScript(cx, script, nullptr, this);
}
if (isGlobalFrame()) {
Rooted<ClonedBlockObject*> lexicalScope(cx);
RootedObject varObjRoot(cx);
if (script->hasNonSyntacticScope()) {
lexicalScope = &extensibleLexicalScope();
varObjRoot = &varObj();
} else {
lexicalScope = &cx->global()->lexicalScope();
varObjRoot = cx->global();
}
if (!CheckGlobalDeclarationConflicts(cx, script, lexicalScope, varObjRoot))
return false;
return probes::EnterScript(cx, script, nullptr, this);
}
AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain());
if (isModuleFrame())
return probes::EnterScript(cx, script, nullptr, this);
MOZ_ASSERT(isNonEvalFunctionFrame());
if (fun()->needsCallObject() && !initFunctionScopeObjects(cx))
return false;
if (isConstructing()) {
if (script->isDerivedClassConstructor()) {
MOZ_ASSERT(callee().isClassConstructor());
thisArgument() = MagicValue(JS_UNINITIALIZED_LEXICAL);
} else if (thisArgument().isPrimitive()) {
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)
{
RootedScript script(cx, this->script());
probes::ExitScript(cx, script, script->functionNonDelazifying(), hasPushedSPSFrame());
if (isEvalFrame()) {
if (isStrictEvalFrame()) {
MOZ_ASSERT_IF(hasCallObj(), scopeChain()->as<CallObject>().isForEval());
if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
DebugScopes::onPopStrictEvalScope(this);
} else if (isNonGlobalEvalFrame()) {
MOZ_ASSERT_IF(isDebuggerEvalFrame(), !IsSyntacticScope(scopeChain()));
}
return;
}
if (isGlobalFrame()) {
// Confusingly, global frames may run in non-global scopes (that is,
// not directly under the GlobalObject and its lexical scope).
//
// Gecko often runs global scripts under custom scopes. See users of
// CreateNonSyntacticScopeChain.
MOZ_ASSERT(IsGlobalLexicalScope(scopeChain()) || !IsSyntacticScope(scopeChain()));
return;
}
if (isModuleFrame())
return;
MOZ_ASSERT(isNonEvalFunctionFrame());
if (fun()->needsCallObject()) {
MOZ_ASSERT_IF(hasCallObj() && !fun()->isGenerator(),
scopeChain()->as<CallObject>().callee().nonLazyScript() == script);
} else {
AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain());
}
if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
DebugScopes::onPopCall(this, cx);
if (!fun()->isGenerator() &&
isConstructing() &&
thisArgument().isObject() &&
returnValue().isPrimitive())
{
setReturnValue(thisArgument());
}
}
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::pushBlock(JSContext* cx, StaticBlockObject& block)
{
MOZ_ASSERT(block.needsClone());
Rooted<StaticBlockObject*> blockHandle(cx, &block);
ClonedBlockObject* clone = ClonedBlockObject::create(cx, blockHandle, this);
if (!clone)
return false;
pushOnScopeChain(*clone);
return true;
}
bool
InterpreterFrame::freshenBlock(JSContext* cx)
{
MOZ_ASSERT(flags_ & HAS_SCOPECHAIN);
Rooted<ClonedBlockObject*> block(cx, &scopeChain_->as<ClonedBlockObject>());
ClonedBlockObject* fresh = ClonedBlockObject::clone(cx, block);
if (!fresh)
return false;
replaceInnermostScope(*fresh);
return true;
}
void
InterpreterFrame::popBlock(JSContext* cx)
{
MOZ_ASSERT(scopeChain_->is<ClonedBlockObject>());
popOffScopeChain();
}
void
InterpreterFrame::popWith(JSContext* cx)
{
if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
DebugScopes::onPopWith(this);
MOZ_ASSERT(scopeChain()->is<DynamicWithObject>());
popOffScopeChain();
}
void
InterpreterFrame::mark(JSTracer* trc)
{
/*
* Normally we would use MarkRoot here, except that generators also take
* this path. However, generators use a special write barrier when the stack
* frame is copied to the floating frame. Therefore, no barrier is needed.
*/
if (flags_ & HAS_SCOPECHAIN)
TraceManuallyBarrieredEdge(trc, &scopeChain_, "scope chain");
if (flags_ & HAS_ARGS_OBJ)
TraceManuallyBarrieredEdge(trc, &argsObj_, "arguments");
if (isFunctionFrame()) {
TraceManuallyBarrieredEdge(trc, &exec.fun, "fun");
if (isEvalFrame())
TraceManuallyBarrieredEdge(trc, &u.evalScript, "eval script");
} else {
TraceManuallyBarrieredEdge(trc, &exec.script, "script");
}
if (trc->isMarkingTracer())
script()->compartment()->zone()->active = true;
if (hasReturnValue())
TraceManuallyBarrieredEdge(trc, &rval_, "rval");
}
void
InterpreterFrame::markValues(JSTracer* trc, unsigned start, unsigned end)
{
if (start < end)
TraceRootRange(trc, end - start, slots() + start, "vm_stack");
}
void
InterpreterFrame::markValues(JSTracer* trc, Value* sp, jsbytecode* pc)
{
MOZ_ASSERT(sp >= slots());
JSScript* script = this->script();
size_t nfixed = script->nfixed();
size_t nlivefixed = script->calculateLiveFixed(pc);
if (nfixed == nlivefixed) {
// All locals are live.
markValues(trc, 0, sp - slots());
} else {
// Mark operand stack.
markValues(trc, nfixed, sp - slots());
// Clear dead block-scoped locals.
while (nfixed > nlivefixed)
unaliasedLocal(--nfixed).setMagic(JS_UNINITIALIZED_LEXICAL);
// Mark live locals.
markValues(trc, 0, nlivefixed);
}
if (hasArgs()) {
// Mark callee, |this| and arguments.
unsigned argc = Max(numActualArgs(), numFormalArgs());
TraceRootRange(trc, argc + 2 + isConstructing(), argv_ - 2, "fp argv");
} else {
// Mark callee and newTarget
TraceRootRange(trc, 2, ((Value*)this) - 2, "stack callee and newTarget");
}
}
static void
MarkInterpreterActivation(JSTracer* trc, InterpreterActivation* act)
{
for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) {
InterpreterFrame* fp = frames.frame();
fp->markValues(trc, frames.sp(), frames.pc());
fp->mark(trc);
}
}
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();
pc = fp()->script()->lastPC();
}
/*****************************************************************************/
InterpreterFrame*
InterpreterStack::pushInvokeFrame(JSContext* cx, const CallArgs& args, InitialFrameFlags initial)
{
LifoAlloc::Mark mark = allocator_.mark();
RootedFunction fun(cx, &args.callee().as<JSFunction>());
RootedScript script(cx, fun->nonLazyScript());
InterpreterFrame::Flags flags = ToFrameFlags(initial);
Value* argv;
InterpreterFrame* fp = getCallFrame(cx, args, script, &flags, &argv);
if (!fp)
return nullptr;
fp->mark_ = mark;
fp->initCallFrame(cx, nullptr, nullptr, nullptr, *fun, script, argv, args.length(), flags);
return fp;
}
InterpreterFrame*
InterpreterStack::pushExecuteFrame(JSContext* cx, HandleScript script, const Value& newTargetValue,
HandleObject scopeChain, ExecuteType type,
AbstractFramePtr evalInFrame)
{
LifoAlloc::Mark mark = allocator_.mark();
unsigned nvars = 2 /* callee, newTarget */ + script->nslots();
uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
if (!buffer)
return nullptr;
InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 2 * sizeof(Value));
fp->mark_ = mark;
fp->initExecuteFrame(cx, script, evalInFrame, newTargetValue, scopeChain, type);
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 JS_SaveFrameChain was called, stop iterating here (unless
// GO_THROUGH_SAVED is set).
if (data_.savedOption_ == STOP_AT_SAVED && activation->hasSavedFrameChain()) {
data_.state_ = DONE;
return;
}
// Skip activations from another context if needed.
MOZ_ASSERT(activation->cx());
MOZ_ASSERT(data_.cx_);
if (data_.contextOption_ == CURRENT_CONTEXT && activation->cx() != data_.cx_) {
++data_.activations_;
continue;
}
// 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_->asJSContext();
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->isAsmJS()) {
data_.asmJSFrames_ = AsmJSFrameIterator(*data_.activations_->asAsmJS());
if (data_.asmJSFrames_.done()) {
++data_.activations_;
continue;
}
data_.state_ = ASMJS;
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, SavedOption savedOption,
ContextOption contextOption, DebuggerEvalOption debuggerEvalOption,
JSPrincipals* principals)
: cx_(cx),
savedOption_(savedOption),
contextOption_(contextOption),
debuggerEvalOption_(debuggerEvalOption),
principals_(principals),
pc_(nullptr),
interpFrames_(nullptr),
activations_(cx->runtime()),
jitFrames_(),
ionInlineFrameNo_(0),
asmJSFrames_()
{
}
FrameIter::Data::Data(const FrameIter::Data& other)
: cx_(other.cx_),
savedOption_(other.savedOption_),
contextOption_(other.contextOption_),
debuggerEvalOption_(other.debuggerEvalOption_),
principals_(other.principals_),
state_(other.state_),
pc_(other.pc_),
interpFrames_(other.interpFrames_),
activations_(other.activations_),
jitFrames_(other.jitFrames_),
ionInlineFrameNo_(other.ionInlineFrameNo_),
asmJSFrames_(other.asmJSFrames_)
{
}
FrameIter::FrameIter(JSContext* cx, SavedOption savedOption)
: data_(cx, savedOption, CURRENT_CONTEXT, FOLLOW_DEBUGGER_EVAL_PREV_LINK, nullptr),
ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr)
{
// settleOnActivation can only GC if principals are given.
JS::AutoSuppressGCAnalysis nogc;
settleOnActivation();
}
FrameIter::FrameIter(JSContext* cx, ContextOption contextOption,
SavedOption savedOption, DebuggerEvalOption debuggerEvalOption)
: data_(cx, savedOption, contextOption, debuggerEvalOption, nullptr),
ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr)
{
// settleOnActivation can only GC if principals are given.
JS::AutoSuppressGCAnalysis nogc;
settleOnActivation();
}
FrameIter::FrameIter(JSContext* cx, ContextOption contextOption,
SavedOption savedOption, DebuggerEvalOption debuggerEvalOption,
JSPrincipals* principals)
: data_(cx, savedOption, contextOption, 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::popAsmJSFrame()
{
MOZ_ASSERT(data_.state_ == ASMJS);
++data_.asmJSFrames_;
if (data_.asmJSFrames_.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();
// Eval-in-frame can cross contexts and works across saved frame
// chains.
ContextOption prevContextOption = data_.contextOption_;
SavedOption prevSavedOption = data_.savedOption_;
data_.contextOption_ = ALL_CONTEXTS;
data_.savedOption_ = GO_THROUGH_SAVED;
popInterpreterFrame();
while (!hasUsableAbstractFramePtr() || abstractFramePtr() != eifPrev) {
if (data_.state_ == JIT)
popJitFrame();
else
popInterpreterFrame();
}
data_.contextOption_ = prevContextOption;
data_.savedOption_ = prevSavedOption;
data_.cx_ = data_.activations_->cx();
break;
}
popInterpreterFrame();
break;
case JIT:
popJitFrame();
break;
case ASMJS:
popAsmJSFrame();
break;
}
return *this;
}
FrameIter::Data*
FrameIter::copyData() const
{
Data* data = data_.cx_->new_<Data>(data_);
if (!data)
return nullptr;
MOZ_ASSERT(data_.state_ != ASMJS);
if (data && data_.jitFrames_.isIonScripted())
data->ionInlineFrameNo_ = ionInlineFrames_.frameNo();
// Give the copied Data the cx of the current activation, which may be
// different than the cx that the current FrameIter was constructed
// with. This ensures that when we instantiate another FrameIter with the
// copied data, its cx is still alive.
data->cx_ = activation()->cx();
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:
case ASMJS:
return nullptr;
case JIT:
return data_.jitFrames_.fp();
case INTERP:
return interpFrame();
}
MOZ_CRASH("Unexpected state");
}
JSCompartment*
FrameIter::compartment() const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
case ASMJS:
return data_.activations_->compartment();
}
MOZ_CRASH("Unexpected state");
}
bool
FrameIter::isFunctionFrame() const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
return interpFrame()->isFunctionFrame();
case JIT:
MOZ_ASSERT(data_.jitFrames_.isScripted());
if (data_.jitFrames_.isBaselineJS())
return data_.jitFrames_.isFunctionFrame();
return ionInlineFrames_.isFunctionFrame();
case ASMJS:
return true;
}
MOZ_CRASH("Unexpected state");
}
bool
FrameIter::isGlobalFrame() const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
return interpFrame()->isGlobalFrame();
case JIT:
if (data_.jitFrames_.isBaselineJS())
return data_.jitFrames_.baselineFrame()->isGlobalFrame();
MOZ_ASSERT(!script()->isForEval());
return !script()->functionNonDelazifying();
case ASMJS:
return false;
}
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 ASMJS:
return false;
}
MOZ_CRASH("Unexpected state");
}
bool
FrameIter::isNonEvalFunctionFrame() const
{
MOZ_ASSERT(!done());
switch (data_.state_) {
case DONE:
break;
case INTERP:
return interpFrame()->isNonEvalFunctionFrame();
case JIT:
return !isEvalFrame() && isFunctionFrame();
case ASMJS:
return true;
}
MOZ_CRASH("Unexpected state");
}
JSAtom*
FrameIter::functionDisplayAtom() const
{
MOZ_ASSERT(isNonEvalFunctionFrame());
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
return calleeTemplate()->displayAtom();
case ASMJS:
return data_.asmJSFrames_.functionDisplayAtom();
}
MOZ_CRASH("Unexpected state");
}
ScriptSource*
FrameIter::scriptSource() const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
return script()->scriptSource();
case ASMJS:
return data_.activations_->asAsmJS()->module().scriptSource();
}
MOZ_CRASH("Unexpected state");
}
const char*
FrameIter::scriptFilename() const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
return script()->filename();
case ASMJS:
return data_.activations_->asAsmJS()->module().scriptSource()->filename();
}
MOZ_CRASH("Unexpected state");
}
const char16_t*
FrameIter::scriptDisplayURL() const
{
ScriptSource* ss = scriptSource();
return ss->hasDisplayURL() ? ss->displayURL() : nullptr;
}
unsigned
FrameIter::computeLine(uint32_t* column) const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
return PCToLineNumber(script(), pc(), column);
case ASMJS:
return data_.asmJSFrames_.computeLine(column);
}
MOZ_CRASH("Unexpected state");
}
bool
FrameIter::mutedErrors() const
{
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
return script()->mutedErrors();
case ASMJS:
return data_.activations_->asAsmJS()->module().scriptSource()->mutedErrors();
}
MOZ_CRASH("Unexpected state");
}
bool
FrameIter::isConstructing() const
{
switch (data_.state_) {
case DONE:
case ASMJS:
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 ASMJS:
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 ASMJS:
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:
case ASMJS:
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;
}
MOZ_CRASH("Unexpected state");
}
JSFunction*
FrameIter::calleeTemplate() const
{
switch (data_.state_) {
case DONE:
case ASMJS:
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 ASMJS:
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 ASMJS:
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::scopeChain(JSContext* cx) const
{
switch (data_.state_) {
case DONE:
case ASMJS:
break;
case JIT:
if (data_.jitFrames_.isIonScripted()) {
jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_);
return ionInlineFrames_.scopeChain(recover);
}
return data_.jitFrames_.baselineFrame()->scopeChain();
case INTERP:
return interpFrame()->scopeChain();
}
MOZ_CRASH("Unexpected state");
}
CallObject&
FrameIter::callObj(JSContext* cx) const
{
MOZ_ASSERT(calleeTemplate()->needsCallObject());
JSObject* pobj = scopeChain(cx);
while (!pobj->is<CallObject>())
pobj = pobj->enclosingScope();
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(isNonEvalFunctionFrame());
switch (data_.state_) {
case DONE:
case ASMJS:
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 ASMJS:
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 ASMJS:
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 ASMJS:
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 ASMJS:
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 ASMJS:
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 = js_sb_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->runtime()->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));
RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations);
if (entryFrame->isFunctionFrame())
entryMonitor_->Entry(cx, entryFrame->fun(), 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));
RootedString asyncCause(cx, cx->runtime()->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),
active_(active),
isLazyLinkExitFrame_(false),
rematerializedFrames_(nullptr),
ionRecovery_(cx),
bailoutData_(nullptr),
lastProfilingFrame_(nullptr),
lastProfilingCallSite_(nullptr)
{
if (active) {
prevJitTop_ = cx->runtime()->jitTop;
prevJitJSContext_ = cx->runtime()->jitJSContext;
prevJitActivation_ = cx->runtime()->jitActivation;
cx->runtime()->jitJSContext = cx;
cx->runtime()->jitActivation = this;
registerProfiling();
} else {
prevJitTop_ = nullptr;
prevJitJSContext_ = nullptr;
prevJitActivation_ = nullptr;
}
}
jit::JitActivation::~JitActivation()
{
if (active_) {
if (isProfiling())
unregisterProfiling();
cx_->runtime()->jitTop = prevJitTop_;
cx_->runtime()->jitJSContext = prevJitJSContext_;
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 GenerateFFIIonExit() with explicit masm instructions so
// changes to the logic here need to be reflected in GenerateFFIIonExit() 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;
prevJitTop_ = cx->runtime()->jitTop;
prevJitJSContext_ = cx->runtime()->jitJSContext;
prevJitActivation_ = cx->runtime()->jitActivation;
cx->runtime()->jitJSContext = cx;
cx->runtime()->jitActivation = this;
registerProfiling();
} else {
unregisterProfiling();
cx->runtime()->jitTop = prevJitTop_;
cx->runtime()->jitJSContext = prevJitJSContext_;
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 empty(cx);
if (!rematerializedFrames_->add(p, top, Move(empty))) {
ReportOutOfMemory(cx);
return nullptr;
}
// 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,
p->value()))
{
return nullptr;
}
// See comment in unsetPrevUpToDateUntil.
DebugScopes::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())
RematerializedFrame::MarkInVector(trc, e.front().value());
}
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);
}
AsmJSActivation::AsmJSActivation(JSContext* cx, AsmJSModule& module)
: Activation(cx, AsmJS),
module_(module),
entrySP_(nullptr),
resumePC_(nullptr),
fp_(nullptr),
packedExitReason_(wasm::ExitReason(wasm::ExitReason::None).pack())
{
(void) entrySP_; // squelch GCC warning
prevAsmJSForModule_ = module.activation();
module.activation() = this;
prevAsmJS_ = cx->runtime()->asmJSActivationStack_;
cx->runtime()->asmJSActivationStack_ = this;
// Now that the AsmJSActivation is fully initialized, make it visible to
// asynchronous profiling.
registerProfiling();
}
AsmJSActivation::~AsmJSActivation()
{
// Hide this activation from the profiler before is is destroyed.
unregisterProfiling();
MOZ_ASSERT(fp_ == nullptr);
MOZ_ASSERT(module_.activation() == this);
module_.activation() = prevAsmJSForModule_;
JSContext* cx = cx_->asJSContext();
MOZ_ASSERT(cx->runtime()->asmJSActivationStack_ == this);
cx->runtime()->asmJSActivationStack_ = prevAsmJS_;
}
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(JSRuntime* rt, const RegisterState& state,
uint32_t sampleBufferGen)
: rt_(rt),
sampleBufferGen_(sampleBufferGen),
activation_(nullptr),
savedPrevJitTop_(nullptr)
{
if (!rt->spsProfiler.enabled())
MOZ_CRASH("ProfilingFrameIterator called when spsProfiler not enabled for runtime.");
if (!rt->profilingActivation())
return;
// If profiler sampling is not enabled, skip.
if (!rt_->isProfilerSamplingEnabled())
return;
activation_ = rt->profilingActivation();
MOZ_ASSERT(activation_->isProfiling());
static_assert(sizeof(AsmJSProfilingFrameIterator) <= 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_->isAsmJS() || activation_->isJit());
if (activation_->isAsmJS()) {
++asmJSIter();
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_->isAsmJS() || activation_->isJit());
if (activation_->isAsmJS()) {
new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS(), 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_->isAsmJS() || activation_->isJit());
if (activation_->isAsmJS()) {
new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS());
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_->isAsmJS() || activation_->isJit());
if (activation_->isAsmJS()) {
asmJSIter().~AsmJSProfilingFrameIterator();
return;
}
// Save prevjitTop for later use
savedPrevJitTop_ = activation_->asJit()->prevJitTop();
jitIter().~JitProfilingFrameIterator();
}
bool
JS::ProfilingFrameIterator::iteratorDone()
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit());
if (activation_->isAsmJS())
return asmJSIter().done();
return jitIter().done();
}
void*
JS::ProfilingFrameIterator::stackAddress() const
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit());
if (activation_->isAsmJS())
return asmJSIter().stackAddress();
return jitIter().stackAddress();
}
Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(jit::JitcodeGlobalEntry* entry) const
{
void* stackAddr = stackAddress();
if (isAsmJS()) {
Frame frame;
frame.kind = Frame_AsmJS;
frame.stackAddress = stackAddr;
frame.returnAddress = nullptr;
frame.activation = activation_;
frame.label = nullptr;
return mozilla::Some(frame);
}
MOZ_ASSERT(isJit());
// Look up an entry for the return address.
void* returnAddr = jitIter().returnAddressToFp();
jit::JitcodeGlobalTable* table = rt_->jitRuntime()->getJitcodeGlobalTable();
if (hasSampleBufferGen())
table->lookupForSampler(returnAddr, entry, rt_, sampleBufferGen_);
else
table->lookupInfallible(returnAddr, entry, rt_);
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_;
frame.label = nullptr;
return mozilla::Some(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 (isAsmJS()) {
frames[offset] = physicalFrame.value();
frames[offset].label = asmJSIter().label();
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;
frames[offset + i] = physicalFrame.value();
frames[offset + i].label = labels[i];
}
return depth;
}
Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const
{
jit::JitcodeGlobalEntry unused;
return getPhysicalFrameAndEntry(&unused);
}
bool
JS::ProfilingFrameIterator::isAsmJS() const
{
MOZ_ASSERT(!done());
return activation_->isAsmJS();
}
bool
JS::ProfilingFrameIterator::isJit() const
{
return activation_->isJit();
}