/* -*- 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 "gc/Marking.h"
#include "jit/BaselineFrame.h"
#include "jit/JitcodeMap.h"
#include "jit/JitCompartment.h"
#include "vm/Debugger.h"
#include "vm/JSContext.h"
#include "vm/Opcodes.h"
#include "jit/JSJitFrameIter-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Probes-inl.h"
using namespace js;
using mozilla::ArrayLength;
using mozilla::DebugOnly;
using mozilla::Maybe;
/*****************************************************************************/
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);
if (newTarget.isNull() && iter.hasScript() &&
iter.script()->bodyScope()->hasOnChain(ScopeKind::Function)) {
newTarget = iter.newTarget();
}
} else if (evalInFramePrev) {
if (newTarget.isNull() && evalInFramePrev.hasScript() &&
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();
}
ArrayObject* InterpreterFrame::createRestParameter(JSContext* cx) {
MOZ_ASSERT(script()->hasRest());
unsigned nformal = callee().nargs() - 1, nactual = numActualArgs();
unsigned nrest = (nactual > nformal) ? nactual - nformal : 0;
Value* restvp = argv() + nformal;
return 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;
case ScopeKind::WasmInstance:
env =
&env->as<WasmInstanceEnvironmentObject>().enclosingEnvironment();
break;
case ScopeKind::WasmFunction:
env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
break;
}
}
}
// In the case of a non-syntactic env chain, the immediate parent of the
// outermost non-syntactic env may be the global lexical env, or, if
// called from Debugger, a DebugEnvironmentProxy.
//
// In the case of a syntactic env chain, the outermost env is always a
// GlobalObject.
MOZ_ASSERT(env->is<GlobalObject>() || IsGlobalLexicalEnvironment(env) ||
env->is<DebugEnvironmentProxy>());
#endif
}
static inline void AssertScopeMatchesEnvironment(InterpreterFrame* fp,
jsbytecode* pc) {
#ifdef DEBUG
// If we OOMed before fully initializing the environment chain, the scope
// and environment will definitely mismatch.
if (fp->script()->initialEnvironmentShape() && fp->hasInitialEnvironment())
AssertScopeMatchesEnvironment(fp->script()->innermostScope(pc),
fp->environmentChain());
#endif
}
bool InterpreterFrame::initFunctionEnvironmentObjects(JSContext* cx) {
return js::InitFunctionEnvironmentObjects(cx, this);
}
bool InterpreterFrame::prologue(JSContext* cx) {
RootedScript script(cx, this->script());
MOZ_ASSERT(cx->interpreterRegs().pc == script->code());
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;
MOZ_ASSERT_IF(isConstructing(),
thisArgument().isObject() ||
thisArgument().isMagic(JS_UNINITIALIZED_LEXICAL));
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(),
hasPushedGeckoProfilerFrame());
// Check that the scope matches the environment at the point of leaving
// the frame.
AssertScopeMatchesEnvironment(this, pc);
EnvironmentIter ei(cx, this, pc);
UnwindAllEnvironmentsInFrame(cx, ei);
if (isFunctionFrame()) {
if (!callee().isGenerator() && !callee().isAsync() && isConstructing() &&
thisArgument().isObject() && returnValue().isPrimitive()) {
setReturnValue(thisArgument());
}
return;
}
MOZ_ASSERT(isEvalFrame() || isGlobalFrame() || isModuleFrame());
}
bool InterpreterFrame::checkReturn(JSContext* cx, HandleValue thisv) {
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 {
// Trace 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 {
// Trace operand stack.
traceValues(trc, nfixed, sp - slots());
// Clear dead block-scoped locals.
while (nfixed > nlivefixed) unaliasedLocal(--nfixed).setUndefined();
// Trace live locals.
traceValues(trc, 0, nlivefixed);
}
if (script->compartment()->debugEnvs)
script->compartment()->debugEnvs->traceLiveFrame(trc, this);
}
void InterpreterFrame::traceValues(JSTracer* trc, unsigned start,
unsigned end) {
if (start < end)
TraceRootRange(trc, end - start, slots() + start, "vm_stack");
}
static void TraceInterpreterActivation(JSTracer* trc,
InterpreterActivation* act) {
for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) {
InterpreterFrame* fp = frames.frame();
fp->trace(trc, frames.sp(), frames.pc());
}
}
void js::TraceInterpreterActivations(JSContext* cx,
const CooperatingContext& target,
JSTracer* trc) {
for (ActivationIterator iter(cx, target); !iter.done(); ++iter) {
Activation* act = iter.activation();
if (act->isInterpreter())
TraceInterpreterActivation(trc, act->asInterpreter());
}
}
/*****************************************************************************/
// Unlike the other methods of this class, this method is defined here so that
// we don't have to #include jsautooplen.h in vm/Stack.h.
void InterpreterRegs::setToEndOfScript() { sp = fp()->base(); }
/*****************************************************************************/
InterpreterFrame* InterpreterStack::pushInvokeFrame(
JSContext* cx, const CallArgs& args, MaybeConstruct constructing) {
LifoAlloc::Mark mark = allocator_.mark();
RootedFunction fun(cx, &args.callee().as<JSFunction>());
RootedScript script(cx, fun->nonLazyScript());
Value* argv;
InterpreterFrame* fp = getCallFrame(cx, args, script, constructing, &argv);
if (!fp) return nullptr;
fp->mark_ = mark;
fp->initCallFrame(nullptr, nullptr, nullptr, *fun, script, argv,
args.length(), constructing);
return fp;
}
InterpreterFrame* InterpreterStack::pushExecuteFrame(
JSContext* cx, HandleScript script, 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;
}
/*****************************************************************************/
JitFrameIter::JitFrameIter(const JitFrameIter& another) { *this = another; }
JitFrameIter& JitFrameIter::operator=(const JitFrameIter& another) {
MOZ_ASSERT(this != &another);
act_ = another.act_;
mustUnwindActivation_ = another.mustUnwindActivation_;
if (isSome()) iter_.destroy();
if (!another.isSome()) return *this;
if (another.isJSJit()) {
iter_.construct<jit::JSJitFrameIter>(another.asJSJit());
} else {
MOZ_ASSERT(another.isWasm());
iter_.construct<wasm::WasmFrameIter>(another.asWasm());
}
return *this;
}
JitFrameIter::JitFrameIter(jit::JitActivation* act, bool mustUnwindActivation) {
act_ = act;
mustUnwindActivation_ = mustUnwindActivation;
MOZ_ASSERT(act->hasExitFP(),
"packedExitFP is used to determine if JSJit or wasm");
if (act->hasJSExitFP()) {
iter_.construct<jit::JSJitFrameIter>(act);
} else {
MOZ_ASSERT(act->hasWasmExitFP());
iter_.construct<wasm::WasmFrameIter>(act);
}
settle();
}
void JitFrameIter::skipNonScriptedJSFrames() {
if (isJSJit()) {
// Stop at the first scripted frame.
jit::JSJitFrameIter& frames = asJSJit();
while (!frames.isScripted() && !frames.done()) ++frames;
settle();
}
}
bool JitFrameIter::done() const {
if (!isSome()) return true;
if (isJSJit()) return asJSJit().done();
if (isWasm()) return asWasm().done();
MOZ_CRASH("unhandled case");
}
void JitFrameIter::settle() {
if (isJSJit()) {
const jit::JSJitFrameIter& jitFrame = asJSJit();
if (jitFrame.type() != jit::JitFrame_WasmToJSJit) return;
// Transition from js jit frames to wasm frames: we're on the
// wasm-to-jit fast path. The current stack layout is as follows:
// (stack grows downward)
//
// [--------------------]
// [WASM FUNC ]
// [WASM JIT EXIT FRAME ]
// [JIT WASM ENTRY FRAME] <-- we're here.
//
// So prevFP points to the wasm jit exit FP, maintaing the invariant in
// WasmFrameIter that the first frame is an exit frame and can be
// popped.
wasm::Frame* prevFP = (wasm::Frame*)jitFrame.prevFp();
if (mustUnwindActivation_) act_->setWasmExitFP(prevFP);
iter_.destroy();
iter_.construct<wasm::WasmFrameIter>(act_, prevFP);
MOZ_ASSERT(!asWasm().done());
return;
}
if (isWasm()) {
const wasm::WasmFrameIter& wasmFrame = asWasm();
if (!wasmFrame.unwoundIonCallerFP()) return;
// Transition from wasm frames to jit frames: we're on the
// jit-to-wasm fast path. The current stack layout is as follows:
// (stack grows downward)
//
// [--------------------]
// [JIT FRAME ]
// [WASM JIT ENTRY FRAME] <-- we're here
//
// The wasm iterator has saved the previous jit frame pointer for us.
MOZ_ASSERT(wasmFrame.done());
uint8_t* prevFP = wasmFrame.unwoundIonCallerFP();
if (mustUnwindActivation_) act_->setJSExitFP(prevFP);
iter_.destroy();
iter_.construct<jit::JSJitFrameIter>(act_, prevFP);
MOZ_ASSERT(!asJSJit().done());
return;
}
}
void JitFrameIter::operator++() {
MOZ_ASSERT(isSome());
if (isJSJit()) {
const jit::JSJitFrameIter& jitFrame = asJSJit();
jit::JitFrameLayout* prevFrame = nullptr;
if (mustUnwindActivation_ && jitFrame.isScripted())
prevFrame = jitFrame.jsFrame();
++asJSJit();
if (prevFrame) {
// Unwind the frame by updating packedExitFP. This is necessary
// so that (1) debugger exception unwind and leave frame hooks
// don't see this frame when they use ScriptFrameIter, and (2)
// ScriptFrameIter does not crash when accessing an IonScript
// that's destroyed by the ionScript->decref call.
EnsureBareExitFrame(act_, prevFrame);
}
} else if (isWasm()) {
++asWasm();
} else {
MOZ_CRASH("unhandled case");
}
settle();
}
OnlyJSJitFrameIter::OnlyJSJitFrameIter(jit::JitActivation* act)
: JitFrameIter(act) {
settle();
}
OnlyJSJitFrameIter::OnlyJSJitFrameIter(JSContext* cx)
: OnlyJSJitFrameIter(cx->activation()->asJit()) {}
OnlyJSJitFrameIter::OnlyJSJitFrameIter(const ActivationIterator& iter)
: OnlyJSJitFrameIter(iter->asJit()) {}
/*****************************************************************************/
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() {
MOZ_ASSERT(!data_.cx_->inUnsafeCallWithABI);
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_ = JitFrameIter(activation->asJit());
data_.jitFrames_.skipNonScriptedJSFrames();
if (data_.jitFrames_.done()) {
// It's possible to have an JitActivation with no scripted
// frames, for instance if we hit an over-recursion during
// bailout.
++data_.activations_;
continue;
}
data_.state_ = JIT;
nextJitFrame();
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),
ionInlineFrameNo_(0) {}
FrameIter::Data::Data(JSContext* cx, const CooperatingContext& target,
DebuggerEvalOption debuggerEvalOption)
: cx_(cx),
debuggerEvalOption_(debuggerEvalOption),
principals_(nullptr),
state_(DONE),
pc_(nullptr),
interpFrames_(nullptr),
activations_(cx, target),
ionInlineFrameNo_(0) {}
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_) {}
FrameIter::FrameIter(JSContext* cx, const CooperatingContext& target,
DebuggerEvalOption debuggerEvalOption)
: data_(cx, target, debuggerEvalOption),
ionInlineFrames_(cx, (js::jit::JSJitFrameIter*)nullptr) {
// settleOnActivation can only GC if principals are given.
JS::AutoSuppressGCAnalysis nogc;
settleOnActivation();
}
FrameIter::FrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption)
: data_(cx, debuggerEvalOption, nullptr),
ionInlineFrames_(cx, (js::jit::JSJitFrameIter*)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::JSJitFrameIter*)nullptr) {
settleOnActivation();
}
FrameIter::FrameIter(const FrameIter& other)
: data_(other.data_),
ionInlineFrames_(other.data_.cx_,
isIonScripted() ? &other.ionInlineFrames_ : nullptr) {}
FrameIter::FrameIter(const Data& data)
: data_(data),
ionInlineFrames_(data.cx_, isIonScripted() ? &jsJitFrame() : nullptr) {
MOZ_ASSERT(data.cx_);
if (isIonScripted()) {
while (ionInlineFrames_.frameNo() != data.ionInlineFrameNo_)
++ionInlineFrames_;
}
}
void FrameIter::nextJitFrame() {
MOZ_ASSERT(data_.jitFrames_.isSome());
if (isJSJit()) {
if (jsJitFrame().isIonScripted()) {
ionInlineFrames_.resetOn(&jsJitFrame());
data_.pc_ = ionInlineFrames_.pc();
} else {
MOZ_ASSERT(jsJitFrame().isBaselineJS());
jsJitFrame().baselineScriptAndPc(nullptr, &data_.pc_);
}
return;
}
MOZ_ASSERT(isWasm());
data_.pc_ = nullptr;
}
void FrameIter::popJitFrame() {
MOZ_ASSERT(data_.state_ == JIT);
MOZ_ASSERT(data_.jitFrames_.isSome());
if (isJSJit() && jsJitFrame().isIonScripted() && ionInlineFrames_.more()) {
++ionInlineFrames_;
data_.pc_ = ionInlineFrames_.pc();
return;
}
++data_.jitFrames_;
data_.jitFrames_.skipNonScriptedJSFrames();
if (!data_.jitFrames_.done()) {
nextJitFrame();
} else {
data_.jitFrames_.reset();
popActivation();
}
}
FrameIter& FrameIter::operator++() {
switch (data_.state_) {
case DONE:
MOZ_CRASH("Unexpected state");
case INTERP:
if (interpFrame()->isDebuggerEvalFrame() &&
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;
}
return *this;
}
FrameIter::Data* FrameIter::copyData() const {
Data* data = data_.cx_->new_<Data>(data_);
if (!data) return nullptr;
if (data && 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 INTERP:
return interpFrame();
case JIT:
if (isJSJit()) return jsJitFrame().fp();
MOZ_ASSERT(isWasm());
return nullptr;
}
MOZ_CRASH("Unexpected state");
}
JSCompartment* FrameIter::compartment() const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
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 (isJSJit()) {
if (jsJitFrame().isBaselineJS())
return jsJitFrame().baselineFrame()->isEvalFrame();
MOZ_ASSERT(!script()->isForEval());
return false;
}
MOZ_ASSERT(isWasm());
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 (isJSJit()) {
if (jsJitFrame().isBaselineJS())
return jsJitFrame().baselineFrame()->isFunctionFrame();
return script()->functionNonDelazifying();
}
MOZ_ASSERT(isWasm());
return false;
}
MOZ_CRASH("Unexpected state");
}
JSAtom* FrameIter::functionDisplayAtom() const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
if (isWasm()) return wasmFrame().functionDisplayAtom();
MOZ_ASSERT(isFunctionFrame());
return calleeTemplate()->displayAtom();
}
MOZ_CRASH("Unexpected state");
}
ScriptSource* FrameIter::scriptSource() const {
switch (data_.state_) {
case DONE:
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:
if (isWasm()) return wasmFrame().filename();
return script()->filename();
}
MOZ_CRASH("Unexpected state");
}
const char16_t* FrameIter::displayURL() const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
if (isWasm()) return wasmFrame().displayURL();
ScriptSource* ss = script()->scriptSource();
return ss->hasDisplayURL() ? ss->displayURL() : nullptr;
}
MOZ_CRASH("Unexpected state");
}
unsigned FrameIter::computeLine(uint32_t* column) const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
if (isWasm()) {
if (column) *column = 0;
return wasmFrame().lineOrBytecode();
}
return PCToLineNumber(script(), pc(), column);
}
MOZ_CRASH("Unexpected state");
}
bool FrameIter::mutedErrors() const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
if (isWasm()) return wasmFrame().mutedErrors();
return script()->mutedErrors();
}
MOZ_CRASH("Unexpected state");
}
bool FrameIter::isConstructing() const {
switch (data_.state_) {
case DONE:
break;
case JIT:
MOZ_ASSERT(isJSJit());
if (jsJitFrame().isIonScripted())
return ionInlineFrames_.isConstructing();
MOZ_ASSERT(jsJitFrame().isBaselineJS());
return jsJitFrame().isConstructing();
case INTERP:
return interpFrame()->isConstructing();
}
MOZ_CRASH("Unexpected state");
}
bool FrameIter::ensureHasRematerializedFrame(JSContext* cx) {
MOZ_ASSERT(isIon());
return !!activation()->asJit()->getRematerializedFrame(cx, jsJitFrame());
}
bool FrameIter::hasUsableAbstractFramePtr() const {
switch (data_.state_) {
case DONE:
return false;
case JIT:
if (isJSJit()) {
if (jsJitFrame().isBaselineJS()) return true;
MOZ_ASSERT(jsJitFrame().isIonScripted());
return !!activation()->asJit()->lookupRematerializedFrame(
jsJitFrame().fp(), ionInlineFrames_.frameNo());
}
MOZ_ASSERT(isWasm());
return wasmFrame().debugEnabled();
case INTERP:
return true;
}
MOZ_CRASH("Unexpected state");
}
AbstractFramePtr FrameIter::abstractFramePtr() const {
MOZ_ASSERT(hasUsableAbstractFramePtr());
switch (data_.state_) {
case DONE:
break;
case JIT: {
if (isJSJit()) {
if (jsJitFrame().isBaselineJS()) return jsJitFrame().baselineFrame();
MOZ_ASSERT(isIonScripted());
return activation()->asJit()->lookupRematerializedFrame(
jsJitFrame().fp(), ionInlineFrames_.frameNo());
}
MOZ_ASSERT(isWasm());
MOZ_ASSERT(wasmFrame().debugEnabled());
return wasmFrame().debugFrame();
}
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 (jsJitFrame().isBaselineJS()) {
jit::BaselineFrame* frame = jsJitFrame().baselineFrame();
jit::JitActivation* activation = data_.activations_->asJit();
// activation's exitFP may be invalid, so create a new
// activation iterator.
data_.activations_ = ActivationIterator(data_.cx_);
while (data_.activations_.activation() != activation)
++data_.activations_;
// Look for the current frame.
data_.jitFrames_ = JitFrameIter(data_.activations_->asJit());
while (!jsJitFrame().isBaselineJS() ||
jsJitFrame().baselineFrame() != frame)
++data_.jitFrames_;
// Update the pc.
MOZ_ASSERT(jsJitFrame().baselineFrame() == frame);
jsJitFrame().baselineScriptAndPc(nullptr, &data_.pc_);
return;
}
break;
}
MOZ_CRASH("Unexpected state");
}
void FrameIter::wasmUpdateBytecodeOffset() {
MOZ_RELEASE_ASSERT(isWasm(), "Unexpected state");
wasm::DebugFrame* frame = wasmFrame().debugFrame();
// Relookup the current frame, updating the bytecode offset in the process.
data_.jitFrames_ = JitFrameIter(data_.activations_->asJit());
while (wasmFrame().debugFrame() != frame) ++data_.jitFrames_;
MOZ_ASSERT(wasmFrame().debugFrame() == frame);
}
JSFunction* FrameIter::calleeTemplate() const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
MOZ_ASSERT(isFunctionFrame());
return &interpFrame()->callee();
case JIT:
if (jsJitFrame().isBaselineJS()) return jsJitFrame().callee();
MOZ_ASSERT(jsJitFrame().isIonScripted());
return ionInlineFrames_.calleeTemplate();
}
MOZ_CRASH("Unexpected state");
}
JSFunction* FrameIter::callee(JSContext* cx) const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
return calleeTemplate();
case JIT:
if (isIonScripted()) {
jit::MaybeReadFallback recover(cx, activation()->asJit(),
&jsJitFrame());
return ionInlineFrames_.callee(recover);
}
MOZ_ASSERT(jsJitFrame().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:
break;
case INTERP:
MOZ_ASSERT(isFunctionFrame());
return interpFrame()->numActualArgs();
case JIT:
if (isIonScripted()) return ionInlineFrames_.numActualArgs();
MOZ_ASSERT(jsJitFrame().isBaselineJS());
return jsJitFrame().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:
break;
case JIT:
if (isJSJit()) {
if (isIonScripted()) {
jit::MaybeReadFallback recover(cx, activation()->asJit(),
&jsJitFrame());
return ionInlineFrames_.environmentChain(recover);
}
return jsJitFrame().baselineFrame()->environmentChain();
}
MOZ_ASSERT(isWasm());
return wasmFrame().debugFrame()->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:
break;
case JIT:
if (isIonScripted()) {
jit::MaybeReadFallback recover(cx, activation()->asJit(),
&jsJitFrame());
return ionInlineFrames_.thisArgument(recover);
}
return jsJitFrame().baselineFrame()->thisArgument();
case INTERP:
return interpFrame()->thisArgument();
}
MOZ_CRASH("Unexpected state");
}
Value FrameIter::newTarget() const {
switch (data_.state_) {
case DONE:
break;
case INTERP:
return interpFrame()->newTarget();
case JIT:
MOZ_ASSERT(jsJitFrame().isBaselineJS());
return jsJitFrame().baselineFrame()->newTarget();
}
MOZ_CRASH("Unexpected state");
}
Value FrameIter::returnValue() const {
switch (data_.state_) {
case DONE:
break;
case JIT:
if (jsJitFrame().isBaselineJS())
return jsJitFrame().baselineFrame()->returnValue();
break;
case INTERP:
return interpFrame()->returnValue();
}
MOZ_CRASH("Unexpected state");
}
void FrameIter::setReturnValue(const Value& v) {
switch (data_.state_) {
case DONE:
break;
case JIT:
if (jsJitFrame().isBaselineJS()) {
jsJitFrame().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:
break;
case JIT: {
if (isIonScripted()) {
return ionInlineFrames_.snapshotIterator().numAllocations() -
ionInlineFrames_.script()->nfixed();
}
jit::BaselineFrame* frame = jsJitFrame().baselineFrame();
return frame->numValueSlots() - jsJitFrame().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:
break;
case JIT:
if (isIonScripted()) {
jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator());
index += ionInlineFrames_.script()->nfixed();
return si.maybeReadAllocByIndex(index);
}
index += jsJitFrame().script()->nfixed();
return *jsJitFrame().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->entryMonitor) {
cx->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)
: Activation(cx, Jit),
packedExitFP_(nullptr),
encodedWasmExitReason_(0),
prevJitActivation_(cx->jitActivation),
rematerializedFrames_(nullptr),
ionRecovery_(cx),
bailoutData_(nullptr),
lastProfilingFrame_(nullptr),
lastProfilingCallSite_(nullptr) {
cx->jitActivation = this;
registerProfiling();
}
jit::JitActivation::~JitActivation() {
if (isProfiling()) unregisterProfiling();
cx_->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_);
MOZ_ASSERT(!isWasmInterrupted());
MOZ_ASSERT(!isWasmTrapping());
clearRematerializedFrames();
js_delete(rematerializedFrames_);
}
void jit::JitActivation::setBailoutData(jit::BailoutFrameInfo* bailoutData) {
MOZ_ASSERT(!bailoutData_);
bailoutData_ = bailoutData;
}
void jit::JitActivation::cleanBailoutData() {
MOZ_ASSERT(bailoutData_);
bailoutData_ = nullptr;
}
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 JSJitFrameIter& 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.
AutoCompartmentUnchecked 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::traceRematerializedFrames(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::traceIonRecovery(JSTracer* trc) {
for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end();
it++)
it->trace(trc);
}
bool jit::JitActivation::startWasmInterrupt(
const JS::ProfilingFrameIterator::RegisterState& state) {
// fp may be null when first entering wasm code from an interpreter entry
// stub.
if (!state.fp) return false;
MOZ_ASSERT(state.pc);
// Execution can only be interrupted in function code. Afterwards, control
// flow does not reenter function code and thus there can be no
// interrupt-during-interrupt.
bool unwound;
wasm::UnwindState unwindState;
MOZ_ALWAYS_TRUE(wasm::StartUnwinding(state, &unwindState, &unwound));
void* pc = unwindState.pc;
if (unwound) {
// In the prologue/epilogue, FP might have been fixed up to the
// caller's FP, and the caller could be the jit entry. Ignore this
// interrupt, in this case, because FP points to a jit frame and not a
// wasm one.
if (!wasm::LookupCode(pc)->lookupFuncRange(pc)) return false;
}
cx_->runtime()->wasmUnwindData.ref().construct<wasm::InterruptData>(pc,
state.pc);
setWasmExitFP(unwindState.fp);
MOZ_ASSERT(compartment() == unwindState.fp->tls->instance->compartment());
MOZ_ASSERT(isWasmInterrupted());
return true;
}
void jit::JitActivation::finishWasmInterrupt() {
MOZ_ASSERT(isWasmInterrupted());
cx_->runtime()->wasmUnwindData.ref().destroy();
packedExitFP_ = nullptr;
}
bool jit::JitActivation::isWasmInterrupted() const {
JSRuntime* rt = cx_->runtime();
if (!rt->wasmUnwindData.ref().constructed<wasm::InterruptData>())
return false;
Activation* act = cx_->activation();
while (act && !act->hasWasmExitFP()) act = act->prev();
if (act != this) return false;
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
DebugOnly<void*> unwindPC = rt->wasmInterruptData().unwindPC;
MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
return true;
}
void* jit::JitActivation::wasmInterruptUnwindPC() const {
MOZ_ASSERT(isWasmInterrupted());
return cx_->runtime()->wasmInterruptData().unwindPC;
}
void* jit::JitActivation::wasmInterruptResumePC() const {
MOZ_ASSERT(isWasmInterrupted());
return cx_->runtime()->wasmInterruptData().resumePC;
}
void jit::JitActivation::startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset,
const wasm::RegisterState& state) {
bool unwound;
wasm::UnwindState unwindState;
MOZ_ALWAYS_TRUE(wasm::StartUnwinding(state, &unwindState, &unwound));
MOZ_ASSERT(unwound == (trap == wasm::Trap::IndirectCallBadSig));
void* pc = unwindState.pc;
wasm::Frame* fp = unwindState.fp;
const wasm::Code& code = fp->tls->instance->code();
MOZ_RELEASE_ASSERT(&code == wasm::LookupCode(pc));
// If the frame was unwound, the bytecodeOffset must be recovered from the
// callsite so that it is accurate.
if (unwound) bytecodeOffset = code.lookupCallSite(pc)->lineOrBytecode();
cx_->runtime()->wasmUnwindData.ref().construct<wasm::TrapData>(
pc, trap, bytecodeOffset);
setWasmExitFP(fp);
}
void jit::JitActivation::finishWasmTrap() {
MOZ_ASSERT(isWasmTrapping());
cx_->runtime()->wasmUnwindData.ref().destroy();
packedExitFP_ = nullptr;
}
bool jit::JitActivation::isWasmTrapping() const {
JSRuntime* rt = cx_->runtime();
if (!rt->wasmUnwindData.ref().constructed<wasm::TrapData>()) return false;
Activation* act = cx_->activation();
while (act && !act->hasWasmExitFP()) act = act->prev();
if (act != this) return false;
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
DebugOnly<void*> unwindPC = rt->wasmTrapData().pc;
MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
return true;
}
void* jit::JitActivation::wasmTrapPC() const {
MOZ_ASSERT(isWasmTrapping());
return cx_->runtime()->wasmTrapData().pc;
}
uint32_t jit::JitActivation::wasmTrapBytecodeOffset() const {
MOZ_ASSERT(isWasmTrapping());
return cx_->runtime()->wasmTrapData().bytecodeOffset;
}
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_->profilingActivation_ = this;
}
void Activation::unregisterProfiling() {
MOZ_ASSERT(isProfiling());
MOZ_ASSERT(cx_->profilingActivation_ == this);
cx_->profilingActivation_ = prevProfiling_;
}
ActivationIterator::ActivationIterator(JSContext* cx)
: activation_(cx->activation_) {
MOZ_ASSERT(cx == TlsContext.get());
}
ActivationIterator::ActivationIterator(JSContext* cx,
const CooperatingContext& target) {
MOZ_ASSERT(cx == TlsContext.get());
// If target was specified --- even if it is the same as cx itself --- then
// we must be in a scope where changes of the active context are prohibited.
// Otherwise our state would be corrupted if the target thread resumed
// execution while we are iterating over its state.
MOZ_ASSERT(cx->runtime()->activeContextChangeProhibited() ||
!cx->runtime()->gc.canChangeActiveContext(cx));
// Tolerate a null target context, in case we are iterating over the
// activations for a zone group that is not in use by any thread.
activation_ =
target.context() ? target.context()->activation_.ref() : nullptr;
}
ActivationIterator& ActivationIterator::operator++() {
MOZ_ASSERT(activation_);
activation_ = activation_->prev();
return *this;
}
JS::ProfilingFrameIterator::ProfilingFrameIterator(
JSContext* cx, const RegisterState& state,
const Maybe<uint64_t>& samplePositionInProfilerBuffer)
: cx_(cx),
samplePositionInProfilerBuffer_(samplePositionInProfilerBuffer),
activation_(nullptr) {
if (!cx->runtime()->geckoProfiler().enabled())
MOZ_CRASH(
"ProfilingFrameIterator called when geckoProfiler not enabled for "
"runtime.");
if (!cx->profilingActivation()) return;
// If profiler sampling is not enabled, skip.
if (!cx->isProfilerSamplingEnabled()) return;
activation_ = cx->profilingActivation();
MOZ_ASSERT(activation_->isProfiling());
static_assert(sizeof(wasm::ProfilingFrameIterator) <= StorageSpace &&
sizeof(jit::JSJitProfilingFrameIterator) <= StorageSpace,
"ProfilingFrameIterator::storage_ is too small");
static_assert(alignof(void*) >= alignof(wasm::ProfilingFrameIterator) &&
alignof(void*) >= alignof(jit::JSJitProfilingFrameIterator),
"ProfilingFrameIterator::storage_ is too weakly aligned");
iteratorConstruct(state);
settle();
}
JS::ProfilingFrameIterator::~ProfilingFrameIterator() {
if (!done()) {
MOZ_ASSERT(activation_->isProfiling());
iteratorDestroy();
}
}
void JS::ProfilingFrameIterator::operator++() {
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isJit());
if (isWasm())
++wasmIter();
else
++jsJitIter();
settle();
}
void JS::ProfilingFrameIterator::settleFrames() {
// Handle transition frames (see comment in JitFrameIter::operator++).
if (isJSJit() && !jsJitIter().done() &&
jsJitIter().frameType() == jit::JitFrame_WasmToJSJit) {
wasm::Frame* fp = (wasm::Frame*)jsJitIter().fp();
iteratorDestroy();
new (storage()) wasm::ProfilingFrameIterator(*activation_->asJit(), fp);
kind_ = Kind::Wasm;
MOZ_ASSERT(!wasmIter().done());
return;
}
if (isWasm() && wasmIter().done() && wasmIter().unwoundIonCallerFP()) {
uint8_t* fp = wasmIter().unwoundIonCallerFP();
iteratorDestroy();
// Using this ctor will skip the first ion->wasm frame, which is
// needed because the profiling iterator doesn't know how to unwind
// when the callee has no script.
new (storage())
jit::JSJitProfilingFrameIterator((jit::CommonFrameLayout*)fp);
kind_ = Kind::JSJit;
MOZ_ASSERT(!jsJitIter().done());
return;
}
}
void JS::ProfilingFrameIterator::settle() {
settleFrames();
while (iteratorDone()) {
iteratorDestroy();
activation_ = activation_->prevProfiling();
if (!activation_) return;
iteratorConstruct();
settleFrames();
}
}
void JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state) {
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isJit());
jit::JitActivation* activation = activation_->asJit();
// We want to know if we should start with a wasm profiling frame iterator
// or not. To determine this, there are three possibilities:
// - we've exited to C++ from wasm, in which case the activation
// exitFP low bit is tagged and we can test hasWasmExitFP().
// - we're in wasm code, so we can do a lookup on PC.
// - in all the other cases, we're not in wasm or we haven't exited from
// wasm.
if (activation->hasWasmExitFP() || wasm::InCompiledCode(state.pc)) {
new (storage()) wasm::ProfilingFrameIterator(*activation, state);
kind_ = Kind::Wasm;
return;
}
new (storage()) jit::JSJitProfilingFrameIterator(cx_, state.pc);
kind_ = Kind::JSJit;
}
void JS::ProfilingFrameIterator::iteratorConstruct() {
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isJit());
jit::JitActivation* activation = activation_->asJit();
// The same reasoning as in the above iteratorConstruct variant applies
// here, except that it's even simpler: since this activation is higher up
// on the stack, it can only have exited to C++, through wasm or ion.
if (activation->hasWasmExitFP()) {
new (storage()) wasm::ProfilingFrameIterator(*activation);
kind_ = Kind::Wasm;
return;
}
auto* fp = (jit::ExitFrameLayout*)activation->jsExitFP();
new (storage()) jit::JSJitProfilingFrameIterator(fp);
kind_ = Kind::JSJit;
}
void JS::ProfilingFrameIterator::iteratorDestroy() {
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isJit());
if (isWasm()) {
wasmIter().~ProfilingFrameIterator();
return;
}
jsJitIter().~JSJitProfilingFrameIterator();
}
bool JS::ProfilingFrameIterator::iteratorDone() {
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isJit());
if (isWasm()) return wasmIter().done();
return jsJitIter().done();
}
void* JS::ProfilingFrameIterator::stackAddress() const {
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isJit());
if (isWasm()) return wasmIter().stackAddress();
return jsJitIter().stackAddress();
}
Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(
jit::JitcodeGlobalEntry* entry) const {
void* stackAddr = stackAddress();
if (isWasm()) {
Frame frame;
frame.kind = Frame_Wasm;
frame.stackAddress = stackAddr;
frame.returnAddress = nullptr;
frame.activation = activation_;
frame.label = nullptr;
return mozilla::Some(frame);
}
MOZ_ASSERT(isJSJit());
// Look up an entry for the return address.
void* returnAddr = jsJitIter().returnAddressToFp();
jit::JitcodeGlobalTable* table =
cx_->runtime()->jitRuntime()->getJitcodeGlobalTable();
if (samplePositionInProfilerBuffer_)
*entry = table->lookupForSamplerInfallible(
returnAddr, cx_->runtime(), *samplePositionInProfilerBuffer_);
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_;
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 (isWasm()) {
frames[offset] = physicalFrame.value();
frames[offset].label = wasmIter().label();
return 1;
}
// Extract the stack for the entry. Assume maximum inlining depth is <64
const char* labels[64];
uint32_t depth =
entry.callStackAtAddr(cx_->runtime(), jsJitIter().returnAddressToFp(),
labels, ArrayLength(labels));
MOZ_ASSERT(depth < ArrayLength(labels));
for (uint32_t i = 0; i < depth; i++) {
if (offset + i >= end) return i;
frames[offset + i] = physicalFrame.value();
frames[offset + i].label = labels[i];
}
return depth;
}
Maybe<JS::ProfilingFrameIterator::Frame>
JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const {
jit::JitcodeGlobalEntry unused;
return getPhysicalFrameAndEntry(&unused);
}
bool JS::ProfilingFrameIterator::isWasm() const {
MOZ_ASSERT(!done());
return kind_ == Kind::Wasm;
}
bool JS::ProfilingFrameIterator::isJSJit() const {
return kind_ == Kind::JSJit;
}