/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef frontend_Stencil_h
#define frontend_Stencil_h

#include "mozilla/Assertions.h"       // MOZ_ASSERT
#include "mozilla/Maybe.h"            // mozilla::{Maybe, Nothing}
#include "mozilla/MemoryReporting.h"  // mozilla::MallocSizeOf
#include "mozilla/Span.h"             // mozilla::Span
#include "mozilla/Variant.h"          // mozilla::Variant

#include <stddef.h>  // size_t
#include <stdint.h>  // char16_t, uint8_t, uint16_t, uint32_t

#include "frontend/AbstractScopePtr.h"  // AbstractScopePtr, ScopeIndex
#include "frontend/ObjLiteral.h"        // ObjLiteralStencil
#include "frontend/ParserAtom.h"        // TaggedParserAtomIndex
#include "frontend/ScriptIndex.h"       // ScriptIndex
#include "frontend/TaggedParserAtomIndexHasher.h"  // frontend::TaggedParserAtomIndexHasher
#include "frontend/TypedIndex.h"                   // TypedIndex
#include "js/AllocPolicy.h"                        // SystemAllocPolicy
#include "js/ColumnNumber.h"                       // JS::ColumnNumberOneOrigin
#include "js/RefCounted.h"                         // AtomicRefCounted
#include "js/RegExpFlags.h"                        // JS::RegExpFlags
#include "js/RootingAPI.h"                         // Handle
#include "js/TypeDecls.h"                          // JSContext
#include "js/UniquePtr.h"                          // js::UniquePtr
#include "js/Utility.h"                            // UniqueTwoByteChars
#include "js/Vector.h"                             // js::Vector
#include "vm/FunctionFlags.h"                      // FunctionFlags
#include "vm/Scope.h"  // Scope, BaseScopeData, FunctionScope, LexicalScope, VarScope, GlobalScope, EvalScope, ModuleScope
#include "vm/ScopeKind.h"      // ScopeKind
#include "vm/SharedStencil.h"  // ImmutableScriptFlags, GCThingIndex, js::SharedImmutableScriptData, MemberInitializers, SourceExtent

namespace js {

class LifoAlloc;
class JSONPrinter;
class RegExpObject;

namespace frontend {

struct CompilationInput;
struct CompilationStencil;
struct CompilationAtomCache;
struct CompilationGCOutput;
struct CompilationStencilMerger;
class RegExpStencil;
class BigIntStencil;
class StencilXDR;

using BaseParserScopeData = AbstractBaseScopeData<TaggedParserAtomIndex>;
using ParserBindingName = AbstractBindingName<TaggedParserAtomIndex>;

template <typename Scope>
using ParserScopeSlotInfo = typename Scope::SlotInfo;
using ParserGlobalScopeSlotInfo = ParserScopeSlotInfo<GlobalScope>;
using ParserEvalScopeSlotInfo = ParserScopeSlotInfo<EvalScope>;
using ParserLexicalScopeSlotInfo = ParserScopeSlotInfo<LexicalScope>;
using ParserClassBodyScopeSlotInfo = ParserScopeSlotInfo<ClassBodyScope>;
using ParserFunctionScopeSlotInfo = ParserScopeSlotInfo<FunctionScope>;
using ParserModuleScopeSlotInfo = ParserScopeSlotInfo<ModuleScope>;
using ParserVarScopeSlotInfo = ParserScopeSlotInfo<VarScope>;

using ParserBindingIter = AbstractBindingIter<TaggedParserAtomIndex>;
using ParserPositionalFormalParameterIter =
    AbstractPositionalFormalParameterIter<TaggedParserAtomIndex>;

// [SMDOC] Script Stencil (Frontend Representation)
//
// Stencils are the set of data structures capturing the result of parsing and
// bytecode emission. The Stencil format is a precursor format that is then used
// to allocate the corresponding scripts on the GC heap that will be used for
// execution. By decoupling from the GC and other runtime systems, robust
// caching and speculation systems can be built that are more thread-agnostic
// and flexible.
//
// See: https://bugzil.la/stencil
//
// There are numerous data structures that make up the Stencil format. The
// structures are designed for fast serialization to and from disk by preferring
// indices over pointers and vectors instead of graphs to allow bulk operations.
//
//
// ParserAtom
// ----------
// Our parser relies on atomized strings as part of its normal operations and so
// a `ParserAtom` type exists that mirrors the `JSAtom` type but does not
// involve the garbage collector. This is possible because the lifetime of these
// ParserAtoms is the lifetime of the Stencil that makes use of them and we do
// not need finer grained collection.
//
//
// ScriptStencil
// -------------
// The key structures generated by parsing are instances of `ScriptStencil`.
// There is a `ScriptStencil` for the top-level script and for each inner
// function. It contains information needed to create the `JSFunction` (if it is
// a function) and the `BaseScript` (if not asm.js) and may or may not have
// bytecode. Each `ScriptStencil` may also reference the following Stencil types
// (similar to the `BaseScript::gcthings()` list):
//
//   * ParserAtom
//   * ScopeStencil
//   * RegExpStencil
//   * BigIntStencil
//   * ObjLiteralStencil
//   * StencilModuleMetadata
//
//
// CompilationStencil / ExtensibleCompilationStencil
// -------------------------------------------------
// Parsing a single JavaScript file may generate a tree of `ScriptStencil` that
// we then package up into the `ExtensibleCompilationStencil` type or
// `CompilationStencil`. They contain a series of vectors/spans segregated by
// data type for fast processing (a.k.a Data Oriented Design).
//
// `ExtensibleCompilationStencil` is mutable type used during parsing, and
// can be allocated either on stack or heap.
// `ExtensibleCompilationStencil` holds vectors of stencils.
//
// `CompilationStencil` is immutable type used for caching the compilation
// result, and is allocated on heap with refcount.
// `CompilationStencil` holds spans of stencils, and it can point either
// owned data or borrowed data.
// The borrowed data can be from other `ExtensibleCompilationStencil` or
// from serialized stencil (XDR) on memory or mmap.
//
// Delazifying a function will generate its bytecode but some fields remain
// unchanged from the initial lazy parse.
// When we delazify a function that was lazily parsed, we generate a new
// Stencil at the point too. These delazifications can be merged into the
// Stencil of the initial parse by using `CompilationStencilMerger`.
//
// Conversion from ExtensibleCompilationStencil to CompilationStencil
// ------------------------------------------------------------------
// There are multiple ways to convert from `ExtensibleCompilationStencil` to
// `CompilationStencil`:
//
// 1. Temporarily borrow `ExtensibleCompilationStencil` content and call
// function that takes `CompilationStencil` reference, and keep using the
// `ExtensibleCompilationStencil` after that:
//
//   ExtensibleCompilationStencil extensible = ...;
//   {
//     BorrowingCompilationStencil stencil(extensible);
//     // Use `stencil reference.
//   }
//
// 2. Take the ownership of an on-heap ExtensibleCompilationStencil. This makes
// the `CompilationStencil` self-contained and it's useful for caching:
//
//   UniquePtr<ExtensibleCompilationStencil> extensible = ...;
//   CompilationStencil stencil(std::move(extensible));
//
// Conversion from CompilationStencil to ExtensibleCompilationStencil
// ------------------------------------------------------------------
// In the same way, there are multiple ways to convert from
// `CompilationStencil` to `ExtensibleCompilationStencil`:
//
// 1. Take the ownership of `CompilationStencil`'s underlying data, Only when
// stencil owns the data and the refcount is 1:
//
//   RefPtr<CompilationStencil> stencil = ...;
//   ExtensibleCompilationStencil extensible(...);
//   // NOTE: This is equivalent to cloneFrom below if `stencil` has refcount
//   //       more than 2, or it doesn't own the data.
//   extensible.steal(fc, std::move(stencil));
//
// 2. Clone the underlying data. This is slow but safe operation:
//
//   CompilationStencil stencil = ...;
//   ExtensibleCompilationStencil extensible(...);
//   extensible.cloneFrom(fc, stencil);
//
// 3. Take the ownership back from the `CompilationStencil` which is created by
// taking the ownership of an on-heap `ExtensibleCompilationStencil`:
//
//   CompilationStencil stencil = ...;
//   ExtensibleCompilationStencil* extensible = stencil.takeOwnedBorrow();
//
// CompilationGCOutput
// -------------------
// When a Stencil is instantiated the equivalent script objects are allocated on
// the GC heap and their pointers are collected into the `CompilationGCOutput`
// structure. This is only used temporarily during instantiation.
//
//
// CompilationState
// ----------------
// This is another temporary structure used by the parser while the Stencil is
// being generated. Once the `CompilationStencil` is complete, this can be
// released.

// Typed indices for the different stencil elements in the compilation result.
using RegExpIndex = TypedIndex<RegExpStencil>;
using BigIntIndex = TypedIndex<BigIntStencil>;
using ObjLiteralIndex = TypedIndex<ObjLiteralStencil>;

// Index into {ExtensibleCompilationStencil,CompilationStencil}.gcThingData.
class CompilationGCThingType {};
using CompilationGCThingIndex = TypedIndex<CompilationGCThingType>;

// A syntax-checked regular expression string.
class RegExpStencil {
  friend class StencilXDR;

  TaggedParserAtomIndex atom_;
  // Use uint32_t to make this struct fully-packed.
  uint32_t flags_;

  friend struct CompilationStencilMerger;

 public:
  RegExpStencil() = default;

  RegExpStencil(TaggedParserAtomIndex atom, JS::RegExpFlags flags)
      : atom_(atom), flags_(flags.value()) {}

  JS::RegExpFlags flags() const { return JS::RegExpFlags(flags_); }

  RegExpObject* createRegExp(JSContext* cx,
                             const CompilationAtomCache& atomCache) const;

  // This is used by `Reflect.parse` when we need the RegExpObject but are not
  // doing a complete instantiation of the CompilationStencil.
  RegExpObject* createRegExpAndEnsureAtom(
      JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms,
      CompilationAtomCache& atomCache) const;

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;
  void dump(JSONPrinter& json, const CompilationStencil* stencil) const;
  void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const;
#endif
};

// This owns a set of characters guaranteed to parse into a BigInt via
// ParseBigIntLiteral. Used to avoid allocating the BigInt on the
// GC heap during parsing.
class BigIntStencil {
  friend class StencilXDR;

  // Source of the BigInt literal.
  // It's not null-terminated, and also trailing 'n' suffix is not included.
  //
  // Int64-sized BigInt values are directly stored inline as int64_t.
  mozilla::Variant<mozilla::Span<char16_t>, int64_t> bigInt_{int64_t{}};

  // Methods used by XDR.
  mozilla::Span<char16_t>& source() {
    if (bigInt_.is<int64_t>()) {
      bigInt_ = mozilla::AsVariant(mozilla::Span<char16_t>{});
    }
    return bigInt_.as<mozilla::Span<char16_t>>();
  }
  const mozilla::Span<char16_t>& source() const {
    return bigInt_.as<mozilla::Span<char16_t>>();
  }

  [[nodiscard]] bool initFromChars(FrontendContext* fc, LifoAlloc& alloc,
                                   mozilla::Span<const char16_t> buf);

 public:
  BigIntStencil() = default;

  [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc,
                          mozilla::Span<const char16_t> buf);

  [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc,
                          const BigIntStencil& other);

  BigInt* createBigInt(JSContext* cx) const;

  // Methods used by constant-folding.
  bool isZero() const;
  bool inplaceNegate();
  bool inplaceBitNot();

#ifdef DEBUG
  bool isContainedIn(const LifoAlloc& alloc) const;
#endif

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;
  void dump(JSONPrinter& json) const;
  void dumpCharsNoQuote(GenericPrinter& out) const;
#endif
};

using BigIntStencilVector = Vector<BigIntStencil, 0, js::SystemAllocPolicy>;

class ScopeStencil {
  friend class StencilXDR;
  friend class InputScope;
  friend class AbstractBindingIter<frontend::TaggedParserAtomIndex>;
  friend struct CompilationStencil;
  friend struct CompilationStencilMerger;

  // The enclosing scope. Valid only if HasEnclosing flag is set.
  // compilation applies.
  ScopeIndex enclosing_;

  // First frame slot to use, or LOCALNO_LIMIT if none are allowed.
  uint32_t firstFrameSlot_ = UINT32_MAX;

  // The number of environment shape's slots.  Valid only if
  // HasEnvironmentShape flag is set.
  uint32_t numEnvironmentSlots_;

  // Canonical function if this is a FunctionScope. Valid only if
  // kind_ is ScopeKind::Function.
  ScriptIndex functionIndex_;

  // The kind determines the corresponding BaseParserScopeData.
  ScopeKind kind_{UINT8_MAX};

  // True if this scope has enclosing scope stencil. Otherwise, the enclosing
  // scope will be read from CompilationInput while instantiating. Self-hosting
  // is a special case and will use `emptyGlobalScope` when there is no
  // enclosing scope stencil.
  static constexpr uint8_t HasEnclosing = 1 << 0;

  // If true, an environment Shape must be created. The shape itself may
  // have no slots if the environment may be extensible later.
  static constexpr uint8_t HasEnvironmentShape = 1 << 1;

  // True if this is a FunctionScope for an arrow function.
  static constexpr uint8_t IsArrow = 1 << 2;

  uint8_t flags_ = 0;

  // To make this struct packed, add explicit field for padding.
  uint16_t padding_ = 0;

 public:
  // For XDR only.
  ScopeStencil() = default;

  ScopeStencil(ScopeKind kind, mozilla::Maybe<ScopeIndex> enclosing,
               uint32_t firstFrameSlot,
               mozilla::Maybe<uint32_t> numEnvironmentSlots,
               mozilla::Maybe<ScriptIndex> functionIndex = mozilla::Nothing(),
               bool isArrow = false)
      : enclosing_(enclosing.valueOr(ScopeIndex(0))),
        firstFrameSlot_(firstFrameSlot),
        numEnvironmentSlots_(numEnvironmentSlots.valueOr(0)),
        functionIndex_(functionIndex.valueOr(ScriptIndex(0))),
        kind_(kind),
        flags_((enclosing.isSome() ? HasEnclosing : 0) |
               (numEnvironmentSlots.isSome() ? HasEnvironmentShape : 0) |
               (isArrow ? IsArrow : 0)) {
    MOZ_ASSERT((kind == ScopeKind::Function) == functionIndex.isSome());
    // Silence -Wunused-private-field warnings.
    (void)padding_;
  }

 private:
  // Create ScopeStencil with `args`, and append ScopeStencil and `data` to
  // `compilationState`, and return the index of them as `indexOut`.
  template <typename... Args>
  static bool appendScopeStencilAndData(FrontendContext* fc,
                                        CompilationState& compilationState,
                                        BaseParserScopeData* data,
                                        ScopeIndex* indexOut, Args&&... args);

 public:
  static bool createForFunctionScope(
      FrontendContext* fc, CompilationState& compilationState,
      FunctionScope::ParserData* dataArg, bool hasParameterExprs,
      bool needsEnvironment, ScriptIndex functionIndex, bool isArrow,
      mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);

  static bool createForLexicalScope(
      FrontendContext* fc, CompilationState& compilationState, ScopeKind kind,
      LexicalScope::ParserData* dataArg, uint32_t firstFrameSlot,
      mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);

  static bool createForClassBodyScope(
      FrontendContext* fc, CompilationState& compilationState, ScopeKind kind,
      ClassBodyScope::ParserData* dataArg, uint32_t firstFrameSlot,
      mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);

  static bool createForVarScope(FrontendContext* fc,
                                CompilationState& compilationState,
                                ScopeKind kind, VarScope::ParserData* dataArg,
                                uint32_t firstFrameSlot, bool needsEnvironment,
                                mozilla::Maybe<ScopeIndex> enclosing,
                                ScopeIndex* index);

  static bool createForGlobalScope(FrontendContext* fc,
                                   CompilationState& compilationState,
                                   ScopeKind kind,
                                   GlobalScope::ParserData* dataArg,
                                   ScopeIndex* index);

  static bool createForEvalScope(FrontendContext* fc,
                                 CompilationState& compilationState,
                                 ScopeKind kind, EvalScope::ParserData* dataArg,
                                 mozilla::Maybe<ScopeIndex> enclosing,
                                 ScopeIndex* index);

  static bool createForModuleScope(FrontendContext* fc,
                                   CompilationState& compilationState,
                                   ModuleScope::ParserData* dataArg,
                                   mozilla::Maybe<ScopeIndex> enclosing,
                                   ScopeIndex* index);

  static bool createForWithScope(FrontendContext* fc,
                                 CompilationState& compilationState,
                                 mozilla::Maybe<ScopeIndex> enclosing,
                                 ScopeIndex* index);

  AbstractScopePtr enclosing(CompilationState& compilationState) const;
  js::Scope* enclosingExistingScope(const CompilationInput& input,
                                    const CompilationGCOutput& gcOutput) const;

 private:
  bool hasEnclosing() const { return flags_ & HasEnclosing; }

  ScopeIndex enclosing() const {
    MOZ_ASSERT(hasEnclosing());
    return enclosing_;
  }

  uint32_t firstFrameSlot() const { return firstFrameSlot_; }

  bool hasEnvironmentShape() const { return flags_ & HasEnvironmentShape; }

  uint32_t numEnvironmentSlots() const {
    MOZ_ASSERT(hasEnvironmentShape());
    return numEnvironmentSlots_;
  }

  bool isFunction() const { return kind_ == ScopeKind::Function; }

  ScriptIndex functionIndex() const { return functionIndex_; }

 public:
  ScopeKind kind() const { return kind_; }

  bool hasEnvironment() const {
    // Check if scope kind alone means we have an env shape, and
    // otherwise check if we have one created.
    return Scope::hasEnvironment(kind(), hasEnvironmentShape());
  }

  bool isArrow() const { return flags_ & IsArrow; }

  Scope* createScope(JSContext* cx, CompilationInput& input,
                     CompilationGCOutput& gcOutput,
                     BaseParserScopeData* baseScopeData) const;
  Scope* createScope(JSContext* cx, CompilationAtomCache& atomCache,
                     Handle<Scope*> enclosingScope,
                     BaseParserScopeData* baseScopeData) const;

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;
  void dump(JSONPrinter& json, const BaseParserScopeData* baseScopeData,
            const CompilationStencil* stencil) const;
  void dumpFields(JSONPrinter& json, const BaseParserScopeData* baseScopeData,
                  const CompilationStencil* stencil) const;
#endif

 private:
  // Transfer ownership into a new UniquePtr.
  template <typename SpecificScopeType>
  UniquePtr<typename SpecificScopeType::RuntimeData> createSpecificScopeData(
      JSContext* cx, CompilationAtomCache& atomCache,
      BaseParserScopeData* baseData) const;

  template <typename SpecificEnvironmentType>
  [[nodiscard]] bool createSpecificShape(
      JSContext* cx, ScopeKind kind, BaseScopeData* scopeData,
      MutableHandle<SharedShape*> shape) const;

  template <typename SpecificScopeType, typename SpecificEnvironmentType>
  Scope* createSpecificScope(JSContext* cx, CompilationAtomCache& atomCache,
                             Handle<Scope*> enclosingScope,
                             BaseParserScopeData* baseData) const;

  template <typename ScopeT>
  static constexpr bool matchScopeKind(ScopeKind kind) {
    switch (kind) {
      case ScopeKind::Function: {
        return std::is_same_v<ScopeT, FunctionScope>;
      }
      case ScopeKind::Lexical:
      case ScopeKind::SimpleCatch:
      case ScopeKind::Catch:
      case ScopeKind::NamedLambda:
      case ScopeKind::StrictNamedLambda:
      case ScopeKind::FunctionLexical: {
        return std::is_same_v<ScopeT, LexicalScope>;
      }
      case ScopeKind::ClassBody: {
        return std::is_same_v<ScopeT, ClassBodyScope>;
      }
      case ScopeKind::FunctionBodyVar: {
        return std::is_same_v<ScopeT, VarScope>;
      }
      case ScopeKind::Global:
      case ScopeKind::NonSyntactic: {
        return std::is_same_v<ScopeT, GlobalScope>;
      }
      case ScopeKind::Eval:
      case ScopeKind::StrictEval: {
        return std::is_same_v<ScopeT, EvalScope>;
      }
      case ScopeKind::Module: {
        return std::is_same_v<ScopeT, ModuleScope>;
      }
      case ScopeKind::With: {
        return std::is_same_v<ScopeT, WithScope>;
      }
      case ScopeKind::WasmFunction:
      case ScopeKind::WasmInstance: {
        return false;
      }
    }
    return false;
  }
};

class StencilModuleImportAttribute {
 public:
  TaggedParserAtomIndex key;
  TaggedParserAtomIndex value;

  StencilModuleImportAttribute() = default;
  StencilModuleImportAttribute(TaggedParserAtomIndex key,
                               TaggedParserAtomIndex value)
      : key(key), value(value) {}

  bool operator!=(const StencilModuleImportAttribute& rhs) const {
    return key != rhs.key || value != rhs.value;
  }

  bool operator==(const StencilModuleImportAttribute& rhs) const {
    return !(*this != rhs);
  }
};

class StencilModuleRequest {
 public:
  TaggedParserAtomIndex specifier;
  TaggedParserAtomIndex firstUnsupportedAttributeKey;

  using ImportAttributeVector =
      Vector<StencilModuleImportAttribute, 0, js::SystemAllocPolicy>;
  ImportAttributeVector attributes;

  // For XDR only.
  StencilModuleRequest() = default;

  explicit StencilModuleRequest(TaggedParserAtomIndex specifier)
      : specifier(specifier) {
    MOZ_ASSERT(specifier);
  }

  StencilModuleRequest(const StencilModuleRequest& other)
      : specifier(other.specifier),
        firstUnsupportedAttributeKey(other.firstUnsupportedAttributeKey) {
    AutoEnterOOMUnsafeRegion oomUnsafe;
    if (!attributes.appendAll(other.attributes)) {
      oomUnsafe.crash("StencilModuleRequest::StencilModuleRequest");
    }
  }

  StencilModuleRequest(StencilModuleRequest&& other) noexcept
      : specifier(other.specifier),
        firstUnsupportedAttributeKey(other.firstUnsupportedAttributeKey),
        attributes(std::move(other.attributes)) {}

  StencilModuleRequest& operator=(StencilModuleRequest& other) {
    specifier = other.specifier;
    firstUnsupportedAttributeKey = other.firstUnsupportedAttributeKey;
    attributes = std::move(other.attributes);
    return *this;
  }

  StencilModuleRequest& operator=(StencilModuleRequest&& other) noexcept {
    specifier = other.specifier;
    firstUnsupportedAttributeKey = other.firstUnsupportedAttributeKey;
    attributes = std::move(other.attributes);
    return *this;
  }

  bool operator==(const StencilModuleRequest& other) const {
    size_t attrLen = attributes.length();
    if (specifier != other.specifier ||
        firstUnsupportedAttributeKey != other.firstUnsupportedAttributeKey ||
        attrLen != other.attributes.length()) {
      return false;
    }

    for (size_t i = 0; i < attrLen; i++) {
      if (attributes[i] != other.attributes[i]) {
        return false;
      }
    }

    return true;
  }

  bool operator!=(const StencilModuleRequest& other) const {
    return !(*this == other);
  }
};

struct StencilModuleRequestHasher {
  using Key = js::frontend::StencilModuleRequest;
  using Lookup = Key;

  static HashNumber hash(const Lookup& l) {
    HashNumber hash = 0;
    size_t attrLen = l.attributes.length();
    for (size_t i = 0; i < attrLen; i++) {
      hash = mozilla::AddToHash(
          hash, TaggedParserAtomIndexHasher::hash(l.attributes[i].key),
          TaggedParserAtomIndexHasher::hash(l.attributes[i].value));
    }
    return mozilla::AddToHash(hash,
                              TaggedParserAtomIndexHasher::hash(l.specifier));
  }

  static bool match(const Key& k, const Lookup& l) { return k == l; }
};

class MaybeModuleRequestIndex {
  static constexpr uint32_t NOTHING = UINT32_MAX;

  uint32_t bits = NOTHING;

 public:
  MaybeModuleRequestIndex() = default;
  explicit MaybeModuleRequestIndex(uint32_t index) : bits(index) {
    MOZ_ASSERT(isSome());
  }

  MaybeModuleRequestIndex(const MaybeModuleRequestIndex& other) = default;
  MaybeModuleRequestIndex& operator=(const MaybeModuleRequestIndex& other) =
      default;

  bool isNothing() const { return bits == NOTHING; }
  bool isSome() const { return !isNothing(); }
  explicit operator bool() const { return isSome(); }

  uint32_t value() const {
    MOZ_ASSERT(isSome());
    return bits;
  }

  uint32_t* operator&() { return &bits; }
};

// Common type for ImportEntry / ExportEntry / ModuleRequest within frontend. We
// use a shared stencil class type to simplify serialization.
//
// https://tc39.es/ecma262/#importentry-record
// https://tc39.es/ecma262/#exportentry-record
//
// Note: We subdivide the spec's ExportEntry into ExportAs / ExportFrom forms
//       for readability.
class StencilModuleEntry {
 public:
  // clang-format off
  //
  //               | RequestedModule | ImportEntry | ImportNamespaceEntry | ExportAs | ExportFrom | ExportNamespaceFrom | ExportBatchFrom |
  //               |----------------------------------------------------------------------------------------------------------------------|
  // moduleRequest | required        | required    | required             | null     | required   | required            | required        |
  // localName     | null            | required    | required             | required | null       | null                | null            |
  // importName    | null            | required    | null                 | null     | required   | null                | null            |
  // exportName    | null            | null        | null                 | required | required   | required            | null            |
  //
  // clang-format on
  MaybeModuleRequestIndex moduleRequest;
  TaggedParserAtomIndex localName;
  TaggedParserAtomIndex importName;
  TaggedParserAtomIndex exportName;

  // Location used for error messages. If this is for a module request entry
  // then it is the module specifier string, otherwise the import/export spec
  // that failed. Exports may not fill these fields if an error cannot be
  // generated such as `export let x;`.

  // Line number (1-origin).
  uint32_t lineno = 0;

  // Column number in UTF-16 code units.
  JS::ColumnNumberOneOrigin column;

 private:
  StencilModuleEntry(uint32_t lineno, JS::ColumnNumberOneOrigin column)
      : lineno(lineno), column(column) {}

 public:
  // For XDR only.
  StencilModuleEntry() = default;

  StencilModuleEntry(const StencilModuleEntry& other)
      : moduleRequest(other.moduleRequest),
        localName(other.localName),
        importName(other.importName),
        exportName(other.exportName),
        lineno(other.lineno),
        column(other.column) {}

  StencilModuleEntry(StencilModuleEntry&& other) noexcept
      : moduleRequest(other.moduleRequest),
        localName(other.localName),
        importName(other.importName),
        exportName(other.exportName),
        lineno(other.lineno),
        column(other.column) {}

  StencilModuleEntry& operator=(StencilModuleEntry& other) {
    moduleRequest = other.moduleRequest;
    localName = other.localName;
    importName = other.importName;
    exportName = other.exportName;
    lineno = other.lineno;
    column = other.column;
    return *this;
  }

  StencilModuleEntry& operator=(StencilModuleEntry&& other) noexcept {
    moduleRequest = other.moduleRequest;
    localName = other.localName;
    importName = other.importName;
    exportName = other.exportName;
    lineno = other.lineno;
    column = other.column;
    return *this;
  }

  static StencilModuleEntry requestedModule(
      MaybeModuleRequestIndex moduleRequest, uint32_t lineno,
      JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(moduleRequest.isSome());
    StencilModuleEntry entry(lineno, column);
    entry.moduleRequest = moduleRequest;
    return entry;
  }

  static StencilModuleEntry importEntry(MaybeModuleRequestIndex moduleRequest,
                                        TaggedParserAtomIndex localName,
                                        TaggedParserAtomIndex importName,
                                        uint32_t lineno,
                                        JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(moduleRequest.isSome());
    MOZ_ASSERT(localName && importName);
    StencilModuleEntry entry(lineno, column);
    entry.moduleRequest = moduleRequest;
    entry.localName = localName;
    entry.importName = importName;
    return entry;
  }

  static StencilModuleEntry importNamespaceEntry(
      MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex localName,
      uint32_t lineno, JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(moduleRequest.isSome());
    MOZ_ASSERT(localName);
    StencilModuleEntry entry(lineno, column);
    entry.moduleRequest = moduleRequest;
    entry.localName = localName;
    return entry;
  }

  static StencilModuleEntry exportAsEntry(TaggedParserAtomIndex localName,
                                          TaggedParserAtomIndex exportName,
                                          uint32_t lineno,
                                          JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(localName && exportName);
    StencilModuleEntry entry(lineno, column);
    entry.localName = localName;
    entry.exportName = exportName;
    return entry;
  }

  static StencilModuleEntry exportFromEntry(
      MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex importName,
      TaggedParserAtomIndex exportName, uint32_t lineno,
      JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(moduleRequest.isSome());
    MOZ_ASSERT(importName && exportName);
    StencilModuleEntry entry(lineno, column);
    entry.moduleRequest = moduleRequest;
    entry.importName = importName;
    entry.exportName = exportName;
    return entry;
  }

  static StencilModuleEntry exportNamespaceFromEntry(
      MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex exportName,
      uint32_t lineno, JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(moduleRequest.isSome());
    MOZ_ASSERT(exportName);
    StencilModuleEntry entry(lineno, column);
    entry.moduleRequest = MaybeModuleRequestIndex(moduleRequest);
    entry.exportName = exportName;
    return entry;
  }

  static StencilModuleEntry exportBatchFromEntry(
      MaybeModuleRequestIndex moduleRequest, uint32_t lineno,
      JS::ColumnNumberOneOrigin column) {
    MOZ_ASSERT(moduleRequest.isSome());
    StencilModuleEntry entry(lineno, column);
    entry.moduleRequest = MaybeModuleRequestIndex(moduleRequest);
    return entry;
  }
};

// Metadata generated by parsing module scripts, including import/export tables.
class StencilModuleMetadata
    : public js::AtomicRefCounted<StencilModuleMetadata> {
 public:
  using RequestVector = Vector<StencilModuleRequest, 0, js::SystemAllocPolicy>;
  using EntryVector = Vector<StencilModuleEntry, 0, js::SystemAllocPolicy>;

  RequestVector moduleRequests;
  EntryVector requestedModules;
  EntryVector importEntries;
  EntryVector localExportEntries;
  EntryVector indirectExportEntries;
  EntryVector starExportEntries;
  FunctionDeclarationVector functionDecls;
  // Set to true if the module has a top-level await keyword.
  bool isAsync = false;

  StencilModuleMetadata() = default;

  bool initModule(JSContext* cx, FrontendContext* fc,
                  CompilationAtomCache& atomCache,
                  JS::Handle<ModuleObject*> module) const;

  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
    return mallocSizeOf(this) +
           requestedModules.sizeOfExcludingThis(mallocSizeOf) +
           importEntries.sizeOfExcludingThis(mallocSizeOf) +
           localExportEntries.sizeOfExcludingThis(mallocSizeOf) +
           indirectExportEntries.sizeOfExcludingThis(mallocSizeOf) +
           starExportEntries.sizeOfExcludingThis(mallocSizeOf) +
           functionDecls.sizeOfExcludingThis(mallocSizeOf);
  }

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;
  void dump(JSONPrinter& json, const CompilationStencil* stencil) const;
  void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const;
#endif

 private:
  bool createModuleRequestObjects(
      JSContext* cx, CompilationAtomCache& atomCache,
      MutableHandle<ModuleRequestVector> output) const;
  bool createRequestedModules(
      JSContext* cx, CompilationAtomCache& atomCache,
      Handle<ModuleRequestVector> moduleRequests,
      MutableHandle<RequestedModuleVector> output) const;
  bool createImportEntries(JSContext* cx, CompilationAtomCache& atomCache,
                           Handle<ModuleRequestVector> moduleRequests,
                           MutableHandle<ImportEntryVector> output) const;
  bool createExportEntries(JSContext* cx, CompilationAtomCache& atomCache,
                           Handle<ModuleRequestVector> moduleRequests,
                           const EntryVector& input,
                           MutableHandle<ExportEntryVector> output) const;
  ModuleRequestObject* createModuleRequestObject(
      JSContext* cx, CompilationAtomCache& atomCache,
      const StencilModuleRequest& request) const;
};

// As an alternative to a ScopeIndex (which references a ScopeStencil), we may
// instead refer to an existing scope from GlobalObject::emptyGlobalScope().
//
// NOTE: This is only used for the self-hosting global.
class EmptyGlobalScopeType {};

// Things pointed by this index all end up being baked into GC things as part
// of stencil instantiation.
//
// 0x0000_0000  Null
// 0x1YYY_YYYY  28-bit ParserAtom
// 0x2YYY_YYYY  Well-known/static atom (See TaggedParserAtomIndex)
// 0x3YYY_YYYY  28-bit BigInt
// 0x4YYY_YYYY  28-bit ObjLiteral
// 0x5YYY_YYYY  28-bit RegExp
// 0x6YYY_YYYY  28-bit Scope
// 0x7YYY_YYYY  28-bit Function
// 0x8000_0000  EmptyGlobalScope
class TaggedScriptThingIndex {
  uint32_t data_;

  static constexpr size_t IndexBit = TaggedParserAtomIndex::IndexBit;
  static constexpr size_t IndexMask = TaggedParserAtomIndex::IndexMask;

  static constexpr size_t TagShift = TaggedParserAtomIndex::TagShift;
  static constexpr size_t TagBit = TaggedParserAtomIndex::TagBit;
  static constexpr size_t TagMask = TaggedParserAtomIndex::TagMask;

 public:
  enum class Kind : uint32_t {
    Null = uint32_t(TaggedParserAtomIndex::Kind::Null),
    ParserAtomIndex = uint32_t(TaggedParserAtomIndex::Kind::ParserAtomIndex),
    WellKnown = uint32_t(TaggedParserAtomIndex::Kind::WellKnown),
    BigInt,
    ObjLiteral,
    RegExp,
    Scope,
    Function,
    EmptyGlobalScope,
  };

 private:
  static constexpr uint32_t NullTag = uint32_t(Kind::Null) << TagShift;
  static_assert(NullTag == TaggedParserAtomIndex::NullTag);
  static constexpr uint32_t ParserAtomIndexTag = uint32_t(Kind::ParserAtomIndex)
                                                 << TagShift;
  static_assert(ParserAtomIndexTag ==
                TaggedParserAtomIndex::ParserAtomIndexTag);
  static constexpr uint32_t WellKnownTag = uint32_t(Kind::WellKnown)
                                           << TagShift;
  static_assert(WellKnownTag == TaggedParserAtomIndex::WellKnownTag);

  static constexpr uint32_t BigIntTag = uint32_t(Kind::BigInt) << TagShift;
  static constexpr uint32_t ObjLiteralTag = uint32_t(Kind::ObjLiteral)
                                            << TagShift;
  static constexpr uint32_t RegExpTag = uint32_t(Kind::RegExp) << TagShift;
  static constexpr uint32_t ScopeTag = uint32_t(Kind::Scope) << TagShift;
  static constexpr uint32_t FunctionTag = uint32_t(Kind::Function) << TagShift;
  static constexpr uint32_t EmptyGlobalScopeTag =
      uint32_t(Kind::EmptyGlobalScope) << TagShift;

 public:
  static constexpr uint32_t IndexLimit = Bit(IndexBit);

  TaggedScriptThingIndex() : data_(NullTag) {}

  explicit TaggedScriptThingIndex(TaggedParserAtomIndex index)
      : data_(index.rawData()) {}
  explicit TaggedScriptThingIndex(BigIntIndex index)
      : data_(uint32_t(index) | BigIntTag) {
    MOZ_ASSERT(uint32_t(index) < IndexLimit);
  }
  explicit TaggedScriptThingIndex(ObjLiteralIndex index)
      : data_(uint32_t(index) | ObjLiteralTag) {
    MOZ_ASSERT(uint32_t(index) < IndexLimit);
  }
  explicit TaggedScriptThingIndex(RegExpIndex index)
      : data_(uint32_t(index) | RegExpTag) {
    MOZ_ASSERT(uint32_t(index) < IndexLimit);
  }
  explicit TaggedScriptThingIndex(ScopeIndex index)
      : data_(uint32_t(index) | ScopeTag) {
    MOZ_ASSERT(uint32_t(index) < IndexLimit);
  }
  explicit TaggedScriptThingIndex(ScriptIndex index)
      : data_(uint32_t(index) | FunctionTag) {
    MOZ_ASSERT(uint32_t(index) < IndexLimit);
  }
  explicit TaggedScriptThingIndex(EmptyGlobalScopeType t)
      : data_(EmptyGlobalScopeTag) {}

  bool isAtom() const {
    return (data_ & TagMask) == ParserAtomIndexTag ||
           (data_ & TagMask) == WellKnownTag;
  }
  bool isNull() const {
    bool result = !data_;
    MOZ_ASSERT_IF(result, (data_ & TagMask) == NullTag);
    return result;
  }
  bool isBigInt() const { return (data_ & TagMask) == BigIntTag; }
  bool isObjLiteral() const { return (data_ & TagMask) == ObjLiteralTag; }
  bool isRegExp() const { return (data_ & TagMask) == RegExpTag; }
  bool isScope() const { return (data_ & TagMask) == ScopeTag; }
  bool isFunction() const { return (data_ & TagMask) == FunctionTag; }
  bool isEmptyGlobalScope() const {
    return (data_ & TagMask) == EmptyGlobalScopeTag;
  }

  TaggedParserAtomIndex toAtom() const {
    MOZ_ASSERT(isAtom());
    return TaggedParserAtomIndex::fromRaw(data_);
  }
  BigIntIndex toBigInt() const { return BigIntIndex(data_ & IndexMask); }
  ObjLiteralIndex toObjLiteral() const {
    return ObjLiteralIndex(data_ & IndexMask);
  }
  RegExpIndex toRegExp() const { return RegExpIndex(data_ & IndexMask); }
  ScopeIndex toScope() const { return ScopeIndex(data_ & IndexMask); }
  ScriptIndex toFunction() const { return ScriptIndex(data_ & IndexMask); }

  TaggedParserAtomIndex toAtomOrNull() const {
    MOZ_ASSERT(isAtom() || isNull());
    return TaggedParserAtomIndex::fromRaw(data_);
  }

  uint32_t* rawDataRef() { return &data_; }
  uint32_t rawData() const { return data_; }

  Kind tag() const { return Kind((data_ & TagMask) >> TagShift); }

  bool operator==(const TaggedScriptThingIndex& rhs) const {
    return data_ == rhs.data_;
  }
};

// Data generated by frontend that will be used to create a js::BaseScript.
class ScriptStencil {
  friend struct CompilationStencilMerger;

 public:
  // Fields for BaseScript.
  // Used by:
  //   * Global script
  //   * Eval
  //   * Module
  //   * non-lazy Function (except asm.js module)
  //   * lazy Function (cannot be asm.js module)

  // GCThings are stored into
  // {ExtensibleCompilationStencil,CompilationStencil}.gcThingData,
  // in [gcThingsOffset, gcThingsOffset + gcThingsLength) range.
  CompilationGCThingIndex gcThingsOffset;
  uint32_t gcThingsLength = 0;

  // Fields for JSFunction.
  // Used by:
  //   * non-lazy Function
  //   * lazy Function
  //   * asm.js module

  // The explicit or implicit name of the function. The FunctionFlags indicate
  // the kind of name.
  TaggedParserAtomIndex functionAtom;

  // If this ScriptStencil refers to a lazy child of the function being
  // compiled, this field holds the child's immediately enclosing scope's index.
  // Once compilation succeeds, we will store the scope pointed by this in the
  // child's BaseScript.  (Debugger may become confused if lazy scripts refer to
  // partially initialized enclosing scopes, so we must avoid storing the
  // scope in the BaseScript until compilation has completed
  // successfully.)
  //
  // OR
  //
  // This may be used for self-hosting canonical name (TaggedParserAtomIndex).
  TaggedScriptThingIndex enclosingScopeOrCanonicalName;

  // See: `FunctionFlags`.
  FunctionFlags functionFlags = {};

  // This is set by the BytecodeEmitter of the enclosing script when a reference
  // to this function is generated.
  static constexpr uint16_t WasEmittedByEnclosingScriptFlag = 1 << 0;

  // If this is for the root of delazification, this represents
  // MutableScriptFlagsEnum::AllowRelazify value of the script *after*
  // delazification.
  // False otherwise.
  static constexpr uint16_t AllowRelazifyFlag = 1 << 1;

  // Set if this is non-lazy script and shared data is created.
  // The shared data is stored into CompilationStencil.sharedData.
  static constexpr uint16_t HasSharedDataFlag = 1 << 2;

  // True if this script is lazy function and has enclosing scope.  In that
  // case, `enclosingScopeOrCanonicalName` will hold the ScopeIndex.
  static constexpr uint16_t HasLazyFunctionEnclosingScopeIndexFlag = 1 << 3;

  // True if this script is a self-hosted function with a canonical name
  // explicitly set. In that case, `enclosingScopeOrCanonicalName` will hold the
  // TaggedParserAtomIndex.
  static constexpr uint16_t HasSelfHostedCanonicalName = 1 << 4;

  uint16_t flags_ = 0;

  // End of fields.

  ScriptStencil() = default;

  bool isFunction() const {
    bool result = functionFlags.toRaw() != 0x0000;
    MOZ_ASSERT_IF(
        result, functionFlags.isAsmJSNative() || functionFlags.hasBaseScript());
    return result;
  }

  bool hasGCThings() const { return gcThingsLength; }

  mozilla::Span<TaggedScriptThingIndex> gcthings(
      const CompilationStencil& stencil) const;

  bool wasEmittedByEnclosingScript() const {
    return flags_ & WasEmittedByEnclosingScriptFlag;
  }

  void setWasEmittedByEnclosingScript() {
    flags_ |= WasEmittedByEnclosingScriptFlag;
  }

  bool allowRelazify() const { return flags_ & AllowRelazifyFlag; }

  void setAllowRelazify() { flags_ |= AllowRelazifyFlag; }

  bool isGhost() const { return functionFlags.isGhost(); }
  void setIsGhost() { functionFlags.setIsGhost(); }

  bool hasSharedData() const { return flags_ & HasSharedDataFlag; }

  void setHasSharedData() { flags_ |= HasSharedDataFlag; }

  bool hasLazyFunctionEnclosingScopeIndex() const {
    return flags_ & HasLazyFunctionEnclosingScopeIndexFlag;
  }

  bool hasSelfHostedCanonicalName() const {
    return flags_ & HasSelfHostedCanonicalName;
  }

 private:
  void setHasLazyFunctionEnclosingScopeIndex() {
    flags_ |= HasLazyFunctionEnclosingScopeIndexFlag;
  }

  void setHasSelfHostedCanonicalName() { flags_ |= HasSelfHostedCanonicalName; }

 public:
  void setLazyFunctionEnclosingScopeIndex(ScopeIndex index) {
    MOZ_ASSERT(enclosingScopeOrCanonicalName.isNull());
    enclosingScopeOrCanonicalName = TaggedScriptThingIndex(index);
    setHasLazyFunctionEnclosingScopeIndex();
  }

  void resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge() {
    flags_ &= ~HasLazyFunctionEnclosingScopeIndexFlag;
    enclosingScopeOrCanonicalName = TaggedScriptThingIndex();
  }

  ScopeIndex lazyFunctionEnclosingScopeIndex() const {
    MOZ_ASSERT(hasLazyFunctionEnclosingScopeIndex());
    return enclosingScopeOrCanonicalName.toScope();
  }

  void setSelfHostedCanonicalName(TaggedParserAtomIndex name) {
    MOZ_ASSERT(enclosingScopeOrCanonicalName.isNull());
    enclosingScopeOrCanonicalName = TaggedScriptThingIndex(name);
    setHasSelfHostedCanonicalName();
  }

  TaggedParserAtomIndex selfHostedCanonicalName() const {
    MOZ_ASSERT(hasSelfHostedCanonicalName());
    return enclosingScopeOrCanonicalName.toAtom();
  }

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;
  void dump(JSONPrinter& json, const CompilationStencil* stencil) const;
  void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const;
#endif
};

// In addition to ScriptStencil, data generated only while initial-parsing.
class ScriptStencilExtra {
 public:
  // See `BaseScript::immutableFlags_`.
  ImmutableScriptFlags immutableFlags;

  // The location of this script in the source.
  SourceExtent extent;

  // See `PrivateScriptData::memberInitializers_`.
  // This data only valid when `UseMemberInitializers` script flag is true.
  uint32_t memberInitializers_ = 0;

  // See `JSFunction::nargs_`.
  uint16_t nargs = 0;

  // To make this struct packed, add explicit field for padding.
  uint16_t padding_ = 0;

  ScriptStencilExtra() = default;

  RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags)

  void setMemberInitializers(MemberInitializers member) {
    MOZ_ASSERT(useMemberInitializers());
    memberInitializers_ = member.serialize();
  }

  MemberInitializers memberInitializers() const {
    MOZ_ASSERT(useMemberInitializers());
    return MemberInitializers::deserialize(memberInitializers_);
  }

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dump() const;
  void dump(JSONPrinter& json) const;
  void dumpFields(JSONPrinter& json) const;
#endif
};

#if defined(DEBUG) || defined(JS_JITSPEW)
void DumpTaggedParserAtomIndex(js::JSONPrinter& json,
                               TaggedParserAtomIndex taggedIndex,
                               const CompilationStencil* stencil);

void DumpTaggedParserAtomIndexNoQuote(GenericPrinter& out,
                                      TaggedParserAtomIndex taggedIndex,
                                      const CompilationStencil* stencil);
#endif

} /* namespace frontend */

#if defined(DEBUG) || defined(JS_JITSPEW)
void DumpImmutableScriptFlags(js::JSONPrinter& json,
                              ImmutableScriptFlags immutableFlags);
void DumpFunctionFlagsItems(js::JSONPrinter& json, FunctionFlags functionFlags);
#endif

} /* namespace js */

#endif /* frontend_Stencil_h */
