/* -*- 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/. */

#include "frontend/Stencil.h"

#include "mozilla/AlreadyAddRefed.h"        // already_AddRefed
#include "mozilla/Assertions.h"             // MOZ_RELEASE_ASSERT
#include "mozilla/CheckedInt.h"             // mozilla::CheckedInt
#include "mozilla/Maybe.h"                  // mozilla::Maybe
#include "mozilla/OperatorNewExtensions.h"  // mozilla::KnownNotNull
#include "mozilla/PodOperations.h"          // mozilla::PodCopy
#include "mozilla/RefPtr.h"                 // RefPtr
#include "mozilla/ScopeExit.h"              // mozilla::ScopeExit
#include "mozilla/Sprintf.h"                // SprintfLiteral

#include <algorithm>  // std::fill
#include <string.h>   // strlen

#include "ds/LifoAlloc.h"               // LifoAlloc
#include "frontend/AbstractScopePtr.h"  // ScopeIndex
#include "frontend/BytecodeCompiler.h"  // CompileGlobalScriptToStencil, InstantiateStencils, CanLazilyParse, ParseModuleToStencil
#include "frontend/BytecodeSection.h"   // EmitScriptThingsVector
#include "frontend/CompilationStencil.h"  // CompilationStencil, CompilationState, ExtensibleCompilationStencil, CompilationGCOutput, CompilationStencilMerger
#include "frontend/FrontendContext.h"
#include "frontend/NameAnalysisTypes.h"  // EnvironmentCoordinate
#include "frontend/ParserAtom.h"  // ParserAtom, ParserAtomIndex, TaggedParserAtomIndex, ParserAtomsTable, Length{1,2,3}StaticParserString, InstantiateMarkedAtoms, InstantiateMarkedAtomsAsPermanent, GetWellKnownAtom
#include "frontend/ScopeBindingCache.h"  // ScopeBindingCache
#include "frontend/SharedContext.h"
#include "frontend/StencilXdr.h"  // XDRStencilEncoder, XDRStencilDecoder
#include "gc/AllocKind.h"         // gc::AllocKind
#include "gc/Tracer.h"            // TraceNullableRoot
#include "jit/BaselineJIT.h"      // jit::BaselineScript
#include "jit/JitRuntime.h"       // jit::JitRuntime
#include "jit/JitScript.h"        // AutoKeepJitScripts
#include "js/CallArgs.h"          // JSNative
#include "js/CompileOptions.h"  // JS::DecodeOptions, JS::ReadOnlyDecodeOptions
#include "js/experimental/CompileScript.h"  // JS::PrepareForInstantiate
#include "js/experimental/JSStencil.h"      // JS::Stencil
#include "js/GCAPI.h"                       // JS::AutoCheckCannotGC
#include "js/Prefs.h"                       // JS::Prefs
#include "js/Printer.h"                     // js::Fprinter
#include "js/RealmOptions.h"                // JS::RealmBehaviors
#include "js/RootingAPI.h"                  // Rooted
#include "js/Transcoding.h"                 // JS::TranscodeBuffer
#include "js/Utility.h"                     // js_malloc, js_calloc, js_free
#include "js/Value.h"                       // ObjectValue
#include "js/WasmModule.h"                  // JS::WasmModule
#include "vm/BigIntType.h"   // ParseBigIntLiteral, BigInt::createFromInt64
#include "vm/BindingKind.h"  // BindingKind
#include "vm/EnvironmentObject.h"
#include "vm/GeneratorAndAsyncKind.h"  // GeneratorKind, FunctionAsyncKind
#include "vm/JSAtomUtils.h"            // AtomToPrintableString
#include "vm/JSContext.h"              // JSContext
#include "vm/JSFunction.h"  // JSFunction, GetFunctionPrototype, NewFunctionWithProto
#include "vm/JSObject.h"      // JSObject, TenuredObject
#include "vm/JSONPrinter.h"   // js::JSONPrinter
#include "vm/JSScript.h"      // BaseScript, JSScript
#include "vm/Realm.h"         // JS::Realm
#include "vm/RegExpObject.h"  // js::RegExpObject
#include "vm/Scope.h"  // Scope, *Scope, ScopeKind::*, ScopeKindString, ScopeIter, ScopeKindIsCatch, BindingIter, GetScopeDataTrailingNames, SizeOfParserScopeData
#include "vm/ScopeKind.h"    // ScopeKind
#include "vm/SelfHosting.h"  // SetClonedSelfHostedFunctionName
#include "vm/StaticStrings.h"
#include "vm/StencilEnums.h"  // ImmutableScriptFlagsEnum
#include "vm/StringType.h"    // JSAtom, js::CopyChars
#include "wasm/AsmJS.h"       // InstantiateAsmJS

#include "jit/JitScript-inl.h"         // AutoKeepJitScripts constructor
#include "vm/EnvironmentObject-inl.h"  // JSObject::enclosingEnvironment
#include "vm/JSFunction-inl.h"         // JSFunction::create
#include "vm/JSScript-inl.h"           // JSScript::baselineScript

using namespace js;
using namespace js::frontend;

// These 2 functions are used to write the same code with lambda using auto
// arguments. The auto argument type is set by the Variant.match function of the
// InputScope variant. Thus dispatching to either a Scope* or to a
// ScopeStencilRef. This function can then be used as a way to specialize the
// code within the lambda without duplicating the code.
//
// Identically, an InputName is constructed using the scope type and the
// matching binding name type. This way, functions which are called by this
// lambda can manipulate an InputName and do not have to be duplicated.
//
// for (InputScopeIter si(...); si; si++) {
//   si.scope().match([](auto& scope) {
//     for (auto bi = InputBindingIter(scope); bi; bi++) {
//       InputName name(scope, bi.name());
//     }
//   });
// }
static js::BindingIter InputBindingIter(Scope* ptr) {
  return js::BindingIter(ptr);
}

static ParserBindingIter InputBindingIter(const ScopeStencilRef& ref) {
  return ParserBindingIter(ref);
}

static ParserBindingIter InputBindingIter(const FakeStencilGlobalScope&) {
  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No bindings on empty global.");
}

InputName InputScript::displayAtom() const {
  return script_.match(
      [](BaseScript* ptr) {
        return InputName(ptr, ptr->function()->fullDisplayAtom());
      },
      [](const ScriptStencilRef& ref) {
        return InputName(ref, ref.scriptData().functionAtom);
      });
}

TaggedParserAtomIndex InputName::internInto(FrontendContext* fc,
                                            ParserAtomsTable& parserAtoms,
                                            CompilationAtomCache& atomCache) {
  return variant_.match(
      [&](JSAtom* ptr) -> TaggedParserAtomIndex {
        return parserAtoms.internJSAtom(fc, atomCache, ptr);
      },
      [&](NameStencilRef& ref) -> TaggedParserAtomIndex {
        return parserAtoms.internExternalParserAtomIndex(fc, ref.context_,
                                                         ref.atomIndex_);
      });
}

bool InputName::isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                          CompilationAtomCache& atomCache,
                          TaggedParserAtomIndex other,
                          JSAtom** otherCached) const {
  return variant_.match(
      [&](const JSAtom* ptr) -> bool {
        if (ptr->hash() != parserAtoms.hash(other)) {
          return false;
        }

        // JSAtom variant is used only on the main thread delazification,
        // where JSContext is always available.
        JSContext* cx = fc->maybeCurrentJSContext();
        MOZ_ASSERT(cx);

        if (!*otherCached) {
          // TODO-Stencil:
          // Here, we convert our name into a JSAtom*, and hard-crash on failure
          // to allocate. This conversion should not be required as we should be
          // able to iterate up snapshotted scope chains that use parser atoms.
          //
          // This will be fixed when the enclosing scopes are snapshotted.
          //
          // See bug 1690277.
          AutoEnterOOMUnsafeRegion oomUnsafe;
          *otherCached = parserAtoms.toJSAtom(cx, fc, other, atomCache);
          if (!*otherCached) {
            oomUnsafe.crash("InputName::isEqualTo");
          }
        } else {
          MOZ_ASSERT(atomCache.getExistingAtomAt(cx, other) == *otherCached);
        }
        return ptr == *otherCached;
      },
      [&](const NameStencilRef& ref) -> bool {
        return parserAtoms.isEqualToExternalParserAtomIndex(other, ref.context_,
                                                            ref.atomIndex_);
      });
}

GenericAtom::GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms,
                         CompilationAtomCache& atomCache,
                         TaggedParserAtomIndex index)
    : ref(EmitterName(fc, parserAtoms, atomCache, index)) {
  hash = parserAtoms.hash(index);
}

GenericAtom::GenericAtom(const CompilationStencil& context,
                         TaggedParserAtomIndex index)
    : ref(StencilName{context, index}) {
  if (index.isParserAtomIndex()) {
    ParserAtom* atom = context.parserAtomData[index.toParserAtomIndex()];
    hash = atom->hash();
  } else {
    hash = index.staticOrWellKnownHash();
  }
}

GenericAtom::GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index)
    : GenericAtom(scope.context_, index) {}

BindingHasher<TaggedParserAtomIndex>::Lookup::Lookup(ScopeStencilRef& scope_ref,
                                                     const GenericAtom& other)
    : keyStencil(scope_ref.context_), other(other) {}

bool GenericAtom::operator==(const GenericAtom& other) const {
  return ref.match(
      [&other](const EmitterName& name) -> bool {
        return other.ref.match(
            [&name](const EmitterName& other) -> bool {
              // We never have multiple Emitter context at the same time.
              MOZ_ASSERT(name.fc == other.fc);
              MOZ_ASSERT(&name.parserAtoms == &other.parserAtoms);
              MOZ_ASSERT(&name.atomCache == &other.atomCache);
              return name.index == other.index;
            },
            [&name](const StencilName& other) -> bool {
              return name.parserAtoms.isEqualToExternalParserAtomIndex(
                  name.index, other.stencil, other.index);
            },
            [&name](JSAtom* other) -> bool {
              // JSAtom variant is used only on the main thread delazification,
              // where JSContext is always available.
              JSContext* cx = name.fc->maybeCurrentJSContext();
              MOZ_ASSERT(cx);
              AutoEnterOOMUnsafeRegion oomUnsafe;
              JSAtom* namePtr = name.parserAtoms.toJSAtom(
                  cx, name.fc, name.index, name.atomCache);
              if (!namePtr) {
                oomUnsafe.crash("GenericAtom(EmitterName == JSAtom*)");
              }
              return namePtr == other;
            });
      },
      [&other](const StencilName& name) -> bool {
        return other.ref.match(
            [&name](const EmitterName& other) -> bool {
              return other.parserAtoms.isEqualToExternalParserAtomIndex(
                  other.index, name.stencil, name.index);
            },
            [&name](const StencilName& other) -> bool {
              // Technically it is possible to have multiple stencils, but in
              // this particular case let's assume we never encounter a case
              // where we are comparing names from different stencils.
              //
              // The reason this assumption is safe today is that we are only
              // using this in the context of a stencil-delazification, where
              // the only StencilNames are coming from the CompilationStencil
              // provided to CompilationInput::initFromStencil.
              MOZ_ASSERT(&name.stencil == &other.stencil);
              return name.index == other.index;
            },
            [](JSAtom* other) -> bool {
              MOZ_CRASH("Never used.");
              return false;
            });
      },
      [&other](JSAtom* name) -> bool {
        return other.ref.match(
            [&name](const EmitterName& other) -> bool {
              // JSAtom variant is used only on the main thread delazification,
              // where JSContext is always available.
              JSContext* cx = other.fc->maybeCurrentJSContext();
              MOZ_ASSERT(cx);
              AutoEnterOOMUnsafeRegion oomUnsafe;
              JSAtom* otherPtr = other.parserAtoms.toJSAtom(
                  cx, other.fc, other.index, other.atomCache);
              if (!otherPtr) {
                oomUnsafe.crash("GenericAtom(JSAtom* == EmitterName)");
              }
              return name == otherPtr;
            },
            [](const StencilName& other) -> bool {
              MOZ_CRASH("Never used.");
              return false;
            },
            [&name](JSAtom* other) -> bool { return name == other; });
      });
}

#ifdef DEBUG
template <typename SpanT, typename VecT>
void AssertBorrowingSpan(const SpanT& span, const VecT& vec) {
  MOZ_ASSERT(span.size() == vec.length());
  MOZ_ASSERT(span.data() == vec.begin());
}
#endif

bool ScopeBindingCache::canCacheFor(Scope* ptr) {
  MOZ_CRASH("Unexpected scope chain type: Scope*");
}

bool ScopeBindingCache::canCacheFor(ScopeStencilRef ref) {
  MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}

bool ScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
  MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}

BindingMap<JSAtom*>* ScopeBindingCache::createCacheFor(Scope* ptr) {
  MOZ_CRASH("Unexpected scope chain type: Scope*");
}

BindingMap<JSAtom*>* ScopeBindingCache::lookupScope(Scope* ptr,
                                                    CacheGeneration gen) {
  MOZ_CRASH("Unexpected scope chain type: Scope*");
}

BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
    ScopeStencilRef ref) {
  MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}

BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
    ScopeStencilRef ref, CacheGeneration gen) {
  MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}

BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
    const FakeStencilGlobalScope& ref) {
  MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}

BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
    const FakeStencilGlobalScope& ref, CacheGeneration gen) {
  MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}

bool NoScopeBindingCache::canCacheFor(Scope* ptr) { return false; }

bool NoScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return false; }

bool NoScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
  return false;
}

bool RuntimeScopeBindingCache::canCacheFor(Scope* ptr) { return true; }

BindingMap<JSAtom*>* RuntimeScopeBindingCache::createCacheFor(Scope* ptr) {
  BaseScopeData* dataPtr = ptr->rawData();
  BindingMap<JSAtom*> bindingCache;
  if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
    return nullptr;
  }

  return lookupScope(ptr, cacheGeneration);
}

BindingMap<JSAtom*>* RuntimeScopeBindingCache::lookupScope(
    Scope* ptr, CacheGeneration gen) {
  MOZ_ASSERT(gen == cacheGeneration);
  BaseScopeData* dataPtr = ptr->rawData();
  auto valuePtr = scopeMap.lookup(dataPtr);
  if (!valuePtr) {
    return nullptr;
  }
  return &valuePtr->value();
}

bool StencilScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return true; }

BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
    ScopeStencilRef ref) {
#ifdef DEBUG
  AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
#endif
  auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
  BindingMap<TaggedParserAtomIndex> bindingCache;
  if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
    return nullptr;
  }

  return lookupScope(ref, 1);
}

BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
    ScopeStencilRef ref, CacheGeneration gen) {
#ifdef DEBUG
  AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
#endif
  auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
  auto ptr = scopeMap.lookup(dataPtr);
  if (!ptr) {
    return nullptr;
  }
  return &ptr->value();
}

static AbstractBaseScopeData<TaggedParserAtomIndex>
    moduleGlobalAbstractScopeData;

bool StencilScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
  return true;
}

BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
    const FakeStencilGlobalScope& ref) {
  auto* dataPtr = &moduleGlobalAbstractScopeData;
  BindingMap<TaggedParserAtomIndex> bindingCache;
  if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
    return nullptr;
  }

  return lookupScope(ref, 1);
}

BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
    const FakeStencilGlobalScope& ref, CacheGeneration gen) {
  auto* dataPtr = &moduleGlobalAbstractScopeData;
  auto ptr = scopeMap.lookup(dataPtr);
  if (!ptr) {
    return nullptr;
  }
  return &ptr->value();
}

bool ScopeContext::init(FrontendContext* fc, CompilationInput& input,
                        ParserAtomsTable& parserAtoms,
                        ScopeBindingCache* scopeCache, InheritThis inheritThis,
                        JSObject* enclosingEnv) {
  // Record the scopeCache to be used while looking up NameLocation bindings.
  this->scopeCache = scopeCache;
  scopeCacheGen = scopeCache->getCurrentGeneration();

  InputScope maybeNonDefaultEnclosingScope(
      input.maybeNonDefaultEnclosingScope());

  // If this eval is in response to Debugger.Frame.eval, we may have an
  // incomplete scope chain. In order to provide a better debugging experience,
  // we inspect the (optional) environment chain to determine it's enclosing
  // FunctionScope if there is one. If there is no such scope, we use the
  // orignal scope provided.
  //
  // NOTE: This is used to compute the ThisBinding kind and to allow access to
  //       private fields and methods, while other contextual information only
  //       uses the actual scope passed to the compile.
  auto effectiveScope =
      determineEffectiveScope(maybeNonDefaultEnclosingScope, enclosingEnv);

  if (inheritThis == InheritThis::Yes) {
    computeThisBinding(effectiveScope);
    computeThisEnvironment(maybeNonDefaultEnclosingScope);
  }
  computeInScope(maybeNonDefaultEnclosingScope);

  cacheEnclosingScope(input.enclosingScope);

  if (input.target == CompilationInput::CompilationTarget::Eval) {
    if (!cacheEnclosingScopeBindingForEval(fc, input, parserAtoms)) {
      return false;
    }
    if (!cachePrivateFieldsForEval(fc, input, enclosingEnv, effectiveScope,
                                   parserAtoms)) {
      return false;
    }
  }

  return true;
}

void ScopeContext::computeThisEnvironment(const InputScope& enclosingScope) {
  uint32_t envCount = 0;
  for (InputScopeIter si(enclosingScope); si; si++) {
    if (si.kind() == ScopeKind::Function) {
      // Arrow function inherit the "this" environment of the enclosing script,
      // so continue ignore them.
      if (!si.scope().isArrow()) {
        allowNewTarget = true;

        if (si.scope().allowSuperProperty()) {
          allowSuperProperty = true;
          enclosingThisEnvironmentHops = envCount;
        }

        if (si.scope().isClassConstructor()) {
          memberInitializers =
              si.scope().useMemberInitializers()
                  ? mozilla::Some(si.scope().getMemberInitializers())
                  : mozilla::Some(MemberInitializers::Empty());
          MOZ_ASSERT(memberInitializers->valid);
        } else {
          if (si.scope().isSyntheticFunction()) {
            allowArguments = false;
          }
        }

        if (si.scope().isDerivedClassConstructor()) {
          allowSuperCall = true;
        }

        // Found the effective "this" environment, so stop.
        return;
      }
    }

    if (si.scope().hasEnvironment()) {
      envCount++;
    }
  }
}

void ScopeContext::computeThisBinding(const InputScope& scope) {
  // Inspect the scope-chain.
  for (InputScopeIter si(scope); si; si++) {
    if (si.kind() == ScopeKind::Module) {
      thisBinding = ThisBinding::Module;
      return;
    }

    if (si.kind() == ScopeKind::Function) {
      // Arrow functions don't have their own `this` binding.
      if (si.scope().isArrow()) {
        continue;
      }

      // Derived class constructors (and their nested arrow functions and evals)
      // use ThisBinding::DerivedConstructor, which ensures TDZ checks happen
      // when accessing |this|.
      if (si.scope().isDerivedClassConstructor()) {
        thisBinding = ThisBinding::DerivedConstructor;
      } else {
        thisBinding = ThisBinding::Function;
      }

      return;
    }
  }

  thisBinding = ThisBinding::Global;
}

void ScopeContext::computeInScope(const InputScope& enclosingScope) {
  for (InputScopeIter si(enclosingScope); si; si++) {
    if (si.kind() == ScopeKind::ClassBody) {
      inClass = true;
    }

    if (si.kind() == ScopeKind::With) {
      inWith = true;
    }
  }
}

void ScopeContext::cacheEnclosingScope(const InputScope& enclosingScope) {
  if (enclosingScope.isNull()) {
    return;
  }

  enclosingScopeEnvironmentChainLength =
      enclosingScope.environmentChainLength();
  enclosingScopeKind = enclosingScope.kind();

  if (enclosingScopeKind == ScopeKind::Function) {
    enclosingScopeIsArrow = enclosingScope.isArrow();
  }

  enclosingScopeHasEnvironment = enclosingScope.hasEnvironment();

#ifdef DEBUG
  hasNonSyntacticScopeOnChain =
      enclosingScope.hasOnChain(ScopeKind::NonSyntactic);

  // This computes a general answer for the query "does the enclosing scope
  // have a function scope that needs a home object?", but it's only asserted
  // if the parser parses eval body that contains `super` that needs a home
  // object.
  for (InputScopeIter si(enclosingScope); si; si++) {
    if (si.kind() == ScopeKind::Function) {
      if (si.scope().isArrow()) {
        continue;
      }
      if (si.scope().allowSuperProperty() && si.scope().needsHomeObject()) {
        hasFunctionNeedsHomeObjectOnChain = true;
      }
      break;
    }
  }
#endif

  // Pre-fill the scope cache by iterating over all the names. Stop iterating
  // as soon as we find a scope which already has a filled scope cache.
  AutoEnterOOMUnsafeRegion oomUnsafe;
  for (InputScopeIter si(enclosingScope); si; si++) {
    // If the current scope already exists, then there is no need to go deeper
    // as the scope which are encoded after this one should already be present
    // in the cache.
    bool hasScopeCache = si.scope().match([&](auto& scope_ref) -> bool {
      MOZ_ASSERT(scopeCache->canCacheFor(scope_ref));
      return scopeCache->lookupScope(scope_ref, scopeCacheGen);
    });
    if (hasScopeCache) {
      return;
    }

    bool hasEnv = si.hasSyntacticEnvironment();
    auto setCatchAll = [&](NameLocation loc) {
      return si.scope().match([&](auto& scope_ref) {
        using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
        BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
        if (!bindingMapPtr) {
          oomUnsafe.crash(
              "ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
          return;
        }

        bindingMapPtr->catchAll.emplace(loc);
      });
    };
    auto createEmpty = [&]() {
      return si.scope().match([&](auto& scope_ref) {
        using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
        BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
        if (!bindingMapPtr) {
          oomUnsafe.crash(
              "ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
          return;
        }
      });
    };

    switch (si.kind()) {
      case ScopeKind::Function:
        if (hasEnv) {
          if (si.scope().funHasExtensibleScope()) {
            setCatchAll(NameLocation::Dynamic());
            return;
          }

          si.scope().match([&](auto& scope_ref) {
            using BindingMapPtr =
                decltype(scopeCache->createCacheFor(scope_ref));
            using Lookup =
                typename std::remove_pointer_t<BindingMapPtr>::Lookup;
            BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
            if (!bindingMapPtr) {
              oomUnsafe.crash(
                  "ScopeContext::cacheEnclosingScope: "
                  "scopeCache->createCacheFor");
              return;
            }

            for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
              NameLocation loc = bi.nameLocation();
              if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
                continue;
              }
              auto ctxFreeKey = bi.name();
              GenericAtom ctxKey(scope_ref, ctxFreeKey);
              Lookup ctxLookup(scope_ref, ctxKey);
              if (!bindingMapPtr->hashMap.put(ctxLookup, ctxFreeKey, loc)) {
                oomUnsafe.crash(
                    "ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
                return;
              }
            }
          });
        } else {
          createEmpty();
        }
        break;

      case ScopeKind::StrictEval:
      case ScopeKind::FunctionBodyVar:
      case ScopeKind::Lexical:
      case ScopeKind::NamedLambda:
      case ScopeKind::StrictNamedLambda:
      case ScopeKind::SimpleCatch:
      case ScopeKind::Catch:
      case ScopeKind::FunctionLexical:
      case ScopeKind::ClassBody:
        if (hasEnv) {
          si.scope().match([&](auto& scope_ref) {
            using BindingMapPtr =
                decltype(scopeCache->createCacheFor(scope_ref));
            using Lookup =
                typename std::remove_pointer_t<BindingMapPtr>::Lookup;
            BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
            if (!bindingMapPtr) {
              oomUnsafe.crash(
                  "ScopeContext::cacheEnclosingScope: "
                  "scopeCache->createCacheFor");
              return;
            }

            for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
              NameLocation loc = bi.nameLocation();
              if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
                continue;
              }
              auto ctxFreeKey = bi.name();
              GenericAtom ctxKey(scope_ref, ctxFreeKey);
              Lookup ctxLookup(scope_ref, ctxKey);
              if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
                oomUnsafe.crash(
                    "ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
                return;
              }
            }
          });
        } else {
          createEmpty();
        }
        break;

      case ScopeKind::Module:
        // This case is used only when delazifying a function inside
        // module.
        // Initial compilation of module doesn't have enlcosing scope.
        if (hasEnv) {
          si.scope().match([&](auto& scope_ref) {
            using BindingMapPtr =
                decltype(scopeCache->createCacheFor(scope_ref));
            using Lookup =
                typename std::remove_pointer_t<BindingMapPtr>::Lookup;
            BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
            if (!bindingMapPtr) {
              oomUnsafe.crash(
                  "ScopeContext::cacheEnclosingScope: "
                  "scopeCache->createCacheFor");
              return;
            }

            for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
              // Imports are on the environment but are indirect
              // bindings and must be accessed dynamically instead of
              // using an EnvironmentCoordinate.
              NameLocation loc = bi.nameLocation();
              if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate &&
                  loc.kind() != NameLocation::Kind::Import) {
                continue;
              }
              auto ctxFreeKey = bi.name();
              GenericAtom ctxKey(scope_ref, ctxFreeKey);
              Lookup ctxLookup(scope_ref, ctxKey);
              if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
                oomUnsafe.crash(
                    "ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
                return;
              }
            }
          });
        } else {
          createEmpty();
        }
        break;

      case ScopeKind::Eval:
        // As an optimization, if the eval doesn't have its own var
        // environment and its immediate enclosing scope is a global
        // scope, all accesses are global.
        if (!hasEnv) {
          ScopeKind kind = si.scope().enclosing().kind();
          if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
            setCatchAll(NameLocation::Global(BindingKind::Var));
            return;
          }
        }

        setCatchAll(NameLocation::Dynamic());
        return;

      case ScopeKind::Global:
        setCatchAll(NameLocation::Global(BindingKind::Var));
        return;

      case ScopeKind::With:
      case ScopeKind::NonSyntactic:
        setCatchAll(NameLocation::Dynamic());
        return;

      case ScopeKind::WasmInstance:
      case ScopeKind::WasmFunction:
        MOZ_CRASH("No direct eval inside wasm functions");
    }
  }

  MOZ_CRASH("Malformed scope chain");
}

// Given an input scope, possibly refine this to a more precise scope.
// This is used during eval in the debugger to provide the appropriate scope and
// ThisBinding kind and environment, which is key to making private field eval
// work correctly.
//
// The trick here is that an eval may have a non-syntatic scope but nevertheless
// have an 'interesting' environment which can be traversed to find the
// appropriate scope the the eval to function as desired. See the diagram below.
//
// Eval Scope    Eval Env         Frame Env    Frame Scope
// ============  =============    =========    =============
//
// NonSyntactic
//    |
//    v
//   null        DebugEnvProxy                 LexicalScope
//                     |                            |
//                     v                            v
//               DebugEnvProxy --> CallObj --> FunctionScope
//                     |              |             |
//                     v              v             v
//                    ...            ...           ...
//
InputScope ScopeContext::determineEffectiveScope(InputScope& scope,
                                                 JSObject* environment) {
  MOZ_ASSERT(effectiveScopeHops == 0);
  // If the scope-chain is non-syntactic, we may still determine a more precise
  // effective-scope to use instead.
  if (environment && scope.hasOnChain(ScopeKind::NonSyntactic)) {
    JSObject* env = environment;
    while (env) {
      // Look at target of any DebugEnvironmentProxy, but be sure to use
      // enclosingEnvironment() of the proxy itself.
      JSObject* unwrapped = env;
      if (env->is<DebugEnvironmentProxy>()) {
        unwrapped = &env->as<DebugEnvironmentProxy>().environment();
#ifdef DEBUG
        enclosingEnvironmentIsDebugProxy_ = true;
#endif
      }

      if (unwrapped->is<CallObject>()) {
        JSFunction* callee = &unwrapped->as<CallObject>().callee();
        return InputScope(callee->nonLazyScript()->bodyScope());
      }

      env = env->enclosingEnvironment();
      effectiveScopeHops++;
    }
  }

  return scope;
}

static uint32_t DepthOfNearestVarScopeForDirectEval(const InputScope& scope) {
  uint32_t depth = 0;
  if (scope.isNull()) {
    return depth;
  }
  for (InputScopeIter si(scope); si; si++) {
    depth++;
    switch (si.scope().kind()) {
      case ScopeKind::Function:
      case ScopeKind::FunctionBodyVar:
      case ScopeKind::Global:
      case ScopeKind::NonSyntactic:
        return depth;
      default:
        break;
    }
  }
  return depth;
}

bool ScopeContext::cacheEnclosingScopeBindingForEval(
    FrontendContext* fc, CompilationInput& input,
    ParserAtomsTable& parserAtoms) {
  enclosingLexicalBindingCache_.emplace();

  uint32_t varScopeDepth =
      DepthOfNearestVarScopeForDirectEval(input.enclosingScope);
  uint32_t depth = 0;
  for (InputScopeIter si(input.enclosingScope); si; si++) {
    bool success = si.scope().match([&](auto& scope_ref) {
      for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
        switch (bi.kind()) {
          case BindingKind::Let: {
            // Annex B.3.5 allows redeclaring simple (non-destructured)
            // catch parameters with var declarations.
            bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch;
            if (!annexB35Allowance) {
              auto kind = ScopeKindIsCatch(si.kind())
                              ? EnclosingLexicalBindingKind::CatchParameter
                              : EnclosingLexicalBindingKind::Let;
              InputName binding(scope_ref, bi.name());
              if (!addToEnclosingLexicalBindingCache(
                      fc, parserAtoms, input.atomCache, binding, kind)) {
                return false;
              }
            }
            break;
          }

#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
          // TODO: Optimize cache population for `using` bindings. (Bug 1899502)
          case BindingKind::Using:
            break;
#endif
          case BindingKind::Const: {
            InputName binding(scope_ref, bi.name());
            if (!addToEnclosingLexicalBindingCache(
                    fc, parserAtoms, input.atomCache, binding,
                    EnclosingLexicalBindingKind::Const)) {
              return false;
            }
            break;
          }

          case BindingKind::Synthetic: {
            InputName binding(scope_ref, bi.name());
            if (!addToEnclosingLexicalBindingCache(
                    fc, parserAtoms, input.atomCache, binding,
                    EnclosingLexicalBindingKind::Synthetic)) {
              return false;
            }
            break;
          }

          case BindingKind::PrivateMethod: {
            InputName binding(scope_ref, bi.name());
            if (!addToEnclosingLexicalBindingCache(
                    fc, parserAtoms, input.atomCache, binding,
                    EnclosingLexicalBindingKind::PrivateMethod)) {
              return false;
            }
            break;
          }

          case BindingKind::Import:
          case BindingKind::FormalParameter:
          case BindingKind::Var:
          case BindingKind::NamedLambdaCallee:
            break;
        }
      }
      return true;
    });
    if (!success) {
      return false;
    }

    if (++depth == varScopeDepth) {
      break;
    }
  }

  return true;
}

bool ScopeContext::addToEnclosingLexicalBindingCache(
    FrontendContext* fc, ParserAtomsTable& parserAtoms,
    CompilationAtomCache& atomCache, InputName& name,
    EnclosingLexicalBindingKind kind) {
  TaggedParserAtomIndex parserName =
      name.internInto(fc, parserAtoms, atomCache);
  if (!parserName) {
    return false;
  }

  // Same lexical binding can appear multiple times across scopes.
  //
  // enclosingLexicalBindingCache_ map is used for detecting conflicting
  // `var` binding, and inner binding should be reported in the error.
  //
  // cacheEnclosingScopeBindingForEval iterates from inner scope, and
  // inner-most binding is added to the map first.
  //
  // Do not overwrite the value with outer bindings.
  auto p = enclosingLexicalBindingCache_->lookupForAdd(parserName);
  if (!p) {
    if (!enclosingLexicalBindingCache_->add(p, parserName, kind)) {
      ReportOutOfMemory(fc);
      return false;
    }
  }

  return true;
}

static bool IsPrivateField(Scope*, JSAtom* atom) {
  MOZ_ASSERT(atom->length() > 0);

  JS::AutoCheckCannotGC nogc;
  if (atom->hasLatin1Chars()) {
    return atom->latin1Chars(nogc)[0] == '#';
  }

  return atom->twoByteChars(nogc)[0] == '#';
}

static bool IsPrivateField(ScopeStencilRef& scope, TaggedParserAtomIndex atom) {
  if (atom.isParserAtomIndex()) {
    const CompilationStencil& context = scope.context_;
    ParserAtom* parserAtom = context.parserAtomData[atom.toParserAtomIndex()];
    return parserAtom->isPrivateName();
  }

#ifdef DEBUG
  if (atom.isWellKnownAtomId()) {
    const auto& info = GetWellKnownAtomInfo(atom.toWellKnownAtomId());
    // #constructor is a well-known term, but it is invalid private name.
    MOZ_ASSERT(!(info.length > 1 && info.content[0] == '#'));
  } else if (atom.isLength2StaticParserString()) {
    char content[2];
    ParserAtomsTable::getLength2Content(atom.toLength2StaticParserString(),
                                        content);
    // # character is not part of the allowed character of static strings.
    MOZ_ASSERT(content[0] != '#');
  }
#endif

  return false;
}

static bool IsPrivateField(const FakeStencilGlobalScope&,
                           TaggedParserAtomIndex) {
  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No private fields on empty global.");
}

bool ScopeContext::cachePrivateFieldsForEval(FrontendContext* fc,
                                             CompilationInput& input,
                                             JSObject* enclosingEnvironment,
                                             const InputScope& effectiveScope,
                                             ParserAtomsTable& parserAtoms) {
  effectiveScopePrivateFieldCache_.emplace();

  // We compute an environment coordinate relative to the effective scope
  // environment. In order to safely consume these environment coordinates,
  // we re-map them to include the hops to get the to the effective scope:
  // see EmitterScope::lookupPrivate
  uint32_t hops = effectiveScopeHops;
  for (InputScopeIter si(effectiveScope); si; si++) {
    if (si.scope().kind() == ScopeKind::ClassBody) {
      uint32_t slots = 0;
      bool success = si.scope().match([&](auto& scope_ref) {
        for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
          if (bi.kind() == BindingKind::PrivateMethod ||
              (bi.kind() == BindingKind::Synthetic &&
               IsPrivateField(scope_ref, bi.name()))) {
            InputName binding(scope_ref, bi.name());
            auto parserName =
                binding.internInto(fc, parserAtoms, input.atomCache);
            if (!parserName) {
              return false;
            }

            NameLocation loc = NameLocation::DebugEnvironmentCoordinate(
                bi.kind(), hops, slots);

            if (!effectiveScopePrivateFieldCache_->put(parserName, loc)) {
              ReportOutOfMemory(fc);
              return false;
            }
          }
          slots++;
        }
        return true;
      });
      if (!success) {
        return false;
      }
    }

    // Hops is only consumed by GetAliasedDebugVar, which uses this to
    // traverse the debug environment chain. See the [SMDOC] for Debug
    // Environment Chain, which explains why we don't check for
    // isEnvironment when computing hops here (basically, debug proxies
    // pretend all scopes have environments, even if they were actually
    // optimized out).
    hops++;
  }

  return true;
}

#ifdef DEBUG
static bool NameIsOnEnvironment(FrontendContext* fc,
                                ParserAtomsTable& parserAtoms,
                                CompilationAtomCache& atomCache,
                                InputScope& scope, TaggedParserAtomIndex name) {
  JSAtom* jsname = nullptr;
  return scope.match([&](auto& scope_ref) {
    if (std::is_same_v<decltype(scope_ref), FakeStencilGlobalScope&>) {
      // This condition is added to handle the FakeStencilGlobalScope which is
      // used to emulate the global object when delazifying while executing, and
      // which is not provided by the Stencil.
      return true;
    }
    for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
      // If found, the name must already be on the environment or an import,
      // or else there is a bug in the closed-over name analysis in the
      // Parser.
      InputName binding(scope_ref, bi.name());
      if (binding.isEqualTo(fc, parserAtoms, atomCache, name, &jsname)) {
        BindingLocation::Kind kind = bi.location().kind();

        if (bi.hasArgumentSlot()) {
          // The following is equivalent to
          // functionScope.script()->functionAllowsParameterRedeclaration()
          if (scope.hasMappedArgsObj()) {
            // Check for duplicate positional formal parameters.
            using InputBindingIter = decltype(bi);
            for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
                 bi2++) {
              InputName binding2(scope_ref, bi2.name());
              if (binding2.isEqualTo(fc, parserAtoms, atomCache, name,
                                     &jsname)) {
                kind = bi2.location().kind();
              }
            }
          }
        }

        return kind == BindingLocation::Kind::Global ||
               kind == BindingLocation::Kind::Environment ||
               kind == BindingLocation::Kind::Import;
      }
    }

    // If not found, assume it's on the global or dynamically accessed.
    return true;
  });
}
#endif

NameLocation ScopeContext::searchInEnclosingScope(FrontendContext* fc,
                                                  CompilationInput& input,
                                                  ParserAtomsTable& parserAtoms,
                                                  TaggedParserAtomIndex name) {
  MOZ_ASSERT(input.target ==
                 CompilationInput::CompilationTarget::Delazification ||
             input.target == CompilationInput::CompilationTarget::Eval);

  MOZ_ASSERT(scopeCache);
  if (scopeCacheGen != scopeCache->getCurrentGeneration()) {
    return searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
  }

#ifdef DEBUG
  // Catch assertion failures in the NoCache variant before looking at the
  // cached content.
  NameLocation expect =
      searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
#endif

  NameLocation found =
      searchInEnclosingScopeWithCache(fc, input, parserAtoms, name);
  MOZ_ASSERT(expect == found);
  return found;
}

NameLocation ScopeContext::searchInEnclosingScopeWithCache(
    FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
    TaggedParserAtomIndex name) {
  MOZ_ASSERT(input.target ==
                 CompilationInput::CompilationTarget::Delazification ||
             input.target == CompilationInput::CompilationTarget::Eval);

  // Generic atom of the looked up name.
  GenericAtom genName(fc, parserAtoms, input.atomCache, name);
  mozilla::Maybe<NameLocation> found;

  // Number of enclosing scope we walked over.
  uint8_t hops = 0;

  for (InputScopeIter si(input.enclosingScope); si; si++) {
    MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
                                   name));

    // If the result happens to be in the cached content of the scope that we
    // are iterating over, then return it.
    si.scope().match([&](auto& scope_ref) {
      using BindingMapPtr =
          decltype(scopeCache->lookupScope(scope_ref, scopeCacheGen));
      BindingMapPtr bindingMapPtr =
          scopeCache->lookupScope(scope_ref, scopeCacheGen);
      MOZ_ASSERT(bindingMapPtr);

      auto& bindingMap = *bindingMapPtr;
      if (bindingMap.catchAll.isSome()) {
        found = bindingMap.catchAll;
        return;
      }

      // The scope_ref is given as argument to know where to lookup the key
      // index of the hash table if the names have to be compared.
      using Lookup = typename std::remove_pointer_t<BindingMapPtr>::Lookup;
      Lookup ctxName(scope_ref, genName);
      auto ptr = bindingMap.hashMap.lookup(ctxName);
      if (!ptr) {
        return;
      }

      found.emplace(ptr->value());
    });

    if (found.isSome()) {
      // Cached entries do not store the number of hops, as it might be reused
      // by multiple inner functions, which might different number of hops.
      found = found.map([&hops](NameLocation loc) {
        if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
          return loc;
        }
        return loc.addHops(hops);
      });
      return found.value();
    }

    bool hasEnv = si.hasSyntacticEnvironment();

    if (hasEnv) {
      MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
      hops++;
    }
  }

  MOZ_CRASH("Malformed scope chain");
}

NameLocation ScopeContext::searchInEnclosingScopeNoCache(
    FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
    TaggedParserAtomIndex name) {
  MOZ_ASSERT(input.target ==
                 CompilationInput::CompilationTarget::Delazification ||
             input.target == CompilationInput::CompilationTarget::Eval);

  // Cached JSAtom equivalent of the TaggedParserAtomIndex `name` argument.
  JSAtom* jsname = nullptr;

  // NameLocation which contains relative locations to access `name`.
  mozilla::Maybe<NameLocation> result;

  // Number of enclosing scoep we walked over.
  uint8_t hops = 0;

  for (InputScopeIter si(input.enclosingScope); si; si++) {
    MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
                                   name));

    bool hasEnv = si.hasSyntacticEnvironment();
    switch (si.kind()) {
      case ScopeKind::Function:
        if (hasEnv) {
          if (si.scope().funHasExtensibleScope()) {
            return NameLocation::Dynamic();
          }

          si.scope().match([&](auto& scope_ref) {
            for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
              InputName binding(scope_ref, bi.name());
              if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
                                     &jsname)) {
                continue;
              }

              BindingLocation bindLoc = bi.location();
              // hasMappedArgsObj == script.functionAllowsParameterRedeclaration
              if (bi.hasArgumentSlot() && si.scope().hasMappedArgsObj()) {
                // Check for duplicate positional formal parameters.
                using InputBindingIter = decltype(bi);
                for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
                     bi2++) {
                  if (bi.name() == bi2.name()) {
                    bindLoc = bi2.location();
                  }
                }
              }

              MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
              result.emplace(NameLocation::EnvironmentCoordinate(
                  bi.kind(), hops, bindLoc.slot()));
              return;
            }
          });
        }
        break;

      case ScopeKind::StrictEval:
      case ScopeKind::FunctionBodyVar:
      case ScopeKind::Lexical:
      case ScopeKind::NamedLambda:
      case ScopeKind::StrictNamedLambda:
      case ScopeKind::SimpleCatch:
      case ScopeKind::Catch:
      case ScopeKind::FunctionLexical:
      case ScopeKind::ClassBody:
        if (hasEnv) {
          si.scope().match([&](auto& scope_ref) {
            for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
              InputName binding(scope_ref, bi.name());
              if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
                                     &jsname)) {
                continue;
              }

              // The name must already have been marked as closed
              // over. If this assertion is hit, there is a bug in the
              // name analysis.
              BindingLocation bindLoc = bi.location();
              MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
              result.emplace(NameLocation::EnvironmentCoordinate(
                  bi.kind(), hops, bindLoc.slot()));
              return;
            }
          });
        }
        break;

      case ScopeKind::Module:
        // This case is used only when delazifying a function inside
        // module.
        // Initial compilation of module doesn't have enlcosing scope.
        if (hasEnv) {
          si.scope().match([&](auto& scope_ref) {
            for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
              InputName binding(scope_ref, bi.name());
              if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
                                     &jsname)) {
                continue;
              }

              BindingLocation bindLoc = bi.location();

              // Imports are on the environment but are indirect
              // bindings and must be accessed dynamically instead of
              // using an EnvironmentCoordinate.
              if (bindLoc.kind() == BindingLocation::Kind::Import) {
                MOZ_ASSERT(si.kind() == ScopeKind::Module);
                result.emplace(NameLocation::Import());
                return;
              }

              MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
              result.emplace(NameLocation::EnvironmentCoordinate(
                  bi.kind(), hops, bindLoc.slot()));
              return;
            }
          });
        }
        break;

      case ScopeKind::Eval:
        // As an optimization, if the eval doesn't have its own var
        // environment and its immediate enclosing scope is a global
        // scope, all accesses are global.
        if (!hasEnv) {
          ScopeKind kind = si.scope().enclosing().kind();
          if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
            return NameLocation::Global(BindingKind::Var);
          }
        }
        return NameLocation::Dynamic();

      case ScopeKind::Global:
        return NameLocation::Global(BindingKind::Var);

      case ScopeKind::With:
      case ScopeKind::NonSyntactic:
        return NameLocation::Dynamic();

      case ScopeKind::WasmInstance:
      case ScopeKind::WasmFunction:
        MOZ_CRASH("No direct eval inside wasm functions");
    }

    if (result.isSome()) {
      return result.value();
    }

    if (hasEnv) {
      MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
      hops++;
    }
  }

  MOZ_CRASH("Malformed scope chain");
}

mozilla::Maybe<ScopeContext::EnclosingLexicalBindingKind>
ScopeContext::lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name) {
  auto p = enclosingLexicalBindingCache_->lookup(name);
  if (!p) {
    return mozilla::Nothing();
  }

  return mozilla::Some(p->value());
}

bool ScopeContext::effectiveScopePrivateFieldCacheHas(
    TaggedParserAtomIndex name) {
  return effectiveScopePrivateFieldCache_->has(name);
}

mozilla::Maybe<NameLocation> ScopeContext::getPrivateFieldLocation(
    TaggedParserAtomIndex name) {
  // The locations returned by this method are only valid for
  // traversing debug environments.
  //
  // See the comment in cachePrivateFieldsForEval
  MOZ_ASSERT(enclosingEnvironmentIsDebugProxy_);
  auto p = effectiveScopePrivateFieldCache_->lookup(name);
  if (!p) {
    return mozilla::Nothing();
  }
  return mozilla::Some(p->value());
}

bool CompilationInput::initScriptSource(FrontendContext* fc) {
  source = do_AddRef(fc->getAllocator()->new_<ScriptSource>());
  if (!source) {
    return false;
  }

  return source->initFromOptions(fc, options);
}

bool CompilationInput::initForStandaloneFunctionInNonSyntacticScope(
    FrontendContext* fc, Handle<Scope*> functionEnclosingScope) {
  MOZ_ASSERT(!functionEnclosingScope->as<GlobalScope>().isSyntactic());

  target = CompilationTarget::StandaloneFunctionInNonSyntacticScope;
  if (!initScriptSource(fc)) {
    return false;
  }
  enclosingScope = InputScope(functionEnclosingScope);
  return true;
}

FunctionSyntaxKind CompilationInput::functionSyntaxKind() const {
  if (functionFlags().isClassConstructor()) {
    if (functionFlags().hasBaseScript() && isDerivedClassConstructor()) {
      return FunctionSyntaxKind::DerivedClassConstructor;
    }
    return FunctionSyntaxKind::ClassConstructor;
  }
  if (functionFlags().isMethod()) {
    if (functionFlags().hasBaseScript() && isSyntheticFunction()) {
      // return FunctionSyntaxKind::FieldInitializer;
      MOZ_ASSERT_UNREACHABLE(
          "Lazy parsing of class field initializers not supported (yet)");
    }
    return FunctionSyntaxKind::Method;
  }
  if (functionFlags().isGetter()) {
    return FunctionSyntaxKind::Getter;
  }
  if (functionFlags().isSetter()) {
    return FunctionSyntaxKind::Setter;
  }
  if (functionFlags().isArrow()) {
    return FunctionSyntaxKind::Arrow;
  }
  return FunctionSyntaxKind::Statement;
}

bool CompilationInput::internExtraBindings(FrontendContext* fc,
                                           ParserAtomsTable& parserAtoms) {
  MOZ_ASSERT(hasExtraBindings());

  for (auto& bindingInfo : *maybeExtraBindings_) {
    if (bindingInfo.isShadowed) {
      continue;
    }

    const char* chars = bindingInfo.nameChars.get();
    auto index = parserAtoms.internUtf8(
        fc, reinterpret_cast<const mozilla::Utf8Unit*>(chars), strlen(chars));
    if (!index) {
      return false;
    }

    bindingInfo.nameIndex = index;
  }

  return true;
}

void InputScope::trace(JSTracer* trc) {
  using ScopePtr = Scope*;
  if (scope_.is<ScopePtr>()) {
    ScopePtr* ptrAddr = &scope_.as<ScopePtr>();
    TraceNullableRoot(trc, ptrAddr, "compilation-input-scope");
  }
}

void InputScript::trace(JSTracer* trc) {
  using ScriptPtr = BaseScript*;
  if (script_.is<ScriptPtr>()) {
    ScriptPtr* ptrAddr = &script_.as<ScriptPtr>();
    TraceNullableRoot(trc, ptrAddr, "compilation-input-lazy");
  }
}

void CompilationInput::trace(JSTracer* trc) {
  atomCache.trace(trc);
  lazy_.trace(trc);
  enclosingScope.trace(trc);
}

bool CompilationSyntaxParseCache::init(FrontendContext* fc, LifoAlloc& alloc,
                                       ParserAtomsTable& parseAtoms,
                                       CompilationAtomCache& atomCache,
                                       const InputScript& lazy) {
  if (!copyFunctionInfo(fc, parseAtoms, atomCache, lazy)) {
    return false;
  }
  bool success = lazy.raw().match([&](auto& ref) {
    if (!copyScriptInfo(fc, alloc, parseAtoms, atomCache, ref)) {
      return false;
    }
    if (!copyClosedOverBindings(fc, alloc, parseAtoms, atomCache, ref)) {
      return false;
    }
    return true;
  });
  if (!success) {
    return false;
  }
#ifdef DEBUG
  isInitialized = true;
#endif
  return true;
}

bool CompilationSyntaxParseCache::copyFunctionInfo(
    FrontendContext* fc, ParserAtomsTable& parseAtoms,
    CompilationAtomCache& atomCache, const InputScript& lazy) {
  InputName name = lazy.displayAtom();
  if (!name.isNull()) {
    displayAtom_ = name.internInto(fc, parseAtoms, atomCache);
    if (!displayAtom_) {
      return false;
    }
  }

  funExtra_.immutableFlags = lazy.immutableFlags();
  funExtra_.extent = lazy.extent();
  if (funExtra_.useMemberInitializers()) {
    funExtra_.setMemberInitializers(lazy.getMemberInitializers());
  }

  return true;
}

bool CompilationSyntaxParseCache::copyScriptInfo(
    FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
    CompilationAtomCache& atomCache, BaseScript* lazy) {
  using GCThingsSpan = mozilla::Span<TaggedScriptThingIndex>;
  using ScriptDataSpan = mozilla::Span<ScriptStencil>;
  using ScriptExtraSpan = mozilla::Span<ScriptStencilExtra>;
  cachedGCThings_ = GCThingsSpan(nullptr);
  cachedScriptData_ = ScriptDataSpan(nullptr);
  cachedScriptExtra_ = ScriptExtraSpan(nullptr);

  auto gcthings = lazy->gcthings();
  size_t length = gcthings.Length();
  if (length == 0) {
    return true;
  }

  // Reduce the length to the first element which is not a function.
  for (size_t i = 0; i < length; i++) {
    gc::Cell* cell = gcthings[i].asCell();
    if (!cell || !cell->is<JSObject>()) {
      length = i;
      break;
    }
    MOZ_ASSERT(cell->as<JSObject>()->is<JSFunction>());
  }

  TaggedScriptThingIndex* gcThingsData =
      alloc.newArrayUninitialized<TaggedScriptThingIndex>(length);
  ScriptStencil* scriptData =
      alloc.newArrayUninitialized<ScriptStencil>(length);
  ScriptStencilExtra* scriptExtra =
      alloc.newArrayUninitialized<ScriptStencilExtra>(length);
  if (!gcThingsData || !scriptData || !scriptExtra) {
    ReportOutOfMemory(fc);
    return false;
  }

  for (size_t i = 0; i < length; i++) {
    gc::Cell* cell = gcthings[i].asCell();
    JSFunction* fun = &cell->as<JSObject>()->as<JSFunction>();
    gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i));
    new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil();
    ScriptStencil& data = scriptData[i];
    new (mozilla::KnownNotNull, &scriptExtra[i]) ScriptStencilExtra();
    ScriptStencilExtra& extra = scriptExtra[i];

    if (fun->fullDisplayAtom()) {
      TaggedParserAtomIndex displayAtom =
          parseAtoms.internJSAtom(fc, atomCache, fun->fullDisplayAtom());
      if (!displayAtom) {
        return false;
      }
      data.functionAtom = displayAtom;
    }
    data.functionFlags = fun->flags();

    BaseScript* lazy = fun->baseScript();
    extra.immutableFlags = lazy->immutableFlags();
    extra.extent = lazy->extent();

    // Info derived from parent compilation should not be set yet for our inner
    // lazy functions. Instead that info will be updated when we finish our
    // compilation.
    MOZ_ASSERT(lazy->hasEnclosingScript());
  }

  cachedGCThings_ = GCThingsSpan(gcThingsData, length);
  cachedScriptData_ = ScriptDataSpan(scriptData, length);
  cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length);
  return true;
}

bool CompilationSyntaxParseCache::copyScriptInfo(
    FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
    CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) {
  using GCThingsSpan = mozilla::Span<TaggedScriptThingIndex>;
  using ScriptDataSpan = mozilla::Span<ScriptStencil>;
  using ScriptExtraSpan = mozilla::Span<ScriptStencilExtra>;
  cachedGCThings_ = GCThingsSpan(nullptr);
  cachedScriptData_ = ScriptDataSpan(nullptr);
  cachedScriptExtra_ = ScriptExtraSpan(nullptr);

  size_t offset = lazy.scriptData().gcThingsOffset.index;
  size_t length = lazy.scriptData().gcThingsLength;
  if (length == 0) {
    return true;
  }

  // Reduce the length to the first element which is not a function.
  for (size_t i = offset; i < offset + length; i++) {
    if (!lazy.context_.gcThingData[i].isFunction()) {
      length = i - offset;
      break;
    }
  }

  TaggedScriptThingIndex* gcThingsData =
      alloc.newArrayUninitialized<TaggedScriptThingIndex>(length);
  ScriptStencil* scriptData =
      alloc.newArrayUninitialized<ScriptStencil>(length);
  ScriptStencilExtra* scriptExtra =
      alloc.newArrayUninitialized<ScriptStencilExtra>(length);
  if (!gcThingsData || !scriptData || !scriptExtra) {
    ReportOutOfMemory(fc);
    return false;
  }

  for (size_t i = 0; i < length; i++) {
    ScriptStencilRef inner{lazy.context_,
                           lazy.context_.gcThingData[i + offset].toFunction()};
    gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i));
    new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil();
    ScriptStencil& data = scriptData[i];
    ScriptStencilExtra& extra = scriptExtra[i];

    InputName name{inner, inner.scriptData().functionAtom};
    if (!name.isNull()) {
      auto displayAtom = name.internInto(fc, parseAtoms, atomCache);
      if (!displayAtom) {
        return false;
      }
      data.functionAtom = displayAtom;
    }
    data.functionFlags = inner.scriptData().functionFlags;

    extra = inner.scriptExtra();
  }

  cachedGCThings_ = GCThingsSpan(gcThingsData, length);
  cachedScriptData_ = ScriptDataSpan(scriptData, length);
  cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length);
  return true;
}

bool CompilationSyntaxParseCache::copyClosedOverBindings(
    FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
    CompilationAtomCache& atomCache, BaseScript* lazy) {
  using ClosedOverBindingsSpan = mozilla::Span<TaggedParserAtomIndex>;
  closedOverBindings_ = ClosedOverBindingsSpan(nullptr);

  // The gcthings() array contains the inner function list followed by the
  // closed-over bindings data. Skip the inner function list, as it is already
  // cached in cachedGCThings_. See also: BaseScript::CreateLazy.
  size_t start = cachedGCThings_.Length();
  auto gcthings = lazy->gcthings();
  size_t length = gcthings.Length();
  MOZ_ASSERT(start <= length);
  if (length - start == 0) {
    return true;
  }

  TaggedParserAtomIndex* closedOverBindings =
      alloc.newArrayUninitialized<TaggedParserAtomIndex>(length - start);
  if (!closedOverBindings) {
    ReportOutOfMemory(fc);
    return false;
  }

  for (size_t i = start; i < length; i++) {
    gc::Cell* cell = gcthings[i].asCell();
    if (!cell) {
      closedOverBindings[i - start] = TaggedParserAtomIndex::null();
      continue;
    }

    MOZ_ASSERT(cell->as<JSString>()->isAtom());

    auto name = static_cast<JSAtom*>(cell);
    auto parserAtom = parseAtoms.internJSAtom(fc, atomCache, name);
    if (!parserAtom) {
      return false;
    }

    closedOverBindings[i - start] = parserAtom;
  }

  closedOverBindings_ =
      ClosedOverBindingsSpan(closedOverBindings, length - start);
  return true;
}

bool CompilationSyntaxParseCache::copyClosedOverBindings(
    FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
    CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) {
  using ClosedOverBindingsSpan = mozilla::Span<TaggedParserAtomIndex>;
  closedOverBindings_ = ClosedOverBindingsSpan(nullptr);

  // The gcthings array contains the inner function list followed by the
  // closed-over bindings data. Skip the inner function list, as it is already
  // cached in cachedGCThings_. See also: BaseScript::CreateLazy.
  size_t offset = lazy.scriptData().gcThingsOffset.index;
  size_t length = lazy.scriptData().gcThingsLength;
  size_t start = cachedGCThings_.Length();
  MOZ_ASSERT(start <= length);
  if (length - start == 0) {
    return true;
  }
  length -= start;
  start += offset;

  // Atoms from the lazy.context (CompilationStencil) are not registered in the
  // the parseAtoms table. Thus we create a new span which will contain all the
  // interned atoms.
  TaggedParserAtomIndex* closedOverBindings =
      alloc.newArrayUninitialized<TaggedParserAtomIndex>(length);
  if (!closedOverBindings) {
    ReportOutOfMemory(fc);
    return false;
  }

  for (size_t i = 0; i < length; i++) {
    auto gcThing = lazy.context_.gcThingData[i + start];
    if (gcThing.isNull()) {
      closedOverBindings[i] = TaggedParserAtomIndex::null();
      continue;
    }

    MOZ_ASSERT(gcThing.isAtom());
    InputName name(lazy, gcThing.toAtom());
    auto parserAtom = name.internInto(fc, parseAtoms, atomCache);
    if (!parserAtom) {
      return false;
    }

    closedOverBindings[i] = parserAtom;
  }

  closedOverBindings_ = ClosedOverBindingsSpan(closedOverBindings, length);
  return true;
}

template <typename T>
PreAllocateableGCArray<T>::~PreAllocateableGCArray() {
  if (elems_) {
    js_free(elems_);
    elems_ = nullptr;
  }
}

template <typename T>
bool PreAllocateableGCArray<T>::allocate(size_t length) {
  MOZ_ASSERT(empty());

  length_ = length;

  if (isInline()) {
    inlineElem_ = nullptr;
    return true;
  }

  elems_ = reinterpret_cast<T*>(js_calloc(sizeof(T) * length_));
  if (!elems_) {
    return false;
  }

  return true;
}

template <typename T>
bool PreAllocateableGCArray<T>::allocateWith(T init, size_t length) {
  MOZ_ASSERT(empty());

  length_ = length;

  if (isInline()) {
    inlineElem_ = init;
    return true;
  }

  elems_ = reinterpret_cast<T*>(js_malloc(sizeof(T) * length_));
  if (!elems_) {
    return false;
  }

  std::fill(elems_, elems_ + length_, init);
  return true;
}

template <typename T>
void PreAllocateableGCArray<T>::steal(Preallocated&& buffer) {
  MOZ_ASSERT(empty());

  length_ = buffer.length_;
  buffer.length_ = 0;

  if (isInline()) {
    inlineElem_ = nullptr;
    return;
  }

  elems_ = reinterpret_cast<T*>(buffer.elems_);
  buffer.elems_ = nullptr;

#ifdef DEBUG
  for (size_t i = 0; i < length_; i++) {
    MOZ_ASSERT(elems_[i] == nullptr);
  }
#endif
}

template <typename T>
void PreAllocateableGCArray<T>::trace(JSTracer* trc) {
  if (empty()) {
    return;
  }

  if (isInline()) {
    TraceNullableRoot(trc, &inlineElem_, "PreAllocateableGCArray::inlineElem_");
    return;
  }

  for (size_t i = 0; i < length_; i++) {
    TraceNullableRoot(trc, &elems_[i], "PreAllocateableGCArray::elems_");
  }
}

template <typename T>
PreAllocateableGCArray<T>::Preallocated::~Preallocated() {
  if (elems_) {
    js_free(elems_);
    elems_ = nullptr;
  }
}

template <typename T>
bool PreAllocateableGCArray<T>::Preallocated::allocate(size_t length) {
  MOZ_ASSERT(empty());

  length_ = length;

  if (isInline()) {
    return true;
  }

  elems_ = reinterpret_cast<uintptr_t*>(js_calloc(sizeof(uintptr_t) * length_));
  if (!elems_) {
    return false;
  }

  return true;
}

template struct js::frontend::PreAllocateableGCArray<JSFunction*>;
template struct js::frontend::PreAllocateableGCArray<js::Scope*>;

void CompilationAtomCache::trace(JSTracer* trc) { atoms_.trace(trc); }

void CompilationGCOutput::trace(JSTracer* trc) {
  TraceNullableRoot(trc, &script, "compilation-gc-output-script");
  TraceNullableRoot(trc, &module, "compilation-gc-output-module");
  TraceNullableRoot(trc, &sourceObject, "compilation-gc-output-source");
  functions.trace(trc);
  scopes.trace(trc);
}

RegExpObject* RegExpStencil::createRegExp(
    JSContext* cx, const CompilationAtomCache& atomCache) const {
  Rooted<JSAtom*> atom(cx, atomCache.getExistingAtomAt(cx, atom_));
  return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject);
}

RegExpObject* RegExpStencil::createRegExpAndEnsureAtom(
    JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms,
    CompilationAtomCache& atomCache) const {
  Rooted<JSAtom*> atom(cx, parserAtoms.toJSAtom(cx, fc, atom_, atomCache));
  if (!atom) {
    return nullptr;
  }
  return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject);
}

AbstractScopePtr ScopeStencil::enclosing(
    CompilationState& compilationState) const {
  if (hasEnclosing()) {
    return AbstractScopePtr(compilationState, enclosing());
  }

  return AbstractScopePtr::compilationEnclosingScope(compilationState);
}

Scope* ScopeStencil::enclosingExistingScope(
    const CompilationInput& input, const CompilationGCOutput& gcOutput) const {
  if (hasEnclosing()) {
    Scope* result = gcOutput.getScopeNoBaseIndex(enclosing());
    MOZ_ASSERT(result, "Scope must already exist to use this method");
    return result;
  }

  // When creating a scope based on the input and a gc-output, we assume that
  // the scope stencil that we are looking at has not been merged into another
  // stencil, and thus that we still have the compilation input of the stencil.
  //
  // Otherwise, if this was in the case of an input generated from a Stencil
  // instead of live-gc values, we would not know its associated gcOutput as it
  // might not even have one yet.
  return input.enclosingScope.variant().as<Scope*>();
}

Scope* ScopeStencil::createScope(JSContext* cx, CompilationInput& input,
                                 CompilationGCOutput& gcOutput,
                                 BaseParserScopeData* baseScopeData) const {
  Rooted<Scope*> enclosingScope(cx, enclosingExistingScope(input, gcOutput));
  return createScope(cx, input.atomCache, enclosingScope, baseScopeData);
}

Scope* ScopeStencil::createScope(JSContext* cx, CompilationAtomCache& atomCache,
                                 Handle<Scope*> enclosingScope,
                                 BaseParserScopeData* baseScopeData) const {
  switch (kind()) {
    case ScopeKind::Function: {
      using ScopeType = FunctionScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, CallObject>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::Lexical:
    case ScopeKind::SimpleCatch:
    case ScopeKind::Catch:
    case ScopeKind::NamedLambda:
    case ScopeKind::StrictNamedLambda:
    case ScopeKind::FunctionLexical: {
      using ScopeType = LexicalScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, BlockLexicalEnvironmentObject>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::ClassBody: {
      using ScopeType = ClassBodyScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, BlockLexicalEnvironmentObject>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::FunctionBodyVar: {
      using ScopeType = VarScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, VarEnvironmentObject>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::Global:
    case ScopeKind::NonSyntactic: {
      using ScopeType = GlobalScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, std::nullptr_t>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::Eval:
    case ScopeKind::StrictEval: {
      using ScopeType = EvalScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, VarEnvironmentObject>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::Module: {
      using ScopeType = ModuleScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, ModuleEnvironmentObject>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::With: {
      using ScopeType = WithScope;
      MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
      return createSpecificScope<ScopeType, std::nullptr_t>(
          cx, atomCache, enclosingScope, baseScopeData);
    }
    case ScopeKind::WasmFunction:
    case ScopeKind::WasmInstance: {
      // ScopeStencil does not support WASM
      break;
    }
  }
  MOZ_CRASH();
}

bool CompilationState::prepareSharedDataStorage(FrontendContext* fc) {
  size_t allScriptCount = scriptData.length();
  size_t nonLazyScriptCount = nonLazyFunctionCount;
  if (!scriptData[0].isFunction()) {
    nonLazyScriptCount++;
  }
  return sharedData.prepareStorageFor(fc, nonLazyScriptCount, allScriptCount);
}

static bool CreateLazyScript(JSContext* cx,
                             const CompilationAtomCache& atomCache,
                             const CompilationStencil& stencil,
                             CompilationGCOutput& gcOutput,
                             const ScriptStencil& script,
                             const ScriptStencilExtra& scriptExtra,
                             ScriptIndex scriptIndex, HandleFunction function) {
  Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);

  size_t ngcthings = script.gcThingsLength;

  Rooted<BaseScript*> lazy(
      cx, BaseScript::CreateRawLazy(cx, ngcthings, function, sourceObject,
                                    scriptExtra.extent,
                                    scriptExtra.immutableFlags));
  if (!lazy) {
    return false;
  }

  if (ngcthings) {
    if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput,
                                script.gcthings(stencil),
                                lazy->gcthingsForInit())) {
      return false;
    }
  }

  if (scriptExtra.useMemberInitializers()) {
    lazy->setMemberInitializers(scriptExtra.memberInitializers());
  }

  function->initScript(lazy);

  return true;
}

// Parser-generated functions with the same prototype will share the same shape.
// By computing the correct values up front, we can save a lot of time in the
// Object creation code. For simplicity, we focus only on plain synchronous
// functions which are by far the most common.
//
// NOTE: Keep this in sync with `js::NewFunctionWithProto`.
static JSFunction* CreateFunctionFast(JSContext* cx,
                                      CompilationAtomCache& atomCache,
                                      Handle<SharedShape*> shape,
                                      const ScriptStencil& script,
                                      const ScriptStencilExtra& scriptExtra) {
  MOZ_ASSERT(
      !scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync));
  MOZ_ASSERT(!scriptExtra.immutableFlags.hasFlag(
      ImmutableScriptFlagsEnum::IsGenerator));
  MOZ_ASSERT(!script.functionFlags.isAsmJSNative());

  FunctionFlags flags = script.functionFlags;
  gc::AllocKind allocKind = flags.isExtended()
                                ? gc::AllocKind::FUNCTION_EXTENDED
                                : gc::AllocKind::FUNCTION;

  JSFunction* fun = JSFunction::create(cx, allocKind, gc::Heap::Tenured, shape);
  if (!fun) {
    return nullptr;
  }

  fun->setArgCount(scriptExtra.nargs);
  fun->setFlags(flags);

  fun->initScript(nullptr);
  fun->initEnvironment(nullptr);

  if (script.functionAtom) {
    JSAtom* atom = atomCache.getExistingAtomAt(cx, script.functionAtom);
    MOZ_ASSERT(atom);
    fun->initAtom(atom);
  }

#ifdef DEBUG
  fun->assertFunctionKindIntegrity();
#endif

  return fun;
}

static JSFunction* CreateFunction(JSContext* cx,
                                  CompilationAtomCache& atomCache,
                                  const CompilationStencil& stencil,
                                  const ScriptStencil& script,
                                  const ScriptStencilExtra& scriptExtra,
                                  ScriptIndex functionIndex) {
  GeneratorKind generatorKind =
      scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsGenerator)
          ? GeneratorKind::Generator
          : GeneratorKind::NotGenerator;
  FunctionAsyncKind asyncKind =
      scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync)
          ? FunctionAsyncKind::AsyncFunction
          : FunctionAsyncKind::SyncFunction;

  // Determine the new function's proto. This must be done for singleton
  // functions.
  RootedObject proto(cx);
  if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
    return nullptr;
  }

  gc::AllocKind allocKind = script.functionFlags.isExtended()
                                ? gc::AllocKind::FUNCTION_EXTENDED
                                : gc::AllocKind::FUNCTION;
  bool isAsmJS = script.functionFlags.isAsmJSNative();

  JSNative maybeNative = isAsmJS ? InstantiateAsmJS : nullptr;

  Rooted<JSAtom*> displayAtom(cx);
  if (script.functionAtom) {
    displayAtom.set(atomCache.getExistingAtomAt(cx, script.functionAtom));
    MOZ_ASSERT(displayAtom);
  }
  RootedFunction fun(
      cx, NewFunctionWithProto(cx, maybeNative, scriptExtra.nargs,
                               script.functionFlags, nullptr, displayAtom,
                               proto, allocKind, TenuredObject));
  if (!fun) {
    return nullptr;
  }

  if (isAsmJS) {
    RefPtr<const JS::WasmModule> asmJS =
        stencil.asmJS->moduleMap.lookup(functionIndex)->value();

    JSObject* moduleObj = asmJS->createObjectForAsmJS(cx);
    if (!moduleObj) {
      return nullptr;
    }

    fun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT,
                         ObjectValue(*moduleObj));
  }

  return fun;
}

static bool InstantiateAtoms(JSContext* cx, FrontendContext* fc,
                             CompilationAtomCache& atomCache,
                             const CompilationStencil& stencil) {
  return InstantiateMarkedAtoms(cx, fc, stencil.parserAtomData, atomCache);
}

static bool InstantiateScriptSourceObject(JSContext* cx,
                                          const JS::InstantiateOptions& options,
                                          const CompilationStencil& stencil,
                                          CompilationGCOutput& gcOutput) {
  MOZ_ASSERT(stencil.source);

  gcOutput.sourceObject = ScriptSourceObject::create(cx, stencil.source.get());
  if (!gcOutput.sourceObject) {
    return false;
  }

  Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
  if (!ScriptSourceObject::initFromOptions(cx, sourceObject, options)) {
    return false;
  }

  return true;
}

// Instantiate ModuleObject. Further initialization is done after the associated
// BaseScript is instantiated in InstantiateTopLevel.
static bool InstantiateModuleObject(JSContext* cx, FrontendContext* fc,
                                    CompilationAtomCache& atomCache,
                                    const CompilationStencil& stencil,
                                    CompilationGCOutput& gcOutput) {
  MOZ_ASSERT(stencil.isModule());

  gcOutput.module = ModuleObject::create(cx);
  if (!gcOutput.module) {
    return false;
  }

  Rooted<ModuleObject*> module(cx, gcOutput.module);
  return stencil.moduleMetadata->initModule(cx, fc, atomCache, module);
}

// Instantiate JSFunctions for each FunctionBox.
static bool InstantiateFunctions(JSContext* cx, FrontendContext* fc,
                                 CompilationAtomCache& atomCache,
                                 const CompilationStencil& stencil,
                                 CompilationGCOutput& gcOutput) {
  using ImmutableFlags = ImmutableScriptFlagsEnum;

  MOZ_ASSERT(gcOutput.functions.length() == stencil.scriptData.size());

  // Most JSFunctions will be have the same Shape so we can compute it now to
  // allow fast object creation. Generators / Async will use the slow path
  // instead.
  Rooted<SharedShape*> functionShape(
      cx, GlobalObject::getFunctionShapeWithDefaultProto(
              cx, /* extended = */ false));
  if (!functionShape) {
    return false;
  }

  Rooted<SharedShape*> extendedShape(
      cx, GlobalObject::getFunctionShapeWithDefaultProto(
              cx, /* extended = */ true));
  if (!extendedShape) {
    return false;
  }

  for (auto item :
       CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
    const auto& scriptStencil = item.script;
    const auto& scriptExtra = (*item.scriptExtra);
    auto index = item.index;

    MOZ_ASSERT(!item.function);

    // Plain functions can use a fast path.
    bool useFastPath =
        !scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsAsync) &&
        !scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsGenerator) &&
        !scriptStencil.functionFlags.isAsmJSNative();

    JSFunction* fun;
    if (useFastPath) {
      Handle<SharedShape*> shape = scriptStencil.functionFlags.isExtended()
                                       ? extendedShape
                                       : functionShape;
      fun =
          CreateFunctionFast(cx, atomCache, shape, scriptStencil, scriptExtra);
    } else {
      fun = CreateFunction(cx, atomCache, stencil, scriptStencil, scriptExtra,
                           index);
    }

    if (!fun) {
      return false;
    }

    // Self-hosted functions may have a canonical name to use when instantiating
    // into other realms.
    if (scriptStencil.hasSelfHostedCanonicalName()) {
      JSAtom* canonicalName = atomCache.getExistingAtomAt(
          cx, scriptStencil.selfHostedCanonicalName());
      fun->setAtom(canonicalName);
    }

    gcOutput.getFunctionNoBaseIndex(index) = fun;
  }

  return true;
}

// Instantiate Scope for each ScopeStencil.
//
// This should be called after InstantiateFunctions, given FunctionScope needs
// associated JSFunction pointer, and also should be called before
// InstantiateScriptStencils, given JSScript needs Scope pointer in gc things.
static bool InstantiateScopes(JSContext* cx, CompilationInput& input,
                              const CompilationStencil& stencil,
                              CompilationGCOutput& gcOutput) {
  // While allocating Scope object from ScopeStencil, Scope object for the
  // enclosing Scope should already be allocated.
  //
  // Enclosing scope of ScopeStencil can be either ScopeStencil or Scope*
  // pointer.
  //
  // If the enclosing scope is ScopeStencil, it's guaranteed to be earlier
  // element in stencil.scopeData, because enclosing_ field holds
  // index into it, and newly created ScopeStencil is pushed back to the array.
  //
  // If the enclosing scope is Scope*, it's CompilationInput.enclosingScope.

  MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size());
  size_t scopeCount = stencil.scopeData.size();
  for (size_t i = 0; i < scopeCount; i++) {
    Scope* scope = stencil.scopeData[i].createScope(cx, input, gcOutput,
                                                    stencil.scopeNames[i]);
    if (!scope) {
      return false;
    }
    gcOutput.scopes[i] = scope;
  }

  return true;
}

// Instantiate js::BaseScripts from ScriptStencils for inner functions of the
// compilation. Note that standalone functions and functions being delazified
// are handled below with other top-levels.
static bool InstantiateScriptStencils(JSContext* cx,
                                      CompilationAtomCache& atomCache,
                                      const CompilationStencil& stencil,
                                      CompilationGCOutput& gcOutput) {
  MOZ_ASSERT(stencil.isInitialStencil());

  Rooted<JSFunction*> fun(cx);
  for (auto item :
       CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
    auto& scriptStencil = item.script;
    auto* scriptExtra = item.scriptExtra;
    fun = item.function;
    auto index = item.index;
    if (scriptStencil.hasSharedData()) {
      // If the function was not referenced by enclosing script's bytecode, we
      // do not generate a BaseScript for it. For example, `(function(){});`.
      //
      // `wasEmittedByEnclosingScript` is false also for standalone
      // functions. They are handled in InstantiateTopLevel.
      if (!scriptStencil.wasEmittedByEnclosingScript()) {
        continue;
      }

      RootedScript script(
          cx, JSScript::fromStencil(cx, atomCache, stencil, gcOutput, index));
      if (!script) {
        return false;
      }

      if (scriptStencil.allowRelazify()) {
        MOZ_ASSERT(script->isRelazifiable());
        script->setAllowRelazify();
      }
    } else if (scriptStencil.functionFlags.isAsmJSNative()) {
      MOZ_ASSERT(fun->isAsmJSNative());
    } else {
      MOZ_ASSERT(fun->isIncomplete());
      if (!CreateLazyScript(cx, atomCache, stencil, gcOutput, scriptStencil,
                            *scriptExtra, index, fun)) {
        return false;
      }
    }
  }

  return true;
}

// Instantiate the Stencil for the top-level script of the compilation. This
// includes standalone functions and functions being delazified.
static bool InstantiateTopLevel(JSContext* cx, CompilationInput& input,
                                const CompilationStencil& stencil,
                                CompilationGCOutput& gcOutput) {
  const ScriptStencil& scriptStencil =
      stencil.scriptData[CompilationStencil::TopLevelIndex];

  // Top-level asm.js does not generate a JSScript.
  if (scriptStencil.functionFlags.isAsmJSNative()) {
    return true;
  }

  MOZ_ASSERT(scriptStencil.hasSharedData());
  MOZ_ASSERT(stencil.sharedData.get(CompilationStencil::TopLevelIndex));

  if (!stencil.isInitialStencil()) {
    MOZ_ASSERT(input.lazyOuterBaseScript());
    RootedScript script(cx,
                        JSScript::CastFromLazy(input.lazyOuterBaseScript()));
    if (!JSScript::fullyInitFromStencil(cx, input.atomCache, stencil, gcOutput,
                                        script,
                                        CompilationStencil::TopLevelIndex)) {
      return false;
    }

    if (scriptStencil.allowRelazify()) {
      MOZ_ASSERT(script->isRelazifiable());
      script->setAllowRelazify();
    }

    gcOutput.script = script;
    return true;
  }

  gcOutput.script =
      JSScript::fromStencil(cx, input.atomCache, stencil, gcOutput,
                            CompilationStencil::TopLevelIndex);
  if (!gcOutput.script) {
    return false;
  }

  if (scriptStencil.allowRelazify()) {
    MOZ_ASSERT(gcOutput.script->isRelazifiable());
    gcOutput.script->setAllowRelazify();
  }

  const ScriptStencilExtra& scriptExtra =
      stencil.scriptExtra[CompilationStencil::TopLevelIndex];

  // Finish initializing the ModuleObject if needed.
  if (scriptExtra.isModule()) {
    RootedScript script(cx, gcOutput.script);
    Rooted<ModuleObject*> module(cx, gcOutput.module);

    script->outermostScope()->as<ModuleScope>().initModule(module);

    module->initScriptSlots(script);

    if (!ModuleObject::createEnvironment(cx, module)) {
      return false;
    }

    if (!ModuleObject::Freeze(cx, module)) {
      return false;
    }
  }

  return true;
}

// When a function is first referenced by enclosing script's bytecode, we need
// to update it with information determined by the BytecodeEmitter. This applies
// to both initial and delazification parses. The functions being update may or
// may not have bytecode at this point.
static void UpdateEmittedInnerFunctions(JSContext* cx,
                                        CompilationAtomCache& atomCache,
                                        const CompilationStencil& stencil,
                                        CompilationGCOutput& gcOutput) {
  for (auto item :
       CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
    auto& scriptStencil = item.script;
    auto& fun = item.function;
    if (!scriptStencil.wasEmittedByEnclosingScript()) {
      continue;
    }

    if (scriptStencil.functionFlags.isAsmJSNative() ||
        fun->baseScript()->hasBytecode()) {
      // Non-lazy inner functions don't use the enclosingScope_ field.
      MOZ_ASSERT(!scriptStencil.hasLazyFunctionEnclosingScopeIndex());
    } else {
      // Apply updates from FunctionEmitter::emitLazy().
      BaseScript* script = fun->baseScript();

      ScopeIndex index = scriptStencil.lazyFunctionEnclosingScopeIndex();
      Scope* scope = gcOutput.getScopeNoBaseIndex(index);
      script->setEnclosingScope(scope);

      // Inferred and Guessed names are computed by BytecodeEmitter and so may
      // need to be applied to existing JSFunctions during delazification.
      if (fun->fullDisplayAtom() == nullptr) {
        JSAtom* funcAtom = nullptr;
        if (scriptStencil.functionFlags.hasInferredName() ||
            scriptStencil.functionFlags.hasGuessedAtom()) {
          funcAtom =
              atomCache.getExistingAtomAt(cx, scriptStencil.functionAtom);
          MOZ_ASSERT(funcAtom);
        }
        if (scriptStencil.functionFlags.hasInferredName()) {
          fun->setInferredName(funcAtom);
        }
        if (scriptStencil.functionFlags.hasGuessedAtom()) {
          fun->setGuessedAtom(funcAtom);
        }
      }
    }
  }
}

// During initial parse we must link lazy-functions-inside-lazy-functions to
// their enclosing script.
static void LinkEnclosingLazyScript(const CompilationStencil& stencil,
                                    CompilationGCOutput& gcOutput) {
  for (auto item :
       CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
    auto& scriptStencil = item.script;
    auto& fun = item.function;
    if (!scriptStencil.functionFlags.hasBaseScript()) {
      continue;
    }

    if (!fun->baseScript()) {
      continue;
    }

    if (fun->baseScript()->hasBytecode()) {
      continue;
    }

    BaseScript* script = fun->baseScript();
    MOZ_ASSERT(!script->hasBytecode());

    for (auto inner : script->gcthings()) {
      if (!inner.is<JSObject>()) {
        continue;
      }
      JSFunction* innerFun = &inner.as<JSObject>().as<JSFunction>();

      MOZ_ASSERT(innerFun->hasBaseScript(),
                 "inner function should have base script");
      if (!innerFun->hasBaseScript()) {
        continue;
      }

      // Check for the case that the inner function has the base script flag,
      // but still doesn't have the actual base script pointer.
      // `baseScript` method asserts the pointer itself, so no extra MOZ_ASSERT
      // here.
      if (!innerFun->baseScript()) {
        continue;
      }

      innerFun->setEnclosingLazyScript(script);
    }
  }
}

#ifdef DEBUG
// Some fields aren't used in delazification, given the target functions and
// scripts are already instantiated, but they still should match.
static void AssertDelazificationFieldsMatch(const CompilationStencil& stencil,
                                            CompilationGCOutput& gcOutput) {
  for (auto item :
       CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
    auto& scriptStencil = item.script;
    auto* scriptExtra = item.scriptExtra;
    auto& fun = item.function;

    MOZ_ASSERT(scriptExtra == nullptr);

    // Names are updated by UpdateInnerFunctions.
    constexpr uint16_t HAS_INFERRED_NAME =
        uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME);
    constexpr uint16_t HAS_GUESSED_ATOM =
        uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM);
    constexpr uint16_t MUTABLE_FLAGS =
        uint16_t(FunctionFlags::Flags::MUTABLE_FLAGS);
    constexpr uint16_t acceptableDifferenceForFunction =
        HAS_INFERRED_NAME | HAS_GUESSED_ATOM | MUTABLE_FLAGS;

    MOZ_ASSERT((fun->flags().toRaw() | acceptableDifferenceForFunction) ==
               (scriptStencil.functionFlags.toRaw() |
                acceptableDifferenceForFunction));

    // Delazification shouldn't delazify inner scripts.
    MOZ_ASSERT_IF(item.index == CompilationStencil::TopLevelIndex,
                  scriptStencil.hasSharedData());
    MOZ_ASSERT_IF(item.index > CompilationStencil::TopLevelIndex,
                  !scriptStencil.hasSharedData());
  }
}
#endif  // DEBUG

// When delazifying, use the existing JSFunctions. The initial and delazifying
// parse are required to generate the same sequence of functions for lazy
// parsing to work at all.
static void FunctionsFromExistingLazy(CompilationInput& input,
                                      CompilationGCOutput& gcOutput) {
  MOZ_ASSERT(!gcOutput.functions[0]);

  size_t instantiatedFunIndex = 0;
  gcOutput.functions[instantiatedFunIndex++] = input.function();

  for (JS::GCCellPtr elem : input.lazyOuterBaseScript()->gcthings()) {
    if (!elem.is<JSObject>()) {
      continue;
    }
    JSFunction* fun = &elem.as<JSObject>().as<JSFunction>();
    gcOutput.functions[instantiatedFunIndex++] = fun;
  }
}

void CompilationStencil::borrowFromExtensibleCompilationStencil(
    ExtensibleCompilationStencil& extensibleStencil) {
  canLazilyParse = extensibleStencil.canLazilyParse;
  functionKey = extensibleStencil.functionKey;

  // Borrow the vector content as span.
  scriptData = extensibleStencil.scriptData;
  scriptExtra = extensibleStencil.scriptExtra;

  gcThingData = extensibleStencil.gcThingData;

  scopeData = extensibleStencil.scopeData;
  scopeNames = extensibleStencil.scopeNames;

  regExpData = extensibleStencil.regExpData;
  bigIntData = extensibleStencil.bigIntData;
  objLiteralData = extensibleStencil.objLiteralData;

  // Borrow the parser atoms as span.
  parserAtomData = extensibleStencil.parserAtoms.entries_;

  // Borrow container.
  sharedData.setBorrow(&extensibleStencil.sharedData);

  // Share ref-counted data.
  source = extensibleStencil.source;
  asmJS = extensibleStencil.asmJS;
  moduleMetadata = extensibleStencil.moduleMetadata;
}

#ifdef DEBUG
void CompilationStencil::assertBorrowingFromExtensibleCompilationStencil(
    const ExtensibleCompilationStencil& extensibleStencil) const {
  MOZ_ASSERT(canLazilyParse == extensibleStencil.canLazilyParse);
  MOZ_ASSERT(functionKey == extensibleStencil.functionKey);

  AssertBorrowingSpan(scriptData, extensibleStencil.scriptData);
  AssertBorrowingSpan(scriptExtra, extensibleStencil.scriptExtra);

  AssertBorrowingSpan(gcThingData, extensibleStencil.gcThingData);

  AssertBorrowingSpan(scopeData, extensibleStencil.scopeData);
  AssertBorrowingSpan(scopeNames, extensibleStencil.scopeNames);

  AssertBorrowingSpan(regExpData, extensibleStencil.regExpData);
  AssertBorrowingSpan(bigIntData, extensibleStencil.bigIntData);
  AssertBorrowingSpan(objLiteralData, extensibleStencil.objLiteralData);

  AssertBorrowingSpan(parserAtomData, extensibleStencil.parserAtoms.entries_);

  MOZ_ASSERT(sharedData.isBorrow());
  MOZ_ASSERT(sharedData.asBorrow() == &extensibleStencil.sharedData);

  MOZ_ASSERT(source == extensibleStencil.source);
  MOZ_ASSERT(asmJS == extensibleStencil.asmJS);
  MOZ_ASSERT(moduleMetadata == extensibleStencil.moduleMetadata);
}
#endif

CompilationStencil::CompilationStencil(
    UniquePtr<ExtensibleCompilationStencil>&& extensibleStencil)
    : alloc(LifoAllocChunkSize, js::BackgroundMallocArena) {
  ownedBorrowStencil = std::move(extensibleStencil);

  storageType = StorageType::OwnedExtensible;

  borrowFromExtensibleCompilationStencil(*ownedBorrowStencil);

#ifdef DEBUG
  assertNoExternalDependency();
#endif
}

/* static */
bool CompilationStencil::instantiateStencils(JSContext* cx,
                                             CompilationInput& input,
                                             const CompilationStencil& stencil,
                                             CompilationGCOutput& gcOutput) {
  AutoReportFrontendContext fc(cx);
  if (!prepareForInstantiate(&fc, input.atomCache, stencil, gcOutput)) {
    return false;
  }

  return instantiateStencilAfterPreparation(cx, input, stencil, gcOutput);
}

/* static */
bool CompilationStencil::instantiateStencilAfterPreparation(
    JSContext* cx, CompilationInput& input, const CompilationStencil& stencil,
    CompilationGCOutput& gcOutput) {
  // Distinguish between the initial (possibly lazy) compile and any subsequent
  // delazification compiles. Delazification will update existing GC things.
  bool isInitialParse = stencil.isInitialStencil();
  MOZ_ASSERT(stencil.isInitialStencil() == input.isInitialStencil());

  // Assert the consistency between the compile option and the target global.
  MOZ_ASSERT_IF(cx->realm()->behaviors().discardSource(),
                !stencil.canLazilyParse);

  CompilationAtomCache& atomCache = input.atomCache;
  const JS::InstantiateOptions options(input.options);

  // Phase 1: Instantiate JSAtom/JSStrings.
  AutoReportFrontendContext fc(cx);
  if (!InstantiateAtoms(cx, &fc, atomCache, stencil)) {
    return false;
  }

  // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions.
  if (isInitialParse) {
    if (!InstantiateScriptSourceObject(cx, options, stencil, gcOutput)) {
      return false;
    }

    if (stencil.moduleMetadata) {
      // The enclosing script of a module is always the global scope. Fetch the
      // scope of the current global and update input data.
      MOZ_ASSERT(input.enclosingScope.isNull());
      input.enclosingScope = InputScope(&cx->global()->emptyGlobalScope());
      MOZ_ASSERT(input.enclosingScope.environmentChainLength() ==
                 ModuleScope::EnclosingEnvironmentChainLength);

      if (!InstantiateModuleObject(cx, &fc, atomCache, stencil, gcOutput)) {
        return false;
      }
    }

    if (!InstantiateFunctions(cx, &fc, atomCache, stencil, gcOutput)) {
      return false;
    }
  } else {
    MOZ_ASSERT(
        stencil.scriptData[CompilationStencil::TopLevelIndex].isFunction());

    // FunctionKey is used when caching to map a delazification stencil to a
    // specific lazy script. It is not used by instantiation, but we should
    // ensure it is correctly defined.
    MOZ_ASSERT(stencil.functionKey == input.extent().toFunctionKey());

    FunctionsFromExistingLazy(input, gcOutput);
    MOZ_ASSERT(gcOutput.functions.length() == stencil.scriptData.size());

#ifdef DEBUG
    AssertDelazificationFieldsMatch(stencil, gcOutput);
#endif
  }

  // Phase 3: Instantiate js::Scopes.
  if (!InstantiateScopes(cx, input, stencil, gcOutput)) {
    return false;
  }

  // Phase 4: Instantiate (inner) BaseScripts.
  if (isInitialParse) {
    if (!InstantiateScriptStencils(cx, atomCache, stencil, gcOutput)) {
      return false;
    }
  }

  // Phase 5: Finish top-level handling
  if (!InstantiateTopLevel(cx, input, stencil, gcOutput)) {
    return false;
  }

  // !! Must be infallible from here forward !!

  // Phase 6: Update lazy scripts.
  if (stencil.canLazilyParse) {
    UpdateEmittedInnerFunctions(cx, atomCache, stencil, gcOutput);

    if (isInitialParse) {
      LinkEnclosingLazyScript(stencil, gcOutput);
    }
  }

  return true;
}

// The top-level self-hosted script is created and executed in each realm that
// needs it. While the stencil has a gcthings list for the various top-level
// functions, we use special machinery to create them on demand. So instead we
// use a placeholder JSFunction that should never be called.
static bool SelfHostedDummyFunction(JSContext* cx, unsigned argc,
                                    JS::Value* vp) {
  MOZ_CRASH("Self-hosting top-level should not use functions directly");
}

bool CompilationStencil::instantiateSelfHostedAtoms(
    JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const {
  MOZ_ASSERT(isInitialStencil());

  // We must instantiate atoms during startup so they can be made permanent
  // across multiple runtimes.
  AutoReportFrontendContext fc(cx);
  return InstantiateMarkedAtomsAsPermanent(cx, &fc, atomSet, parserAtomData,
                                           atomCache);
}

JSScript* CompilationStencil::instantiateSelfHostedTopLevelForRealm(
    JSContext* cx, CompilationInput& input) {
  MOZ_ASSERT(isInitialStencil());

  Rooted<CompilationGCOutput> gcOutput(cx);

  gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx);
  if (!gcOutput.get().sourceObject) {
    return nullptr;
  }

  // The top-level script has ScriptIndex references in its gcthings list, but
  // we do not want to instantiate those functions here since they are instead
  // created on demand from the stencil. Create a dummy function and populate
  // the functions array of the CompilationGCOutput with references to it.
  RootedFunction dummy(
      cx, NewNativeFunction(cx, SelfHostedDummyFunction, 0, nullptr));
  if (!dummy) {
    return nullptr;
  }

  if (!gcOutput.get().functions.allocateWith(dummy, scriptData.size())) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  if (!InstantiateTopLevel(cx, input, *this, gcOutput.get())) {
    return nullptr;
  }

  return gcOutput.get().script;
}

JSFunction* CompilationStencil::instantiateSelfHostedLazyFunction(
    JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index,
    Handle<JSAtom*> name) {
  MOZ_ASSERT(cx->zone()->suppressAllocationMetadataBuilder);

  GeneratorKind generatorKind = scriptExtra[index].immutableFlags.hasFlag(
                                    ImmutableScriptFlagsEnum::IsGenerator)
                                    ? GeneratorKind::Generator
                                    : GeneratorKind::NotGenerator;
  FunctionAsyncKind asyncKind = scriptExtra[index].immutableFlags.hasFlag(
                                    ImmutableScriptFlagsEnum::IsAsync)
                                    ? FunctionAsyncKind::AsyncFunction
                                    : FunctionAsyncKind::SyncFunction;

  Rooted<JSAtom*> funName(cx);
  if (scriptData[index].hasSelfHostedCanonicalName()) {
    // SetCanonicalName was used to override the name.
    funName = atomCache.getExistingAtomAt(
        cx, scriptData[index].selfHostedCanonicalName());
  } else if (name) {
    // Our caller has a name it wants to use.
    funName = name;
  } else {
    MOZ_ASSERT(scriptData[index].functionAtom);
    funName = atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom);
  }

  RootedObject proto(cx);
  if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
    return nullptr;
  }

  RootedObject env(cx, &cx->global()->lexicalEnvironment());

  RootedFunction fun(
      cx,
      NewFunctionWithProto(cx, nullptr, scriptExtra[index].nargs,
                           scriptData[index].functionFlags, env, funName, proto,
                           gc::AllocKind::FUNCTION_EXTENDED, TenuredObject));
  if (!fun) {
    return nullptr;
  }

  fun->initSelfHostedLazyScript(&cx->runtime()->selfHostedLazyScript.ref());

  JSAtom* selfHostedName =
      atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom);
  SetClonedSelfHostedFunctionName(fun, selfHostedName->asPropertyName());

  return fun;
}

bool CompilationStencil::delazifySelfHostedFunction(
    JSContext* cx, CompilationAtomCache& atomCache, ScriptIndexRange range,
    Handle<JSAtom*> name, HandleFunction fun) {
  // Determine the equivalent ScopeIndex range by looking at the outermost scope
  // of the scripts defining the range. Take special care if this is the last
  // script in the list.
  auto getOutermostScope = [this](ScriptIndex scriptIndex) -> ScopeIndex {
    MOZ_ASSERT(scriptData[scriptIndex].hasSharedData());
    auto gcthings = scriptData[scriptIndex].gcthings(*this);
    return gcthings[GCThingIndex::outermostScopeIndex()].toScope();
  };
  ScopeIndex scopeIndex = getOutermostScope(range.start);
  ScopeIndex scopeLimit = (range.limit < scriptData.size())
                              ? getOutermostScope(range.limit)
                              : ScopeIndex(scopeData.size());
  Rooted<JSAtom*> jitCacheKey(cx, name);

  // Prepare to instantiate by allocating the output arrays. We also set a base
  // index to avoid allocations in most cases.
  AutoReportFrontendContext fc(cx);
  Rooted<CompilationGCOutput> gcOutput(cx);
  if (!gcOutput.get().ensureAllocatedWithBaseIndex(
          &fc, range.start, range.limit, scopeIndex, scopeLimit)) {
    return false;
  }

  // Phase 1: Instantiate JSAtoms.
  //  NOTE: The self-hosted atoms are all "permanent" and the
  //        CompilationAtomCache is already stored on the JSRuntime.

  // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions.

  // Get the corresponding ScriptSourceObject to use in current realm.
  gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx);
  if (!gcOutput.get().sourceObject) {
    return false;
  }

  size_t instantiatedFunIndex = 0;

  // Delazification target function.
  gcOutput.get().functions[instantiatedFunIndex++] = fun;

  // Allocate inner functions. Self-hosted functions do not allocate these with
  // the initial function.
  for (size_t i = range.start + 1; i < range.limit; i++) {
    JSFunction* innerFun = CreateFunction(cx, atomCache, *this, scriptData[i],
                                          scriptExtra[i], ScriptIndex(i));
    if (!innerFun) {
      return false;
    }
    gcOutput.get().functions[instantiatedFunIndex++] = innerFun;
  }

  // Phase 3: Instantiate js::Scopes.
  // NOTE: When the enclosing scope is not a stencil, directly use the
  //       `emptyGlobalScope` instead of reading from CompilationInput. This is
  //       a special case for self-hosted delazification that allows us to reuse
  //       the CompilationInput between different realms.
  size_t instantiatedScopeIndex = 0;
  for (size_t i = scopeIndex; i < scopeLimit; i++) {
    ScopeStencil& data = scopeData[i];
    Rooted<Scope*> enclosingScope(
        cx, data.hasEnclosing() ? gcOutput.get().getScope(data.enclosing())
                                : &cx->global()->emptyGlobalScope());

    js::Scope* scope =
        data.createScope(cx, atomCache, enclosingScope, scopeNames[i]);
    if (!scope) {
      return false;
    }
    gcOutput.get().scopes[instantiatedScopeIndex++] = scope;
  }

  // Phase 4: Instantiate (inner) BaseScripts.
  ScriptIndex innerStart(range.start + 1);
  for (size_t i = innerStart; i < range.limit; i++) {
    if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(),
                               ScriptIndex(i))) {
      return false;
    }
  }

  // Phase 5: Finish top-level handling
  // NOTE: We do not have a `CompilationInput` handy here, so avoid using the
  //       `InstantiateTopLevel` helper and directly create the JSScript. Our
  //       caller also handles the `AllowRelazify` flag for us since self-hosted
  //       delazification is a special case.
  Rooted<JSScript*> script(
      cx,
      JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(), range.start));
  if (!script) {
    return false;
  }

  if (JS::Prefs::experimental_self_hosted_cache()) {
    // We eagerly baseline-compile self-hosted functions, and cache their
    // JitCode for reuse across the runtime. If the cache already contains an
    // entry for this function, update the JitScript. If not, compile it now and
    // store it in the cache.
    UniqueChars nameStr;
    if (JS_SHOULD_LOG(selfHosted, Debug)) {
      nameStr = AtomToPrintableString(cx, name);
    }
    auto& jitCache = cx->runtime()->selfHostJitCache.ref();
    auto v = jitCache.readonlyThreadsafeLookup(jitCacheKey);
    if (v && v->value()->method()) {
      JS_LOG(selfHosted, Debug,
             "self_hosted_cache: reusing JIT code for script '%s'",
             nameStr.get());

      if (!cx->zone()->ensureJitZoneExists(cx)) {
        return false;
      }
      jit::AutoKeepJitScripts keepJitScript(cx);
      if (!script->ensureHasJitScript(cx, keepJitScript)) {
        return false;
      }
      MOZ_ASSERT(!script->hasBaselineScript());

      // JSScript destroys its BaselineScript on finalize, so we need another
      // copy here (for now)
      jit::BaselineScript* baselineScript =
          jit::BaselineScript::Copy(cx, v->value());
      if (!baselineScript) {
        return false;
      }
      mozilla::DebugOnly<bool> instrumentationEnabled =
          cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
              cx->runtime());
      MOZ_ASSERT(instrumentationEnabled ==
                 baselineScript->isProfilerInstrumentationOn());
      script->jitScript()->setBaselineScript(script, baselineScript);
    } else if (jit::IsBaselineJitEnabled(cx) && script->canBaselineCompile() &&
               !script->hasBaselineScript() &&
               jit::CanBaselineInterpretScript(script)) {
      JS_LOG(selfHosted, Debug,
             "self_hosted_cache: new JIT code entry for script '%s'",
             nameStr.get());

      if (!cx->zone()->ensureJitZoneExists(cx)) {
        return false;
      }

      jit::AutoKeepJitScripts keep(cx);
      if (!script->ensureHasJitScript(cx, keep)) {
        return false;
      }

      jit::BaselineOptions options(
          {jit::BaselineOption::ForceMainThreadCompilation});
      jit::MethodStatus result =
          jit::BaselineCompile(cx, script.get(), options);
      if (result != jit::Method_Compiled) {
        return false;
      }
      MOZ_ASSERT(script->hasBaselineScript());

      jit::BaselineScript* baselineScript =
          jit::BaselineScript::Copy(cx, script->baselineScript());
      if (!baselineScript) {
        return false;
      }
      if (!jitCache.put(jitCacheKey, baselineScript)) {
        return false;
      }
    } else {
      JS_LOG(selfHosted, Debug,
             "self_hosted_cache: script '%s' is not eligible for Baseline "
             "compilation",
             nameStr.get());
    }
  }

  // Phase 6: Update lazy scripts.
  //  NOTE: Self-hosting is always fully parsed so there is nothing to do here.

  return true;
}

/* static */
bool CompilationStencil::prepareForInstantiate(
    FrontendContext* fc, CompilationAtomCache& atomCache,
    const CompilationStencil& stencil, CompilationGCOutput& gcOutput) {
  // Allocate the `gcOutput` arrays.
  if (!gcOutput.ensureAllocated(fc, stencil.scriptData.size(),
                                stencil.scopeData.size())) {
    return false;
  }

  return atomCache.allocate(fc, stencil.parserAtomData.size());
}

/* static */
bool CompilationStencil::prepareForInstantiate(
    FrontendContext* fc, const CompilationStencil& stencil,
    PreallocatedCompilationGCOutput& gcOutput) {
  return gcOutput.allocate(fc, stencil.scriptData.size(),
                           stencil.scopeData.size());
}

bool JS::PrepareForInstantiate(JS::FrontendContext* fc, JS::Stencil& stencil,
                               JS::InstantiationStorage& storage) {
  if (!storage.gcOutput_) {
    storage.gcOutput_ =
        fc->getAllocator()
            ->new_<js::frontend::PreallocatedCompilationGCOutput>();
    if (!storage.gcOutput_) {
      return false;
    }
  }

  return CompilationStencil::prepareForInstantiate(fc, *stencil.getInitial(),
                                                   *storage.gcOutput_);
}

ExtensibleCompilationStencil::ExtensibleCompilationStencil(ScriptSource* source)
    : alloc(CompilationStencil::LifoAllocChunkSize, js::BackgroundMallocArena),
      source(source),
      parserAtoms(alloc) {}

ExtensibleCompilationStencil::ExtensibleCompilationStencil(
    CompilationInput& input)
    : canLazilyParse(CanLazilyParse(input.options)),
      alloc(CompilationStencil::LifoAllocChunkSize, js::BackgroundMallocArena),
      source(input.source),
      parserAtoms(alloc) {}

ExtensibleCompilationStencil::ExtensibleCompilationStencil(
    const JS::ReadOnlyCompileOptions& options, RefPtr<ScriptSource> source)
    : canLazilyParse(CanLazilyParse(options)),
      alloc(CompilationStencil::LifoAllocChunkSize, js::BackgroundMallocArena),
      source(std::move(source)),
      parserAtoms(alloc) {}

CompilationState::CompilationState(FrontendContext* fc,
                                   LifoAllocScope& parserAllocScope,
                                   CompilationInput& input)
    : ExtensibleCompilationStencil(input),
      directives(input.options.forceStrictMode()),
      usedNames(fc),
      parserAllocScope(parserAllocScope),
      input(input) {}

BorrowingCompilationStencil::BorrowingCompilationStencil(
    ExtensibleCompilationStencil& extensibleStencil)
    : CompilationStencil(extensibleStencil.source) {
  storageType = StorageType::Borrowed;

  borrowFromExtensibleCompilationStencil(extensibleStencil);
}

SharedDataContainer::~SharedDataContainer() {
  if (isEmpty()) {
    // Nothing to do.
  } else if (isSingle()) {
    asSingle()->Release();
  } else if (isVector()) {
    js_delete(asVector());
  } else if (isMap()) {
    js_delete(asMap());
  } else {
    MOZ_ASSERT(isBorrow());
    // Nothing to do.
  }
}

bool SharedDataContainer::initVector(FrontendContext* fc) {
  MOZ_ASSERT(isEmpty());

  auto* vec = js_new<SharedDataVector>();
  if (!vec) {
    ReportOutOfMemory(fc);
    return false;
  }
  data_ = uintptr_t(vec) | VectorTag;
  return true;
}

bool SharedDataContainer::initMap(FrontendContext* fc) {
  MOZ_ASSERT(isEmpty());

  auto* map = js_new<SharedDataMap>();
  if (!map) {
    ReportOutOfMemory(fc);
    return false;
  }
  data_ = uintptr_t(map) | MapTag;
  return true;
}

bool SharedDataContainer::prepareStorageFor(FrontendContext* fc,
                                            size_t nonLazyScriptCount,
                                            size_t allScriptCount) {
  MOZ_ASSERT(isEmpty());

  if (nonLazyScriptCount <= 1) {
    MOZ_ASSERT(isSingle());
    return true;
  }

  // If the ratio of scripts with bytecode is small, allocating the Vector
  // storage with the number of all scripts isn't space-efficient.
  // In that case use HashMap instead.
  //
  // In general, we expect either all scripts to contain bytecode (priviledge
  // and self-hosted), or almost none to (eg standard lazy parsing output).
  constexpr size_t thresholdRatio = 8;
  bool useHashMap = nonLazyScriptCount < allScriptCount / thresholdRatio;
  if (useHashMap) {
    if (!initMap(fc)) {
      return false;
    }
    if (!asMap()->reserve(nonLazyScriptCount)) {
      ReportOutOfMemory(fc);
      return false;
    }
  } else {
    if (!initVector(fc)) {
      return false;
    }
    if (!asVector()->resize(allScriptCount)) {
      ReportOutOfMemory(fc);
      return false;
    }
  }

  return true;
}

bool SharedDataContainer::cloneFrom(FrontendContext* fc,
                                    const SharedDataContainer& other) {
  MOZ_ASSERT(isEmpty());

  if (other.isBorrow()) {
    return cloneFrom(fc, *other.asBorrow());
  }

  if (other.isSingle()) {
    // As we clone, we add an extra reference.
    RefPtr<SharedImmutableScriptData> ref(other.asSingle());
    setSingle(ref.forget());
  } else if (other.isVector()) {
    if (!initVector(fc)) {
      return false;
    }
    if (!asVector()->appendAll(*other.asVector())) {
      ReportOutOfMemory(fc);
      return false;
    }
  } else if (other.isMap()) {
    if (!initMap(fc)) {
      return false;
    }
    auto& otherMap = *other.asMap();
    if (!asMap()->reserve(otherMap.count())) {
      ReportOutOfMemory(fc);
      return false;
    }
    auto& map = *asMap();
    for (auto iter = otherMap.iter(); !iter.done(); iter.next()) {
      auto& entry = iter.get();
      map.putNewInfallible(entry.key(), entry.value());
    }
  }
  return true;
}

js::SharedImmutableScriptData* SharedDataContainer::get(
    ScriptIndex index) const {
  if (isSingle()) {
    if (index == CompilationStencil::TopLevelIndex) {
      return asSingle();
    }
    return nullptr;
  }

  if (isVector()) {
    auto& vec = *asVector();
    if (index.index < vec.length()) {
      return vec[index];
    }
    return nullptr;
  }

  if (isMap()) {
    auto& map = *asMap();
    auto p = map.lookup(index);
    if (p) {
      return p->value();
    }
    return nullptr;
  }

  MOZ_ASSERT(isBorrow());
  return asBorrow()->get(index);
}

bool SharedDataContainer::convertFromSingleToMap(FrontendContext* fc) {
  MOZ_ASSERT(isSingle());

  // Use a temporary container so that on OOM we do not break the stencil.
  SharedDataContainer other;
  if (!other.initMap(fc)) {
    return false;
  }

  if (!other.asMap()->putNew(CompilationStencil::TopLevelIndex, asSingle())) {
    ReportOutOfMemory(fc);
    return false;
  }

  std::swap(data_, other.data_);
  return true;
}

bool SharedDataContainer::addAndShare(FrontendContext* fc, ScriptIndex index,
                                      js::SharedImmutableScriptData* data) {
  MOZ_ASSERT(!isBorrow());

  if (isSingle()) {
    MOZ_ASSERT(index == CompilationStencil::TopLevelIndex);
    RefPtr<SharedImmutableScriptData> ref(data);
    if (!SharedImmutableScriptData::shareScriptData(fc, ref)) {
      return false;
    }
    setSingle(ref.forget());
    return true;
  }

  if (isVector()) {
    auto& vec = *asVector();
    // Resized by SharedDataContainer::prepareStorageFor.
    vec[index] = data;
    return SharedImmutableScriptData::shareScriptData(fc, vec[index]);
  }

  MOZ_ASSERT(isMap());
  auto& map = *asMap();
  // Reserved by SharedDataContainer::prepareStorageFor.
  map.putNewInfallible(index, data);
  auto p = map.lookup(index);
  MOZ_ASSERT(p);
  return SharedImmutableScriptData::shareScriptData(fc, p->value());
}

bool SharedDataContainer::addExtraWithoutShare(
    FrontendContext* fc, ScriptIndex index,
    js::SharedImmutableScriptData* data) {
  MOZ_ASSERT(!isEmpty());

  if (isSingle()) {
    if (!convertFromSingleToMap(fc)) {
      return false;
    }
  }

  if (isVector()) {
    // SharedDataContainer::prepareStorageFor allocates space for all scripts.
    (*asVector())[index] = data;
    return true;
  }

  MOZ_ASSERT(isMap());
  // SharedDataContainer::prepareStorageFor doesn't allocate space for
  // delazification, and this can fail.
  if (!asMap()->putNew(index, data)) {
    ReportOutOfMemory(fc);
    return false;
  }
  return true;
}

#ifdef DEBUG
void CompilationStencil::assertNoExternalDependency() const {
  if (ownedBorrowStencil) {
    ownedBorrowStencil->assertNoExternalDependency();

    assertBorrowingFromExtensibleCompilationStencil(*ownedBorrowStencil);
    return;
  }

  MOZ_ASSERT_IF(!scriptData.empty(), alloc.contains(scriptData.data()));
  MOZ_ASSERT_IF(!scriptExtra.empty(), alloc.contains(scriptExtra.data()));

  MOZ_ASSERT_IF(!scopeData.empty(), alloc.contains(scopeData.data()));
  MOZ_ASSERT_IF(!scopeNames.empty(), alloc.contains(scopeNames.data()));
  for (const auto* data : scopeNames) {
    MOZ_ASSERT_IF(data, alloc.contains(data));
  }

  MOZ_ASSERT_IF(!regExpData.empty(), alloc.contains(regExpData.data()));

  MOZ_ASSERT_IF(!bigIntData.empty(), alloc.contains(bigIntData.data()));
  for (const auto& data : bigIntData) {
    MOZ_ASSERT(data.isContainedIn(alloc));
  }

  MOZ_ASSERT_IF(!objLiteralData.empty(), alloc.contains(objLiteralData.data()));
  for (const auto& data : objLiteralData) {
    MOZ_ASSERT(data.isContainedIn(alloc));
  }

  MOZ_ASSERT_IF(!parserAtomData.empty(), alloc.contains(parserAtomData.data()));
  for (const auto* data : parserAtomData) {
    MOZ_ASSERT_IF(data, alloc.contains(data));
  }

  MOZ_ASSERT(!sharedData.isBorrow());
}

void ExtensibleCompilationStencil::assertNoExternalDependency() const {
  for (const auto& data : bigIntData) {
    MOZ_ASSERT(data.isContainedIn(alloc));
  }

  for (const auto& data : objLiteralData) {
    MOZ_ASSERT(data.isContainedIn(alloc));
  }

  for (const auto* data : scopeNames) {
    MOZ_ASSERT_IF(data, alloc.contains(data));
  }

  for (const auto* data : parserAtoms.entries()) {
    MOZ_ASSERT_IF(data, alloc.contains(data));
  }

  MOZ_ASSERT(!sharedData.isBorrow());
}
#endif  // DEBUG

template <typename T, typename VectorT>
[[nodiscard]] bool CopySpanToVector(FrontendContext* fc, VectorT& vec,
                                    mozilla::Span<T>& span) {
  auto len = span.size();
  if (len == 0) {
    return true;
  }

  if (!vec.append(span.data(), len)) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  return true;
}

template <typename T, typename IntoSpanT, size_t Inline, typename AllocPolicy>
[[nodiscard]] bool CopyToVector(FrontendContext* fc,
                                mozilla::Vector<T, Inline, AllocPolicy>& vec,
                                const IntoSpanT& source) {
  mozilla::Span<const T> span = source;
  return CopySpanToVector(fc, vec, span);
}

// Span and Vector do not share the same method names.
template <typename T, size_t Inline, typename AllocPolicy>
size_t GetLength(const mozilla::Vector<T, Inline, AllocPolicy>& vec) {
  return vec.length();
}
template <typename T>
size_t GetLength(const mozilla::Span<T>& span) {
  return span.Length();
}

// Copy scope names from `src` into `alloc`, and returns the allocated data.
BaseParserScopeData* CopyScopeData(FrontendContext* fc, LifoAlloc& alloc,
                                   ScopeKind kind,
                                   const BaseParserScopeData* src) {
  MOZ_ASSERT(kind != ScopeKind::With);

  size_t dataSize = SizeOfParserScopeData(kind, src->length);

  auto* dest = static_cast<BaseParserScopeData*>(alloc.alloc(dataSize));
  if (!dest) {
    js::ReportOutOfMemory(fc);
    return nullptr;
  }
  memcpy(dest, src, dataSize);

  return dest;
}

template <typename Stencil>
bool ExtensibleCompilationStencil::cloneFromImpl(FrontendContext* fc,
                                                 const Stencil& other) {
  MOZ_ASSERT(alloc.isEmpty());

  canLazilyParse = other.canLazilyParse;
  functionKey = other.functionKey;

  if (!CopyToVector(fc, scriptData, other.scriptData)) {
    return false;
  }

  if (!CopyToVector(fc, scriptExtra, other.scriptExtra)) {
    return false;
  }

  if (!CopyToVector(fc, gcThingData, other.gcThingData)) {
    return false;
  }

  size_t scopeSize = GetLength(other.scopeData);
  if (!CopyToVector(fc, scopeData, other.scopeData)) {
    return false;
  }
  if (!scopeNames.reserve(scopeSize)) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (size_t i = 0; i < scopeSize; i++) {
    if (other.scopeNames[i]) {
      BaseParserScopeData* data = CopyScopeData(
          fc, alloc, other.scopeData[i].kind(), other.scopeNames[i]);
      if (!data) {
        return false;
      }
      scopeNames.infallibleEmplaceBack(data);
    } else {
      scopeNames.infallibleEmplaceBack(nullptr);
    }
  }

  if (!CopyToVector(fc, regExpData, other.regExpData)) {
    return false;
  }

  // If CompilationStencil has external dependency, peform deep copy.

  size_t bigIntSize = GetLength(other.bigIntData);
  if (!bigIntData.resize(bigIntSize)) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (size_t i = 0; i < bigIntSize; i++) {
    if (!bigIntData[i].init(fc, alloc, other.bigIntData[i])) {
      return false;
    }
  }

  size_t objLiteralSize = GetLength(other.objLiteralData);
  if (!objLiteralData.reserve(objLiteralSize)) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (const auto& data : other.objLiteralData) {
    size_t length = data.code().size();
    auto* code = alloc.newArrayUninitialized<uint8_t>(length);
    if (!code) {
      js::ReportOutOfMemory(fc);
      return false;
    }
    memcpy(code, data.code().data(), length);
    objLiteralData.infallibleEmplaceBack(code, length, data.kind(),
                                         data.flags(), data.propertyCount());
  }

  // Regardless of whether CompilationStencil has external dependency or not,
  // ParserAtoms should be interned, to populate internal HashMap.
  for (const auto* entry : other.parserAtomsSpan()) {
    if (!entry) {
      if (!parserAtoms.addPlaceholder(fc)) {
        return false;
      }
      continue;
    }

    auto index = parserAtoms.internExternalParserAtom(fc, entry);
    if (!index) {
      return false;
    }
  }

  // We copy the stencil and increment the reference count of each
  // SharedImmutableScriptData.
  if (!sharedData.cloneFrom(fc, other.sharedData)) {
    return false;
  }

  // Note: moduleMetadata and asmJS are known after the first parse, and are
  // not mutated by any delazifications later on. Thus we can safely increment
  // the reference counter and keep these as-is.
  moduleMetadata = other.moduleMetadata;
  asmJS = other.asmJS;

#ifdef DEBUG
  assertNoExternalDependency();
#endif

  return true;
}

bool ExtensibleCompilationStencil::cloneFrom(FrontendContext* fc,
                                             const CompilationStencil& other) {
  return cloneFromImpl(fc, other);
}
bool ExtensibleCompilationStencil::cloneFrom(
    FrontendContext* fc, const ExtensibleCompilationStencil& other) {
  return cloneFromImpl(fc, other);
}

bool ExtensibleCompilationStencil::steal(FrontendContext* fc,
                                         RefPtr<CompilationStencil>&& other) {
  MOZ_ASSERT(alloc.isEmpty());
  using StorageType = CompilationStencil::StorageType;
  StorageType storageType = other->storageType;
  if (other->hasMultipleReference()) {
    storageType = StorageType::Borrowed;
  }

  if (storageType == StorageType::OwnedExtensible) {
    auto& otherExtensible = other->ownedBorrowStencil;

    canLazilyParse = otherExtensible->canLazilyParse;
    functionKey = otherExtensible->functionKey;

    alloc.steal(&otherExtensible->alloc);

    source = std::move(otherExtensible->source);

    scriptData = std::move(otherExtensible->scriptData);
    scriptExtra = std::move(otherExtensible->scriptExtra);
    gcThingData = std::move(otherExtensible->gcThingData);
    scopeData = std::move(otherExtensible->scopeData);
    scopeNames = std::move(otherExtensible->scopeNames);
    regExpData = std::move(otherExtensible->regExpData);
    bigIntData = std::move(otherExtensible->bigIntData);
    objLiteralData = std::move(otherExtensible->objLiteralData);

    parserAtoms = std::move(otherExtensible->parserAtoms);
    parserAtoms.fixupAlloc(alloc);

    sharedData = std::move(otherExtensible->sharedData);
    moduleMetadata = std::move(otherExtensible->moduleMetadata);
    asmJS = std::move(otherExtensible->asmJS);

#ifdef DEBUG
    assertNoExternalDependency();
#endif

    return true;
  }

  if (storageType == StorageType::Borrowed) {
    return cloneFrom(fc, *other);
  }

  MOZ_ASSERT(storageType == StorageType::Owned);

  canLazilyParse = other->canLazilyParse;
  functionKey = other->functionKey;

#ifdef DEBUG
  other->assertNoExternalDependency();
  MOZ_ASSERT(!other->hasMultipleReference());
#endif

  // If CompilationStencil has no external dependency,
  // steal LifoAlloc and perform shallow copy.
  alloc.steal(&other->alloc);

  if (!CopySpanToVector(fc, scriptData, other->scriptData)) {
    return false;
  }

  if (!CopySpanToVector(fc, scriptExtra, other->scriptExtra)) {
    return false;
  }

  if (!CopySpanToVector(fc, gcThingData, other->gcThingData)) {
    return false;
  }

  if (!CopySpanToVector(fc, scopeData, other->scopeData)) {
    return false;
  }
  if (!CopySpanToVector(fc, scopeNames, other->scopeNames)) {
    return false;
  }

  if (!CopySpanToVector(fc, regExpData, other->regExpData)) {
    return false;
  }

  if (!CopySpanToVector(fc, bigIntData, other->bigIntData)) {
    return false;
  }

  if (!CopySpanToVector(fc, objLiteralData, other->objLiteralData)) {
    return false;
  }

  // Regardless of whether CompilationStencil has external dependency or not,
  // ParserAtoms should be interned, to populate internal HashMap.
  for (const auto* entry : other->parserAtomData) {
    if (!entry) {
      if (!parserAtoms.addPlaceholder(fc)) {
        return false;
      }
      continue;
    }

    auto index = parserAtoms.internExternalParserAtom(fc, entry);
    if (!index) {
      return false;
    }
  }

  sharedData = std::move(other->sharedData);
  moduleMetadata = std::move(other->moduleMetadata);
  asmJS = std::move(other->asmJS);

#ifdef DEBUG
  assertNoExternalDependency();
#endif

  return true;
}

bool CompilationStencil::isModule() const {
  return scriptExtra[CompilationStencil::TopLevelIndex].isModule();
}

bool ExtensibleCompilationStencil::isModule() const {
  return scriptExtra[CompilationStencil::TopLevelIndex].isModule();
}

bool CompilationStencil::hasAsmJS() const { return asmJS; }

bool ExtensibleCompilationStencil::hasAsmJS() const { return asmJS; }

bool InitialStencilAndDelazifications::hasAsmJS() const {
  return initial_->hasAsmJS();
}

InitialStencilAndDelazifications::~InitialStencilAndDelazifications() {
  MOZ_ASSERT(refCount_ == 0);

  for (size_t i = 0; i < delazifications_.length(); i++) {
    CompilationStencil* delazification = delazifications_[i].exchange(nullptr);
    if (delazification) {
      delazification->Release();
    }
  }
}

void InitialStencilAndDelazifications::AddRef() { refCount_++; }

void InitialStencilAndDelazifications::Release() {
  MOZ_RELEASE_ASSERT(refCount_ > 0);
  if (--refCount_ == 0) {
    js_delete(this);
  }
}

bool InitialStencilAndDelazifications::init(FrontendContext* fc,
                                            const CompilationStencil* initial) {
  MOZ_ASSERT(initial->isInitialStencil());

  initial_ = initial;

  if (!canLazilyParse()) {
    // If the initial stencil is known to be fully-parsed, delazification
    // never happens, and the delazifications_ vector and the
    // functionKeyToInitialScriptIndex_ map is never used.
    return true;
  }

  if (!delazifications_.resize(initial_->scriptData.size())) {
    ReportOutOfMemory(fc);
    return false;
  }

  return functionKeyToInitialScriptIndex_.init(fc, initial_);
}

const CompilationStencil* InitialStencilAndDelazifications::getInitial() const {
  return initial_.get();
}

const CompilationStencil* InitialStencilAndDelazifications::getDelazificationAt(
    size_t functionIndex) const {
  MOZ_ASSERT(canLazilyParse());
  MOZ_ASSERT(functionIndex > 0);

  return delazifications_[functionIndex - 1];
}

const CompilationStencil*
InitialStencilAndDelazifications::getDelazificationFor(
    const SourceExtent& extent) const {
  MOZ_ASSERT(canLazilyParse());
  auto maybeIndex =
      functionKeyToInitialScriptIndex_.get(extent.toFunctionKey());
  MOZ_ASSERT(maybeIndex,
             "The extent parameter should be for a function inside the script");
  return getDelazificationAt(*maybeIndex);
}

const CompilationStencil* InitialStencilAndDelazifications::storeDelazification(
    RefPtr<CompilationStencil>&& delazification) {
  MOZ_ASSERT(!delazification->hasMultipleReference());
  MOZ_ASSERT(canLazilyParse());

  auto maybeIndex =
      functionKeyToInitialScriptIndex_.get(delazification->functionKey);
  MOZ_ASSERT(maybeIndex);
  size_t functionIndex = *maybeIndex;

  CompilationStencil* raw = delazification.forget().take();
  if (delazifications_[functionIndex - 1].compareExchange(nullptr, raw)) {
    return raw;
  }

  raw->Release();
  return delazifications_[functionIndex - 1];
}

CompilationStencil* InitialStencilAndDelazifications::getMerged(
    FrontendContext* fc) const {
  MOZ_ASSERT(canLazilyParse());

  UniquePtr<ExtensibleCompilationStencil> extensibleStencil(
      fc->getAllocator()->new_<ExtensibleCompilationStencil>(initial_->source));
  if (!extensibleStencil) {
    return nullptr;
  }

  if (!extensibleStencil->cloneFrom(fc, *initial_)) {
    return nullptr;
  }

  CompilationStencilMerger merger;
  if (!merger.setInitial(fc, std::move(extensibleStencil))) {
    return nullptr;
  }

  for (const auto& delazification : delazifications_) {
    if (!delazification) {
      continue;
    }

    // NOTE: The delazifications_ vector can be modified by other threads
    //       during the iteration.
    //       The enclosing delazification's is not guaranteed to be iterated
    //       over in this iteration.
    //       If the enclosing function wasn't merged, all inner functions are
    //       ignored inside maybeAddDelazification.
    if (!merger.maybeAddDelazification(fc, *delazification)) {
      return nullptr;
    }
  }

  UniquePtr<ExtensibleCompilationStencil> merged = merger.takeResult();
  return fc->getAllocator()->new_<CompilationStencil>(std::move(merged));
}

/* static */
bool InitialStencilAndDelazifications::instantiateStencils(
    JSContext* cx, CompilationInput& input,
    InitialStencilAndDelazifications& stencils, CompilationGCOutput& gcOutput) {
  if (!CompilationStencil::instantiateStencils(cx, input, *stencils.initial_,
                                               gcOutput)) {
    return false;
  }

  if (input.options.populateDelazificationCache()) {
    RefPtr<InitialStencilAndDelazifications> stencilsPtr = &stencils;
    ScriptSourceObject* sso = gcOutput.script->sourceObject();
    MOZ_ASSERT(!sso->maybeGetStencils());
    if (!stencils.hasAsmJS()) {
      sso->setStencils(stencilsPtr.forget());
      sso->setSharingDelazifications();
    }
  }

  // At this point, gcOutput.script contains the top-level script, and
  // gcOutput.functions[i] contains i-th function, where 0-th item is
  // always nullptr.
  // gcOutput.functions[i]->baseScript() is either JSScript or lazy script.
  for (size_t i = 0, length = stencils.delazifications_.length(); i < length;
       i++) {
    const auto& delazification = stencils.delazifications_[i];
    if (!delazification) {
      continue;
    }

    ScriptIndex scriptIndex = ScriptIndex(i + 1);
    JS::Rooted<JSFunction*> fun(cx, gcOutput.functions[scriptIndex]);
    if (!fun->baseScript()->isReadyForDelazification()) {
      // NOTE: The delazifications_ vector can be modified by other threads
      //       during the iteration.
      //       The enclosing delazification's is not guaranteed to be iterated
      //       over in this iteration.
      //       If the enclosing function wasn't instantiated, ignore all inner
      //       functions.
      continue;
    }

    JS::Rooted<CompilationInput> inputForFunc(cx,
                                              CompilationInput(input.options));
    inputForFunc.get().initFromLazy(cx, fun->baseScript(), input.source);

    // TODO: The preparation can be shared across iterations.
    JS::Rooted<CompilationGCOutput> gcOutputForFunc(cx);
    if (!CompilationStencil::instantiateStencils(
            cx, inputForFunc.get(), *delazification, gcOutputForFunc.get())) {
      return false;
    }
  }

  return true;
}

size_t InitialStencilAndDelazifications::sizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  size_t size = 0;

  if (initial_) {
    // The initial stencil can be shared between multiple owners, but
    // in most case this instance is considered as the main owner, in term
    // of the memory reporting.
    size += initial_->sizeOfExcludingThis(mallocSizeOf);
  }

  size += delazifications_.sizeOfExcludingThis(mallocSizeOf);

  for (const auto& delazification : delazifications_) {
    if (!delazification) {
      continue;
    }

    // Delazifications are exclusively owned by this instance.
    size += (*delazification).sizeOfExcludingThis(mallocSizeOf);
  }

  size += functionKeyToInitialScriptIndex_.sizeOfExcludingThis(mallocSizeOf);

  return size;
}

mozilla::Span<TaggedScriptThingIndex> ScriptStencil::gcthings(
    const CompilationStencil& stencil) const {
  return stencil.gcThingData.Subspan(gcThingsOffset, gcThingsLength);
}

bool BigIntStencil::initFromChars(FrontendContext* fc, LifoAlloc& alloc,
                                  mozilla::Span<const char16_t> buf) {
  MOZ_ASSERT(ParseBigInt64Literal(buf).isNothing(),
             "int64-sized BigInts are stored inline");

  size_t length = buf.size();
  MOZ_ASSERT(length > 0);

  char16_t* p = alloc.template newArrayUninitialized<char16_t>(length);
  if (!p) {
    ReportOutOfMemory(fc);
    return false;
  }
  mozilla::PodCopy(p, buf.data(), length);
  bigInt_ = mozilla::AsVariant(mozilla::Span(p, length));
  return true;
}

bool BigIntStencil::init(FrontendContext* fc, LifoAlloc& alloc,
                         mozilla::Span<const char16_t> buf) {
  if (auto int64 = ParseBigInt64Literal(buf)) {
    bigInt_ = mozilla::AsVariant(*int64);
    return true;
  }
  return initFromChars(fc, alloc, buf);
}

bool BigIntStencil::init(FrontendContext* fc, LifoAlloc& alloc,
                         const BigIntStencil& other) {
  if (other.bigInt_.is<int64_t>()) {
    bigInt_ = other.bigInt_;
    return true;
  }
  return initFromChars(fc, alloc, other.source());
}

BigInt* BigIntStencil::createBigInt(JSContext* cx) const {
  return bigInt_.match(
      [cx](mozilla::Span<char16_t> source) {
        return js::ParseBigIntLiteral(cx, source);
      },
      [cx](int64_t int64) {
        // BigInts are stored in the script's data vector and therefore need to
        // be allocated in the tenured heap.
        constexpr gc::Heap heap = gc::Heap::Tenured;
        return BigInt::createFromInt64(cx, int64, heap);
      });
}

bool BigIntStencil::isZero() const {
  return bigInt_.match([](mozilla::Span<char16_t>) { return false; },
                       [](int64_t int64) { return int64 == 0; });
}

bool BigIntStencil::inplaceNegate() {
  return bigInt_.match([](mozilla::Span<char16_t>) { return false; },
                       [](int64_t& int64) {
                         auto negated = -mozilla::CheckedInt<int64_t>{int64};
                         if (!negated.isValid()) {
                           return false;
                         }
                         int64 = negated.value();
                         return true;
                       });
}

bool BigIntStencil::inplaceBitNot() {
  return bigInt_.match([](mozilla::Span<char16_t>) { return false; },
                       [](int64_t& int64) {
                         int64 = ~int64;
                         return true;
                       });
}

#ifdef DEBUG
bool BigIntStencil::isContainedIn(const LifoAlloc& alloc) const {
  return bigInt_.match(
      [&alloc](mozilla::Span<char16_t> source) {
        return alloc.contains(source.data());
      },
      [](int64_t) { return true; });
}
#endif

#if defined(DEBUG) || defined(JS_JITSPEW)

void frontend::DumpTaggedParserAtomIndex(js::JSONPrinter& json,
                                         TaggedParserAtomIndex taggedIndex,
                                         const CompilationStencil* stencil) {
  if (taggedIndex.isParserAtomIndex()) {
    json.property("tag", "AtomIndex");
    auto index = taggedIndex.toParserAtomIndex();
    if (stencil && stencil->parserAtomData[index]) {
      GenericPrinter& out = json.beginStringProperty("atom");
      stencil->parserAtomData[index]->dumpCharsNoQuote(out);
      json.endString();
    } else {
      json.property("index", size_t(index));
    }
    return;
  }

  if (taggedIndex.isWellKnownAtomId()) {
    json.property("tag", "WellKnown");
    auto index = taggedIndex.toWellKnownAtomId();
    switch (index) {
      case WellKnownAtomId::empty_:
        json.property("atom", "");
        break;

#  define CASE_(name, _) case WellKnownAtomId::name:
        FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_)
#  undef CASE_

#  define CASE_(name, _) case WellKnownAtomId::name:
        JS_FOR_EACH_PROTOTYPE(CASE_)
#  undef CASE_

#  define CASE_(name) case WellKnownAtomId::name:
        JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_)
#  undef CASE_

        {
          GenericPrinter& out = json.beginStringProperty("atom");
          ParserAtomsTable::dumpCharsNoQuote(out, index);
          json.endString();
          break;
        }

      default:
        // This includes tiny WellKnownAtomId atoms, which is invalid.
        json.property("index", size_t(index));
        break;
    }
    return;
  }

  if (taggedIndex.isLength1StaticParserString()) {
    json.property("tag", "Length1Static");
    auto index = taggedIndex.toLength1StaticParserString();
    GenericPrinter& out = json.beginStringProperty("atom");
    ParserAtomsTable::dumpCharsNoQuote(out, index);
    json.endString();
    return;
  }

  if (taggedIndex.isLength2StaticParserString()) {
    json.property("tag", "Length2Static");
    auto index = taggedIndex.toLength2StaticParserString();
    GenericPrinter& out = json.beginStringProperty("atom");
    ParserAtomsTable::dumpCharsNoQuote(out, index);
    json.endString();
    return;
  }

  if (taggedIndex.isLength3StaticParserString()) {
    json.property("tag", "Length3Static");
    auto index = taggedIndex.toLength3StaticParserString();
    GenericPrinter& out = json.beginStringProperty("atom");
    ParserAtomsTable::dumpCharsNoQuote(out, index);
    json.endString();
    return;
  }

  MOZ_ASSERT(taggedIndex.isNull());
  json.property("tag", "null");
}

void frontend::DumpTaggedParserAtomIndexNoQuote(
    GenericPrinter& out, TaggedParserAtomIndex taggedIndex,
    const CompilationStencil* stencil) {
  if (taggedIndex.isParserAtomIndex()) {
    auto index = taggedIndex.toParserAtomIndex();
    if (stencil && stencil->parserAtomData[index]) {
      stencil->parserAtomData[index]->dumpCharsNoQuote(out);
    } else {
      out.printf("AtomIndex#%zu", size_t(index));
    }
    return;
  }

  if (taggedIndex.isWellKnownAtomId()) {
    auto index = taggedIndex.toWellKnownAtomId();
    switch (index) {
      case WellKnownAtomId::empty_:
        out.put("#<zero-length name>");
        break;

#  define CASE_(name, _) case WellKnownAtomId::name:
        FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_)
#  undef CASE_

#  define CASE_(name, _) case WellKnownAtomId::name:
        JS_FOR_EACH_PROTOTYPE(CASE_)
#  undef CASE_

#  define CASE_(name) case WellKnownAtomId::name:
        JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_)
#  undef CASE_

        {
          ParserAtomsTable::dumpCharsNoQuote(out, index);
          break;
        }

      default:
        // This includes tiny WellKnownAtomId atoms, which is invalid.
        out.printf("WellKnown#%zu", size_t(index));
        break;
    }
    return;
  }

  if (taggedIndex.isLength1StaticParserString()) {
    auto index = taggedIndex.toLength1StaticParserString();
    ParserAtomsTable::dumpCharsNoQuote(out, index);
    return;
  }

  if (taggedIndex.isLength2StaticParserString()) {
    auto index = taggedIndex.toLength2StaticParserString();
    ParserAtomsTable::dumpCharsNoQuote(out, index);
    return;
  }

  if (taggedIndex.isLength3StaticParserString()) {
    auto index = taggedIndex.toLength3StaticParserString();
    ParserAtomsTable::dumpCharsNoQuote(out, index);
    return;
  }

  MOZ_ASSERT(taggedIndex.isNull());
  out.put("#<null name>");
}

void RegExpStencil::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json, nullptr);
}

void RegExpStencil::dump(js::JSONPrinter& json,
                         const CompilationStencil* stencil) const {
  json.beginObject();
  dumpFields(json, stencil);
  json.endObject();
}

void RegExpStencil::dumpFields(js::JSONPrinter& json,
                               const CompilationStencil* stencil) const {
  json.beginObjectProperty("pattern");
  DumpTaggedParserAtomIndex(json, atom_, stencil);
  json.endObject();

  GenericPrinter& out = json.beginStringProperty("flags");

  if (flags().global()) {
    out.put("g");
  }
  if (flags().ignoreCase()) {
    out.put("i");
  }
  if (flags().multiline()) {
    out.put("m");
  }
  if (flags().dotAll()) {
    out.put("s");
  }
  if (flags().unicode()) {
    out.put("u");
  }
  if (flags().sticky()) {
    out.put("y");
  }

  json.endStringProperty();
}

void BigIntStencil::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json);
}

void BigIntStencil::dump(js::JSONPrinter& json) const {
  GenericPrinter& out = json.beginString();
  dumpCharsNoQuote(out);
  json.endString();
}

void BigIntStencil::dumpCharsNoQuote(GenericPrinter& out) const {
  bigInt_.match(
      [&out](mozilla::Span<char16_t> source) {
        for (char16_t c : source) {
          out.putChar(char(c));
        }
      },
      [&out](int64_t int64) { out.printf("%" PRId64, int64); });
}

void ScopeStencil::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json, nullptr, nullptr);
}

void ScopeStencil::dump(js::JSONPrinter& json,
                        const BaseParserScopeData* baseScopeData,
                        const CompilationStencil* stencil) const {
  json.beginObject();
  dumpFields(json, baseScopeData, stencil);
  json.endObject();
}

void ScopeStencil::dumpFields(js::JSONPrinter& json,
                              const BaseParserScopeData* baseScopeData,
                              const CompilationStencil* stencil) const {
  json.property("kind", ScopeKindString(kind_));

  if (hasEnclosing()) {
    json.formatProperty("enclosing", "ScopeIndex(%zu)", size_t(enclosing()));
  }

  json.property("firstFrameSlot", firstFrameSlot_);

  if (hasEnvironmentShape()) {
    json.formatProperty("numEnvironmentSlots", "%zu",
                        size_t(numEnvironmentSlots_));
  }

  if (isFunction()) {
    json.formatProperty("functionIndex", "ScriptIndex(%zu)",
                        size_t(functionIndex_));
  }

  json.beginListProperty("flags");
  if (flags_ & HasEnclosing) {
    json.value("HasEnclosing");
  }
  if (flags_ & HasEnvironmentShape) {
    json.value("HasEnvironmentShape");
  }
  if (flags_ & IsArrow) {
    json.value("IsArrow");
  }
  json.endList();

  if (!baseScopeData) {
    return;
  }

  json.beginObjectProperty("data");

  mozilla::Span<const ParserBindingName> trailingNames;
  switch (kind_) {
    case ScopeKind::Function: {
      const auto* data =
          static_cast<const FunctionScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
      json.property("hasParameterExprs", data->slotInfo.hasParameterExprs());
      json.property("nonPositionalFormalStart",
                    data->slotInfo.nonPositionalFormalStart);
      json.property("varStart", data->slotInfo.varStart);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::FunctionBodyVar: {
      const auto* data =
          static_cast<const VarScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::Lexical:
    case ScopeKind::SimpleCatch:
    case ScopeKind::Catch:
    case ScopeKind::NamedLambda:
    case ScopeKind::StrictNamedLambda:
    case ScopeKind::FunctionLexical: {
      const auto* data =
          static_cast<const LexicalScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
      json.property("constStart", data->slotInfo.constStart);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::ClassBody: {
      const auto* data =
          static_cast<const ClassBodyScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
      json.property("privateMethodStart", data->slotInfo.privateMethodStart);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::With: {
      break;
    }

    case ScopeKind::Eval:
    case ScopeKind::StrictEval: {
      const auto* data =
          static_cast<const EvalScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::Global:
    case ScopeKind::NonSyntactic: {
      const auto* data =
          static_cast<const GlobalScope::ParserData*>(baseScopeData);
      json.property("letStart", data->slotInfo.letStart);
      json.property("constStart", data->slotInfo.constStart);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::Module: {
      const auto* data =
          static_cast<const ModuleScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
      json.property("varStart", data->slotInfo.varStart);
      json.property("letStart", data->slotInfo.letStart);
      json.property("constStart", data->slotInfo.constStart);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::WasmInstance: {
      const auto* data =
          static_cast<const WasmInstanceScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
      json.property("globalsStart", data->slotInfo.globalsStart);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    case ScopeKind::WasmFunction: {
      const auto* data =
          static_cast<const WasmFunctionScope::ParserData*>(baseScopeData);
      json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);

      trailingNames = GetScopeDataTrailingNames(data);
      break;
    }

    default: {
      MOZ_CRASH("Unexpected ScopeKind");
      break;
    }
  }

  if (!trailingNames.empty()) {
    char index[64];
    json.beginObjectProperty("trailingNames");
    for (size_t i = 0; i < trailingNames.size(); i++) {
      const auto& name = trailingNames[i];
      SprintfLiteral(index, "%zu", i);
      json.beginObjectProperty(index);

      json.boolProperty("closedOver", name.closedOver());

      json.boolProperty("isTopLevelFunction", name.isTopLevelFunction());

      json.beginObjectProperty("name");
      DumpTaggedParserAtomIndex(json, name.name(), stencil);
      json.endObject();

      json.endObject();
    }
    json.endObject();
  }

  json.endObject();
}

static void DumpModuleRequestVectorItems(
    js::JSONPrinter& json, const StencilModuleMetadata::RequestVector& requests,
    const CompilationStencil* stencil) {
  for (const auto& request : requests) {
    json.beginObject();
    if (request.specifier) {
      json.beginObjectProperty("specifier");
      DumpTaggedParserAtomIndex(json, request.specifier, stencil);
      json.endObject();
    }
    json.endObject();
  }
}

static void DumpModuleEntryVectorItems(
    js::JSONPrinter& json, const StencilModuleMetadata::EntryVector& entries,
    const CompilationStencil* stencil) {
  for (const auto& entry : entries) {
    json.beginObject();
    if (entry.moduleRequest) {
      json.property("moduleRequest", entry.moduleRequest.value());
    }
    if (entry.localName) {
      json.beginObjectProperty("localName");
      DumpTaggedParserAtomIndex(json, entry.localName, stencil);
      json.endObject();
    }
    if (entry.importName) {
      json.beginObjectProperty("importName");
      DumpTaggedParserAtomIndex(json, entry.importName, stencil);
      json.endObject();
    }
    if (entry.exportName) {
      json.beginObjectProperty("exportName");
      DumpTaggedParserAtomIndex(json, entry.exportName, stencil);
      json.endObject();
    }
    // TODO: Dump assertions.
    json.endObject();
  }
}

void StencilModuleMetadata::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json, nullptr);
}

void StencilModuleMetadata::dump(js::JSONPrinter& json,
                                 const CompilationStencil* stencil) const {
  json.beginObject();
  dumpFields(json, stencil);
  json.endObject();
}

void StencilModuleMetadata::dumpFields(
    js::JSONPrinter& json, const CompilationStencil* stencil) const {
  json.beginListProperty("moduleRequests");
  DumpModuleRequestVectorItems(json, moduleRequests, stencil);
  json.endList();

  json.beginListProperty("requestedModules");
  DumpModuleEntryVectorItems(json, requestedModules, stencil);
  json.endList();

  json.beginListProperty("importEntries");
  DumpModuleEntryVectorItems(json, importEntries, stencil);
  json.endList();

  json.beginListProperty("localExportEntries");
  DumpModuleEntryVectorItems(json, localExportEntries, stencil);
  json.endList();

  json.beginListProperty("indirectExportEntries");
  DumpModuleEntryVectorItems(json, indirectExportEntries, stencil);
  json.endList();

  json.beginListProperty("starExportEntries");
  DumpModuleEntryVectorItems(json, starExportEntries, stencil);
  json.endList();

  json.beginListProperty("functionDecls");
  for (const auto& index : functionDecls) {
    json.value("ScriptIndex(%zu)", size_t(index));
  }
  json.endList();

  json.boolProperty("isAsync", isAsync);
}

void js::DumpImmutableScriptFlags(js::JSONPrinter& json,
                                  ImmutableScriptFlags immutableFlags) {
  for (uint32_t i = 1; i; i = i << 1) {
    if (uint32_t(immutableFlags) & i) {
      switch (ImmutableScriptFlagsEnum(i)) {
        case ImmutableScriptFlagsEnum::IsForEval:
          json.value("IsForEval");
          break;
        case ImmutableScriptFlagsEnum::IsModule:
          json.value("IsModule");
          break;
        case ImmutableScriptFlagsEnum::IsFunction:
          json.value("IsFunction");
          break;
        case ImmutableScriptFlagsEnum::SelfHosted:
          json.value("SelfHosted");
          break;
        case ImmutableScriptFlagsEnum::ForceStrict:
          json.value("ForceStrict");
          break;
        case ImmutableScriptFlagsEnum::HasNonSyntacticScope:
          json.value("HasNonSyntacticScope");
          break;
        case ImmutableScriptFlagsEnum::NoScriptRval:
          json.value("NoScriptRval");
          break;
        case ImmutableScriptFlagsEnum::TreatAsRunOnce:
          json.value("TreatAsRunOnce");
          break;
        case ImmutableScriptFlagsEnum::Strict:
          json.value("Strict");
          break;
        case ImmutableScriptFlagsEnum::HasModuleGoal:
          json.value("HasModuleGoal");
          break;
        case ImmutableScriptFlagsEnum::HasInnerFunctions:
          json.value("HasInnerFunctions");
          break;
        case ImmutableScriptFlagsEnum::HasDirectEval:
          json.value("HasDirectEval");
          break;
        case ImmutableScriptFlagsEnum::BindingsAccessedDynamically:
          json.value("BindingsAccessedDynamically");
          break;
        case ImmutableScriptFlagsEnum::HasCallSiteObj:
          json.value("HasCallSiteObj");
          break;
        case ImmutableScriptFlagsEnum::IsAsync:
          json.value("IsAsync");
          break;
        case ImmutableScriptFlagsEnum::IsGenerator:
          json.value("IsGenerator");
          break;
        case ImmutableScriptFlagsEnum::FunHasExtensibleScope:
          json.value("FunHasExtensibleScope");
          break;
        case ImmutableScriptFlagsEnum::FunctionHasThisBinding:
          json.value("FunctionHasThisBinding");
          break;
        case ImmutableScriptFlagsEnum::NeedsHomeObject:
          json.value("NeedsHomeObject");
          break;
        case ImmutableScriptFlagsEnum::IsDerivedClassConstructor:
          json.value("IsDerivedClassConstructor");
          break;
        case ImmutableScriptFlagsEnum::IsSyntheticFunction:
          json.value("IsSyntheticFunction");
          break;
        case ImmutableScriptFlagsEnum::UseMemberInitializers:
          json.value("UseMemberInitializers");
          break;
        case ImmutableScriptFlagsEnum::HasRest:
          json.value("HasRest");
          break;
        case ImmutableScriptFlagsEnum::NeedsFunctionEnvironmentObjects:
          json.value("NeedsFunctionEnvironmentObjects");
          break;
        case ImmutableScriptFlagsEnum::FunctionHasExtraBodyVarScope:
          json.value("FunctionHasExtraBodyVarScope");
          break;
        case ImmutableScriptFlagsEnum::ShouldDeclareArguments:
          json.value("ShouldDeclareArguments");
          break;
        case ImmutableScriptFlagsEnum::NeedsArgsObj:
          json.value("NeedsArgsObj");
          break;
        case ImmutableScriptFlagsEnum::HasMappedArgsObj:
          json.value("HasMappedArgsObj");
          break;
        case ImmutableScriptFlagsEnum::IsInlinableLargeFunction:
          json.value("IsInlinableLargeFunction");
          break;
        case ImmutableScriptFlagsEnum::FunctionHasNewTargetBinding:
          json.value("FunctionHasNewTargetBinding");
          break;
        case ImmutableScriptFlagsEnum::UsesArgumentsIntrinsics:
          json.value("UsesArgumentsIntrinsics");
          break;
        default:
          json.value("Unknown(%x)", i);
          break;
      }
    }
  }
}

void js::DumpFunctionFlagsItems(js::JSONPrinter& json,
                                FunctionFlags functionFlags) {
  switch (functionFlags.kind()) {
    case FunctionFlags::FunctionKind::NormalFunction:
      json.value("NORMAL_KIND");
      break;
    case FunctionFlags::FunctionKind::AsmJS:
      json.value("ASMJS_KIND");
      break;
    case FunctionFlags::FunctionKind::Wasm:
      json.value("WASM_KIND");
      break;
    case FunctionFlags::FunctionKind::Arrow:
      json.value("ARROW_KIND");
      break;
    case FunctionFlags::FunctionKind::Method:
      json.value("METHOD_KIND");
      break;
    case FunctionFlags::FunctionKind::ClassConstructor:
      json.value("CLASSCONSTRUCTOR_KIND");
      break;
    case FunctionFlags::FunctionKind::Getter:
      json.value("GETTER_KIND");
      break;
    case FunctionFlags::FunctionKind::Setter:
      json.value("SETTER_KIND");
      break;
    default:
      json.value("Unknown(%x)", uint8_t(functionFlags.kind()));
      break;
  }

  static_assert(FunctionFlags::FUNCTION_KIND_MASK == 0x0007,
                "FunctionKind should use the lowest 3 bits");
  for (uint16_t i = 1 << 3; i; i = i << 1) {
    if (functionFlags.toRaw() & i) {
      switch (FunctionFlags::Flags(i)) {
        case FunctionFlags::Flags::EXTENDED:
          json.value("EXTENDED");
          break;
        case FunctionFlags::Flags::SELF_HOSTED:
          json.value("SELF_HOSTED");
          break;
        case FunctionFlags::Flags::BASESCRIPT:
          json.value("BASESCRIPT");
          break;
        case FunctionFlags::Flags::SELFHOSTLAZY:
          json.value("SELFHOSTLAZY");
          break;
        case FunctionFlags::Flags::CONSTRUCTOR:
          json.value("CONSTRUCTOR");
          break;
        case FunctionFlags::Flags::LAZY_ACCESSOR_NAME:
          json.value("LAZY_ACCESSOR_NAME");
          break;
        case FunctionFlags::Flags::LAMBDA:
          json.value("LAMBDA");
          break;
        case FunctionFlags::Flags::NATIVE_JIT_ENTRY:
          json.value("NATIVE_JIT_ENTRY");
          break;
        case FunctionFlags::Flags::HAS_INFERRED_NAME:
          json.value("HAS_INFERRED_NAME");
          break;
        case FunctionFlags::Flags::HAS_GUESSED_ATOM:
          json.value("HAS_GUESSED_ATOM");
          break;
        case FunctionFlags::Flags::RESOLVED_NAME:
          json.value("RESOLVED_NAME");
          break;
        case FunctionFlags::Flags::RESOLVED_LENGTH:
          json.value("RESOLVED_LENGTH");
          break;
        case FunctionFlags::Flags::GHOST_FUNCTION:
          json.value("GHOST_FUNCTION");
          break;
        default:
          json.value("Unknown(%x)", i);
          break;
      }
    }
  }
}

static void DumpScriptThing(js::JSONPrinter& json,
                            const CompilationStencil* stencil,
                            TaggedScriptThingIndex thing) {
  switch (thing.tag()) {
    case TaggedScriptThingIndex::Kind::ParserAtomIndex:
    case TaggedScriptThingIndex::Kind::WellKnown:
      json.beginObject();
      json.property("type", "Atom");
      DumpTaggedParserAtomIndex(json, thing.toAtom(), stencil);
      json.endObject();
      break;
    case TaggedScriptThingIndex::Kind::Null:
      json.nullValue();
      break;
    case TaggedScriptThingIndex::Kind::BigInt:
      json.value("BigIntIndex(%zu)", size_t(thing.toBigInt()));
      break;
    case TaggedScriptThingIndex::Kind::ObjLiteral:
      json.value("ObjLiteralIndex(%zu)", size_t(thing.toObjLiteral()));
      break;
    case TaggedScriptThingIndex::Kind::RegExp:
      json.value("RegExpIndex(%zu)", size_t(thing.toRegExp()));
      break;
    case TaggedScriptThingIndex::Kind::Scope:
      json.value("ScopeIndex(%zu)", size_t(thing.toScope()));
      break;
    case TaggedScriptThingIndex::Kind::Function:
      json.value("ScriptIndex(%zu)", size_t(thing.toFunction()));
      break;
    case TaggedScriptThingIndex::Kind::EmptyGlobalScope:
      json.value("EmptyGlobalScope");
      break;
  }
}

void ScriptStencil::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json, nullptr);
}

void ScriptStencil::dump(js::JSONPrinter& json,
                         const CompilationStencil* stencil) const {
  json.beginObject();
  dumpFields(json, stencil);
  json.endObject();
}

void ScriptStencil::dumpFields(js::JSONPrinter& json,
                               const CompilationStencil* stencil) const {
  json.formatProperty("gcThingsOffset", "CompilationGCThingIndex(%u)",
                      gcThingsOffset.index);
  json.property("gcThingsLength", gcThingsLength);

  if (stencil) {
    json.beginListProperty("gcThings");
    for (const auto& thing : gcthings(*stencil)) {
      DumpScriptThing(json, stencil, thing);
    }
    json.endList();
  }

  json.beginListProperty("flags");
  if (flags_ & WasEmittedByEnclosingScriptFlag) {
    json.value("WasEmittedByEnclosingScriptFlag");
  }
  if (flags_ & AllowRelazifyFlag) {
    json.value("AllowRelazifyFlag");
  }
  if (flags_ & HasSharedDataFlag) {
    json.value("HasSharedDataFlag");
  }
  if (flags_ & HasLazyFunctionEnclosingScopeIndexFlag) {
    json.value("HasLazyFunctionEnclosingScopeIndexFlag");
  }
  json.endList();

  if (isFunction()) {
    json.beginObjectProperty("functionAtom");
    DumpTaggedParserAtomIndex(json, functionAtom, stencil);
    json.endObject();

    json.beginListProperty("functionFlags");
    DumpFunctionFlagsItems(json, functionFlags);
    json.endList();

    if (hasLazyFunctionEnclosingScopeIndex()) {
      json.formatProperty("lazyFunctionEnclosingScopeIndex", "ScopeIndex(%zu)",
                          size_t(lazyFunctionEnclosingScopeIndex()));
    }

    if (hasSelfHostedCanonicalName()) {
      json.beginObjectProperty("selfHostCanonicalName");
      DumpTaggedParserAtomIndex(json, selfHostedCanonicalName(), stencil);
      json.endObject();
    }
  }
}

void ScriptStencilExtra::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json);
}

void ScriptStencilExtra::dump(js::JSONPrinter& json) const {
  json.beginObject();
  dumpFields(json);
  json.endObject();
}

void ScriptStencilExtra::dumpFields(js::JSONPrinter& json) const {
  json.beginListProperty("immutableFlags");
  DumpImmutableScriptFlags(json, immutableFlags);
  json.endList();

  json.beginObjectProperty("extent");
  json.property("sourceStart", extent.sourceStart);
  json.property("sourceEnd", extent.sourceEnd);
  json.property("toStringStart", extent.toStringStart);
  json.property("toStringEnd", extent.toStringEnd);
  json.property("lineno", extent.lineno);
  json.property("column", extent.column.oneOriginValue());
  json.endObject();

  json.property("memberInitializers", memberInitializers_);

  json.property("nargs", nargs);
}

void SharedDataContainer::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json);
}

void SharedDataContainer::dump(js::JSONPrinter& json) const {
  json.beginObject();
  dumpFields(json);
  json.endObject();
}

void SharedDataContainer::dumpFields(js::JSONPrinter& json) const {
  if (isEmpty()) {
    json.nullProperty("ScriptIndex(0)");
    return;
  }

  if (isSingle()) {
    json.formatProperty("ScriptIndex(0)", "u8[%zu]",
                        asSingle()->immutableDataLength());
    return;
  }

  if (isVector()) {
    auto& vec = *asVector();

    char index[64];
    for (size_t i = 0; i < vec.length(); i++) {
      SprintfLiteral(index, "ScriptIndex(%zu)", i);
      if (vec[i]) {
        json.formatProperty(index, "u8[%zu]", vec[i]->immutableDataLength());
      } else {
        json.nullProperty(index);
      }
    }
    return;
  }

  if (isMap()) {
    auto& map = *asMap();

    char index[64];
    for (auto iter = map.iter(); !iter.done(); iter.next()) {
      SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index);
      json.formatProperty(index, "u8[%zu]",
                          iter.get().value()->immutableDataLength());
    }
    return;
  }

  MOZ_ASSERT(isBorrow());
  asBorrow()->dumpFields(json);
}

struct DumpOptionsFields {
  js::JSONPrinter& json;

  void operator()(const char* name, JS::AsmJSOption value) {
    const char* valueStr = nullptr;
    switch (value) {
      case JS::AsmJSOption::Enabled:
        valueStr = "JS::AsmJSOption::Enabled";
        break;
      case JS::AsmJSOption::DisabledByAsmJSPref:
        valueStr = "JS::AsmJSOption::DisabledByAsmJSPref";
        break;
      case JS::AsmJSOption::DisabledByLinker:
        valueStr = "JS::AsmJSOption::DisabledByLinker";
        break;
      case JS::AsmJSOption::DisabledByNoWasmCompiler:
        valueStr = "JS::AsmJSOption::DisabledByNoWasmCompiler";
        break;
      case JS::AsmJSOption::DisabledByDebugger:
        valueStr = "JS::AsmJSOption::DisabledByDebugger";
        break;
    }
    json.property(name, valueStr);
  }

  void operator()(const char* name, JS::DelazificationOption value) {
    const char* valueStr = nullptr;
    switch (value) {
#  define SelectValueStr_(Strategy)                      \
    case JS::DelazificationOption::Strategy:             \
      valueStr = "JS::DelazificationOption::" #Strategy; \
      break;

      FOREACH_DELAZIFICATION_STRATEGY(SelectValueStr_)
#  undef SelectValueStr_
    }
    json.property(name, valueStr);
  }

  void operator()(const char* name, char16_t* value) {}

  void operator()(const char* name, bool value) { json.property(name, value); }

  void operator()(const char* name, uint32_t value) {
    json.property(name, value);
  }

  void operator()(const char* name, uint64_t value) {
    json.property(name, value);
  }

  void operator()(const char* name, const char* value) {
    if (value) {
      json.property(name, value);
      return;
    }
    json.nullProperty(name);
  }

  void operator()(const char* name, JS::ConstUTF8CharsZ value) {
    if (value) {
      json.property(name, value.c_str());
      return;
    }
    json.nullProperty(name);
  }
};

static void DumpOptionsFields(js::JSONPrinter& json,
                              const JS::ReadOnlyCompileOptions& options) {
  struct DumpOptionsFields printer{json};
  options.dumpWith(printer);
}

static void DumpInputScopeFields(js::JSONPrinter& json,
                                 const InputScope& scope) {
  json.property("kind", ScopeKindString(scope.kind()));

  InputScope enclosing = scope.enclosing();
  if (enclosing.isNull()) {
    json.nullProperty("enclosing");
  } else {
    json.beginObjectProperty("enclosing");
    DumpInputScopeFields(json, enclosing);
    json.endObject();
  }
}

static void DumpInputScriptFields(js::JSONPrinter& json,
                                  const InputScript& script) {
  json.beginObjectProperty("extent");
  {
    SourceExtent extent = script.extent();
    json.property("sourceStart", extent.sourceStart);
    json.property("sourceEnd", extent.sourceEnd);
    json.property("toStringStart", extent.toStringStart);
    json.property("toStringEnd", extent.toStringEnd);
    json.property("lineno", extent.lineno);
    json.property("column", extent.column.oneOriginValue());
  }
  json.endObject();

  json.beginListProperty("immutableFlags");
  DumpImmutableScriptFlags(json, script.immutableFlags());
  json.endList();

  json.beginListProperty("functionFlags");
  DumpFunctionFlagsItems(json, script.functionFlags());
  json.endList();

  json.property("hasPrivateScriptData", script.hasPrivateScriptData());

  InputScope scope = script.enclosingScope();
  if (scope.isNull()) {
    json.nullProperty("enclosingScope");
  } else {
    json.beginObjectProperty("enclosingScope");
    DumpInputScopeFields(json, scope);
    json.endObject();
  }

  if (script.useMemberInitializers()) {
    json.property("memberInitializers",
                  script.getMemberInitializers().serialize());
  }
}

void CompilationInput::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json);
  out.put("\n");
}

void CompilationInput::dump(js::JSONPrinter& json) const {
  json.beginObject();
  dumpFields(json);
  json.endObject();
}

void CompilationInput::dumpFields(js::JSONPrinter& json) const {
  const char* targetStr = nullptr;
  switch (target) {
    case CompilationTarget::Global:
      targetStr = "CompilationTarget::Global";
      break;
    case CompilationTarget::SelfHosting:
      targetStr = "CompilationTarget::SelfHosting";
      break;
    case CompilationTarget::StandaloneFunction:
      targetStr = "CompilationTarget::StandaloneFunction";
      break;
    case CompilationTarget::StandaloneFunctionInNonSyntacticScope:
      targetStr = "CompilationTarget::StandaloneFunctionInNonSyntacticScope";
      break;
    case CompilationTarget::Eval:
      targetStr = "CompilationTarget::Eval";
      break;
    case CompilationTarget::Module:
      targetStr = "CompilationTarget::Module";
      break;
    case CompilationTarget::Delazification:
      targetStr = "CompilationTarget::Delazification";
      break;
  }
  json.property("target", targetStr);

  json.beginObjectProperty("options");
  DumpOptionsFields(json, options);
  json.endObject();

  if (lazy_.isNull()) {
    json.nullProperty("lazy_");
  } else {
    json.beginObjectProperty("lazy_");
    DumpInputScriptFields(json, lazy_);
    json.endObject();
  }

  if (enclosingScope.isNull()) {
    json.nullProperty("enclosingScope");
  } else {
    json.beginObjectProperty("enclosingScope");
    DumpInputScopeFields(json, enclosingScope);
    json.endObject();
  }

  // TODO: Support printing the atomCache and the source fields.
}

void CompilationStencil::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json);
  out.put("\n");
}

void CompilationStencil::dump(js::JSONPrinter& json) const {
  json.beginObject();
  dumpFields(json);
  json.endObject();
}

void CompilationStencil::dumpFields(js::JSONPrinter& json) const {
  char index[64];

  json.beginObjectProperty("scriptData");
  for (size_t i = 0; i < scriptData.size(); i++) {
    SprintfLiteral(index, "ScriptIndex(%zu)", i);
    json.beginObjectProperty(index);
    scriptData[i].dumpFields(json, this);
    json.endObject();
  }
  json.endObject();

  json.beginObjectProperty("scriptExtra");
  for (size_t i = 0; i < scriptExtra.size(); i++) {
    SprintfLiteral(index, "ScriptIndex(%zu)", i);
    json.beginObjectProperty(index);
    scriptExtra[i].dumpFields(json);
    json.endObject();
  }
  json.endObject();

  json.beginObjectProperty("scopeData");
  MOZ_ASSERT(scopeData.size() == scopeNames.size());
  for (size_t i = 0; i < scopeData.size(); i++) {
    SprintfLiteral(index, "ScopeIndex(%zu)", i);
    json.beginObjectProperty(index);
    scopeData[i].dumpFields(json, scopeNames[i], this);
    json.endObject();
  }
  json.endObject();

  json.beginObjectProperty("sharedData");
  sharedData.dumpFields(json);
  json.endObject();

  json.beginObjectProperty("regExpData");
  for (size_t i = 0; i < regExpData.size(); i++) {
    SprintfLiteral(index, "RegExpIndex(%zu)", i);
    json.beginObjectProperty(index);
    regExpData[i].dumpFields(json, this);
    json.endObject();
  }
  json.endObject();

  json.beginObjectProperty("bigIntData");
  for (size_t i = 0; i < bigIntData.size(); i++) {
    SprintfLiteral(index, "BigIntIndex(%zu)", i);
    GenericPrinter& out = json.beginStringProperty(index);
    bigIntData[i].dumpCharsNoQuote(out);
    json.endStringProperty();
  }
  json.endObject();

  json.beginObjectProperty("objLiteralData");
  for (size_t i = 0; i < objLiteralData.size(); i++) {
    SprintfLiteral(index, "ObjLiteralIndex(%zu)", i);
    json.beginObjectProperty(index);
    objLiteralData[i].dumpFields(json, this);
    json.endObject();
  }
  json.endObject();

  if (moduleMetadata) {
    json.beginObjectProperty("moduleMetadata");
    moduleMetadata->dumpFields(json, this);
    json.endObject();
  }

  json.beginObjectProperty("asmJS");
  if (asmJS) {
    for (auto iter = asmJS->moduleMap.iter(); !iter.done(); iter.next()) {
      SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index);
      json.formatProperty(index, "asm.js");
    }
  }
  json.endObject();
}

void CompilationStencil::dumpAtom(TaggedParserAtomIndex index) const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  json.beginObject();
  DumpTaggedParserAtomIndex(json, index, this);
  json.endObject();
}

void ExtensibleCompilationStencil::dump() {
  frontend::BorrowingCompilationStencil borrowingStencil(*this);
  borrowingStencil.dump();
}

void ExtensibleCompilationStencil::dump(js::JSONPrinter& json) {
  frontend::BorrowingCompilationStencil borrowingStencil(*this);
  borrowingStencil.dump(json);
}

void ExtensibleCompilationStencil::dumpFields(js::JSONPrinter& json) {
  frontend::BorrowingCompilationStencil borrowingStencil(*this);
  borrowingStencil.dumpFields(json);
}

void ExtensibleCompilationStencil::dumpAtom(TaggedParserAtomIndex index) {
  frontend::BorrowingCompilationStencil borrowingStencil(*this);
  borrowingStencil.dumpAtom(index);
}

void InitialStencilAndDelazifications::dump() const {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json);
  out.put("\n");
}

void InitialStencilAndDelazifications::dump(js::JSONPrinter& json) const {
  json.beginObject();
  dumpFields(json);
  json.endObject();
}

void InitialStencilAndDelazifications::dumpFields(js::JSONPrinter& json) const {
  if (initial_) {
    json.beginObjectProperty("initial_");
    initial_->dumpFields(json);
    json.endObject();
  } else {
    json.nullProperty("initial_");
  }

  for (size_t i = 0; i < delazifications_.length(); i++) {
    const CompilationStencil* delazification = delazifications_[i];

    char index[64];
    SprintfLiteral(index, "ScriptIndex(%zu)", i + 1);

    if (delazification) {
      json.beginObjectProperty(index);
      delazification->dumpFields(json);
      json.endObject();
    } else {
      json.nullProperty(index);
    }
  }
}

#endif  // defined(DEBUG) || defined(JS_JITSPEW)

JSString* CompilationAtomCache::getExistingStringAt(
    ParserAtomIndex index) const {
  MOZ_RELEASE_ASSERT(atoms_.length() >= index);
  return atoms_[index];
}

JSString* CompilationAtomCache::getExistingStringAt(
    JSContext* cx, TaggedParserAtomIndex taggedIndex) const {
  if (taggedIndex.isParserAtomIndex()) {
    auto index = taggedIndex.toParserAtomIndex();
    return getExistingStringAt(index);
  }

  if (taggedIndex.isWellKnownAtomId()) {
    auto index = taggedIndex.toWellKnownAtomId();
    return GetWellKnownAtom(cx, index);
  }

  if (taggedIndex.isLength1StaticParserString()) {
    auto index = taggedIndex.toLength1StaticParserString();
    return cx->staticStrings().getUnit(char16_t(index));
  }

  if (taggedIndex.isLength2StaticParserString()) {
    auto index = taggedIndex.toLength2StaticParserString();
    return cx->staticStrings().getLength2FromIndex(size_t(index));
  }

  MOZ_ASSERT(taggedIndex.isLength3StaticParserString());
  auto index = taggedIndex.toLength3StaticParserString();
  return cx->staticStrings().getUint(uint32_t(index));
}

JSString* CompilationAtomCache::getStringAt(ParserAtomIndex index) const {
  if (size_t(index) >= atoms_.length()) {
    return nullptr;
  }
  return atoms_[index];
}

JSAtom* CompilationAtomCache::getExistingAtomAt(ParserAtomIndex index) const {
  return &getExistingStringAt(index)->asAtom();
}

JSAtom* CompilationAtomCache::getExistingAtomAt(
    JSContext* cx, TaggedParserAtomIndex taggedIndex) const {
  return &getExistingStringAt(cx, taggedIndex)->asAtom();
}

JSAtom* CompilationAtomCache::getAtomAt(ParserAtomIndex index) const {
  if (size_t(index) >= atoms_.length()) {
    return nullptr;
  }
  if (!atoms_[index]) {
    return nullptr;
  }
  return &atoms_[index]->asAtom();
}

bool CompilationAtomCache::hasAtomAt(ParserAtomIndex index) const {
  if (size_t(index) >= atoms_.length()) {
    return false;
  }
  return !!atoms_[index];
}

bool CompilationAtomCache::setAtomAt(FrontendContext* fc, ParserAtomIndex index,
                                     JSString* atom) {
  if (size_t(index) < atoms_.length()) {
    atoms_[index] = atom;
    return true;
  }

  if (!atoms_.resize(size_t(index) + 1)) {
    ReportOutOfMemory(fc);
    return false;
  }

  atoms_[index] = atom;
  return true;
}

bool CompilationAtomCache::allocate(FrontendContext* fc, size_t length) {
  MOZ_ASSERT(length >= atoms_.length());
  if (length == atoms_.length()) {
    return true;
  }

  if (!atoms_.resize(length)) {
    ReportOutOfMemory(fc);
    return false;
  }

  return true;
}

void CompilationAtomCache::stealBuffer(AtomCacheVector& atoms) {
  atoms_ = std::move(atoms);
  // Destroy elements, without unreserving.
  atoms_.clear();
}

void CompilationAtomCache::releaseBuffer(AtomCacheVector& atoms) {
  atoms = std::move(atoms_);
}

bool CompilationState::allocateGCThingsUninitialized(
    FrontendContext* fc, ScriptIndex scriptIndex, size_t length,
    TaggedScriptThingIndex** cursor) {
  MOZ_ASSERT(gcThingData.length() <= UINT32_MAX);

  auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length());

  if (length > INDEX_LIMIT) {
    ReportAllocationOverflow(fc);
    return false;
  }
  uint32_t gcThingsLength = length;

  if (!gcThingData.growByUninitialized(length)) {
    js::ReportOutOfMemory(fc);
    return false;
  }

  if (gcThingData.length() > UINT32_MAX) {
    ReportAllocationOverflow(fc);
    return false;
  }

  ScriptStencil& script = scriptData[scriptIndex];
  script.gcThingsOffset = gcThingsOffset;
  script.gcThingsLength = gcThingsLength;

  *cursor = gcThingData.begin() + gcThingsOffset;
  return true;
}

bool CompilationState::appendScriptStencilAndData(FrontendContext* fc) {
  if (!scriptData.emplaceBack()) {
    js::ReportOutOfMemory(fc);
    return false;
  }

  if (isInitialStencil()) {
    if (!scriptExtra.emplaceBack()) {
      scriptData.popBack();
      MOZ_ASSERT(scriptData.length() == scriptExtra.length());

      js::ReportOutOfMemory(fc);
      return false;
    }
  }

  return true;
}

bool CompilationState::appendGCThings(
    FrontendContext* fc, ScriptIndex scriptIndex,
    mozilla::Span<const TaggedScriptThingIndex> things) {
  MOZ_ASSERT(gcThingData.length() <= UINT32_MAX);

  auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length());

  if (things.size() > INDEX_LIMIT) {
    ReportAllocationOverflow(fc);
    return false;
  }
  uint32_t gcThingsLength = uint32_t(things.size());

  if (!gcThingData.append(things.data(), things.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }

  if (gcThingData.length() > UINT32_MAX) {
    ReportAllocationOverflow(fc);
    return false;
  }

  ScriptStencil& script = scriptData[scriptIndex];
  script.gcThingsOffset = gcThingsOffset;
  script.gcThingsLength = gcThingsLength;
  return true;
}

CompilationState::CompilationStatePosition CompilationState::getPosition() {
  return CompilationStatePosition{scriptData.length(),
                                  asmJS ? asmJS->moduleMap.count() : 0};
}

void CompilationState::rewind(
    const CompilationState::CompilationStatePosition& pos) {
  if (asmJS && asmJS->moduleMap.count() != pos.asmJSCount) {
    for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) {
      asmJS->moduleMap.remove(ScriptIndex(i));
    }
    MOZ_ASSERT(asmJS->moduleMap.count() == pos.asmJSCount);
  }
  // scriptExtra is empty for delazification.
  if (scriptExtra.length()) {
    MOZ_ASSERT(scriptExtra.length() == scriptData.length());
    scriptExtra.shrinkTo(pos.scriptDataLength);
  }
  scriptData.shrinkTo(pos.scriptDataLength);
}

void CompilationState::markGhost(
    const CompilationState::CompilationStatePosition& pos) {
  for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) {
    scriptData[i].setIsGhost();
  }
}

ScriptIndex CompilationStencilMerger::getInitialScriptIndexFor(
    const CompilationStencil& delazification) const {
  auto maybeIndex =
      functionKeyToInitialScriptIndex_.get(delazification.functionKey);
  MOZ_ASSERT(maybeIndex);
  return *maybeIndex;
}

template <typename T>
bool FunctionKeyToScriptIndexMap::init(FrontendContext* fc,
                                       const T& scriptExtra,
                                       size_t scriptExtraSize) {
  if (!map_.reserve(scriptExtraSize - 1)) {
    ReportOutOfMemory(fc);
    return false;
  }

  for (size_t i = 1; i < scriptExtraSize; i++) {
    const auto& extra = scriptExtra[i];
    auto key = extra.extent.toFunctionKey();

    // There can be multiple ScriptStencilExtra with same extent if
    // the function is parsed multiple times because of rewind for
    // arrow function, and in that case the last one's index should be used.
    // Overwrite with the last one.
    //
    // Already reserved above, but OOMTest can hit failure mode in
    // HashTable::add.
    if (!map_.put(key, ScriptIndex(i))) {
      ReportOutOfMemory(fc);
      return false;
    }
  }

  return true;
}

bool FunctionKeyToScriptIndexMap::init(FrontendContext* fc,
                                       const CompilationStencil* initial) {
  return init(fc, initial->scriptExtra, initial->scriptExtra.size());
}

bool FunctionKeyToScriptIndexMap::init(
    FrontendContext* fc, const ExtensibleCompilationStencil* initial) {
  return init(fc, initial->scriptExtra, initial->scriptExtra.length());
}

mozilla::Maybe<ScriptIndex> FunctionKeyToScriptIndexMap::get(
    FunctionKey key) const {
  auto p = map_.readonlyThreadsafeLookup(key);
  if (!p) {
    return mozilla::Nothing();
  }
  return mozilla::Some(p->value());
}

size_t FunctionKeyToScriptIndexMap::sizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  return map_.shallowSizeOfExcludingThis(mallocSizeOf);
}

bool CompilationStencilMerger::buildAtomIndexMap(
    FrontendContext* fc, const CompilationStencil& delazification,
    AtomIndexMap& atomIndexMap) {
  uint32_t atomCount = delazification.parserAtomData.size();
  if (!atomIndexMap.reserve(atomCount)) {
    ReportOutOfMemory(fc);
    return false;
  }
  for (const auto& atom : delazification.parserAtomData) {
    auto mappedIndex = initial_->parserAtoms.internExternalParserAtom(fc, atom);
    if (!mappedIndex) {
      return false;
    }
    atomIndexMap.infallibleAppend(mappedIndex);
  }
  return true;
}

bool CompilationStencilMerger::setInitial(
    FrontendContext* fc, UniquePtr<ExtensibleCompilationStencil>&& initial) {
  MOZ_ASSERT(!initial_);

  initial_ = std::move(initial);

  return functionKeyToInitialScriptIndex_.init(fc, initial_.get());
}

template <typename GCThingIndexMapFunc, typename AtomIndexMapFunc,
          typename ScopeIndexMapFunc>
static void MergeScriptStencil(ScriptStencil& dest, const ScriptStencil& src,
                               GCThingIndexMapFunc mapGCThingIndex,
                               AtomIndexMapFunc mapAtomIndex,
                               ScopeIndexMapFunc mapScopeIndex,
                               bool isTopLevel) {
  // If this function was lazy, all inner functions should have been lazy.
  MOZ_ASSERT(!dest.hasSharedData());

  // If the inner lazy function is skipped, gcThingsLength is empty.
  if (src.gcThingsLength) {
    dest.gcThingsOffset = mapGCThingIndex(src.gcThingsOffset);
    dest.gcThingsLength = src.gcThingsLength;
  }

  if (src.functionAtom) {
    dest.functionAtom = mapAtomIndex(src.functionAtom);
  }

  if (!dest.hasLazyFunctionEnclosingScopeIndex() &&
      src.hasLazyFunctionEnclosingScopeIndex()) {
    // Both enclosing function and this function were lazy, and
    // now enclosing function is non-lazy and this function is still lazy.
    dest.setLazyFunctionEnclosingScopeIndex(
        mapScopeIndex(src.lazyFunctionEnclosingScopeIndex()));
  } else if (dest.hasLazyFunctionEnclosingScopeIndex() &&
             !src.hasLazyFunctionEnclosingScopeIndex()) {
    // The enclosing function was non-lazy and this function was lazy, and
    // now this function is non-lazy.
    dest.resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge();
  } else {
    // The enclosing function is still lazy.
    MOZ_ASSERT(!dest.hasLazyFunctionEnclosingScopeIndex());
    MOZ_ASSERT(!src.hasLazyFunctionEnclosingScopeIndex());
  }

#ifdef DEBUG
  uint16_t BASESCRIPT = uint16_t(FunctionFlags::Flags::BASESCRIPT);
  uint16_t HAS_INFERRED_NAME =
      uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME);
  uint16_t HAS_GUESSED_ATOM = uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM);
  uint16_t acceptableDifferenceForLazy = HAS_INFERRED_NAME | HAS_GUESSED_ATOM;
  uint16_t acceptableDifferenceForNonLazy =
      BASESCRIPT | HAS_INFERRED_NAME | HAS_GUESSED_ATOM;

  MOZ_ASSERT_IF(
      isTopLevel,
      (dest.functionFlags.toRaw() | acceptableDifferenceForNonLazy) ==
          (src.functionFlags.toRaw() | acceptableDifferenceForNonLazy));

  // NOTE: Currently we don't delazify inner functions.
  MOZ_ASSERT_IF(!isTopLevel,
                (dest.functionFlags.toRaw() | acceptableDifferenceForLazy) ==
                    (src.functionFlags.toRaw() | acceptableDifferenceForLazy));
#endif  // DEBUG
  dest.functionFlags = src.functionFlags;

  // Other flags.

  if (src.wasEmittedByEnclosingScript()) {
    // NOTE: the top-level function of the delazification have
    //       src.wasEmittedByEnclosingScript() == false, and that shouldn't
    //       be copied.
    dest.setWasEmittedByEnclosingScript();
  }

  if (src.allowRelazify()) {
    dest.setAllowRelazify();
  }

  if (src.hasSharedData()) {
    dest.setHasSharedData();
  }
}

bool CompilationStencilMerger::addDelazification(
    FrontendContext* fc, const CompilationStencil& delazification) {
  MOZ_ASSERT(initial_);

  auto delazifiedFunctionIndex = getInitialScriptIndexFor(delazification);
  auto& destFun = initial_->scriptData[delazifiedFunctionIndex];

  if (destFun.hasSharedData()) {
    // If the function was already non-lazy, it means the following happened:
    //   A. delazified twice within single collecting delazifications
    //     1. this function is lazily parsed
    //     2. collecting delazifications is started
    //     3. this function is delazified, encoded, and merged
    //     4. this function is relazified
    //     5. this function is delazified, encoded, and merged
    //
    //   B. delazified twice across decode
    //     1. this function is lazily parsed
    //     2. collecting delazifications is started
    //     3. this function is delazified, encoded, and merged
    //     4. collecting delazifications is finished
    //     5. decoded
    //     6. collecting delazifications is started
    //        here, this function is non-lazy
    //     7. this function is relazified
    //     8. this function is delazified, encoded, and merged
    //
    // A can happen with public API.
    //
    // B cannot happen with public API, but can happen if incremental
    // encoding at step B.6 is explicitly started by internal function.
    // See Evaluate and StartCollectingDelazifications in js/src/shell/js.cpp.
    return true;
  }

  // If any failure happens, the initial stencil is left in the broken state.
  // Immediately discard it.
  auto failureCase = mozilla::MakeScopeExit([&] { initial_.reset(); });

  mozilla::Maybe<ScopeIndex> functionEnclosingScope;
  if (destFun.hasLazyFunctionEnclosingScopeIndex()) {
    // lazyFunctionEnclosingScopeIndex_ can be Nothing if this is
    // top-level function.
    functionEnclosingScope =
        mozilla::Some(destFun.lazyFunctionEnclosingScopeIndex());
  }

  // A map from ParserAtomIndex in delazification to TaggedParserAtomIndex
  // in initial_.
  AtomIndexMap atomIndexMap;
  if (!buildAtomIndexMap(fc, delazification, atomIndexMap)) {
    return false;
  }
  auto mapAtomIndex = [&](TaggedParserAtomIndex index) {
    if (index.isParserAtomIndex()) {
      return atomIndexMap[index.toParserAtomIndex()];
    }

    return index;
  };

  size_t gcThingOffset = initial_->gcThingData.length();
  size_t regExpOffset = initial_->regExpData.length();
  size_t bigIntOffset = initial_->bigIntData.length();
  size_t objLiteralOffset = initial_->objLiteralData.length();
  size_t scopeOffset = initial_->scopeData.length();

  // Map delazification's ScriptIndex to initial's ScriptIndex.
  //
  // The lazy function's gcthings list stores inner function's ScriptIndex.
  // The n-th gcthing holds the ScriptIndex of the (n+1)-th script in
  // delazification.
  //
  // NOTE: Currently we don't delazify inner functions.
  auto lazyFunctionGCThingsOffset = destFun.gcThingsOffset;
  auto mapScriptIndex = [&](ScriptIndex index) {
    if (index == CompilationStencil::TopLevelIndex) {
      return delazifiedFunctionIndex;
    }

    return initial_->gcThingData[lazyFunctionGCThingsOffset + index.index - 1]
        .toFunction();
  };

  // Map other delazification's indices into initial's indices.
  auto mapGCThingIndex = [&](CompilationGCThingIndex offset) {
    return CompilationGCThingIndex(gcThingOffset + offset.index);
  };
  auto mapRegExpIndex = [&](RegExpIndex index) {
    return RegExpIndex(regExpOffset + index.index);
  };
  auto mapBigIntIndex = [&](BigIntIndex index) {
    return BigIntIndex(bigIntOffset + index.index);
  };
  auto mapObjLiteralIndex = [&](ObjLiteralIndex index) {
    return ObjLiteralIndex(objLiteralOffset + index.index);
  };
  auto mapScopeIndex = [&](ScopeIndex index) {
    return ScopeIndex(scopeOffset + index.index);
  };

  // Append gcThingData, with mapping TaggedScriptThingIndex.
  if (!initial_->gcThingData.append(delazification.gcThingData.data(),
                                    delazification.gcThingData.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (size_t i = gcThingOffset; i < initial_->gcThingData.length(); i++) {
    auto& index = initial_->gcThingData[i];
    if (index.isNull()) {
      // Nothing to do.
    } else if (index.isAtom()) {
      index = TaggedScriptThingIndex(mapAtomIndex(index.toAtom()));
    } else if (index.isBigInt()) {
      index = TaggedScriptThingIndex(mapBigIntIndex(index.toBigInt()));
    } else if (index.isObjLiteral()) {
      index = TaggedScriptThingIndex(mapObjLiteralIndex(index.toObjLiteral()));
    } else if (index.isRegExp()) {
      index = TaggedScriptThingIndex(mapRegExpIndex(index.toRegExp()));
    } else if (index.isScope()) {
      index = TaggedScriptThingIndex(mapScopeIndex(index.toScope()));
    } else if (index.isFunction()) {
      index = TaggedScriptThingIndex(mapScriptIndex(index.toFunction()));
    } else {
      MOZ_ASSERT(index.isEmptyGlobalScope());
      // Nothing to do
    }
  }

  // Append regExpData, with mapping RegExpStencil.atom_.
  if (!initial_->regExpData.append(delazification.regExpData.data(),
                                   delazification.regExpData.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (size_t i = regExpOffset; i < initial_->regExpData.length(); i++) {
    auto& data = initial_->regExpData[i];
    data.atom_ = mapAtomIndex(data.atom_);
  }

  // Append bigIntData, with copying BigIntStencil.bigInt_.
  if (!initial_->bigIntData.reserve(bigIntOffset +
                                    delazification.bigIntData.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (const auto& data : delazification.bigIntData) {
    initial_->bigIntData.infallibleEmplaceBack();
    if (!initial_->bigIntData.back().init(fc, initial_->alloc, data)) {
      return false;
    }
  }

  // Append objLiteralData, with copying ObjLiteralStencil.code_, and mapping
  // TaggedParserAtomIndex in it.
  if (!initial_->objLiteralData.reserve(objLiteralOffset +
                                        delazification.objLiteralData.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (const auto& data : delazification.objLiteralData) {
    size_t length = data.code().size();
    auto* code = initial_->alloc.newArrayUninitialized<uint8_t>(length);
    if (!code) {
      js::ReportOutOfMemory(fc);
      return false;
    }
    memcpy(code, data.code().data(), length);

    ObjLiteralModifier modifier(mozilla::Span(code, length));
    modifier.mapAtom(mapAtomIndex);

    initial_->objLiteralData.infallibleEmplaceBack(
        code, length, data.kind(), data.flags(), data.propertyCount());
  }

  // Append scopeData, with mapping indices in ScopeStencil fields.
  // And append scopeNames, with copying the entire data, and mapping
  // trailingNames.
  if (!initial_->scopeData.reserve(scopeOffset +
                                   delazification.scopeData.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  if (!initial_->scopeNames.reserve(scopeOffset +
                                    delazification.scopeNames.size())) {
    js::ReportOutOfMemory(fc);
    return false;
  }
  for (size_t i = 0; i < delazification.scopeData.size(); i++) {
    const auto& srcData = delazification.scopeData[i];
    const auto* srcNames = delazification.scopeNames[i];

    mozilla::Maybe<ScriptIndex> functionIndex = mozilla::Nothing();
    if (srcData.isFunction()) {
      // Inner functions should be in the same order as initial, beginning from
      // the delazification's index.
      functionIndex = mozilla::Some(mapScriptIndex(srcData.functionIndex()));
    }

    BaseParserScopeData* destNames = nullptr;
    if (srcNames) {
      destNames = CopyScopeData(fc, initial_->alloc, srcData.kind(), srcNames);
      if (!destNames) {
        return false;
      }
      auto trailingNames =
          GetParserScopeDataTrailingNames(srcData.kind(), destNames);
      for (auto& name : trailingNames) {
        if (name.name()) {
          name.updateNameAfterStencilMerge(mapAtomIndex(name.name()));
        }
      }
    }

    initial_->scopeData.infallibleEmplaceBack(
        srcData.kind(),
        srcData.hasEnclosing()
            ? mozilla::Some(mapScopeIndex(srcData.enclosing()))
            : functionEnclosingScope,
        srcData.firstFrameSlot(),
        srcData.hasEnvironmentShape()
            ? mozilla::Some(srcData.numEnvironmentSlots())
            : mozilla::Nothing(),
        functionIndex, srcData.isArrow());

    initial_->scopeNames.infallibleEmplaceBack(destNames);
  }

  // Add delazified function's shared data.
  //
  // NOTE: Currently we don't delazify inner functions.
  if (!initial_->sharedData.addExtraWithoutShare(
          fc, delazifiedFunctionIndex,
          delazification.sharedData.get(CompilationStencil::TopLevelIndex))) {
    return false;
  }

  // Update scriptData, with mapping indices in ScriptStencil fields.
  for (uint32_t i = 0; i < delazification.scriptData.size(); i++) {
    auto destIndex = mapScriptIndex(ScriptIndex(i));
    MergeScriptStencil(initial_->scriptData[destIndex],
                       delazification.scriptData[i], mapGCThingIndex,
                       mapAtomIndex, mapScopeIndex,
                       i == CompilationStencil::TopLevelIndex);
  }

  // WARNING: moduleMetadata and asmJS fields are known at script/module
  // top-level parsing, any mutation made in this function should be reflected
  // to ExtensibleCompilationStencil::steal and CompilationStencil::clone.

  // Function shouldn't be a module.
  MOZ_ASSERT(!delazification.moduleMetadata);

  // asm.js shouldn't appear inside delazification, given asm.js forces
  // full-parse.
  MOZ_ASSERT(!delazification.hasAsmJS());

  failureCase.release();
  return true;
}

bool CompilationStencilMerger::maybeAddDelazification(
    FrontendContext* fc, const CompilationStencil& delazification) {
  auto delazifiedFunctionIndex = getInitialScriptIndexFor(delazification);
  auto& destFun = initial_->scriptData[delazifiedFunctionIndex];

  if (!destFun.hasLazyFunctionEnclosingScopeIndex()) {
    // The enclosing function is still lazy, and this inner function cannot
    // be added.
    return true;
  }

  return addDelazification(fc, delazification);
}

void CompilationStencil::AddRef() { refCount_++; }
void CompilationStencil::Release() {
  MOZ_RELEASE_ASSERT(refCount_ > 0);
  if (--refCount_ == 0) {
    js_delete(this);
  }
}

void JS::StencilAddRef(JS::Stencil* stencil) { stencil->AddRef(); }
void JS::StencilRelease(JS::Stencil* stencil) { stencil->Release(); }

JS_PUBLIC_API JSScript* JS::InstantiateGlobalStencil(
    JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil,
    JS::InstantiationStorage* storage) {
  MOZ_ASSERT_IF(storage, storage->isValid());

  CompileOptions compileOptions(cx);
  options.copyTo(compileOptions);
  Rooted<CompilationInput> input(cx, CompilationInput(compileOptions));
  Rooted<CompilationGCOutput> gcOutput(cx);
  if (storage) {
    gcOutput.get().steal(std::move(*storage->gcOutput_));
  }

  if (!InstantiateStencils(cx, input.get(), *stencil, gcOutput.get())) {
    return nullptr;
  }
  return gcOutput.get().script;
}

JS_PUBLIC_API bool JS::StencilIsBorrowed(JS::Stencil* stencil) {
  return stencil->getInitial()->storageType ==
         CompilationStencil::StorageType::Borrowed;
}

JS_PUBLIC_API JSObject* JS::InstantiateModuleStencil(
    JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil,
    JS::InstantiationStorage* storage) {
  MOZ_ASSERT_IF(storage, storage->isValid());

  CompileOptions compileOptions(cx);
  options.copyTo(compileOptions);
  compileOptions.setModule();
  Rooted<CompilationInput> input(cx, CompilationInput(compileOptions));
  Rooted<CompilationGCOutput> gcOutput(cx);
  if (storage) {
    gcOutput.get().steal(std::move(*storage->gcOutput_));
  }

  if (!InstantiateStencils(cx, input.get(), *stencil, gcOutput.get())) {
    return nullptr;
  }
  return gcOutput.get().module;
}

JS_PUBLIC_API size_t JS::SizeOfStencil(Stencil* stencil,
                                       mozilla::MallocSizeOf mallocSizeOf) {
  return stencil->sizeOfIncludingThis(mallocSizeOf);
}

JS::InstantiationStorage::~InstantiationStorage() {
  if (gcOutput_) {
    js_delete(gcOutput_);
    gcOutput_ = nullptr;
  }
}

bool JS::IsStencilCacheable(JS::Stencil* stencil) {
  if (stencil->hasAsmJS()) {
    return false;
  }

  return true;
}
