diff options
Diffstat (limited to 'js/src/vm/Stack.h')
-rw-r--r-- | js/src/vm/Stack.h | 2077 |
1 files changed, 2077 insertions, 0 deletions
diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h new file mode 100644 index 000000000..552738d89 --- /dev/null +++ b/js/src/vm/Stack.h @@ -0,0 +1,2077 @@ +/* -*- 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/. */ + +#ifndef vm_Stack_h +#define vm_Stack_h + +#include "mozilla/Atomics.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Variant.h" + +#include "jsfun.h" +#include "jsscript.h" +#include "jsutil.h" + +#include "gc/Rooting.h" +#include "jit/JitFrameIterator.h" +#ifdef CHECK_OSIPOINT_REGISTERS +#include "jit/Registers.h" // for RegisterDump +#endif +#include "js/RootingAPI.h" +#include "vm/ArgumentsObject.h" +#include "vm/SavedFrame.h" +#include "wasm/WasmFrameIterator.h" + +struct JSCompartment; + +namespace JS { +namespace dbg { +#ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" +#endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING + +class JS_PUBLIC_API(AutoEntryMonitor); + +#ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING +#pragma GCC diagnostic pop +#endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING +} // namespace dbg +} // namespace JS + +namespace js { + +class InterpreterRegs; +class CallObject; +class FrameIter; +class EnvironmentObject; +class ScriptFrameIter; +class SPSProfiler; +class InterpreterFrame; +class LexicalEnvironmentObject; +class EnvironmentIter; +class EnvironmentCoordinate; + +class SavedFrame; + +namespace jit { +class CommonFrameLayout; +} +namespace wasm { +class Instance; +} + +// VM stack layout +// +// A JSRuntime's stack consists of a linked list of activations. Every activation +// contains a number of scripted frames that are either running in the interpreter +// (InterpreterActivation) or JIT code (JitActivation). The frames inside a single +// activation are contiguous: whenever C++ calls back into JS, a new activation is +// pushed. +// +// Every activation is tied to a single JSContext and JSCompartment. This means we +// can reconstruct a given context's stack by skipping activations belonging to other +// contexts. This happens whenever an embedding enters the JS engine on cx1 and +// then, from a native called by the JS engine, reenters the VM on cx2. + +// Interpreter frames (InterpreterFrame) +// +// Each interpreter script activation (global or function code) is given a +// fixed-size header (js::InterpreterFrame). The frame contains bookkeeping +// information about the activation and links to the previous frame. +// +// The values after an InterpreterFrame in memory are its locals followed by its +// expression stack. InterpreterFrame::argv_ points to the frame's arguments. +// Missing formal arguments are padded with |undefined|, so the number of +// arguments is always >= the number of formals. +// +// The top of an activation's current frame's expression stack is pointed to by +// the activation's "current regs", which contains the stack pointer 'sp'. In +// the interpreter, sp is adjusted as individual values are pushed and popped +// from the stack and the InterpreterRegs struct (pointed to by the +// InterpreterActivation) is a local var of js::Interpret. + +enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false }; +enum MaybeCheckTDZ { CheckTDZ = true, DontCheckTDZ = false }; + +/*****************************************************************************/ + +namespace jit { + class BaselineFrame; + class RematerializedFrame; +} // namespace jit + +/* + * Pointer to either a ScriptFrameIter::Data, an InterpreterFrame, or a Baseline + * JIT frame. + * + * The Debugger may cache ScriptFrameIter::Data as a bookmark to reconstruct a + * ScriptFrameIter without doing a full stack walk. + * + * There is no way to directly create such an AbstractFramePtr. To do so, the + * user must call ScriptFrameIter::copyDataAsAbstractFramePtr(). + * + * ScriptFrameIter::abstractFramePtr() will never return an AbstractFramePtr + * that is in fact a ScriptFrameIter::Data. + * + * To recover a ScriptFrameIter settled at the location pointed to by an + * AbstractFramePtr, use the THIS_FRAME_ITER macro in Debugger.cpp. As an + * aside, no asScriptFrameIterData() is provided because C++ is stupid and + * cannot forward declare inner classes. + */ + +class AbstractFramePtr +{ + friend class FrameIter; + + uintptr_t ptr_; + + enum { + Tag_ScriptFrameIterData = 0x0, + Tag_InterpreterFrame = 0x1, + Tag_BaselineFrame = 0x2, + Tag_RematerializedFrame = 0x3, + TagMask = 0x3 + }; + + public: + AbstractFramePtr() + : ptr_(0) + {} + + MOZ_IMPLICIT AbstractFramePtr(InterpreterFrame* fp) + : ptr_(fp ? uintptr_t(fp) | Tag_InterpreterFrame : 0) + { + MOZ_ASSERT_IF(fp, asInterpreterFrame() == fp); + } + + MOZ_IMPLICIT AbstractFramePtr(jit::BaselineFrame* fp) + : ptr_(fp ? uintptr_t(fp) | Tag_BaselineFrame : 0) + { + MOZ_ASSERT_IF(fp, asBaselineFrame() == fp); + } + + MOZ_IMPLICIT AbstractFramePtr(jit::RematerializedFrame* fp) + : ptr_(fp ? uintptr_t(fp) | Tag_RematerializedFrame : 0) + { + MOZ_ASSERT_IF(fp, asRematerializedFrame() == fp); + } + + static AbstractFramePtr FromRaw(void* raw) { + AbstractFramePtr frame; + frame.ptr_ = uintptr_t(raw); + return frame; + } + + bool isScriptFrameIterData() const { + return !!ptr_ && (ptr_ & TagMask) == Tag_ScriptFrameIterData; + } + bool isInterpreterFrame() const { + return (ptr_ & TagMask) == Tag_InterpreterFrame; + } + InterpreterFrame* asInterpreterFrame() const { + MOZ_ASSERT(isInterpreterFrame()); + InterpreterFrame* res = (InterpreterFrame*)(ptr_ & ~TagMask); + MOZ_ASSERT(res); + return res; + } + bool isBaselineFrame() const { + return (ptr_ & TagMask) == Tag_BaselineFrame; + } + jit::BaselineFrame* asBaselineFrame() const { + MOZ_ASSERT(isBaselineFrame()); + jit::BaselineFrame* res = (jit::BaselineFrame*)(ptr_ & ~TagMask); + MOZ_ASSERT(res); + return res; + } + bool isRematerializedFrame() const { + return (ptr_ & TagMask) == Tag_RematerializedFrame; + } + jit::RematerializedFrame* asRematerializedFrame() const { + MOZ_ASSERT(isRematerializedFrame()); + jit::RematerializedFrame* res = (jit::RematerializedFrame*)(ptr_ & ~TagMask); + MOZ_ASSERT(res); + return res; + } + + void* raw() const { return reinterpret_cast<void*>(ptr_); } + + bool operator ==(const AbstractFramePtr& other) const { return ptr_ == other.ptr_; } + bool operator !=(const AbstractFramePtr& other) const { return ptr_ != other.ptr_; } + + explicit operator bool() const { return !!ptr_; } + + inline JSObject* environmentChain() const; + inline CallObject& callObj() const; + inline bool initFunctionEnvironmentObjects(JSContext* cx); + inline bool pushVarEnvironment(JSContext* cx, HandleScope scope); + template <typename SpecificEnvironment> + inline void pushOnEnvironmentChain(SpecificEnvironment& env); + template <typename SpecificEnvironment> + inline void popOffEnvironmentChain(); + + inline JSCompartment* compartment() const; + + inline bool hasInitialEnvironment() const; + inline bool isGlobalFrame() const; + inline bool isModuleFrame() const; + inline bool isEvalFrame() const; + inline bool isDebuggerEvalFrame() const; + inline bool hasCachedSavedFrame() const; + inline void setHasCachedSavedFrame(); + + inline JSScript* script() const; + inline JSFunction* callee() const; + inline Value calleev() const; + inline Value& thisArgument() const; + + inline Value newTarget() const; + + inline bool debuggerNeedsCheckPrimitiveReturn() const; + + inline bool isFunctionFrame() const; + inline bool isNonStrictDirectEvalFrame() const; + inline bool isStrictEvalFrame() const; + + inline unsigned numActualArgs() const; + inline unsigned numFormalArgs() const; + + inline Value* argv() const; + + inline bool hasArgs() const; + inline bool hasArgsObj() const; + inline ArgumentsObject& argsObj() const; + inline void initArgsObj(ArgumentsObject& argsobj) const; + inline bool createSingleton() const; + + inline Value& unaliasedLocal(uint32_t i); + inline Value& unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING); + inline Value& unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING); + template <class Op> inline void unaliasedForEachActual(JSContext* cx, Op op); + + inline bool prevUpToDate() const; + inline void setPrevUpToDate() const; + inline void unsetPrevUpToDate() const; + + inline bool isDebuggee() const; + inline void setIsDebuggee(); + inline void unsetIsDebuggee(); + + inline HandleValue returnValue() const; + inline void setReturnValue(const Value& rval) const; + + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, void*); + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, InterpreterFrame*); + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::BaselineFrame*); + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::RematerializedFrame*); +}; + +class NullFramePtr : public AbstractFramePtr +{ + public: + NullFramePtr() + : AbstractFramePtr() + { } +}; + +enum MaybeConstruct { NO_CONSTRUCT = false, CONSTRUCT = true }; + +/*****************************************************************************/ + +class InterpreterFrame +{ + enum Flags : uint32_t { + CONSTRUCTING = 0x1, /* frame is for a constructor invocation */ + + RESUMED_GENERATOR = 0x2, /* frame is for a resumed generator invocation */ + + /* Function prologue state */ + HAS_INITIAL_ENV = 0x4, /* callobj created for function or var env for eval */ + HAS_ARGS_OBJ = 0x8, /* ArgumentsObject created for needsArgsObj script */ + + /* Lazy frame initialization */ + HAS_RVAL = 0x10, /* frame has rval_ set */ + + /* Debugger state */ + PREV_UP_TO_DATE = 0x20, /* see DebugScopes::updateLiveScopes */ + + /* + * See comment above 'isDebuggee' in jscompartment.h for explanation of + * invariants of debuggee compartments, scripts, and frames. + */ + DEBUGGEE = 0x40, /* Execution is being observed by Debugger */ + + /* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */ + HAS_PUSHED_SPS_FRAME = 0x80, /* SPS was notified of entry */ + + /* + * If set, we entered one of the JITs and ScriptFrameIter should skip + * this frame. + */ + RUNNING_IN_JIT = 0x100, + + /* Miscellaneous state. */ + CREATE_SINGLETON = 0x200, /* Constructed |this| object should be singleton. */ + + /* + * If set, this frame has been on the stack when + * |js::SavedStacks::saveCurrentStack| was called, and so there is a + * |js::SavedFrame| object cached for this frame. + */ + HAS_CACHED_SAVED_FRAME = 0x400, + }; + + mutable uint32_t flags_; /* bits described by Flags */ + uint32_t nactual_; /* number of actual arguments, for function frames */ + JSScript* script_; /* the script we're executing */ + JSObject* envChain_; /* current environment chain */ + Value rval_; /* if HAS_RVAL, return value of the frame */ + ArgumentsObject* argsObj_; /* if HAS_ARGS_OBJ, the call's arguments object */ + + /* + * Previous frame and its pc and sp. Always nullptr for + * InterpreterActivation's entry frame, always non-nullptr for inline + * frames. + */ + InterpreterFrame* prev_; + jsbytecode* prevpc_; + Value* prevsp_; + + void* unused; + + /* + * For an eval-in-frame DEBUGGER_EVAL frame, the frame in whose scope + * we're evaluating code. Iteration treats this as our previous frame. + */ + AbstractFramePtr evalInFramePrev_; + + Value* argv_; /* If hasArgs(), points to frame's arguments. */ + LifoAlloc::Mark mark_; /* Used to release memory for this frame. */ + + static void staticAsserts() { + JS_STATIC_ASSERT(offsetof(InterpreterFrame, rval_) % sizeof(Value) == 0); + JS_STATIC_ASSERT(sizeof(InterpreterFrame) % sizeof(Value) == 0); + } + + /* + * The utilities are private since they are not able to assert that only + * unaliased vars/formals are accessed. Normal code should prefer the + * InterpreterFrame::unaliased* members (or InterpreterRegs::stackDepth for + * the usual "depth is at least" assertions). + */ + Value* slots() const { return (Value*)(this + 1); } + Value* base() const { return slots() + script()->nfixed(); } + + friend class FrameIter; + friend class InterpreterRegs; + friend class InterpreterStack; + friend class jit::BaselineFrame; + + /* + * Frame initialization, called by InterpreterStack operations after acquiring + * the raw memory for the frame: + */ + + /* Used for Invoke and Interpret. */ + void initCallFrame(JSContext* cx, InterpreterFrame* prev, jsbytecode* prevpc, Value* prevsp, + JSFunction& callee, JSScript* script, Value* argv, uint32_t nactual, + MaybeConstruct constructing); + + /* Used for global and eval frames. */ + void initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr prev, + const Value& newTargetValue, HandleObject envChain); + + public: + /* + * Frame prologue/epilogue + * + * Every stack frame must have 'prologue' called before executing the + * first op and 'epilogue' called after executing the last op and before + * popping the frame (whether the exit is exceptional or not). + * + * For inline JS calls/returns, it is easy to call the prologue/epilogue + * exactly once. When calling JS from C++, Invoke/Execute push the stack + * frame but do *not* call the prologue/epilogue. That means Interpret + * must call the prologue/epilogue for the entry frame. This scheme + * simplifies jit compilation. + * + * An important corner case is what happens when an error occurs (OOM, + * over-recursed) after pushing the stack frame but before 'prologue' is + * called or completes fully. To simplify usage, 'epilogue' does not assume + * 'prologue' has completed and handles all the intermediate state details. + */ + + bool prologue(JSContext* cx); + void epilogue(JSContext* cx, jsbytecode* pc); + + bool checkReturn(JSContext* cx, HandleValue thisv); + + bool initFunctionEnvironmentObjects(JSContext* cx); + + /* + * Initialize locals of newly-pushed frame to undefined. + */ + void initLocals(); + + /* + * Stack frame type + * + * A stack frame may have one of four types, which determines which + * members of the frame may be accessed and other invariants: + * + * global frame: execution of global code + * function frame: execution of function code + * module frame: execution of a module + * eval frame: execution of eval code + */ + + bool isGlobalFrame() const { + return script_->isGlobalCode(); + } + + bool isModuleFrame() const { + return script_->module(); + } + + bool isEvalFrame() const { + return script_->isForEval(); + } + + bool isFunctionFrame() const { + return script_->functionNonDelazifying(); + } + + inline bool isStrictEvalFrame() const { + return isEvalFrame() && script()->strict(); + } + + bool isNonStrictEvalFrame() const { + return isEvalFrame() && !script()->strict(); + } + + bool isNonGlobalEvalFrame() const; + + bool isNonStrictDirectEvalFrame() const { + return isNonStrictEvalFrame() && isNonGlobalEvalFrame(); + } + + /* + * Previous frame + * + * A frame's 'prev' frame is either null or the previous frame pointed to + * by cx->regs->fp when this frame was pushed. Often, given two prev-linked + * frames, the next-frame is a function or eval that was called by the + * prev-frame, but not always: the prev-frame may have called a native that + * reentered the VM through JS_CallFunctionValue on the same context + * (without calling JS_SaveFrameChain) which pushed the next-frame. Thus, + * 'prev' has little semantic meaning and basically just tells the VM what + * to set cx->regs->fp to when this frame is popped. + */ + + InterpreterFrame* prev() const { + return prev_; + } + + AbstractFramePtr evalInFramePrev() const { + MOZ_ASSERT(isEvalFrame()); + return evalInFramePrev_; + } + + /* + * (Unaliased) locals and arguments + * + * Only non-eval function frames have arguments. The arguments pushed by + * the caller are the 'actual' arguments. The declared arguments of the + * callee are the 'formal' arguments. When the caller passes less actual + * arguments, missing formal arguments are padded with |undefined|. + * + * When a local/formal variable is aliased (accessed by nested closures, + * environment operations, or 'arguments'), the canonical location for + * that value is the slot of an environment object. Aliased locals don't + * have stack slots assigned to them. These functions assert that + * accesses to stack values are unaliased. + */ + + inline Value& unaliasedLocal(uint32_t i); + + bool hasArgs() const { return isFunctionFrame(); } + inline Value& unaliasedFormal(unsigned i, MaybeCheckAliasing = CHECK_ALIASING); + inline Value& unaliasedActual(unsigned i, MaybeCheckAliasing = CHECK_ALIASING); + template <class Op> inline void unaliasedForEachActual(Op op); + + unsigned numFormalArgs() const { MOZ_ASSERT(hasArgs()); return callee().nargs(); } + unsigned numActualArgs() const { MOZ_ASSERT(hasArgs()); return nactual_; } + + /* Watch out, this exposes a pointer to the unaliased formal arg array. */ + Value* argv() const { MOZ_ASSERT(hasArgs()); return argv_; } + + /* + * Arguments object + * + * If a non-eval function has script->needsArgsObj, an arguments object is + * created in the prologue and stored in the local variable for the + * 'arguments' binding (script->argumentsLocal). Since this local is + * mutable, the arguments object can be overwritten and we can "lose" the + * arguments object. Thus, InterpreterFrame keeps an explicit argsObj_ field so + * that the original arguments object is always available. + */ + + ArgumentsObject& argsObj() const; + void initArgsObj(ArgumentsObject& argsobj); + + JSObject* createRestParameter(JSContext* cx); + + /* + * Environment chain + * + * In theory, the environment chain would contain an object for every + * lexical scope. However, only objects that are required for dynamic + * lookup are actually created. + * + * Given that an InterpreterFrame corresponds roughly to a ES Execution + * Context (ES 10.3), InterpreterFrame::varObj corresponds to the + * VariableEnvironment component of a Exection Context. Intuitively, the + * variables object is where new bindings (variables and functions) are + * stored. One might expect that this is either the Call object or + * envChain.globalObj for function or global code, respectively, however + * the JSAPI allows calls of Execute to specify a variables object on the + * environment chain other than the call/global object. This allows + * embeddings to run multiple scripts under the same global, each time + * using a new variables object to collect and discard the script's global + * variables. + */ + + inline HandleObject environmentChain() const; + + inline EnvironmentObject& aliasedEnvironment(EnvironmentCoordinate ec) const; + inline GlobalObject& global() const; + inline CallObject& callObj() const; + inline JSObject& varObj() const; + inline LexicalEnvironmentObject& extensibleLexicalEnvironment() const; + + template <typename SpecificEnvironment> + inline void pushOnEnvironmentChain(SpecificEnvironment& env); + template <typename SpecificEnvironment> + inline void popOffEnvironmentChain(); + inline void replaceInnermostEnvironment(EnvironmentObject& env); + + // Push a VarEnvironmentObject for function frames of functions that have + // parameter expressions with closed over var bindings. + bool pushVarEnvironment(JSContext* cx, HandleScope scope); + + /* + * For lexical envs with aliased locals, these interfaces push and pop + * entries on the environment chain. The "freshen" operation replaces the + * current lexical env with a fresh copy of it, to implement semantics + * providing distinct bindings per iteration of a for(;;) loop whose head + * has a lexical declaration. The "recreate" operation replaces the + * current lexical env with a copy of it containing uninitialized + * bindings, to implement semantics providing distinct bindings per + * iteration of a for-in/of loop. + */ + + bool pushLexicalEnvironment(JSContext* cx, Handle<LexicalScope*> scope); + bool freshenLexicalEnvironment(JSContext* cx); + bool recreateLexicalEnvironment(JSContext* cx); + + /* + * Script + * + * All frames have an associated JSScript which holds the bytecode being + * executed for the frame. + */ + + JSScript* script() const { + return script_; + } + + /* Return the previous frame's pc. */ + jsbytecode* prevpc() { + MOZ_ASSERT(prev_); + return prevpc_; + } + + /* Return the previous frame's sp. */ + Value* prevsp() { + MOZ_ASSERT(prev_); + return prevsp_; + } + + /* + * Return the 'this' argument passed to a non-eval function frame. This is + * not necessarily the frame's this-binding, for instance non-strict + * functions will box primitive 'this' values and thisArgument() will + * return the original, unboxed Value. + */ + Value& thisArgument() const { + MOZ_ASSERT(isFunctionFrame()); + return argv()[-1]; + } + + /* + * Callee + * + * Only function frames have a callee. An eval frame in a function has the + * same callee as its containing function frame. + */ + + JSFunction& callee() const { + MOZ_ASSERT(isFunctionFrame()); + return calleev().toObject().as<JSFunction>(); + } + + const Value& calleev() const { + MOZ_ASSERT(isFunctionFrame()); + return argv()[-2]; + } + + /* + * New Target + * + * Only function frames have a meaningful newTarget. An eval frame in a + * function will have a copy of the newTarget of the enclosing function + * frame. + */ + Value newTarget() const { + if (isEvalFrame()) + return ((Value*)this)[-1]; + + MOZ_ASSERT(isFunctionFrame()); + + if (callee().isArrow()) + return callee().getExtendedSlot(FunctionExtended::ARROW_NEWTARGET_SLOT); + + if (isConstructing()) { + unsigned pushedArgs = Max(numFormalArgs(), numActualArgs()); + return argv()[pushedArgs]; + } + return UndefinedValue(); + } + + /* Profiler flags */ + + bool hasPushedSPSFrame() { + return !!(flags_ & HAS_PUSHED_SPS_FRAME); + } + + void setPushedSPSFrame() { + flags_ |= HAS_PUSHED_SPS_FRAME; + } + + void unsetPushedSPSFrame() { + flags_ &= ~HAS_PUSHED_SPS_FRAME; + } + + /* Return value */ + + bool hasReturnValue() const { + return flags_ & HAS_RVAL; + } + + MutableHandleValue returnValue() { + if (!hasReturnValue()) + rval_.setUndefined(); + return MutableHandleValue::fromMarkedLocation(&rval_); + } + + void markReturnValue() { + flags_ |= HAS_RVAL; + } + + void setReturnValue(const Value& v) { + rval_ = v; + markReturnValue(); + } + + void clearReturnValue() { + rval_.setUndefined(); + markReturnValue(); + } + + void resumeGeneratorFrame(JSObject* envChain) { + MOZ_ASSERT(script()->isGenerator()); + MOZ_ASSERT(isFunctionFrame()); + flags_ |= HAS_INITIAL_ENV; + envChain_ = envChain; + } + + /* + * Other flags + */ + + bool isConstructing() const { + return !!(flags_ & CONSTRUCTING); + } + + void setResumedGenerator() { + flags_ |= RESUMED_GENERATOR; + } + bool isResumedGenerator() const { + return !!(flags_ & RESUMED_GENERATOR); + } + + /* + * These two queries should not be used in general: the presence/absence of + * the call/args object is determined by the static(ish) properties of the + * JSFunction/JSScript. These queries should only be performed when probing + * a stack frame that may be in the middle of the prologue (during which + * time the call/args object are created). + */ + + inline bool hasInitialEnvironment() const; + + bool hasInitialEnvironmentUnchecked() const { + return flags_ & HAS_INITIAL_ENV; + } + + bool hasArgsObj() const { + MOZ_ASSERT(script()->needsArgsObj()); + return flags_ & HAS_ARGS_OBJ; + } + + void setCreateSingleton() { + MOZ_ASSERT(isConstructing()); + flags_ |= CREATE_SINGLETON; + } + bool createSingleton() const { + MOZ_ASSERT(isConstructing()); + return flags_ & CREATE_SINGLETON; + } + + /* + * Debugger eval frames. + * + * - If evalInFramePrev_ is non-null, frame was created for an "eval in + * frame" call, which can push a successor to any live frame; so its + * logical "prev" frame is not necessarily the previous frame in memory. + * Iteration should treat evalInFramePrev_ as this frame's previous frame. + * + * - Don't bother to JIT it, because it's probably short-lived. + * + * - It is required to have a environment chain object outside the + * js::EnvironmentObject hierarchy: either a global object, or a + * DebugEnvironmentProxy. + */ + bool isDebuggerEvalFrame() const { + return isEvalFrame() && !!evalInFramePrev_; + } + + bool prevUpToDate() const { + return !!(flags_ & PREV_UP_TO_DATE); + } + + void setPrevUpToDate() { + flags_ |= PREV_UP_TO_DATE; + } + + void unsetPrevUpToDate() { + flags_ &= ~PREV_UP_TO_DATE; + } + + bool isDebuggee() const { + return !!(flags_ & DEBUGGEE); + } + + void setIsDebuggee() { + flags_ |= DEBUGGEE; + } + + inline void unsetIsDebuggee(); + + bool hasCachedSavedFrame() const { + return flags_ & HAS_CACHED_SAVED_FRAME; + } + void setHasCachedSavedFrame() { + flags_ |= HAS_CACHED_SAVED_FRAME; + } + + public: + void trace(JSTracer* trc, Value* sp, jsbytecode* pc); + void traceValues(JSTracer* trc, unsigned start, unsigned end); + + // Entered Baseline/Ion from the interpreter. + bool runningInJit() const { + return !!(flags_ & RUNNING_IN_JIT); + } + void setRunningInJit() { + flags_ |= RUNNING_IN_JIT; + } + void clearRunningInJit() { + flags_ &= ~RUNNING_IN_JIT; + } +}; + +/*****************************************************************************/ + +class InterpreterRegs +{ + public: + Value* sp; + jsbytecode* pc; + private: + InterpreterFrame* fp_; + public: + InterpreterFrame* fp() const { return fp_; } + + unsigned stackDepth() const { + MOZ_ASSERT(sp >= fp_->base()); + return sp - fp_->base(); + } + + Value* spForStackDepth(unsigned depth) const { + MOZ_ASSERT(fp_->script()->nfixed() + depth <= fp_->script()->nslots()); + return fp_->base() + depth; + } + + /* For generators. */ + void rebaseFromTo(const InterpreterRegs& from, InterpreterFrame& to) { + fp_ = &to; + sp = to.slots() + (from.sp - from.fp_->slots()); + pc = from.pc; + MOZ_ASSERT(fp_); + } + + void popInlineFrame() { + pc = fp_->prevpc(); + unsigned spForNewTarget = fp_->isResumedGenerator() ? 0 : fp_->isConstructing(); + sp = fp_->prevsp() - fp_->numActualArgs() - 1 - spForNewTarget; + fp_ = fp_->prev(); + MOZ_ASSERT(fp_); + } + void prepareToRun(InterpreterFrame& fp, JSScript* script) { + pc = script->code(); + sp = fp.slots() + script->nfixed(); + fp_ = &fp; + } + + void setToEndOfScript(); + + MutableHandleValue stackHandleAt(int i) { + return MutableHandleValue::fromMarkedLocation(&sp[i]); + } + + HandleValue stackHandleAt(int i) const { + return HandleValue::fromMarkedLocation(&sp[i]); + } + + friend void GDBTestInitInterpreterRegs(InterpreterRegs&, js::InterpreterFrame*, + JS::Value*, uint8_t*); +}; + +/*****************************************************************************/ + +class InterpreterStack +{ + friend class InterpreterActivation; + + static const size_t DEFAULT_CHUNK_SIZE = 4 * 1024; + LifoAlloc allocator_; + + // Number of interpreter frames on the stack, for over-recursion checks. + static const size_t MAX_FRAMES = 50 * 1000; + static const size_t MAX_FRAMES_TRUSTED = MAX_FRAMES + 1000; + size_t frameCount_; + + inline uint8_t* allocateFrame(JSContext* cx, size_t size); + + inline InterpreterFrame* + getCallFrame(JSContext* cx, const CallArgs& args, HandleScript script, + MaybeConstruct constructing, Value** pargv); + + void releaseFrame(InterpreterFrame* fp) { + frameCount_--; + allocator_.release(fp->mark_); + } + + public: + InterpreterStack() + : allocator_(DEFAULT_CHUNK_SIZE), + frameCount_(0) + { } + + ~InterpreterStack() { + MOZ_ASSERT(frameCount_ == 0); + } + + // For execution of eval or global code. + InterpreterFrame* pushExecuteFrame(JSContext* cx, HandleScript script, + const Value& newTargetValue, HandleObject envChain, + AbstractFramePtr evalInFrame); + + // Called to invoke a function. + InterpreterFrame* pushInvokeFrame(JSContext* cx, const CallArgs& args, + MaybeConstruct constructing); + + // The interpreter can push light-weight, "inline" frames without entering a + // new InterpreterActivation or recursively calling Interpret. + bool pushInlineFrame(JSContext* cx, InterpreterRegs& regs, const CallArgs& args, + HandleScript script, MaybeConstruct constructing); + + void popInlineFrame(InterpreterRegs& regs); + + bool resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs, + HandleFunction callee, HandleValue newTarget, + HandleObject envChain); + + inline void purge(JSRuntime* rt); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return allocator_.sizeOfExcludingThis(mallocSizeOf); + } +}; + +void MarkInterpreterActivations(JSRuntime* rt, JSTracer* trc); + +/*****************************************************************************/ + +/** Base class for all function call args. */ +class AnyInvokeArgs : public JS::CallArgs +{ +}; + +/** Base class for all function construction args. */ +class AnyConstructArgs : public JS::CallArgs +{ + // Only js::Construct (or internal methods that call the qualified CallArgs + // versions) should do these things! + void setCallee(const Value& v) = delete; + void setThis(const Value& v) = delete; + MutableHandleValue newTarget() const = delete; + MutableHandleValue rval() const = delete; +}; + +namespace detail { + +/** Function call/construct args of statically-unknown count. */ +template <MaybeConstruct Construct> +class GenericArgsBase + : public mozilla::Conditional<Construct, AnyConstructArgs, AnyInvokeArgs>::Type +{ + protected: + AutoValueVector v_; + + explicit GenericArgsBase(JSContext* cx) : v_(cx) {} + + public: + bool init(JSContext* cx, unsigned argc) { + if (argc > ARGS_LENGTH_MAX) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_MANY_ARGUMENTS); + return false; + } + + // callee, this, arguments[, new.target iff constructing] + size_t len = 2 + argc + uint32_t(Construct); + MOZ_ASSERT(len > argc); // no overflow + if (!v_.resize(len)) + return false; + + *static_cast<JS::CallArgs*>(this) = CallArgsFromVp(argc, v_.begin()); + this->constructing_ = Construct; + if (Construct) + this->CallArgs::setThis(MagicValue(JS_IS_CONSTRUCTING)); + return true; + } +}; + +/** Function call/construct args of statically-known count. */ +template <MaybeConstruct Construct, size_t N> +class FixedArgsBase + : public mozilla::Conditional<Construct, AnyConstructArgs, AnyInvokeArgs>::Type +{ + static_assert(N <= ARGS_LENGTH_MAX, "o/~ too many args o/~"); + + protected: + JS::AutoValueArray<2 + N + uint32_t(Construct)> v_; + + explicit FixedArgsBase(JSContext* cx) : v_(cx) { + *static_cast<JS::CallArgs*>(this) = CallArgsFromVp(N, v_.begin()); + this->constructing_ = Construct; + if (Construct) + this->CallArgs::setThis(MagicValue(JS_IS_CONSTRUCTING)); + } +}; + +} // namespace detail + +/** Function call args of statically-unknown count. */ +class InvokeArgs : public detail::GenericArgsBase<NO_CONSTRUCT> +{ + using Base = detail::GenericArgsBase<NO_CONSTRUCT>; + + public: + explicit InvokeArgs(JSContext* cx) : Base(cx) {} +}; + +/** Function call args of statically-known count. */ +template <size_t N> +class FixedInvokeArgs : public detail::FixedArgsBase<NO_CONSTRUCT, N> +{ + using Base = detail::FixedArgsBase<NO_CONSTRUCT, N>; + + public: + explicit FixedInvokeArgs(JSContext* cx) : Base(cx) {} +}; + +/** Function construct args of statically-unknown count. */ +class ConstructArgs : public detail::GenericArgsBase<CONSTRUCT> +{ + using Base = detail::GenericArgsBase<CONSTRUCT>; + + public: + explicit ConstructArgs(JSContext* cx) : Base(cx) {} +}; + +/** Function call args of statically-known count. */ +template <size_t N> +class FixedConstructArgs : public detail::FixedArgsBase<CONSTRUCT, N> +{ + using Base = detail::FixedArgsBase<CONSTRUCT, N>; + + public: + explicit FixedConstructArgs(JSContext* cx) : Base(cx) {} +}; + +template <class Args, class Arraylike> +inline bool +FillArgumentsFromArraylike(JSContext* cx, Args& args, const Arraylike& arraylike) +{ + uint32_t len = arraylike.length(); + if (!args.init(cx, len)) + return false; + + for (uint32_t i = 0; i < len; i++) + args[i].set(arraylike[i]); + + return true; +} + +template <> +struct DefaultHasher<AbstractFramePtr> { + typedef AbstractFramePtr Lookup; + + static js::HashNumber hash(const Lookup& key) { + return size_t(key.raw()); + } + + static bool match(const AbstractFramePtr& k, const Lookup& l) { + return k == l; + } +}; + +/*****************************************************************************/ + +// SavedFrame caching to minimize stack walking. +// +// SavedFrames are hash consed to minimize expensive (with regards to both space +// and time) allocations in the face of many stack frames that tend to share the +// same older tail frames. Despite that, in scenarios where we are frequently +// saving the same or similar stacks, such as when the Debugger's allocation +// site tracking is enabled, these older stack frames still get walked +// repeatedly just to create the lookup structs to find their corresponding +// SavedFrames in the hash table. This stack walking is slow, and we would like +// to minimize it. +// +// We have reserved a bit on most of SpiderMonkey's various frame +// representations (the exceptions being asm and inlined ion frames). As we +// create SavedFrame objects for live stack frames in SavedStacks::insertFrames, +// we set this bit and append the SavedFrame object to the cache. As we walk the +// stack, if we encounter a frame that has this bit set, that indicates that we +// have already captured a SavedFrame object for the given stack frame (but not +// necessarily the current pc) during a previous call to insertFrames. We know +// that the frame's parent was also captured and has its bit set as well, but +// additionally we know the parent was captured at its current pc. For the +// parent, rather than continuing the expensive stack walk, we do a quick and +// cache-friendly linear search through the frame cache. Upon finishing search +// through the frame cache, stale entries are removed. +// +// The frame cache maintains the invariant that its first E[0] .. E[j-1] +// entries are live and sorted from oldest to younger frames, where 0 < j < n +// and n = the length of the cache. When searching the cache, we require +// that we are considering the youngest live frame whose bit is set. Every +// cache entry E[i] where i >= j is a stale entry. Consider the following +// scenario: +// +// P > Q > R > S Initial stack, bits not set. +// P* > Q* > R* > S* Capture a SavedFrame stack, set bits. +// P* > Q* > R* Return from S. +// P* > Q* Return from R. +// P* > Q* > T Call T, its bit is not set. +// +// The frame cache was populated with [P, Q, R, S] when we captured a +// SavedFrame stack, but because we returned from frames R and S, their +// entries in the frame cache are now stale. This fact is unbeknownst to us +// because we do not observe frame pops. Upon capturing a second stack, we +// start stack walking at the youngest frame T, which does not have its bit +// set and must take the hash table lookup slow path rather than the frame +// cache short circuit. Next we proceed to Q and find that it has its bit +// set, and it is therefore the youngest live frame with its bit set. We +// search through the frame cache from oldest to youngest and find the cache +// entry matching Q. We know that T is the next younger live frame from Q +// and that T does not have an entry in the frame cache because its bit was +// not set. Therefore, we have found entry E[j-1] and the subsequent entries +// are stale and should be purged from the frame cache. +// +// We have a LiveSavedFrameCache for each activation to minimize the number of +// entries that must be scanned through, and to avoid the headaches of +// maintaining a cache for each compartment and invalidating stale cache entries +// in the presence of cross-compartment calls. +class LiveSavedFrameCache +{ + public: + using FramePtr = mozilla::Variant<AbstractFramePtr, jit::CommonFrameLayout*>; + + private: + struct Entry + { + FramePtr framePtr; + jsbytecode* pc; + HeapPtr<SavedFrame*> savedFrame; + + Entry(FramePtr& framePtr, jsbytecode* pc, SavedFrame* savedFrame) + : framePtr(framePtr) + , pc(pc) + , savedFrame(savedFrame) + { } + }; + + using EntryVector = Vector<Entry, 0, SystemAllocPolicy>; + EntryVector* frames; + + LiveSavedFrameCache(const LiveSavedFrameCache&) = delete; + LiveSavedFrameCache& operator=(const LiveSavedFrameCache&) = delete; + + public: + explicit LiveSavedFrameCache() : frames(nullptr) { } + + LiveSavedFrameCache(LiveSavedFrameCache&& rhs) + : frames(rhs.frames) + { + MOZ_ASSERT(this != &rhs, "self-move disallowed"); + rhs.frames = nullptr; + } + + ~LiveSavedFrameCache() { + if (frames) { + js_delete(frames); + frames = nullptr; + } + } + + bool initialized() const { return !!frames; } + bool init(JSContext* cx) { + frames = js_new<EntryVector>(); + if (!frames) { + JS_ReportOutOfMemory(cx); + return false; + } + return true; + } + + static mozilla::Maybe<FramePtr> getFramePtr(FrameIter& iter); + void trace(JSTracer* trc); + + void find(JSContext* cx, FrameIter& frameIter, MutableHandleSavedFrame frame) const; + bool insert(JSContext* cx, FramePtr& framePtr, jsbytecode* pc, HandleSavedFrame savedFrame); +}; + +static_assert(sizeof(LiveSavedFrameCache) == sizeof(uintptr_t), + "Every js::Activation has a LiveSavedFrameCache, so we need to be pretty careful " + "about avoiding bloat. If you're adding members to LiveSavedFrameCache, maybe you " + "should consider figuring out a way to make js::Activation have a " + "LiveSavedFrameCache* instead of a Rooted<LiveSavedFrameCache>."); + +/*****************************************************************************/ + +class InterpreterActivation; +class WasmActivation; + +namespace jit { + class JitActivation; +} // namespace jit + +// This class is separate from Activation, because it calls JSCompartment::wrap() +// which can GC and walk the stack. It's not safe to do that within the +// JitActivation constructor. +class MOZ_RAII ActivationEntryMonitor +{ + JSContext* cx_; + + // The entry point monitor that was set on cx_->runtime() when this + // ActivationEntryMonitor was created. + JS::dbg::AutoEntryMonitor* entryMonitor_; + + explicit ActivationEntryMonitor(JSContext* cx); + + ActivationEntryMonitor(const ActivationEntryMonitor& other) = delete; + void operator=(const ActivationEntryMonitor& other) = delete; + + Value asyncStack(JSContext* cx); + + public: + ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame); + ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken); + inline ~ActivationEntryMonitor(); +}; + +class Activation +{ + protected: + JSContext* cx_; + JSCompartment* compartment_; + Activation* prev_; + Activation* prevProfiling_; + + // Counter incremented by JS::HideScriptedCaller and decremented by + // JS::UnhideScriptedCaller. If > 0 for the top activation, + // DescribeScriptedCaller will return null instead of querying that + // activation, which should prompt the caller to consult embedding-specific + // data structures instead. + size_t hideScriptedCallerCount_; + + // The cache of SavedFrame objects we have already captured when walking + // this activation's stack. + Rooted<LiveSavedFrameCache> frameCache_; + + // Youngest saved frame of an async stack that will be iterated during stack + // capture in place of the actual stack of previous activations. Note that + // the stack of this activation is captured entirely before this is used. + // + // Usually this is nullptr, meaning that normal stack capture will occur. + // When this is set, the stack of any previous activation is ignored. + Rooted<SavedFrame*> asyncStack_; + + // Value of asyncCause to be attached to asyncStack_. + const char* asyncCause_; + + // True if the async call was explicitly requested, e.g. via + // callFunctionWithAsyncStack. + bool asyncCallIsExplicit_; + + enum Kind { Interpreter, Jit, Wasm }; + Kind kind_; + + inline Activation(JSContext* cx, Kind kind); + inline ~Activation(); + + public: + JSContext* cx() const { + return cx_; + } + JSCompartment* compartment() const { + return compartment_; + } + Activation* prev() const { + return prev_; + } + Activation* prevProfiling() const { return prevProfiling_; } + inline Activation* mostRecentProfiling(); + + bool isInterpreter() const { + return kind_ == Interpreter; + } + bool isJit() const { + return kind_ == Jit; + } + bool isWasm() const { + return kind_ == Wasm; + } + + inline bool isProfiling() const; + void registerProfiling(); + void unregisterProfiling(); + + InterpreterActivation* asInterpreter() const { + MOZ_ASSERT(isInterpreter()); + return (InterpreterActivation*)this; + } + jit::JitActivation* asJit() const { + MOZ_ASSERT(isJit()); + return (jit::JitActivation*)this; + } + WasmActivation* asWasm() const { + MOZ_ASSERT(isWasm()); + return (WasmActivation*)this; + } + + void hideScriptedCaller() { + hideScriptedCallerCount_++; + } + void unhideScriptedCaller() { + MOZ_ASSERT(hideScriptedCallerCount_ > 0); + hideScriptedCallerCount_--; + } + bool scriptedCallerIsHidden() const { + return hideScriptedCallerCount_ > 0; + } + + static size_t offsetOfPrevProfiling() { + return offsetof(Activation, prevProfiling_); + } + + SavedFrame* asyncStack() { + return asyncStack_; + } + + const char* asyncCause() const { + return asyncCause_; + } + + bool asyncCallIsExplicit() const { + return asyncCallIsExplicit_; + } + + inline LiveSavedFrameCache* getLiveSavedFrameCache(JSContext* cx); + + private: + Activation(const Activation& other) = delete; + void operator=(const Activation& other) = delete; +}; + +// This variable holds a special opcode value which is greater than all normal +// opcodes, and is chosen such that the bitwise or of this value with any +// opcode is this value. +static const jsbytecode EnableInterruptsPseudoOpcode = -1; + +static_assert(EnableInterruptsPseudoOpcode >= JSOP_LIMIT, + "EnableInterruptsPseudoOpcode must be greater than any opcode"); +static_assert(EnableInterruptsPseudoOpcode == jsbytecode(-1), + "EnableInterruptsPseudoOpcode must be the maximum jsbytecode value"); + +class InterpreterFrameIterator; +class RunState; + +class InterpreterActivation : public Activation +{ + friend class js::InterpreterFrameIterator; + + InterpreterRegs regs_; + InterpreterFrame* entryFrame_; + size_t opMask_; // For debugger interrupts, see js::Interpret. + +#ifdef DEBUG + size_t oldFrameCount_; +#endif + + public: + inline InterpreterActivation(RunState& state, JSContext* cx, InterpreterFrame* entryFrame); + inline ~InterpreterActivation(); + + inline bool pushInlineFrame(const CallArgs& args, HandleScript script, + MaybeConstruct constructing); + inline void popInlineFrame(InterpreterFrame* frame); + + inline bool resumeGeneratorFrame(HandleFunction callee, HandleValue newTarget, + HandleObject envChain); + + InterpreterFrame* current() const { + return regs_.fp(); + } + InterpreterRegs& regs() { + return regs_; + } + InterpreterFrame* entryFrame() const { + return entryFrame_; + } + size_t opMask() const { + return opMask_; + } + + bool isProfiling() const { + return false; + } + + // If this js::Interpret frame is running |script|, enable interrupts. + void enableInterruptsIfRunning(JSScript* script) { + if (regs_.fp()->script() == script) + enableInterruptsUnconditionally(); + } + void enableInterruptsUnconditionally() { + opMask_ = EnableInterruptsPseudoOpcode; + } + void clearInterruptsMask() { + opMask_ = 0; + } +}; + +// Iterates over a thread's activation list. If given a runtime, iterate over +// the runtime's main thread's activation list. +class ActivationIterator +{ + uint8_t* jitTop_; + + protected: + Activation* activation_; + + private: + void settle(); + + public: + explicit ActivationIterator(JSRuntime* rt); + + ActivationIterator& operator++(); + + Activation* operator->() const { + return activation_; + } + Activation* activation() const { + return activation_; + } + uint8_t* jitTop() const { + MOZ_ASSERT(activation_->isJit()); + return jitTop_; + } + bool done() const { + return activation_ == nullptr; + } +}; + +namespace jit { + +class BailoutFrameInfo; + +// A JitActivation is used for frames running in Baseline or Ion. +class JitActivation : public Activation +{ + uint8_t* prevJitTop_; + JitActivation* prevJitActivation_; + bool active_; + + // Rematerialized Ion frames which has info copied out of snapshots. Maps + // frame pointers (i.e. jitTop) to a vector of rematerializations of all + // inline frames associated with that frame. + // + // This table is lazily initialized by calling getRematerializedFrame. + typedef GCVector<RematerializedFrame*> RematerializedFrameVector; + typedef HashMap<uint8_t*, RematerializedFrameVector> RematerializedFrameTable; + RematerializedFrameTable* rematerializedFrames_; + + // This vector is used to remember the outcome of the evaluation of recover + // instructions. + // + // RInstructionResults are appended into this vector when Snapshot values + // have to be read, or when the evaluation has to run before some mutating + // code. Each RInstructionResults belongs to one frame which has to bailout + // as soon as we get back to it. + typedef Vector<RInstructionResults, 1> IonRecoveryMap; + IonRecoveryMap ionRecovery_; + + // If we are bailing out from Ion, then this field should be a non-null + // pointer which references the BailoutFrameInfo used to walk the inner + // frames. This field is used for all newly constructed JitFrameIterators to + // read the innermost frame information from this bailout data instead of + // reading it from the stack. + BailoutFrameInfo* bailoutData_; + + // When profiling is enabled, these fields will be updated to reflect the + // last pushed frame for this activation, and if that frame has been + // left for a call, the native code site of the call. + mozilla::Atomic<void*, mozilla::Relaxed> lastProfilingFrame_; + mozilla::Atomic<void*, mozilla::Relaxed> lastProfilingCallSite_; + static_assert(sizeof(mozilla::Atomic<void*, mozilla::Relaxed>) == sizeof(void*), + "Atomic should have same memory format as underlying type."); + + void clearRematerializedFrames(); + +#ifdef CHECK_OSIPOINT_REGISTERS + protected: + // Used to verify that live registers don't change between a VM call and + // the OsiPoint that follows it. Protected to silence Clang warning. + uint32_t checkRegs_; + RegisterDump regs_; +#endif + + public: + explicit JitActivation(JSContext* cx, bool active = true); + ~JitActivation(); + + bool isActive() const { + return active_; + } + void setActive(JSContext* cx, bool active = true); + + bool isProfiling() const; + + uint8_t* prevJitTop() const { + return prevJitTop_; + } + JitActivation* prevJitActivation() const { + return prevJitActivation_; + } + static size_t offsetOfPrevJitTop() { + return offsetof(JitActivation, prevJitTop_); + } + static size_t offsetOfPrevJitActivation() { + return offsetof(JitActivation, prevJitActivation_); + } + static size_t offsetOfActiveUint8() { + MOZ_ASSERT(sizeof(bool) == 1); + return offsetof(JitActivation, active_); + } + +#ifdef CHECK_OSIPOINT_REGISTERS + void setCheckRegs(bool check) { + checkRegs_ = check; + } + static size_t offsetOfCheckRegs() { + return offsetof(JitActivation, checkRegs_); + } + static size_t offsetOfRegs() { + return offsetof(JitActivation, regs_); + } +#endif + + // Look up a rematerialized frame keyed by the fp, rematerializing the + // frame if one doesn't already exist. A frame can only be rematerialized + // if an IonFrameIterator pointing to the nearest uninlined frame can be + // provided, as values need to be read out of snapshots. + // + // The inlineDepth must be within bounds of the frame pointed to by iter. + RematerializedFrame* getRematerializedFrame(JSContext* cx, const JitFrameIterator& iter, + size_t inlineDepth = 0); + + // Look up a rematerialized frame by the fp. If inlineDepth is out of + // bounds of what has been rematerialized, nullptr is returned. + RematerializedFrame* lookupRematerializedFrame(uint8_t* top, size_t inlineDepth = 0); + + // Remove all rematerialized frames associated with the fp top from the + // Debugger. + void removeRematerializedFramesFromDebugger(JSContext* cx, uint8_t* top); + + bool hasRematerializedFrame(uint8_t* top, size_t inlineDepth = 0) { + return !!lookupRematerializedFrame(top, inlineDepth); + } + + // Remove a previous rematerialization by fp. + void removeRematerializedFrame(uint8_t* top); + + void markRematerializedFrames(JSTracer* trc); + + + // Register the results of on Ion frame recovery. + bool registerIonFrameRecovery(RInstructionResults&& results); + + // Return the pointer to the Ion frame recovery, if it is already registered. + RInstructionResults* maybeIonFrameRecovery(JitFrameLayout* fp); + + // If an Ion frame recovery exists for the |fp| frame exists, then remove it + // from the activation. + void removeIonFrameRecovery(JitFrameLayout* fp); + + void markIonRecovery(JSTracer* trc); + + // Return the bailout information if it is registered. + const BailoutFrameInfo* bailoutData() const { return bailoutData_; } + + // Register the bailout data when it is constructed. + void setBailoutData(BailoutFrameInfo* bailoutData); + + // Unregister the bailout data when the frame is reconstructed. + void cleanBailoutData(); + + static size_t offsetOfLastProfilingFrame() { + return offsetof(JitActivation, lastProfilingFrame_); + } + void* lastProfilingFrame() { + return lastProfilingFrame_; + } + void setLastProfilingFrame(void* ptr) { + lastProfilingFrame_ = ptr; + } + + static size_t offsetOfLastProfilingCallSite() { + return offsetof(JitActivation, lastProfilingCallSite_); + } + void* lastProfilingCallSite() { + return lastProfilingCallSite_; + } + void setLastProfilingCallSite(void* ptr) { + lastProfilingCallSite_ = ptr; + } +}; + +// A filtering of the ActivationIterator to only stop at JitActivations. +class JitActivationIterator : public ActivationIterator +{ + void settle() { + while (!done() && !activation_->isJit()) + ActivationIterator::operator++(); + } + + public: + explicit JitActivationIterator(JSRuntime* rt) + : ActivationIterator(rt) + { + settle(); + } + + JitActivationIterator& operator++() { + ActivationIterator::operator++(); + settle(); + return *this; + } +}; + +} // namespace jit + +// Iterates over the frames of a single InterpreterActivation. +class InterpreterFrameIterator +{ + InterpreterActivation* activation_; + InterpreterFrame* fp_; + jsbytecode* pc_; + Value* sp_; + + public: + explicit InterpreterFrameIterator(InterpreterActivation* activation) + : activation_(activation), + fp_(nullptr), + pc_(nullptr), + sp_(nullptr) + { + if (activation) { + fp_ = activation->current(); + pc_ = activation->regs().pc; + sp_ = activation->regs().sp; + } + } + + InterpreterFrame* frame() const { + MOZ_ASSERT(!done()); + return fp_; + } + jsbytecode* pc() const { + MOZ_ASSERT(!done()); + return pc_; + } + Value* sp() const { + MOZ_ASSERT(!done()); + return sp_; + } + + InterpreterFrameIterator& operator++(); + + bool done() const { + return fp_ == nullptr; + } +}; + +// A WasmActivation is part of two activation linked lists: +// - the normal Activation list used by FrameIter +// - a list of only WasmActivations that is signal-safe since it is accessed +// from the profiler at arbitrary points +// +// An eventual goal is to remove WasmActivation and to run asm code in a +// JitActivation interleaved with Ion/Baseline jit code. This would allow +// efficient calls back and forth but requires that we can walk the stack for +// all kinds of jit code. +class WasmActivation : public Activation +{ + WasmActivation* prevWasm_; + void* entrySP_; + void* resumePC_; + uint8_t* fp_; + wasm::ExitReason exitReason_; + + public: + explicit WasmActivation(JSContext* cx); + ~WasmActivation(); + + WasmActivation* prevWasm() const { return prevWasm_; } + + bool isProfiling() const { + return true; + } + + // Returns a pointer to the base of the innermost stack frame of wasm code + // in this activation. + uint8_t* fp() const { return fp_; } + + // Returns the reason why wasm code called out of wasm code. + wasm::ExitReason exitReason() const { return exitReason_; } + + // Read by JIT code: + static unsigned offsetOfContext() { return offsetof(WasmActivation, cx_); } + static unsigned offsetOfResumePC() { return offsetof(WasmActivation, resumePC_); } + + // Written by JIT code: + static unsigned offsetOfEntrySP() { return offsetof(WasmActivation, entrySP_); } + static unsigned offsetOfFP() { return offsetof(WasmActivation, fp_); } + static unsigned offsetOfExitReason() { return offsetof(WasmActivation, exitReason_); } + + // Read/written from SIGSEGV handler: + void setResumePC(void* pc) { resumePC_ = pc; } + void* resumePC() const { return resumePC_; } +}; + +// A FrameIter walks over the runtime's stack of JS script activations, +// abstracting over whether the JS scripts were running in the interpreter or +// different modes of compiled code. +// +// FrameIter is parameterized by what it includes in the stack iteration: +// - When provided, the optional JSPrincipal argument will cause FrameIter to +// only show frames in globals whose JSPrincipals are subsumed (via +// JSSecurityCallbacks::subsume) by the given JSPrincipal. +// +// Additionally, there are derived FrameIter types that automatically skip +// certain frames: +// - ScriptFrameIter only shows frames that have an associated JSScript +// (currently everything other than wasm stack frames). When !hasScript(), +// clients must stick to the portion of the +// interface marked below. +// - NonBuiltinScriptFrameIter additionally filters out builtin (self-hosted) +// scripts. +class FrameIter +{ + public: + enum DebuggerEvalOption { FOLLOW_DEBUGGER_EVAL_PREV_LINK, + IGNORE_DEBUGGER_EVAL_PREV_LINK }; + enum State { DONE, INTERP, JIT, WASM }; + + // Unlike ScriptFrameIter itself, ScriptFrameIter::Data can be allocated on + // the heap, so this structure should not contain any GC things. + struct Data + { + JSContext * cx_; + DebuggerEvalOption debuggerEvalOption_; + JSPrincipals * principals_; + + State state_; + + jsbytecode * pc_; + + InterpreterFrameIterator interpFrames_; + ActivationIterator activations_; + + jit::JitFrameIterator jitFrames_; + unsigned ionInlineFrameNo_; + wasm::FrameIterator wasmFrames_; + + Data(JSContext* cx, DebuggerEvalOption debuggerEvalOption, JSPrincipals* principals); + Data(const Data& other); + }; + + explicit FrameIter(JSContext* cx, + DebuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK); + FrameIter(JSContext* cx, DebuggerEvalOption, JSPrincipals*); + FrameIter(const FrameIter& iter); + MOZ_IMPLICIT FrameIter(const Data& data); + MOZ_IMPLICIT FrameIter(AbstractFramePtr frame); + + bool done() const { return data_.state_ == DONE; } + + // ------------------------------------------------------- + // The following functions can only be called when !done() + // ------------------------------------------------------- + + FrameIter& operator++(); + + JSCompartment* compartment() const; + Activation* activation() const { return data_.activations_.activation(); } + + bool isInterp() const { MOZ_ASSERT(!done()); return data_.state_ == INTERP; } + bool isJit() const { MOZ_ASSERT(!done()); return data_.state_ == JIT; } + bool isWasm() const { MOZ_ASSERT(!done()); return data_.state_ == WASM; } + inline bool isIon() const; + inline bool isBaseline() const; + inline bool isPhysicalIonFrame() const; + + bool isEvalFrame() const; + bool isFunctionFrame() const; + bool hasArgs() const { return isFunctionFrame(); } + + // These two methods may not be called with asm frames. + inline bool hasCachedSavedFrame() const; + inline void setHasCachedSavedFrame(); + + ScriptSource* scriptSource() const; + const char* filename() const; + const char16_t* displayURL() const; + unsigned computeLine(uint32_t* column = nullptr) const; + JSAtom* functionDisplayAtom() const; + bool mutedErrors() const; + + bool hasScript() const { return !isWasm(); } + + // ----------------------------------------------------------- + // The following functions can only be called when hasScript() + // ----------------------------------------------------------- + + inline JSScript* script() const; + + bool isConstructing() const; + jsbytecode* pc() const { MOZ_ASSERT(!done()); return data_.pc_; } + void updatePcQuadratic(); + + // The function |calleeTemplate()| returns either the function from which + // the current |callee| was cloned or the |callee| if it can be read. As + // long as we do not have to investigate the environment chain or build a + // new frame, we should prefer to use |calleeTemplate| instead of + // |callee|, as requesting the |callee| might cause the invalidation of + // the frame. (see js::Lambda) + JSFunction* calleeTemplate() const; + JSFunction* callee(JSContext* cx) const; + + JSFunction* maybeCallee(JSContext* cx) const { + return isFunctionFrame() ? callee(cx) : nullptr; + } + + bool matchCallee(JSContext* cx, HandleFunction fun) const; + + unsigned numActualArgs() const; + unsigned numFormalArgs() const; + Value unaliasedActual(unsigned i, MaybeCheckAliasing = CHECK_ALIASING) const; + template <class Op> inline void unaliasedForEachActual(JSContext* cx, Op op); + + JSObject* environmentChain(JSContext* cx) const; + CallObject& callObj(JSContext* cx) const; + + bool hasArgsObj() const; + ArgumentsObject& argsObj() const; + + // Get the original |this| value passed to this function. May not be the + // actual this-binding (for instance, derived class constructors will + // change their this-value later and non-strict functions will box + // primitives). + Value thisArgument(JSContext* cx) const; + + Value newTarget() const; + + Value returnValue() const; + void setReturnValue(const Value& v); + + // These are only valid for the top frame. + size_t numFrameSlots() const; + Value frameSlotValue(size_t index) const; + + // Ensures that we have rematerialized the top frame and its associated + // inline frames. Can only be called when isIon(). + bool ensureHasRematerializedFrame(JSContext* cx); + + // True when isInterp() or isBaseline(). True when isIon() if it + // has a rematerialized frame. False otherwise false otherwise. + bool hasUsableAbstractFramePtr() const; + + // ----------------------------------------------------------- + // The following functions can only be called when isInterp(), + // isBaseline(), or isIon(). Further, abstractFramePtr() can + // only be called when hasUsableAbstractFramePtr(). + // ----------------------------------------------------------- + + AbstractFramePtr abstractFramePtr() const; + AbstractFramePtr copyDataAsAbstractFramePtr() const; + Data* copyData() const; + + // This can only be called when isInterp(): + inline InterpreterFrame* interpFrame() const; + + // This can only be called when isPhysicalIonFrame(): + inline jit::CommonFrameLayout* physicalIonFrame() const; + + // This is used to provide a raw interface for debugging. + void* rawFramePtr() const; + + private: + Data data_; + jit::InlineFrameIterator ionInlineFrames_; + + void popActivation(); + void popInterpreterFrame(); + void nextJitFrame(); + void popJitFrame(); + void popWasmFrame(); + void settleOnActivation(); +}; + +class ScriptFrameIter : public FrameIter +{ + void settle() { + while (!done() && !hasScript()) + FrameIter::operator++(); + } + + public: + explicit ScriptFrameIter(JSContext* cx, + DebuggerEvalOption debuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK) + : FrameIter(cx, debuggerEvalOption) + { + settle(); + } + + ScriptFrameIter(JSContext* cx, + DebuggerEvalOption debuggerEvalOption, + JSPrincipals* prin) + : FrameIter(cx, debuggerEvalOption, prin) + { + settle(); + } + + ScriptFrameIter(const ScriptFrameIter& iter) : FrameIter(iter) { settle(); } + explicit ScriptFrameIter(const FrameIter::Data& data) : FrameIter(data) { settle(); } + explicit ScriptFrameIter(AbstractFramePtr frame) : FrameIter(frame) { settle(); } + + ScriptFrameIter& operator++() { + FrameIter::operator++(); + settle(); + return *this; + } +}; + +#ifdef DEBUG +bool SelfHostedFramesVisible(); +#else +static inline bool +SelfHostedFramesVisible() +{ + return false; +} +#endif + +/* A filtering of the FrameIter to only stop at non-self-hosted scripts. */ +class NonBuiltinFrameIter : public FrameIter +{ + void settle(); + + public: + explicit NonBuiltinFrameIter(JSContext* cx, + FrameIter::DebuggerEvalOption debuggerEvalOption = + FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK) + : FrameIter(cx, debuggerEvalOption) + { + settle(); + } + + NonBuiltinFrameIter(JSContext* cx, + FrameIter::DebuggerEvalOption debuggerEvalOption, + JSPrincipals* principals) + : FrameIter(cx, debuggerEvalOption, principals) + { + settle(); + } + + NonBuiltinFrameIter(JSContext* cx, JSPrincipals* principals) + : FrameIter(cx, FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, principals) + { + settle(); + } + + explicit NonBuiltinFrameIter(const FrameIter::Data& data) + : FrameIter(data) + {} + + NonBuiltinFrameIter& operator++() { + FrameIter::operator++(); + settle(); + return *this; + } +}; + +/* A filtering of the ScriptFrameIter to only stop at non-self-hosted scripts. */ +class NonBuiltinScriptFrameIter : public ScriptFrameIter +{ + void settle(); + + public: + explicit NonBuiltinScriptFrameIter(JSContext* cx, + ScriptFrameIter::DebuggerEvalOption debuggerEvalOption = + ScriptFrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK) + : ScriptFrameIter(cx, debuggerEvalOption) + { + settle(); + } + + NonBuiltinScriptFrameIter(JSContext* cx, + ScriptFrameIter::DebuggerEvalOption debuggerEvalOption, + JSPrincipals* principals) + : ScriptFrameIter(cx, debuggerEvalOption, principals) + { + settle(); + } + + explicit NonBuiltinScriptFrameIter(const ScriptFrameIter::Data& data) + : ScriptFrameIter(data) + {} + + NonBuiltinScriptFrameIter& operator++() { + ScriptFrameIter::operator++(); + settle(); + return *this; + } +}; + +/* + * Blindly iterate over all frames in the current thread's stack. These frames + * can be from different contexts and compartments, so beware. + */ +class AllFramesIter : public FrameIter +{ + public: + explicit AllFramesIter(JSContext* cx) + : FrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK) + {} +}; + +/* Iterates over all script frame in the current thread's stack. + * See also AllFramesIter and ScriptFrameIter. + */ +class AllScriptFramesIter : public ScriptFrameIter +{ + public: + explicit AllScriptFramesIter(JSContext* cx) + : ScriptFrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK) + {} +}; + +/* Popular inline definitions. */ + +inline JSScript* +FrameIter::script() const +{ + MOZ_ASSERT(!done()); + if (data_.state_ == INTERP) + return interpFrame()->script(); + MOZ_ASSERT(data_.state_ == JIT); + if (data_.jitFrames_.isIonJS()) + return ionInlineFrames_.script(); + return data_.jitFrames_.script(); +} + +inline bool +FrameIter::isIon() const +{ + return isJit() && data_.jitFrames_.isIonJS(); +} + +inline bool +FrameIter::isBaseline() const +{ + return isJit() && data_.jitFrames_.isBaselineJS(); +} + +inline InterpreterFrame* +FrameIter::interpFrame() const +{ + MOZ_ASSERT(data_.state_ == INTERP); + return data_.interpFrames_.frame(); +} + +inline bool +FrameIter::isPhysicalIonFrame() const +{ + return isJit() && + data_.jitFrames_.isIonScripted() && + ionInlineFrames_.frameNo() == 0; +} + +inline jit::CommonFrameLayout* +FrameIter::physicalIonFrame() const +{ + MOZ_ASSERT(isPhysicalIonFrame()); + return data_.jitFrames_.current(); +} + +} /* namespace js */ +#endif /* vm_Stack_h */ |