/* -*- 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 // ptrdiff_t #include // 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 mainOffset_ = {}; // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be // initialized. mozilla::Maybe 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 scriptStartOffset = {}; // The end location of a function body that is being emitted. mozilla::Maybe 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 BytecodeEmitter(FrontendContext* fc, Parser* parser, SharedContext* sc, CompilationState& compilationState, EmitterMode emitterMode = Normal) : BytecodeEmitter(fc, EitherParser(parser), sc, compilationState, emitterMode) {} [[nodiscard]] bool init(); [[nodiscard]] bool init(TokenPos bodyPosition); template T* findInnermostNestableControl() const; template 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& 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 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 mozilla::Maybe 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 locationOfNameBoundInFunctionScope( TaggedParserAtomIndex name) { return locationOfNameBoundInScopeType( 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& 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 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 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& 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 [[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 [[nodiscard]] bool emitNewPrivateNames(ListNode* classMembers); [[nodiscard]] bool emitNewPrivateNames(TaggedParserAtomIndex privateBrandName, ListNode* classMembers); [[nodiscard]] js::UniquePtr 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 */