diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 1095 |
1 files changed, 1095 insertions, 0 deletions
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h new file mode 100644 index 0000000000..6d9c6eed0a --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.h @@ -0,0 +1,1095 @@ +/* -*- 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/. */ + +/* JS bytecode generation. */ + +#ifndef frontend_BytecodeEmitter_h +#define frontend_BytecodeEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some +#include "mozilla/Saturate.h" // mozilla::SaturateUint8 +#include "mozilla/Span.h" // mozilla::Span + +#include <stddef.h> // ptrdiff_t +#include <stdint.h> // uint16_t, uint32_t + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeSection.h" // BytecodeSection, PerScriptData, CGScopeList +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EitherParser.h" // EitherParser +#include "frontend/IteratorKind.h" // IteratorKind +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameCollections.h" // AtomIndexMap +#include "frontend/ParseNode.h" // ParseNode and subclasses +#include "frontend/Parser.h" // Parser, PropListType +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, ParserAtom +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SelfHostedIter.h" // SelfHostedIter +#include "frontend/SourceNotes.h" // SrcNoteType +#include "frontend/ValueUsage.h" // ValueUsage +#include "js/AllocPolicy.h" // ReportOutOfMemory +#include "js/TypeDecls.h" // jsbytecode +#include "vm/BuiltinObjectKind.h" // BuiltinObjectKind +#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind +#include "vm/CompletionKind.h" // CompletionKind +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorResumeKind.h" // GeneratorResumeKind +#include "vm/Opcodes.h" // JSOp +#include "vm/SharedStencil.h" // GCThingIndex, MemberInitializers +#include "vm/StencilEnums.h" // TryNoteKind +#include "vm/ThrowMsgKind.h" // ThrowMsgKind, ThrowCondition + +namespace js { + +class FrontendContext; + +namespace frontend { + +class BytecodeOffset; +class CallOrNewEmitter; +class ClassEmitter; +class ElemOpEmitter; +class EmitterScope; +class ErrorReporter; +class FullParseHandler; +class NestableControl; +class PrivateOpEmitter; +class PropertyEmitter; +class PropOpEmitter; +class OptionalEmitter; +class SharedContext; +class TDZCheckCache; +class TryEmitter; + +struct TokenPos; + +enum class ValueIsOnStack { Yes, No }; + +// [SMDOC] Bytecode emission +// +// Bytecode emitter class and helper classes for generating bytecode and related +// stencil data from AST generated by JS parser. +// +// +// BytecodeEmitter +// --------------- +// +// BytecodeEmitter receives an AST, and utilizes helper classes to generate the +// bytecode sequence, and related stencil data. +// +// BytecodeEmitter can be nested, in order to emit inner non-lazy function +// scripts. +// +// +// Bytecode structures +// ------------------- +// +// While bytecode is being emitted, it is separated into 2 parts, the prologue +// and the main part. The prologue part contains instantiation of the declared +// variables, functions, and special names in function. The main part contains +// the remaining part of the bytecode. +// +// The generated bytecode is stored into the following 2 classes, before +// converting them into stencil data (See ImmutableScriptData and +// BytecodeEmitter::createImmutableScriptData): +// +// * BytecodeSection +// * PerScriptData +// +// BytecodeSection stores the bytecode sequence and data directly associated +// with opcode or index inside the bytecode sequence. +// +// PerScriptData contains data referred from the bytecode, that is mostly the +// list of GC things. +// +// +// Bindings +// -------- +// +// # Scope and bindings +// +// When emitting AST node that's associated with a given scope, EmitterScope is +// allocated to store/cache the bindings information. +// +// This information is used when emitting an opcode that accesses bindings, to +// determine where the binding is stored, and how the binding should be +// accessed, including which opcode to use and what operand to use for it. +// +// +// # Temporal Dead Zone (TDZ) check cache +// +// The spec requires TDZ check for all lexical variable access, but emitting +// TDZ check for all operation increases the bytecode size and affects the +// performance. TDZCheckCache is a cache to optimize away unnecessary TDZ check +// operations. +// +// See comments for TDZCheckCache for more details. +// +// +// Control structures +// ------------------ +// +// # Jump list +// +// When emitting jump-related bytecode (if-else, break/continue, try-catch), +// forward jump is tracked by JumpList class, in order to patch the jump +// after the jump target is emitted. +// +// See the comment above JumpList class for mode details. +// +// +// # Loop and label +// +// Control structure related to break/continue is handled by NestableControl and +// its subclasses. Those classes handle jump with labelled and un-labelled +// break/continue, stack balancing around them, TDZ check cache for the +// loop's basic block, and association between the control and the scope. +// +// +// Emitter helpers +// --------------- +// +// Bytecode sequence or structure specific to certain syntax (e.g. if, for, try) +// are handled by emitter helper classes. +// +// Each emitter helper is defined in *Emitter.{cpp,h} in this directory. +// +// Emitter helpers should meet the following requirements: +// * helper classes should be ParseNode-agnostic +// * helper classes shouldn't contain `JS::Rooted` field, given they can be +// held in `mozilla::Maybe` in the consumer or other helper classes +// * instantiation (ctor/dtor) of the emitter helper class shouldn't +// modify BytecodeEmitter, except for nestable controls +// * instantiation (ctor/dtor) of the emitter helper class shouldn't +// read BytecodeEmitter field that can change before the first method call. +// Such data should be explicitly passed as parameter, or be accessed inside +// the method +// * methods that emits bytecode should be named `emit*` or `prepareFor*` +// * methods and their names shouldn't require the consumer knowing the +// details of the bytecode sequence/structure that the helper emits +// * implicit branch or scope/control handling should be hidden from the +// consumer +// * If there are multiple operations between bytecode that the consumer +// emits, they should be wrapped into single `emit*` or `prepareFor*` +// method +// e.g. +// // Bad! +// helper.emitJumpAroundA(); +// helper.allocateScopeForA(); +// ... // emit bytecode for A here +// helper.deallocateScopeForA(); +// helper.emitJumpAroundB(); +// helper.allocateScopeForB(); +// ... // emit bytecode for B here +// helper.deallocateScopeForB(); +// helper.emitJumpTarget(); +// +// // Good! +// helper.prepareForA(); +// ... // emit bytecode for A here +// helper.prepareForB(); +// ... // emit bytecode for B here +// helper.emitEnd(); +// * helper classes should track state transition and assert it in each +// method call, to avoid misuse +// * it's recommended to defer receiving parameter until the parameter value +// is actually used in the method, instead of receiving and storing them +// into instance fields +// +// See comment block above each helper class for more details and example usage. + +struct MOZ_STACK_CLASS BytecodeEmitter { + // Context shared between parsing and bytecode generation. + SharedContext* const sc = nullptr; + + FrontendContext* const fc = nullptr; + + // Enclosing function or global context. + BytecodeEmitter* const parent = nullptr; + + BytecodeSection bytecodeSection_; + + public: + BytecodeSection& bytecodeSection() { return bytecodeSection_; } + const BytecodeSection& bytecodeSection() const { return bytecodeSection_; } + + private: + PerScriptData perScriptData_; + + public: + PerScriptData& perScriptData() { return perScriptData_; } + const PerScriptData& perScriptData() const { return perScriptData_; } + + private: + // switchToMain sets this to the bytecode offset of the main section. + mozilla::Maybe<uint32_t> mainOffset_ = {}; + + // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be + // initialized. + mozilla::Maybe<EitherParser> ep_ = {}; + + const ErrorReporter& errorReporter_; + + public: + CompilationState& compilationState; + + uint32_t maxFixedSlots = 0; /* maximum number of fixed frame slots so far */ + + // Index into scopeList of the body scope. + GCThingIndex bodyScopeIndex = ScopeNote::NoScopeIndex; + + EmitterScope* varEmitterScope = nullptr; + NestableControl* innermostNestableControl = nullptr; + EmitterScope* innermostEmitterScope_ = nullptr; + TDZCheckCache* innermostTDZCheckCache = nullptr; + + // When compiling in self-hosted mode, we have special intrinsics that act as + // decorators for exported functions. To keeps things simple, we only allow + // these to target the last top-level function emitted. This field tracks that + // function. + FunctionBox* prevSelfHostedTopLevelFunction = nullptr; + +#ifdef DEBUG + bool unstableEmitterScope = false; + + friend class AutoCheckUnstableEmitterScope; +#endif + + const ErrorReporter& errorReporter() const { return errorReporter_; } + + ParserAtomsTable& parserAtoms() { return compilationState.parserAtoms; } + const ParserAtomsTable& parserAtoms() const { + return compilationState.parserAtoms; + } + + EmitterScope* innermostEmitterScope() const { + MOZ_ASSERT(!unstableEmitterScope); + return innermostEmitterScopeNoCheck(); + } + EmitterScope* innermostEmitterScopeNoCheck() const { + return innermostEmitterScope_; + } + + // When parsing internal code such as self-hosted functions or synthetic + // class constructors, we do not emit breakpoint and srcnote data since there + // is no direcly corresponding user-visible sources. + const bool suppressBreakpointsAndSourceNotes = false; + + // Script contains finally block. + bool hasTryFinally = false; + + enum EmitterMode { + Normal, + + // Emit JSOp::GetIntrinsic instead of JSOp::GetName and assert that + // JSOp::GetName and JSOp::*GName don't ever get emitted. See the comment + // for the field |selfHostingMode| in Parser.h for details. + SelfHosting, + + // Check the static scope chain of the root function for resolving free + // variable accesses in the script. + LazyFunction + }; + + const EmitterMode emitterMode = Normal; + + mozilla::Maybe<uint32_t> scriptStartOffset = {}; + + // The end location of a function body that is being emitted. + mozilla::Maybe<uint32_t> functionBodyEndPos = {}; + + // Jump target just before the final CheckReturn opcode in a derived class + // constructor body. + JumpList endOfDerivedClassConstructorBody = {}; + + // Jump target just before the final yield in a generator or async function. + JumpList finalYields = {}; + + // In order to heuristically determine the size of the allocation if this is a + // constructor function, we track expressions which add properties in the + // constructor. + mozilla::SaturateUint8 propertyAdditionEstimate = {}; + + /* + * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" + * space above their tempMark points. This means that you cannot alloc from + * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter + * destruction. + */ + private: + // Internal constructor, for delegation use only. + BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc, + SharedContext* sc, const ErrorReporter& errorReporter, + CompilationState& compilationState, EmitterMode emitterMode); + + BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc); + + void initFromBodyPosition(TokenPos bodyPosition); + + public: + BytecodeEmitter(FrontendContext* fc, const EitherParser& parser, + SharedContext* sc, CompilationState& compilationState, + EmitterMode emitterMode = Normal); + + template <typename Unit> + BytecodeEmitter(FrontendContext* fc, Parser<FullParseHandler, Unit>* parser, + SharedContext* sc, CompilationState& compilationState, + EmitterMode emitterMode = Normal) + : BytecodeEmitter(fc, EitherParser(parser), sc, compilationState, + emitterMode) {} + + [[nodiscard]] bool init(); + [[nodiscard]] bool init(TokenPos bodyPosition); + + template <typename T> + T* findInnermostNestableControl() const; + + template <typename T, typename Predicate /* (T*) -> bool */> + T* findInnermostNestableControl(Predicate predicate) const; + + NameLocation lookupName(TaggedParserAtomIndex name); + + // See EmitterScope::lookupPrivate for details around brandLoc + void lookupPrivate(TaggedParserAtomIndex name, NameLocation& loc, + mozilla::Maybe<NameLocation>& brandLoc); + + // To implement Annex B and the formal parameter defaults scope semantics + // requires accessing names that would otherwise be shadowed. This method + // returns the access location of a name that is known to be bound in a + // target scope. + mozilla::Maybe<NameLocation> locationOfNameBoundInScope( + TaggedParserAtomIndex name, EmitterScope* target); + + // Get the location of a name known to be bound in a given scope, + // starting at the source scope. + template <typename T> + mozilla::Maybe<NameLocation> locationOfNameBoundInScopeType( + TaggedParserAtomIndex name, EmitterScope* source); + + // Get the location of a name known to be bound in the function scope, + // starting at the source scope. + mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope( + TaggedParserAtomIndex name) { + return locationOfNameBoundInScopeType<FunctionScope>( + name, innermostEmitterScope()); + } + + void setVarEmitterScope(EmitterScope* emitterScope) { + MOZ_ASSERT(emitterScope); + MOZ_ASSERT(!varEmitterScope); + varEmitterScope = emitterScope; + } + + AbstractScopePtr outermostScope() const { + return perScriptData().gcThingList().firstScope(); + } + AbstractScopePtr innermostScope() const; + ScopeIndex innermostScopeIndex() const; + + [[nodiscard]] MOZ_ALWAYS_INLINE bool makeAtomIndex( + TaggedParserAtomIndex atom, ParserAtom::Atomize atomize, + GCThingIndex* indexp) { + MOZ_ASSERT(perScriptData().atomIndices()); + AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom); + if (p) { + compilationState.parserAtoms.markAtomize(atom, atomize); + *indexp = GCThingIndex(p->value()); + return true; + } + + GCThingIndex index; + if (!perScriptData().gcThingList().append(atom, atomize, &index)) { + return false; + } + + // `atomIndices()` uses uint32_t instead of GCThingIndex, because + // GCThingIndex isn't trivial type. + if (!perScriptData().atomIndices()->add(p, atom, index.index)) { + ReportOutOfMemory(fc); + return false; + } + + *indexp = index; + return true; + } + + bool isInLoop(); + [[nodiscard]] bool checkSingletonContext(); + + bool needsImplicitThis(); + + size_t countThisEnvironmentHops(); + [[nodiscard]] bool emitThisEnvironmentCallee(); + [[nodiscard]] bool emitSuperBase(); + + uint32_t mainOffset() const { return *mainOffset_; } + + bool inPrologue() const { return mainOffset_.isNothing(); } + + void switchToMain() { + MOZ_ASSERT(inPrologue()); + mainOffset_.emplace(bytecodeSection().code().length()); + } + + void setFunctionBodyEndPos(uint32_t pos) { + functionBodyEndPos = mozilla::Some(pos); + } + + void setScriptStartOffsetIfUnset(uint32_t pos) { + if (scriptStartOffset.isNothing()) { + scriptStartOffset = mozilla::Some(pos); + } + } + + void reportError(ParseNode* pn, unsigned errorNumber, ...); + void reportError(uint32_t offset, unsigned errorNumber, ...); + + // Fill in a ScriptStencil using this BCE data. + bool intoScriptStencil(ScriptIndex scriptIndex); + + // If pn contains a useful expression, return true with *answer set to true. + // If pn contains a useless expression, return true with *answer set to + // false. Return false on error. + // + // The caller should initialize *answer to false and invoke this function on + // an expression statement or similar subtree to decide whether the tree + // could produce code that has any side effects. For an expression + // statement, we define useless code as code with no side effects, because + // the main effect, the value left on the stack after the code executes, + // will be discarded by a pop bytecode. + [[nodiscard]] bool checkSideEffects(ParseNode* pn, bool* answer); + +#ifdef DEBUG + [[nodiscard]] bool checkStrictOrSloppy(JSOp op); +#endif + + // Add TryNote to the tryNoteList array. The start and end offset are + // relative to current section. + [[nodiscard]] bool addTryNote(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end); + + // Indicates the emitter should not generate location or debugger source + // notes. This lets us avoid generating notes for non-user code. + bool skipLocationSrcNotes() const { + return inPrologue() || suppressBreakpointsAndSourceNotes; + } + bool skipBreakpointSrcNotes() const { + return inPrologue() || suppressBreakpointsAndSourceNotes; + } + + // Append a new source note of the given type (and therefore size) to the + // notes dynamic array, updating noteCount. Return the new note's index + // within the array pointed at by current->notes as outparam. + [[nodiscard]] bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); + [[nodiscard]] bool newSrcNote2(SrcNoteType type, ptrdiff_t operand, + unsigned* indexp = nullptr); + + [[nodiscard]] bool newSrcNoteOperand(ptrdiff_t operand); + + // Control whether emitTree emits a line number note. + enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE }; + + // Emit code for the tree rooted at pn. + [[nodiscard]] bool emitTree(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue, + EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + + [[nodiscard]] bool emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage = ValueUsage::WantValue); + + [[nodiscard]] bool emitDeclarationInstantiation(ParseNode* body); + + // Emit global, eval, or module code for tree rooted at body. Always + // encompasses the entire source. + [[nodiscard]] bool emitScript(ParseNode* body); + + // Calculate the `nslots` value for BCEScriptStencil constructor parameter. + // Fails if it overflows. + [[nodiscard]] bool getNslots(uint32_t* nslots); + + // Emit function code for the tree rooted at body. + [[nodiscard]] bool emitFunctionScript(FunctionNode* funNode); + + [[nodiscard]] bool markStepBreakpoint(); + [[nodiscard]] bool markSimpleBreakpoint(); + [[nodiscard]] bool updateLineNumberNotes(uint32_t offset); + [[nodiscard]] bool updateSourceCoordNotes(uint32_t offset); + + JSOp strictifySetNameOp(JSOp op); + + [[nodiscard]] bool emitCheck(JSOp op, ptrdiff_t delta, + BytecodeOffset* offset); + + // Emit one bytecode. + [[nodiscard]] bool emit1(JSOp op); + + // Emit two bytecodes, an opcode (op) with a byte of immediate operand + // (op1). + [[nodiscard]] bool emit2(JSOp op, uint8_t op1); + + // Emit three bytecodes, an opcode with two bytes of immediate operands. + [[nodiscard]] bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); + + // Helper to duplicate one or more stack values. |slotFromTop| is the value's + // depth on the JS stack, as measured from the top. |count| is the number of + // values to duplicate, in theiro original order. + [[nodiscard]] bool emitDupAt(unsigned slotFromTop, unsigned count = 1); + + // Helper to emit JSOp::Pop or JSOp::PopN. + [[nodiscard]] bool emitPopN(unsigned n); + + // Helper to emit JSOp::Swap or JSOp::Pick. + [[nodiscard]] bool emitPickN(uint8_t n); + + // Helper to emit JSOp::Swap or JSOp::Unpick. + [[nodiscard]] bool emitUnpickN(uint8_t n); + + // Helper to emit JSOp::CheckIsObj. + [[nodiscard]] bool emitCheckIsObj(CheckIsObjectKind kind); + + // Helper to emit JSOp::BuiltinObject. + [[nodiscard]] bool emitBuiltinObject(BuiltinObjectKind kind); + + // Emit a bytecode followed by an uint16 immediate operand stored in + // big-endian order. + [[nodiscard]] bool emitUint16Operand(JSOp op, uint32_t operand); + + // Emit a bytecode followed by an uint32 immediate operand. + [[nodiscard]] bool emitUint32Operand(JSOp op, uint32_t operand); + + // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. + [[nodiscard]] bool emitN(JSOp op, size_t extra, + BytecodeOffset* offset = nullptr); + + [[nodiscard]] bool emitDouble(double dval); + [[nodiscard]] bool emitNumberOp(double dval); + + [[nodiscard]] bool emitBigIntOp(BigIntLiteral* bigint); + + [[nodiscard]] bool emitThisLiteral(ThisLiteral* pn); + [[nodiscard]] bool emitGetFunctionThis(NameNode* thisName); + [[nodiscard]] bool emitGetThisForSuperBase(UnaryNode* superBase); + [[nodiscard]] bool emitSetThis(BinaryNode* setThisNode); + [[nodiscard]] bool emitCheckDerivedClassConstructorReturn(); + + private: + [[nodiscard]] bool emitNewTarget(); + + public: + [[nodiscard]] bool emitNewTarget(NewTargetNode* pn); + [[nodiscard]] bool emitNewTarget(CallNode* pn); + + // Handle jump opcodes and jump targets. + [[nodiscard]] bool emitJumpTargetOp(JSOp op, BytecodeOffset* off); + [[nodiscard]] bool emitJumpTarget(JumpTarget* target); + [[nodiscard]] bool emitJumpNoFallthrough(JSOp op, JumpList* jump); + [[nodiscard]] bool emitJump(JSOp op, JumpList* jump); + void patchJumpsToTarget(JumpList jump, JumpTarget target); + [[nodiscard]] bool emitJumpTargetAndPatch(JumpList jump); + + [[nodiscard]] bool emitCall( + JSOp op, uint16_t argc, + const mozilla::Maybe<uint32_t>& sourceCoordOffset); + [[nodiscard]] bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); + [[nodiscard]] bool emitCallIncDec(UnaryNode* incDec); + + uint32_t getOffsetForLoop(ParseNode* nextpn); + + enum class GotoKind { Break, Continue }; + [[nodiscard]] bool emitGoto(NestableControl* target, GotoKind kind); + + [[nodiscard]] bool emitGCIndexOp(JSOp op, GCThingIndex index); + + [[nodiscard]] bool emitAtomOp(JSOp op, TaggedParserAtomIndex atom); + [[nodiscard]] bool emitAtomOp(JSOp op, GCThingIndex atomIndex); + + [[nodiscard]] bool emitStringOp(JSOp op, TaggedParserAtomIndex atom); + [[nodiscard]] bool emitStringOp(JSOp op, GCThingIndex atomIndex); + + [[nodiscard]] bool emitArrayLiteral(ListNode* array); + [[nodiscard]] bool emitArray(ListNode* array); + [[nodiscard]] bool emitSpreadIntoArray(UnaryNode* elem); + + [[nodiscard]] bool emitInternedScopeOp(GCThingIndex index, JSOp op); + [[nodiscard]] bool emitInternedObjectOp(GCThingIndex index, JSOp op); + [[nodiscard]] bool emitRegExp(GCThingIndex index); + + [[nodiscard]] MOZ_NEVER_INLINE bool emitFunction(FunctionNode* funNode, + bool needsProto = false); + [[nodiscard]] MOZ_NEVER_INLINE bool emitObject(ListNode* objNode); + + [[nodiscard]] bool emitHoistedFunctionsInList(ListNode* stmtList); + + // Can we use the object-literal writer either in singleton-object mode (with + // values) or in template mode (field names only, no values) for the property + // list? + void isPropertyListObjLiteralCompatible(ListNode* obj, bool* withValues, + bool* withoutValues); + bool isArrayObjLiteralCompatible(ListNode* array); + + [[nodiscard]] bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type); + + [[nodiscard]] bool emitPropertyListObjLiteral(ListNode* obj, JSOp op, + bool useObjLiteralValues); + + [[nodiscard]] bool emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern); + + [[nodiscard]] bool emitObjLiteralArray(ListNode* array); + + // Is a field value OBJLITERAL-compatible? + [[nodiscard]] bool isRHSObjLiteralCompatible(ParseNode* value); + + [[nodiscard]] bool emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value); + + mozilla::Maybe<MemberInitializers> setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement); + [[nodiscard]] bool emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement); + [[nodiscard]] bool emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement); + const MemberInitializers& findMemberInitializersForCall(); + [[nodiscard]] bool emitInitializeInstanceMembers( + bool isDerivedClassConstructor); + [[nodiscard]] bool emitInitializeStaticFields(ListNode* classMembers); + + [[nodiscard]] bool emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj); + [[nodiscard]] bool emitPrivateMethodInitializer( + ClassMethod* classMethod, TaggedParserAtomIndex storedMethodAtom); + + // To catch accidental misuse, emitUint16Operand/emit3 assert that they are + // not used to unconditionally emit JSOp::GetLocal. Variable access should + // instead be emitted using EmitVarOp. In special cases, when the caller + // definitely knows that a given local slot is unaliased, this function may be + // used as a non-asserting version of emitUint16Operand. + [[nodiscard]] bool emitLocalOp(JSOp op, uint32_t slot); + + [[nodiscard]] bool emitArgOp(JSOp op, uint16_t slot); + [[nodiscard]] bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec); + + [[nodiscard]] bool emitGetNameAtLocation(TaggedParserAtomIndex name, + const NameLocation& loc); + [[nodiscard]] bool emitGetName(TaggedParserAtomIndex name) { + return emitGetNameAtLocation(name, lookupName(name)); + } + [[nodiscard]] bool emitGetName(NameNode* name); + [[nodiscard]] bool emitGetPrivateName(NameNode* name); + [[nodiscard]] bool emitGetPrivateName(TaggedParserAtomIndex name); + + [[nodiscard]] bool emitTDZCheckIfNeeded(TaggedParserAtomIndex name, + const NameLocation& loc, + ValueIsOnStack isOnStack); + + [[nodiscard]] bool emitNameIncDec(UnaryNode* incDec, ValueUsage valueUsage); + + [[nodiscard]] bool emitDeclarationList(ListNode* declList); + [[nodiscard]] bool emitSingleDeclaration(ListNode* declList, NameNode* decl, + ParseNode* initializer); + [[nodiscard]] bool emitAssignmentRhs(ParseNode* rhs, + TaggedParserAtomIndex anonFunctionName); + [[nodiscard]] bool emitAssignmentRhs(uint8_t offset); + + [[nodiscard]] bool emitPrepareIteratorResult(); + [[nodiscard]] bool emitFinishIteratorResult(bool done); + + // Convert and add `writer` data to stencil. + // Iff it suceeds, `outIndex` out parameter is initialized to the index of the + // object in GC things vector. + [[nodiscard]] bool addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex); + + [[nodiscard]] bool emitGetDotGeneratorInInnermostScope() { + return emitGetDotGeneratorInScope(*innermostEmitterScope()); + } + [[nodiscard]] bool emitGetDotGeneratorInScope(EmitterScope& currentScope); + + [[nodiscard]] bool allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex); + [[nodiscard]] bool allocateResumeIndexRange( + mozilla::Span<BytecodeOffset> offsets, uint32_t* firstResumeIndex); + + [[nodiscard]] bool emitInitialYield(UnaryNode* yieldNode); + [[nodiscard]] bool emitYield(UnaryNode* yieldNode); + [[nodiscard]] bool emitYieldOp(JSOp op); + [[nodiscard]] bool emitYieldStar(ParseNode* iter); + [[nodiscard]] bool emitAwaitInInnermostScope() { + return emitAwaitInScope(*innermostEmitterScope()); + } + [[nodiscard]] bool emitAwaitInInnermostScope(UnaryNode* awaitNode); + [[nodiscard]] bool emitAwaitInScope(EmitterScope& currentScope); + + [[nodiscard]] bool emitPushResumeKind(GeneratorResumeKind kind); + + [[nodiscard]] bool emitPropLHS(PropertyAccess* prop); + [[nodiscard]] bool emitPropIncDec(UnaryNode* incDec, ValueUsage valueUsage); + + [[nodiscard]] bool emitComputedPropertyName(UnaryNode* computedPropName); + + [[nodiscard]] bool emitObjAndKey(ParseNode* exprOrSuper, ParseNode* key, + ElemOpEmitter& eoe); + + // Emit bytecode to put operands for a JSOp::GetElem/CallElem/SetElem/DelElem + // opcode onto the stack in the right order. In the case of SetElem, the + // value to be assigned must already be pushed. + enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref }; + [[nodiscard]] bool emitElemOperands(PropertyByValue* elem, + EmitElemOption opts); + + [[nodiscard]] bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe); + [[nodiscard]] bool emitElemOpBase(JSOp op); + + [[nodiscard]] bool emitElemIncDec(UnaryNode* incDec, ValueUsage valueUsage); + [[nodiscard]] bool emitObjAndPrivateName(PrivateMemberAccess* elem, + ElemOpEmitter& eoe); + [[nodiscard]] bool emitPrivateIncDec(UnaryNode* incDec, + ValueUsage valueUsage); + + [[nodiscard]] bool emitCatch(BinaryNode* catchClause); + [[nodiscard]] bool emitIf(TernaryNode* ifNode); + [[nodiscard]] bool emitWith(BinaryNode* withNode); + + [[nodiscard]] MOZ_NEVER_INLINE bool emitLabeledStatement( + const LabeledStatement* labeledStmt); + [[nodiscard]] MOZ_NEVER_INLINE bool emitLexicalScope( + LexicalScopeNode* lexicalScope); + [[nodiscard]] bool emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + [[nodiscard]] MOZ_NEVER_INLINE bool emitSwitch(SwitchStatement* switchStmt); + [[nodiscard]] MOZ_NEVER_INLINE bool emitTry(TryNode* tryNode); + + [[nodiscard]] bool emitJumpToFinally(JumpList* jump, uint32_t idx); + + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + [[nodiscard]] bool emitDestructuringLHSRef(ParseNode* target, + size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + [[nodiscard]] bool emitSetOrInitializeDestructuring(ParseNode* target, + DestructuringFlavor flav); + + // emitDestructuringObjRestExclusionSet emits the property exclusion set + // for the rest-property in an object pattern. + [[nodiscard]] bool emitDestructuringObjRestExclusionSet(ListNode* pattern); + + // emitDestructuringOps assumes the to-be-destructured value has been + // pushed on the stack and emits code to destructure each part of a [] or + // {} lhs expression. + [[nodiscard]] bool emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav); + [[nodiscard]] bool emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav); + [[nodiscard]] bool emitDestructuringOpsObject(ListNode* pattern, + DestructuringFlavor flav); + + enum class CopyOption { Filtered, Unfiltered }; + + // Calls either the |CopyDataProperties| or the + // |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or + // two in the latter case) elements from the stack. + [[nodiscard]] bool emitCopyDataProperties(CopyOption option); + + JSOp getIterCallOp(JSOp callOp, SelfHostedIter selfHostedIter); + + // emitIterator expects the iterable to already be on the stack. + // It will replace that stack value with the corresponding iterator + [[nodiscard]] bool emitIterator( + SelfHostedIter selfHostedIter = SelfHostedIter::Deny, + bool isIteratorMethodOnStack = false); + + [[nodiscard]] bool emitAsyncIterator( + SelfHostedIter selfHostedIter = SelfHostedIter::Deny, + bool isIteratorMethodOnStack = false); + + // Pops iterator from the top of the stack. Pushes the result of |.next()| + // onto the stack. + [[nodiscard]] bool emitIteratorNext( + const mozilla::Maybe<uint32_t>& callSourceCoordOffset, + IteratorKind kind = IteratorKind::Sync, + SelfHostedIter selfHostedIter = SelfHostedIter::Deny); + [[nodiscard]] bool emitIteratorCloseInScope( + EmitterScope& currentScope, IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + SelfHostedIter selfHostedIter = SelfHostedIter::Deny); + [[nodiscard]] bool emitIteratorCloseInInnermostScope( + IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + SelfHostedIter selfHostedIter = SelfHostedIter::Deny) { + return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind, + completionKind, selfHostedIter); + } + + template <typename InnerEmitter> + [[nodiscard]] bool wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter); + + [[nodiscard]] bool defineHoistedTopLevelFunctions(ParseNode* body); + + // Check if the value on top of the stack is "undefined". If so, replace + // that value on the stack with the value defined by |defaultExpr|. + // |pattern| is a lhs node of the default expression. If it's an + // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| + // is called at compile time. + [[nodiscard]] bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + [[nodiscard]] bool emitAnonymousFunctionWithName(ParseNode* node, + TaggedParserAtomIndex name); + + [[nodiscard]] bool emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind); + + [[nodiscard]] bool setFunName(FunctionBox* fun, TaggedParserAtomIndex name); + [[nodiscard]] bool emitInitializer(ParseNode* initializer, + ParseNode* pattern); + + [[nodiscard]] bool emitCallSiteObjectArray(ObjLiteralWriter& writer, + ListNode* cookedOrRaw, + ParseNode* head, uint32_t count); + [[nodiscard]] bool emitCallSiteObject(CallSiteNode* callSiteObj); + [[nodiscard]] bool emitTemplateString(ListNode* templateString); + [[nodiscard]] bool emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs); + [[nodiscard]] bool emitShortCircuitAssignment(AssignmentNode* node); + + [[nodiscard]] bool emitReturn(UnaryNode* returnNode); + [[nodiscard]] bool finishReturn(BytecodeOffset setRvalOffset); + + [[nodiscard]] bool emitExpressionStatement(UnaryNode* exprStmt); + [[nodiscard]] bool emitStatementList(ListNode* stmtList); + + [[nodiscard]] bool emitDeleteName(UnaryNode* deleteNode); + [[nodiscard]] bool emitDeleteProperty(UnaryNode* deleteNode); + [[nodiscard]] bool emitDeleteElement(UnaryNode* deleteNode); + [[nodiscard]] bool emitDeleteExpression(UnaryNode* deleteNode); + + // Optional methods which emit Optional Jump Target + [[nodiscard]] bool emitOptionalChain(UnaryNode* expr, ValueUsage valueUsage); + [[nodiscard]] bool emitCalleeAndThisForOptionalChain(UnaryNode* expr, + CallNode* callNode, + CallOrNewEmitter& cone); + [[nodiscard]] bool emitDeleteOptionalChain(UnaryNode* deleteNode); + + // Optional methods which emit a shortCircuit jump. They need to be called by + // a method which emits an Optional Jump Target, see below. + [[nodiscard]] bool emitOptionalDotExpression(PropertyAccessBase* expr, + PropOpEmitter& poe, bool isSuper, + OptionalEmitter& oe); + [[nodiscard]] bool emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& eoe, + bool isSuper, + OptionalEmitter& oe); + [[nodiscard]] bool emitOptionalPrivateExpression( + PrivateMemberAccessBase* privateExpr, PrivateOpEmitter& xoe, + OptionalEmitter& oe); + [[nodiscard]] bool emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage); + [[nodiscard]] bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe); + [[nodiscard]] bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe); + + // |op| must be JSOp::Typeof or JSOp::TypeofExpr. + [[nodiscard]] bool emitTypeof(UnaryNode* typeofNode, JSOp op); + + [[nodiscard]] bool emitUnary(UnaryNode* unaryNode); + [[nodiscard]] bool emitRightAssociative(ListNode* node); + [[nodiscard]] bool emitLeftAssociative(ListNode* node); + [[nodiscard]] bool emitPrivateInExpr(ListNode* node); + [[nodiscard]] bool emitShortCircuit(ListNode* node, ValueUsage valueUsage); + [[nodiscard]] bool emitSequenceExpr(ListNode* node, ValueUsage valueUsage); + + [[nodiscard]] MOZ_NEVER_INLINE bool emitIncOrDec(UnaryNode* incDec, + ValueUsage valueUsage); + + [[nodiscard]] bool emitConditionalExpression( + ConditionalExpression& conditional, ValueUsage valueUsage); + + [[nodiscard]] ParseNode* getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList); + + [[nodiscard]] bool emitArguments(ListNode* argsList, bool isCall, + bool isSpread, CallOrNewEmitter& cone); + [[nodiscard]] bool emitCallOrNew(CallNode* callNode, ValueUsage valueUsage); + [[nodiscard]] bool emitDebugCheckSelfHosted(); + [[nodiscard]] bool emitSelfHostedCallFunction(CallNode* callNode, JSOp op); + [[nodiscard]] bool emitSelfHostedResumeGenerator(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedForceInterpreter(); + [[nodiscard]] bool emitSelfHostedAllowContentIter(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedAllowContentIterWith(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedDefineDataProperty(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetPropertySuper(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedHasOwn(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedToNumeric(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedToString(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedIsNullOrUndefined(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetBuiltinConstructor(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetBuiltinPrototype(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetBuiltinSymbol(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedSetIsInlinableLargeFunction( + CallNode* callNode); + [[nodiscard]] bool emitSelfHostedSetCanonicalName(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedArgumentsLength(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetArgument(CallNode* callNode); +#ifdef DEBUG + void assertSelfHostedExpectedTopLevel(ParseNode* node); + void assertSelfHostedUnsafeGetReservedSlot(ListNode* argsList); + void assertSelfHostedUnsafeSetReservedSlot(ListNode* argsList); +#endif + + [[nodiscard]] bool emitDo(BinaryNode* doNode); + [[nodiscard]] bool emitWhile(BinaryNode* whileNode); + + [[nodiscard]] bool emitFor( + ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + [[nodiscard]] bool emitCStyleFor(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + [[nodiscard]] bool emitForIn(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + [[nodiscard]] bool emitForOf(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + + [[nodiscard]] bool emitInitializeForInOrOfTarget(TernaryNode* forHead); + + [[nodiscard]] bool emitBreak(TaggedParserAtomIndex label); + [[nodiscard]] bool emitContinue(TaggedParserAtomIndex label); + + [[nodiscard]] bool emitFunctionFormalParameters(ParamsBodyNode* paramsBody); + [[nodiscard]] bool emitInitializeFunctionSpecialNames(); + [[nodiscard]] bool emitLexicalInitialization(NameNode* name); + [[nodiscard]] bool emitLexicalInitialization(TaggedParserAtomIndex name); + + // Emit bytecode for the spread operator. + // + // emitSpread expects some values representing the spread target (an array or + // a tuple), the iterator and it's next() method to be on the stack in that + // order (iterator's next() on the bottom). + // The number of values representing the spread target is + // `spreadeeStackItems`: it's 2 for arrays (one for the array and one for the + // index) and 1 for tuples (the tuple itself). + // Since arrays and tuples use different opcodes to initialize new elements, + // it must be specified using `storeElementOp`. + // When emitSpread() finishes, the stack only contains the values representing + // the spread target. + [[nodiscard]] bool emitSpread(SelfHostedIter selfHostedIter, + int spreadeeStackItems, JSOp storeElementOp); + // This shortcut can be used when spreading into arrays, as it assumes + // `spreadeeStackItems = 2` (|ARRAY INDEX|) and `storeElementOp = + // JSOp::InitElemInc` + [[nodiscard]] bool emitSpread( + SelfHostedIter selfHostedIter = SelfHostedIter::Deny); + + enum class ClassNameKind { + // The class name is defined through its BindingIdentifier, if present. + BindingName, + + // The class is anonymous and has a statically inferred name. + InferredName, + + // The class is anonymous and has a dynamically computed name. + ComputedName + }; + + [[nodiscard]] bool emitClass( + ClassNode* classNode, ClassNameKind nameKind = ClassNameKind::BindingName, + TaggedParserAtomIndex nameForAnonymousClass = + TaggedParserAtomIndex::null()); + + [[nodiscard]] bool emitSuperElemOperands( + PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get); + [[nodiscard]] bool emitSuperGetElem(PropertyByValue* elem, + bool isCall = false); + + [[nodiscard]] bool emitCalleeAndThis(ParseNode* callee, ParseNode* call, + CallOrNewEmitter& cone); + + [[nodiscard]] bool emitOptionalCalleeAndThis(ParseNode* callee, + CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe); + +#ifdef ENABLE_RECORD_TUPLE + [[nodiscard]] bool emitRecordLiteral(ListNode* record); + [[nodiscard]] bool emitTupleLiteral(ListNode* tuple); +#endif + + [[nodiscard]] bool emitExportDefault(BinaryNode* exportNode); + + [[nodiscard]] bool emitReturnRval() { return emit1(JSOp::RetRval); } + + [[nodiscard]] bool emitCheckPrivateField(ThrowCondition throwCondition, + ThrowMsgKind msgKind) { + return emit3(JSOp::CheckPrivateField, uint8_t(throwCondition), + uint8_t(msgKind)); + } + + [[nodiscard]] bool emitNewPrivateName(TaggedParserAtomIndex bindingName, + TaggedParserAtomIndex symbolName); + + template <class ClassMemberType> + [[nodiscard]] bool emitNewPrivateNames(ListNode* classMembers); + + [[nodiscard]] bool emitNewPrivateNames(TaggedParserAtomIndex privateBrandName, + ListNode* classMembers); + + [[nodiscard]] js::UniquePtr<ImmutableScriptData> createImmutableScriptData(); + + private: + [[nodiscard]] SelfHostedIter getSelfHostedIterFor(ParseNode* parseNode); + + [[nodiscard]] bool emitSelfHostedGetBuiltinConstructorOrPrototype( + CallNode* callNode, bool isConstructor); + + public: +#if defined(DEBUG) || defined(JS_JITSPEW) + void dumpAtom(TaggedParserAtomIndex index) const; +#endif +}; + +class MOZ_RAII AutoCheckUnstableEmitterScope { +#ifdef DEBUG + bool prev_; + BytecodeEmitter* bce_; +#endif + + public: + AutoCheckUnstableEmitterScope() = delete; + explicit AutoCheckUnstableEmitterScope(BytecodeEmitter* bce) +#ifdef DEBUG + : bce_(bce) +#endif + { +#ifdef DEBUG + prev_ = bce_->unstableEmitterScope; + bce_->unstableEmitterScope = true; +#endif + } + ~AutoCheckUnstableEmitterScope() { +#ifdef DEBUG + bce_->unstableEmitterScope = prev_; +#endif + } +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeEmitter_h */ |