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