/* -*- 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
#ifdef ENABLE_DECORATORS
                                                  ,
                                                  bool hasHeritage
#endif
  );
  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);

  // Push the operands for emit(Async)Iterator onto the stack.
  [[nodiscard]] bool emitIterable(ParseNode* value,
                                  SelfHostedIter selfHostedIter,
                                  IteratorKind iterKind = IteratorKind::Sync);

  // 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);

  [[nodiscard]] bool emitAsyncIterator(SelfHostedIter selfHostedIter);

  // 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 emitSelfHostedAllowContentIterWithNext(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 emitSelfHostedIteratorClose(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);

  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, CallNode* 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 */
