diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /js/src/vm/Stack.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/Stack.h')
-rw-r--r-- | js/src/vm/Stack.h | 999 |
1 files changed, 999 insertions, 0 deletions
diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h new file mode 100644 index 0000000000..d537cb80af --- /dev/null +++ b/js/src/vm/Stack.h @@ -0,0 +1,999 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/HashFunctions.h" +#include "mozilla/MemoryReporting.h" + +#include <algorithm> +#include <type_traits> + +#include "js/ErrorReport.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/ValueArray.h" +#include "vm/ArgumentsObject.h" +#include "vm/JSFunction.h" +#include "vm/JSScript.h" +#include "wasm/WasmDebugFrame.h" // js::wasm::DebugFrame + +namespace js { + +class InterpreterRegs; +class CallObject; +class FrameIter; +class ClassBodyScope; +class EnvironmentObject; +class BlockLexicalEnvironmentObject; +class ExtensibleLexicalEnvironmentObject; +class GeckoProfilerRuntime; +class InterpreterFrame; +class EnvironmentIter; +class EnvironmentCoordinate; + +namespace jit { +class CommonFrameLayout; +} +namespace wasm { +class Instance; +} // namespace wasm + +// [SMDOC] 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 JS::Compartment. 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 }; + +} // namespace js + +/*****************************************************************************/ + +namespace js { + +namespace jit { +class BaselineFrame; +class RematerializedFrame; +} // namespace jit + +/** + * Pointer to a live JS or WASM stack frame. + */ +class AbstractFramePtr { + friend class FrameIter; + + uintptr_t ptr_; + + enum { + Tag_InterpreterFrame = 0x1, + Tag_BaselineFrame = 0x2, + Tag_RematerializedFrame = 0x3, + Tag_WasmDebugFrame = 0x4, + TagMask = 0x7 + }; + + 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); + } + + MOZ_IMPLICIT AbstractFramePtr(wasm::DebugFrame* fp) + : ptr_(fp ? uintptr_t(fp) | Tag_WasmDebugFrame : 0) { + static_assert(wasm::DebugFrame::Alignment >= TagMask, "aligned"); + MOZ_ASSERT_IF(fp, asWasmDebugFrame() == fp); + } + + 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; + } + bool isWasmDebugFrame() const { + return (ptr_ & TagMask) == Tag_WasmDebugFrame; + } + wasm::DebugFrame* asWasmDebugFrame() const { + MOZ_ASSERT(isWasmDebugFrame()); + wasm::DebugFrame* res = (wasm::DebugFrame*)(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, Handle<Scope*> scope); + template <typename SpecificEnvironment> + inline void pushOnEnvironmentChain(SpecificEnvironment& env); + template <typename SpecificEnvironment> + inline void popOffEnvironmentChain(); + + inline JS::Realm* realm() const; + + inline bool hasInitialEnvironment() const; + inline bool isGlobalFrame() const; + inline bool isModuleFrame() const; + inline bool isEvalFrame() const; + inline bool isDebuggerEvalFrame() const; + + inline bool hasScript() const; + inline JSScript* script() const; + inline wasm::Instance* wasmInstance() const; + inline GlobalObject* global() const; + inline bool hasGlobal(const GlobalObject* global) const; + inline JSFunction* callee() const; + inline Value calleev() const; + inline Value& thisArgument() const; + + inline bool isConstructing() const; + + inline bool debuggerNeedsCheckPrimitiveReturn() const; + + inline bool isFunctionFrame() const; + inline bool isGeneratorFrame() const; + + inline bool saveGeneratorSlots(JSContext* cx, unsigned nslots, + ArrayObject* dest) const; + + inline bool hasCachedSavedFrame() 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 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&, InterpreterFrame*); + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, + jit::BaselineFrame*); + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, + jit::RematerializedFrame*); + friend void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, + wasm::DebugFrame* ptr); +}; + +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 Realm.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/GeckoProfiler.cpp) */ + HAS_PUSHED_PROF_FRAME = 0x80, /* Gecko Profiler was notified of entry */ + + /* + * If set, we entered one of the JITs and ScriptFrameIter should skip + * this frame. + */ + RUNNING_IN_JIT = 0x100, + + /* + * 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 = 0x200, + }; + + 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_; + + /* + * 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() { + static_assert(offsetof(InterpreterFrame, rval_) % sizeof(Value) == 0); + 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(InterpreterFrame* prev, jsbytecode* prevpc, Value* prevsp, + JSFunction& callee, JSScript* script, Value* argv, + uint32_t nactual, MaybeConstruct constructing); + + /* Used for eval, module or global frames. */ + void initExecuteFrame(JSContext* cx, HandleScript script, + AbstractFramePtr prev, 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, MutableHandleValue result); + + 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_->isModule(); } + + bool isEvalFrame() const { return script_->isForEval(); } + + bool isFunctionFrame() const { return script_->isFunction(); } + + /* + * 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); + + ArrayObject* 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), GetVariablesObject 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 EnvironmentObject& aliasedEnvironmentMaybeDebug( + EnvironmentCoordinate ec) const; + inline GlobalObject& global() const; + inline CallObject& callObj() const; + inline ExtensibleLexicalEnvironmentObject& extensibleLexicalEnvironment() + const; + + template <typename SpecificEnvironment> + inline void pushOnEnvironmentChain(SpecificEnvironment& env); + template <typename SpecificEnvironment> + inline void popOffEnvironmentChain(); + inline void replaceInnermostEnvironment(BlockLexicalEnvironmentObject& env); + + // Push a VarEnvironmentObject for function frames of functions that have + // parameter expressions with closed over var bindings. + bool pushVarEnvironment(JSContext* cx, Handle<Scope*> 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); + + bool pushClassBodyEnvironment(JSContext* cx, Handle<ClassBodyScope*> scope); + + /* + * 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 true callee. An eval frame in a function has + * the same callee as its containing function frame. An async module has to + * create a wrapper callee to allow passing the script to generators for + * pausing and resuming. + */ + + JSFunction& callee() const { + MOZ_ASSERT(isFunctionFrame()); + return calleev().toObject().as<JSFunction>(); + } + + const Value& calleev() const { + MOZ_ASSERT(isFunctionFrame()); + return argv()[-2]; + } + + /* + * New Target + * + * Only non-arrow function frames have a meaningful newTarget. + */ + Value newTarget() const { + MOZ_ASSERT(isFunctionFrame()); + MOZ_ASSERT(!callee().isArrow()); + + if (isConstructing()) { + unsigned pushedArgs = std::max(numFormalArgs(), numActualArgs()); + return argv()[pushedArgs]; + } + return UndefinedValue(); + } + + /* Profiler flags */ + + bool hasPushedGeckoProfilerFrame() { + return !!(flags_ & HAS_PUSHED_PROF_FRAME); + } + + void setPushedGeckoProfilerFrame() { flags_ |= HAS_PUSHED_PROF_FRAME; } + + void unsetPushedGeckoProfilerFrame() { flags_ &= ~HAS_PUSHED_PROF_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(); + } + + // Copy values from this frame into a private Array, owned by the + // GeneratorObject, for suspending. + [[nodiscard]] inline bool saveGeneratorSlots(JSContext* cx, unsigned nslots, + ArrayObject* dest) const; + + // Copy values from the Array into this stack frame, for resuming. + inline void restoreGeneratorSlots(ArrayObject* src); + + void resumeGeneratorFrame(JSObject* envChain) { + MOZ_ASSERT(script()->isGenerator() || script()->isAsync()); + MOZ_ASSERT_IF(!script()->isModule(), 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; + } + + /* + * 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; } + void clearHasCachedSavedFrame() { 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; + } + + void popInlineFrame() { + pc = fp_->prevpc(); + unsigned spForNewTarget = + fp_->isResumedGenerator() ? 0 : fp_->isConstructing(); + // This code is called when resuming from async and generator code. + // In the case of modules, we don't have arguments, so we can't use + // numActualArgs, which asserts 'hasArgs'. + unsigned nActualArgs = fp_->isModuleFrame() ? 0 : fp_->numActualArgs(); + sp = fp_->prevsp() - nActualArgs - 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, module or global code. + InterpreterFrame* pushExecuteFrame(JSContext* cx, HandleScript script, + 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, HandleObject envChain); + + inline void purge(JSRuntime* rt); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return allocator_.sizeOfExcludingThis(mallocSizeOf); + } +}; + +void TraceInterpreterActivations(JSContext* cx, 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 std::conditional_t<Construct, AnyConstructArgs, AnyInvokeArgs> { + protected: + RootedValueVector v_; + + explicit GenericArgsBase(JSContext* cx) : v_(cx) {} + + public: + bool init(JSContext* cx, uint64_t 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 std::conditional_t<Construct, AnyConstructArgs, AnyInvokeArgs> { + // Add +1 here to avoid noisy warning on gcc when N=0 (0 <= unsigned). + static_assert(N + 1 <= ARGS_LENGTH_MAX + 1, "o/~ too many args o/~"); + + protected: + JS::RootedValueArray<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-unknown count. */ +class InvokeArgsMaybeIgnoresReturnValue + : public detail::GenericArgsBase<NO_CONSTRUCT> { + using Base = detail::GenericArgsBase<NO_CONSTRUCT>; + + public: + explicit InvokeArgsMaybeIgnoresReturnValue(JSContext* cx) : Base(cx) {} + + bool init(JSContext* cx, unsigned argc, bool ignoresReturnValue) { + if (!Base::init(cx, argc)) { + return false; + } + this->ignoresReturnValue_ = ignoresReturnValue; + return true; + } +}; + +/** 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; +} + +} // namespace js + +namespace mozilla { + +template <> +struct DefaultHasher<js::AbstractFramePtr> { + using Lookup = js::AbstractFramePtr; + + static js::HashNumber hash(const Lookup& key) { + return mozilla::HashGeneric(key.raw()); + } + + static bool match(const js::AbstractFramePtr& k, const Lookup& l) { + return k == l; + } +}; + +} // namespace mozilla + +#endif // vm_Stack_h |