From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- js/src/frontend/CompilationStencil.h | 1842 ++++++++++++++++++++++++++++++++++ 1 file changed, 1842 insertions(+) create mode 100644 js/src/frontend/CompilationStencil.h (limited to 'js/src/frontend/CompilationStencil.h') diff --git a/js/src/frontend/CompilationStencil.h b/js/src/frontend/CompilationStencil.h new file mode 100644 index 0000000000..617a3dd487 --- /dev/null +++ b/js/src/frontend/CompilationStencil.h @@ -0,0 +1,1842 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_CompilationStencil_h +#define frontend_CompilationStencil_h + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Atomics.h" // mozilla::Atomic +#include "mozilla/Attributes.h" // MOZ_RAII +#include "mozilla/HashTable.h" // mozilla::HashMap +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/Span.h" +#include "mozilla/Variant.h" // mozilla::Variant + +#include "ds/LifoAlloc.h" +#include "frontend/FrontendContext.h" // AutoReportFrontendContext +#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" +#include "frontend/Stencil.h" +#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher +#include "frontend/UsedNameTracker.h" +#include "js/GCVector.h" +#include "js/HashTable.h" +#include "js/RefCounted.h" // AtomicRefCounted +#include "js/Transcoding.h" +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Vector.h" +#include "js/WasmModule.h" +#include "vm/GlobalObject.h" // GlobalObject +#include "vm/JSContext.h" +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSScript.h" // SourceExtent +#include "vm/Realm.h" +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/SharedStencil.h" // SharedImmutableScriptData + +class JSAtom; +class JSString; + +namespace JS { +class JS_PUBLIC_API ReadOnlyCompileOptions; +} + +namespace js { + +class AtomSet; +class JSONPrinter; +class ModuleObject; + +namespace frontend { + +struct CompilationInput; +struct CompilationStencil; +struct CompilationGCOutput; +class ScriptStencilIterable; +struct InputName; +class ScopeBindingCache; + +// Reference to a Scope within a CompilationStencil. +struct ScopeStencilRef { + const CompilationStencil& context_; + const ScopeIndex scopeIndex_; + + // Lookup the ScopeStencil referenced by this ScopeStencilRef. + inline const ScopeStencil& scope() const; +}; + +// Wraps a scope for a CompilationInput. The scope is either as a GC pointer to +// an instantiated scope, or as a reference to a CompilationStencil. +// +// Note: A scope reference may be nullptr/InvalidIndex if there is no such +// scope, such as the enclosingScope at the end of a scope chain. See `isNull` +// helper. +class InputScope { + using InputScopeStorage = mozilla::Variant; + InputScopeStorage scope_; + + public: + // Create an InputScope given an instantiated scope. + explicit InputScope(Scope* ptr) : scope_(ptr) {} + + // Create an InputScope given a CompilationStencil and the ScopeIndex which is + // an offset within the same CompilationStencil given as argument. + InputScope(const CompilationStencil& context, ScopeIndex scopeIndex) + : scope_(ScopeStencilRef{context, scopeIndex}) {} + + // Returns the variant used by the InputScope. This can be useful for complex + // cases where the following accessors are not enough. + const InputScopeStorage& variant() const { return scope_; } + InputScopeStorage& variant() { return scope_; } + + // This match function will unwrap the variant for each type, and will + // specialize and call the Matcher given as argument with the type and value + // of the stored pointer / reference. + // + // This is useful for cases where the code is literally identical despite + // having different specializations. This is achiveable by relying on + // function overloading when the usage differ between the 2 types. + // + // Example: + // inputScope.match([](auto& scope) { + // // scope is either a `Scope*` or a `ScopeStencilRef`. + // for (auto bi = InputBindingIter(scope); bi; bi++) { + // InputName name(scope, bi.name()); + // // ... + // } + // }); + template + decltype(auto) match(Matcher&& matcher) const& { + return scope_.match(std::forward(matcher)); + } + template + decltype(auto) match(Matcher&& matcher) & { + return scope_.match(std::forward(matcher)); + } + + bool isNull() const { + return scope_.match( + [](const Scope* ptr) { return !ptr; }, + [](const ScopeStencilRef& ref) { return !ref.scopeIndex_.isValid(); }); + } + + ScopeKind kind() const { + return scope_.match( + [](const Scope* ptr) { return ptr->kind(); }, + [](const ScopeStencilRef& ref) { return ref.scope().kind(); }); + }; + bool hasEnvironment() const { + return scope_.match([](const Scope* ptr) { return ptr->hasEnvironment(); }, + [](const ScopeStencilRef& ref) { + return ref.scope().hasEnvironment(); + }); + }; + inline InputScope enclosing() const; + bool hasOnChain(ScopeKind kind) const { + return scope_.match( + [=](const Scope* ptr) { return ptr->hasOnChain(kind); }, + [=](const ScopeStencilRef& ref) { + ScopeStencilRef it = ref; + while (true) { + const ScopeStencil& scope = it.scope(); + if (scope.kind() == kind) { + return true; + } + if (!scope.hasEnclosing()) { + break; + } + new (&it) ScopeStencilRef{ref.context_, scope.enclosing()}; + } + return false; + }); + } + uint32_t environmentChainLength() const { + return scope_.match( + [](const Scope* ptr) { return ptr->environmentChainLength(); }, + [](const ScopeStencilRef& ref) { + uint32_t length = 0; + ScopeStencilRef it = ref; + while (true) { + const ScopeStencil& scope = it.scope(); + if (scope.hasEnvironment() && + scope.kind() != ScopeKind::NonSyntactic) { + length++; + } + if (!scope.hasEnclosing()) { + break; + } + new (&it) ScopeStencilRef{ref.context_, scope.enclosing()}; + } + return length; + }); + } + void trace(JSTracer* trc); + bool isStencil() const { return scope_.is(); }; + + // Various accessors which are valid only when the InputScope is a + // FunctionScope. Some of these accessors are returning values associated with + // the canonical function. + private: + inline FunctionFlags functionFlags() const; + inline ImmutableScriptFlags immutableFlags() const; + + public: + inline MemberInitializers getMemberInitializers() const; + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags()) + bool isArrow() const { return functionFlags().isArrow(); } + bool allowSuperProperty() const { + return functionFlags().allowSuperProperty(); + } + bool isClassConstructor() const { + return functionFlags().isClassConstructor(); + } +}; + +// Reference to a Script within a CompilationStencil. +struct ScriptStencilRef { + const CompilationStencil& context_; + const ScriptIndex scriptIndex_; + + inline const ScriptStencil& scriptData() const; + inline const ScriptStencilExtra& scriptExtra() const; +}; + +// Wraps a script for a CompilationInput. The script is either as a BaseScript +// pointer to an instantiated script, or as a reference to a CompilationStencil. +class InputScript { + using InputScriptStorage = mozilla::Variant; + InputScriptStorage script_; + + public: + // Create an InputScript given an instantiated BaseScript pointer. + explicit InputScript(BaseScript* ptr) : script_(ptr) {} + + // Create an InputScript given a CompilationStencil and the ScriptIndex which + // is an offset within the same CompilationStencil given as argument. + InputScript(const CompilationStencil& context, ScriptIndex scriptIndex) + : script_(ScriptStencilRef{context, scriptIndex}) {} + + const InputScriptStorage& raw() const { return script_; } + InputScriptStorage& raw() { return script_; } + + SourceExtent extent() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->extent(); }, + [](const ScriptStencilRef& ref) { return ref.scriptExtra().extent; }); + } + ImmutableScriptFlags immutableFlags() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->immutableFlags(); }, + [](const ScriptStencilRef& ref) { + return ref.scriptExtra().immutableFlags; + }); + } + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags()) + FunctionFlags functionFlags() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->function()->flags(); }, + [](const ScriptStencilRef& ref) { + return ref.scriptData().functionFlags; + }); + } + bool hasPrivateScriptData() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->hasPrivateScriptData(); }, + [](const ScriptStencilRef& ref) { + // See BaseScript::CreateRawLazy. + return ref.scriptData().hasGCThings() || + ref.scriptExtra().useMemberInitializers(); + }); + } + InputScope enclosingScope() const { + return script_.match( + [](const BaseScript* ptr) { + return InputScope(ptr->function()->enclosingScope()); + }, + [](const ScriptStencilRef& ref) { + // The ScriptStencilRef only reference lazy Script, otherwise we + // should fetch the enclosing scope using the bodyScope field of the + // immutable data which is a reference to the vector of gc-things. + MOZ_RELEASE_ASSERT(!ref.scriptData().hasSharedData()); + MOZ_ASSERT(ref.scriptData().hasLazyFunctionEnclosingScopeIndex()); + auto scopeIndex = ref.scriptData().lazyFunctionEnclosingScopeIndex(); + return InputScope(ref.context_, scopeIndex); + }); + } + MemberInitializers getMemberInitializers() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->getMemberInitializers(); }, + [](const ScriptStencilRef& ref) { + return ref.scriptExtra().memberInitializers(); + }); + } + + InputName displayAtom() const; + void trace(JSTracer* trc); + bool isNull() const { + return script_.match([](const BaseScript* ptr) { return !ptr; }, + [](const ScriptStencilRef& ref) { return false; }); + } + bool isStencil() const { + return script_.match([](const BaseScript* ptr) { return false; }, + [](const ScriptStencilRef&) { return true; }); + }; +}; + +// Iterator for walking the scope chain, this is identical to ScopeIter but +// accept an InputScope instead of a Scope pointer. +// +// It may be placed in GC containers; for example: +// +// for (Rooted si(cx, InputScopeIter(scope)); si; si++) { +// use(si); +// SomeMayGCOperation(); +// use(si); +// } +// +class MOZ_STACK_CLASS InputScopeIter { + InputScope scope_; + + public: + explicit InputScopeIter(const InputScope& scope) : scope_(scope) {} + + InputScope& scope() { + MOZ_ASSERT(!done()); + return scope_; + } + + const InputScope& scope() const { + MOZ_ASSERT(!done()); + return scope_; + } + + bool done() const { return scope_.isNull(); } + explicit operator bool() const { return !done(); } + void operator++(int) { scope_ = scope_.enclosing(); } + ScopeKind kind() const { return scope_.kind(); } + + // Returns whether this scope has a syntactic environment (i.e., an + // Environment that isn't a non-syntactic With or NonSyntacticVariables) + // on the environment chain. + bool hasSyntacticEnvironment() const { + return scope_.hasEnvironment() && scope_.kind() != ScopeKind::NonSyntactic; + } + + void trace(JSTracer* trc) { scope_.trace(trc); } +}; + +// Reference to a Binding Name within an existing CompilationStencil. +// TaggedParserAtomIndex are in some cases indexes in the parserAtomData of the +// CompilationStencil. +struct NameStencilRef { + const CompilationStencil& context_; + const TaggedParserAtomIndex atomIndex_; +}; + +// Wraps a name for a CompilationInput. The name is either as a GC pointer to +// a JSAtom, or a TaggedParserAtomIndex which might reference to a non-included. +// +// The constructor for this class are using an InputScope as argument. This +// InputScope is made to fetch back the CompilationStencil associated with the +// TaggedParserAtomIndex when using a Stencil as input. +struct InputName { + using InputNameStorage = mozilla::Variant; + InputNameStorage variant_; + + InputName(Scope*, JSAtom* ptr) : variant_(ptr) {} + InputName(const ScopeStencilRef& scope, TaggedParserAtomIndex index) + : variant_(NameStencilRef{scope.context_, index}) {} + InputName(BaseScript*, JSAtom* ptr) : variant_(ptr) {} + InputName(const ScriptStencilRef& script, TaggedParserAtomIndex index) + : variant_(NameStencilRef{script.context_, index}) {} + + // The InputName is either from an instantiated name, or from another + // CompilationStencil. This method interns the current name in the parser atom + // table of a CompilationState, which has a corresponding CompilationInput. + TaggedParserAtomIndex internInto(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache); + + // Compare an InputName, which is not yet interned, with `other` is either an + // interned name or a well-known or static string. + // + // The `otherCached` argument should be a reference to a JSAtom*, initialized + // to nullptr, which is used to cache the JSAtom representation of the `other` + // argument if needed. If a different `other` parameter is provided, the + // `otherCached` argument should be reset to nullptr. + bool isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, TaggedParserAtomIndex other, + JSAtom** otherCached) const; + + bool isNull() const { + return variant_.match( + [](JSAtom* ptr) { return !ptr; }, + [](const NameStencilRef& ref) { return !ref.atomIndex_; }); + } +}; + +// ScopeContext holds information derived from the scope and environment chains +// to try to avoid the parser needing to traverse VM structures directly. +struct ScopeContext { + // Cache: Scope -> (JSAtom/TaggedParserAtomIndex -> NameLocation) + // + // This cache maps the scope to a hash table which can lookup a name of the + // scope to the equivalent NameLocation. + ScopeBindingCache* scopeCache = nullptr; + + // Generation number of the `scopeCache` collected before filling the cache + // with enclosing scope information. + // + // The generation number, obtained from `scopeCache->getCurrentGeneration()` + // is incremented each time the GC invalidate the content of the cache. The + // `scopeCache` can only be used when the generation number collected before + // filling the cache is identical to the generation number seen when querying + // the cached content. + size_t scopeCacheGen = 0; + + // Class field initializer info if we are nested within a class constructor. + // We may be an combination of arrow and eval context within the constructor. + mozilla::Maybe memberInitializers = {}; + + enum class EnclosingLexicalBindingKind { + Let, + Const, + CatchParameter, + Synthetic, + PrivateMethod, + }; + + using EnclosingLexicalBindingCache = + mozilla::HashMap; + + // Cache of enclosing lexical bindings. + // Used only for eval. + mozilla::Maybe enclosingLexicalBindingCache_; + + // A map of private names to NameLocations used to allow evals to + // provide correct private name semantics (particularly around early + // errors and private brand lookup). + using EffectiveScopePrivateFieldCache = + mozilla::HashMap; + + // Cache of enclosing class's private fields. + // Used only for eval. + mozilla::Maybe + effectiveScopePrivateFieldCache_; + +#ifdef DEBUG + bool enclosingEnvironmentIsDebugProxy_ = false; +#endif + + // How many hops required to navigate from 'enclosingScope' to effective + // scope. + uint32_t effectiveScopeHops = 0; + + uint32_t enclosingScopeEnvironmentChainLength = 0; + + // Eval and arrow scripts also inherit the "this" environment -- used by + // `super` expressions -- from their enclosing script. We count the number of + // environment hops needed to get from enclosing scope to the nearest + // appropriate environment. This value is undefined if the script we are + // compiling is not an eval or arrow-function. + uint32_t enclosingThisEnvironmentHops = 0; + + // The kind of enclosing scope. + ScopeKind enclosingScopeKind = ScopeKind::Global; + + // The type of binding required for `this` of the top level context, as + // indicated by the enclosing scopes of this parse. + // + // NOTE: This is computed based on the effective scope (defined above). + ThisBinding thisBinding = ThisBinding::Global; + + // Eval and arrow scripts inherit certain syntax allowances from their + // enclosing scripts. + bool allowNewTarget = false; + bool allowSuperProperty = false; + bool allowSuperCall = false; + bool allowArguments = true; + + // Indicates there is a 'class' or 'with' scope on enclosing scope chain. + bool inClass = false; + bool inWith = false; + + // True if the enclosing scope is for FunctionScope of arrow function. + bool enclosingScopeIsArrow = false; + + // True if the enclosing scope has environment. + bool enclosingScopeHasEnvironment = false; + +#ifdef DEBUG + // True if the enclosing scope has non-syntactic scope on chain. + bool hasNonSyntacticScopeOnChain = false; + + // True if the enclosing scope has function scope where the function needs + // home object. + bool hasFunctionNeedsHomeObjectOnChain = false; +#endif + + bool init(FrontendContext* fc, CompilationInput& input, + ParserAtomsTable& parserAtoms, ScopeBindingCache* scopeCache, + InheritThis inheritThis, JSObject* enclosingEnv); + + mozilla::Maybe + lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name); + + NameLocation searchInEnclosingScope(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name); + + bool effectiveScopePrivateFieldCacheHas(TaggedParserAtomIndex name); + mozilla::Maybe getPrivateFieldLocation( + TaggedParserAtomIndex name); + + private: + void computeThisBinding(const InputScope& scope); + void computeThisEnvironment(const InputScope& enclosingScope); + void computeInScope(const InputScope& enclosingScope); + void cacheEnclosingScope(const InputScope& enclosingScope); + NameLocation searchInEnclosingScopeWithCache(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name); + NameLocation searchInEnclosingScopeNoCache(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name); + + InputScope determineEffectiveScope(InputScope& scope, JSObject* environment); + + bool cachePrivateFieldsForEval(FrontendContext* fc, CompilationInput& input, + JSObject* enclosingEnvironment, + const InputScope& effectiveScope, + ParserAtomsTable& parserAtoms); + + bool cacheEnclosingScopeBindingForEval(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms); + + bool addToEnclosingLexicalBindingCache(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + InputName& name, + EnclosingLexicalBindingKind kind); +}; + +struct CompilationAtomCache { + public: + using AtomCacheVector = JS::GCVector; + + private: + // Atoms lowered into or converted from CompilationStencil.parserAtomData. + // + // This field is here instead of in CompilationGCOutput because atoms lowered + // from JSAtom is part of input (enclosing scope bindings, lazy function name, + // etc), and having 2 vectors in both input/output is error prone. + AtomCacheVector atoms_; + + public: + JSString* getExistingStringAt(ParserAtomIndex index) const; + JSString* getExistingStringAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + JSString* getStringAt(ParserAtomIndex index) const; + + JSAtom* getExistingAtomAt(ParserAtomIndex index) const; + JSAtom* getExistingAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + JSAtom* getAtomAt(ParserAtomIndex index) const; + + bool hasAtomAt(ParserAtomIndex index) const; + bool setAtomAt(FrontendContext* fc, ParserAtomIndex index, JSString* atom); + bool allocate(FrontendContext* fc, size_t length); + + bool empty() const { return atoms_.empty(); } + size_t size() const { return atoms_.length(); } + + void stealBuffer(AtomCacheVector& atoms); + void releaseBuffer(AtomCacheVector& atoms); + + void trace(JSTracer* trc); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return atoms_.sizeOfExcludingThis(mallocSizeOf); + } +}; + +// Input of the compilation, including source and enclosing context. +struct CompilationInput { + enum class CompilationTarget { + Global, + SelfHosting, + StandaloneFunction, + StandaloneFunctionInNonSyntacticScope, + Eval, + Module, + Delazification, + }; + CompilationTarget target = CompilationTarget::Global; + + const JS::ReadOnlyCompileOptions& options; + + CompilationAtomCache atomCache; + + private: + InputScript lazy_ = InputScript(nullptr); + + public: + RefPtr source; + + // * If the target is Global, null. + // * If the target is SelfHosting, null. Instantiation code for self-hosting + // will ignore this and use the appropriate empty global scope instead. + // * If the target is StandaloneFunction, an empty global scope. + // * If the target is StandaloneFunctionInNonSyntacticScope, the non-null + // enclosing scope of the function + // * If the target is Eval, the non-null enclosing scope of the `eval`. + // * If the target is Module, null that means empty global scope + // (See EmitterScope::checkEnvironmentChainLength) + // * If the target is Delazification, the non-null enclosing scope of + // the function + InputScope enclosingScope = InputScope(nullptr); + + explicit CompilationInput(const JS::ReadOnlyCompileOptions& options) + : options(options) {} + + private: + bool initScriptSource(FrontendContext* fc); + + public: + bool initForGlobal(FrontendContext* fc) { + target = CompilationTarget::Global; + return initScriptSource(fc); + } + + bool initForSelfHostingGlobal(FrontendContext* fc) { + target = CompilationTarget::SelfHosting; + return initScriptSource(fc); + } + + bool initForStandaloneFunction(JSContext* cx, FrontendContext* fc) { + target = CompilationTarget::StandaloneFunction; + if (!initScriptSource(fc)) { + return false; + } + enclosingScope = InputScope(&cx->global()->emptyGlobalScope()); + return true; + } + + bool initForStandaloneFunctionInNonSyntacticScope( + FrontendContext* fc, Handle functionEnclosingScope); + + bool initForEval(FrontendContext* fc, Handle evalEnclosingScope) { + target = CompilationTarget::Eval; + if (!initScriptSource(fc)) { + return false; + } + enclosingScope = InputScope(evalEnclosingScope); + return true; + } + + bool initForModule(FrontendContext* fc) { + target = CompilationTarget::Module; + if (!initScriptSource(fc)) { + return false; + } + // The `enclosingScope` is the emptyGlobalScope. + return true; + } + + void initFromLazy(JSContext* cx, BaseScript* lazyScript, ScriptSource* ss) { + MOZ_ASSERT(cx->compartment() == lazyScript->compartment()); + + // We can only compile functions whose parents have previously been + // compiled, because compilation requires full information about the + // function's immediately enclosing scope. + MOZ_ASSERT(lazyScript->isReadyForDelazification()); + target = CompilationTarget::Delazification; + lazy_ = InputScript(lazyScript); + source = ss; + enclosingScope = lazy_.enclosingScope(); + } + + void initFromStencil(CompilationStencil& context, ScriptIndex scriptIndex, + ScriptSource* ss) { + target = CompilationTarget::Delazification; + lazy_ = InputScript(context, scriptIndex); + source = ss; + enclosingScope = lazy_.enclosingScope(); + } + + // Returns true if enclosingScope field is provided to init* function, + // instead of setting to empty global internally. + bool hasNonDefaultEnclosingScope() const { + return target == CompilationTarget::StandaloneFunctionInNonSyntacticScope || + target == CompilationTarget::Eval || + target == CompilationTarget::Delazification; + } + + // Returns the enclosing scope provided to init* function. + // nullptr otherwise. + InputScope maybeNonDefaultEnclosingScope() const { + if (hasNonDefaultEnclosingScope()) { + return enclosingScope; + } + return InputScope(nullptr); + } + + // The BaseScript* is needed when instantiating a lazy function. + // See InstantiateTopLevel and FunctionsFromExistingLazy. + InputScript lazyOuterScript() { return lazy_; } + BaseScript* lazyOuterBaseScript() { return lazy_.raw().as(); } + + // The JSFunction* is needed when instantiating a lazy function. + // See FunctionsFromExistingLazy. + JSFunction* function() const { + return lazy_.raw().as()->function(); + } + + // When compiling an inner function, we want to know the unique identifier + // which identify a function. This is computed from the source extend. + SourceExtent extent() const { return lazy_.extent(); } + + // See `BaseScript::immutableFlags_`. + ImmutableScriptFlags immutableFlags() const { return lazy_.immutableFlags(); } + + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags()) + + FunctionFlags functionFlags() const { return lazy_.functionFlags(); } + + // When delazifying, return the kind of function which is defined. + FunctionSyntaxKind functionSyntaxKind() const; + + bool hasPrivateScriptData() const { + // This is equivalent to: ngcthings != 0 || useMemberInitializers() + // See BaseScript::CreateRawLazy. + return lazy_.hasPrivateScriptData(); + } + + // Whether this CompilationInput is parsing the top-level of a script, or + // false if we are parsing an inner function. + bool isInitialStencil() { return lazy_.isNull(); } + + // Whether this CompilationInput is parsing a specific function with already + // pre-parsed contextual information. + bool isDelazifying() { return target == CompilationTarget::Delazification; } + + void trace(JSTracer* trc); + + // Size of dynamic data. Note that GC data is counted by GC and not here. We + // also ignore ScriptSource which is a shared RefPtr. + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return atomCache.sizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::JSONPrinter& json) const; + void dumpFields(js::JSONPrinter& json) const; +#endif +}; + +// When compiling a function which was previously Syntaxly Parsed, we generated +// some information which made it possible to skip over some parsing phases, +// such as computing closed over bindings as well as parsing inner functions. +// This class contains all information which is generated by the SyntaxParse and +// reused in the FullParse. +class CompilationSyntaxParseCache { + // When delazifying, we should prepare an array which contains all + // stencil-like gc-things such that it can be used by the parser. + // + // When compiling from a Stencil, this will alias the existing Stencil. + mozilla::Span cachedGCThings_; + + // When delazifying, we should perpare an array which contains all + // stencil-like information about scripts, such that it can be used by the + // parser. + // + // When compiling from a Stencil, these will alias the existing Stencil. + mozilla::Span cachedScriptData_; + mozilla::Span cachedScriptExtra_; + + // When delazifying, we copy the atom, either from JSAtom, or from another + // Stencil into TaggedParserAtomIndex which are valid in this current + // CompilationState. + mozilla::Span closedOverBindings_; + + // Atom of the function being compiled. This atom index is valid in the + // current CompilationState. + TaggedParserAtomIndex displayAtom_; + + // Stencil-like data about the function which is being compiled. + ScriptStencilExtra funExtra_; + +#ifdef DEBUG + // Whether any of these data should be considered or not. + bool isInitialized = false; +#endif + + public: + // When doing a full-parse of an incomplete BaseScript*, we have to iterate + // over functions and closed-over bindings, to avoid costly recursive decent + // in inner functions. This function will clone the BaseScript* information to + // make it available as a stencil-like data to the full-parser. + mozilla::Span closedOverBindings() const { + MOZ_ASSERT(isInitialized); + return closedOverBindings_; + } + const ScriptStencil& scriptData(size_t functionIndex) const { + return cachedScriptData_[scriptIndex(functionIndex)]; + } + const ScriptStencilExtra& scriptExtra(size_t functionIndex) const { + return cachedScriptExtra_[scriptIndex(functionIndex)]; + } + + // Return the name of the function being delazified, if any. + TaggedParserAtomIndex displayAtom() const { + MOZ_ASSERT(isInitialized); + return displayAtom_; + } + + // Return the extra information about the function being delazified, if any. + const ScriptStencilExtra& funExtra() const { + MOZ_ASSERT(isInitialized); + return funExtra_; + } + + // Initialize the SynaxParse cache given a LifoAlloc. The JSContext is only + // used for reporting allocation errors. + [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const InputScript& lazy); + + private: + // Return the script index of a given inner function. + // + // WARNING: The ScriptIndex returned by this function corresponds to the index + // in the cachedScriptExtra_ and cachedScriptData_ spans. With the + // cachedGCThings_ span, these might be reference to an actual Stencil from + // another compilation. Thus, the ScriptIndex returned by this function should + // not be confused with any ScriptIndex from the CompilationState. + ScriptIndex scriptIndex(size_t functionIndex) const { + MOZ_ASSERT(isInitialized); + auto taggedScriptIndex = cachedGCThings_[functionIndex]; + MOZ_ASSERT(taggedScriptIndex.isFunction()); + return taggedScriptIndex.toFunction(); + } + + [[nodiscard]] bool copyFunctionInfo(FrontendContext* fc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const InputScript& lazy); + [[nodiscard]] bool copyScriptInfo(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + BaseScript* lazy); + [[nodiscard]] bool copyScriptInfo(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const ScriptStencilRef& lazy); + [[nodiscard]] bool copyClosedOverBindings(FrontendContext* fc, + LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + BaseScript* lazy); + [[nodiscard]] bool copyClosedOverBindings(FrontendContext* fc, + LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const ScriptStencilRef& lazy); +}; + +// AsmJS scripts are very rare on-average, so we use a HashMap to associate +// data with a ScriptStencil. The ScriptStencil has a flag to indicate if we +// need to even do this lookup. +using StencilAsmJSMap = + HashMap, + mozilla::DefaultHasher, js::SystemAllocPolicy>; + +struct StencilAsmJSContainer + : public js::AtomicRefCounted { + StencilAsmJSMap moduleMap; + + StencilAsmJSContainer() = default; + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return moduleMap.shallowSizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } +}; + +// Store shared data for non-lazy script. +struct SharedDataContainer { + // NOTE: While stored, we must hold a ref-count and care must be taken when + // updating or clearing the pointer. + using SingleSharedDataPtr = SharedImmutableScriptData*; + + using SharedDataVector = + Vector, 0, js::SystemAllocPolicy>; + using SharedDataVectorPtr = SharedDataVector*; + + using SharedDataMap = + HashMap, + mozilla::DefaultHasher, js::SystemAllocPolicy>; + using SharedDataMapPtr = SharedDataMap*; + + private: + enum { + SingleTag = 0, + VectorTag = 1, + MapTag = 2, + BorrowTag = 3, + + TagMask = 3, + }; + + uintptr_t data_ = 0; + + public: + // Defaults to SingleSharedData. + SharedDataContainer() = default; + + SharedDataContainer(const SharedDataContainer&) = delete; + SharedDataContainer(SharedDataContainer&& other) noexcept { + std::swap(data_, other.data_); + MOZ_ASSERT(other.isEmpty()); + } + + SharedDataContainer& operator=(const SharedDataContainer&) = delete; + SharedDataContainer& operator=(SharedDataContainer&& other) noexcept { + std::swap(data_, other.data_); + MOZ_ASSERT(other.isEmpty()); + return *this; + } + + ~SharedDataContainer(); + + [[nodiscard]] bool initVector(FrontendContext* fc); + [[nodiscard]] bool initMap(FrontendContext* fc); + + private: + [[nodiscard]] bool convertFromSingleToMap(FrontendContext* fc); + + public: + bool isEmpty() const { return (data_) == SingleTag; } + bool isSingle() const { return (data_ & TagMask) == SingleTag; } + bool isVector() const { return (data_ & TagMask) == VectorTag; } + bool isMap() const { return (data_ & TagMask) == MapTag; } + bool isBorrow() const { return (data_ & TagMask) == BorrowTag; } + + void setSingle(already_AddRefed&& data) { + MOZ_ASSERT(isEmpty()); + data_ = reinterpret_cast(data.take()); + MOZ_ASSERT(isSingle()); + MOZ_ASSERT(!isEmpty()); + } + + void setBorrow(SharedDataContainer* sharedData) { + MOZ_ASSERT(isEmpty()); + data_ = reinterpret_cast(sharedData) | BorrowTag; + MOZ_ASSERT(isBorrow()); + } + + SingleSharedDataPtr asSingle() const { + MOZ_ASSERT(isSingle()); + MOZ_ASSERT(!isEmpty()); + static_assert(SingleTag == 0); + return reinterpret_cast(data_); + } + SharedDataVectorPtr asVector() const { + MOZ_ASSERT(isVector()); + return reinterpret_cast(data_ & ~TagMask); + } + SharedDataMapPtr asMap() const { + MOZ_ASSERT(isMap()); + return reinterpret_cast(data_ & ~TagMask); + } + SharedDataContainer* asBorrow() const { + MOZ_ASSERT(isBorrow()); + return reinterpret_cast(data_ & ~TagMask); + } + + [[nodiscard]] bool prepareStorageFor(FrontendContext* fc, + size_t nonLazyScriptCount, + size_t allScriptCount); + [[nodiscard]] bool cloneFrom(FrontendContext* fc, + const SharedDataContainer& other); + + // Returns index-th script's shared data, or nullptr if it doesn't have. + js::SharedImmutableScriptData* get(ScriptIndex index) const; + + // Add data for index-th script and share it with VM. + [[nodiscard]] bool addAndShare(FrontendContext* fc, ScriptIndex index, + js::SharedImmutableScriptData* data); + + // Add data for index-th script without sharing it with VM. + // The data should already be shared with VM. + // + // The data is supposed to be added from delazification. + [[nodiscard]] bool addExtraWithoutShare(FrontendContext* fc, + ScriptIndex index, + js::SharedImmutableScriptData* data); + + // Dynamic memory associated with this container. Does not include the + // SharedImmutableScriptData since we are not the unique owner of it. + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + if (isVector()) { + return asVector()->sizeOfIncludingThis(mallocSizeOf); + } + if (isMap()) { + return asMap()->shallowSizeOfIncludingThis(mallocSizeOf); + } + MOZ_ASSERT(isSingle() || isBorrow()); + return 0; + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::JSONPrinter& json) const; + void dumpFields(js::JSONPrinter& json) const; +#endif +}; + +struct ExtensibleCompilationStencil; + +// The top level struct of stencil specialized for non-extensible case. +// Used as the compilation output, and also XDR decode output. +// +// In XDR decode output case, the span and not-owning pointer fields point +// the internal LifoAlloc and the external XDR buffer. +// +// In BorrowingCompilationStencil usage, span and not-owning pointer fields +// point the ExtensibleCompilationStencil and its LifoAlloc. +// +// The dependent XDR buffer or ExtensibleCompilationStencil must be kept +// alive manually. +// +// See SMDOC in Stencil.h for more info. +struct CompilationStencil { + friend struct ExtensibleCompilationStencil; + + static constexpr ScriptIndex TopLevelIndex = ScriptIndex(0); + + static constexpr size_t LifoAllocChunkSize = 512; + + // The lifetime of this CompilationStencil may be managed by stack allocation, + // UniquePtr, or RefPtr. If a RefPtr is used, this ref-count will track + // the lifetime, otherwise it is ignored. + // + // NOTE: Internal code and public APIs use a mix of these different allocation + // modes. + // + // See: JS::StencilAddRef/Release + mutable mozilla::Atomic refCount{0}; + + private: + // On-heap ExtensibleCompilationStencil that this CompilationStencil owns, + // and this CompilationStencil borrows each data from. + UniquePtr ownedBorrowStencil; + + public: + enum class StorageType { + // Pointers and spans point LifoAlloc or owned buffer. + Owned, + + // Pointers and spans point external data, such as XDR buffer, or not-owned + // ExtensibleCompilationStencil (see BorrowingCompilationStencil). + Borrowed, + + // Pointers and spans point data owned by ownedBorrowStencil. + OwnedExtensible, + }; + StorageType storageType = StorageType::Owned; + + // Value of CanLazilyParse(CompilationInput) on compilation. + // Used during instantiation. + bool canLazilyParse = false; + + // If this stencil is a delazification, this identifies location of the + // function in the source text. + using FunctionKey = SourceExtent::FunctionKey; + FunctionKey functionKey = SourceExtent::NullFunctionKey; + + // This holds allocations that do not require destructors to be run but are + // live until the stencil is released. + LifoAlloc alloc; + + // The source text holder for the script. This may be an empty placeholder if + // the code will fully parsed and options indicate the source will never be + // needed again. + RefPtr source; + + // Stencil for all function and non-function scripts. The TopLevelIndex is + // reserved for the top-level script. This top-level may or may not be a + // function. + mozilla::Span scriptData; + + // Immutable data computed during initial compilation and never updated during + // delazification. + mozilla::Span scriptExtra; + + mozilla::Span gcThingData; + + // scopeData and scopeNames have the same size, and i-th scopeNames contains + // the names for the bindings contained in the slot defined by i-th scopeData. + mozilla::Span scopeData; + mozilla::Span scopeNames; + + // Hold onto the RegExpStencil, BigIntStencil, and ObjLiteralStencil that are + // allocated during parse to ensure correct destruction. + mozilla::Span regExpData; + mozilla::Span bigIntData; + mozilla::Span objLiteralData; + + // List of parser atoms for this compilation. This may contain nullptr entries + // when round-tripping with XDR if the atom was generated in original parse + // but not used by stencil. + ParserAtomSpan parserAtomData; + + // Variable sized container for bytecode and other immutable data. A valid + // stencil always contains at least an entry for `TopLevelIndex` script. + SharedDataContainer sharedData; + + // Module metadata if this is a module compile. + RefPtr moduleMetadata; + + // AsmJS modules generated by parsing. These scripts are never lazy and + // therefore only generated during initial parse. + RefPtr asmJS; + + // End of fields. + + // Construct a CompilationStencil + explicit CompilationStencil(ScriptSource* source) + : alloc(LifoAllocChunkSize), source(source) {} + + // Take the ownership of on-heap ExtensibleCompilationStencil and + // borrow from it. + explicit CompilationStencil( + UniquePtr&& extensibleStencil); + + protected: + void borrowFromExtensibleCompilationStencil( + ExtensibleCompilationStencil& extensibleStencil); + +#ifdef DEBUG + void assertBorrowingFromExtensibleCompilationStencil( + const ExtensibleCompilationStencil& extensibleStencil) const; +#endif + + public: + bool isInitialStencil() const { + return functionKey == SourceExtent::NullFunctionKey; + } + + [[nodiscard]] static bool instantiateStencilAfterPreparation( + JSContext* cx, CompilationInput& input, const CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + [[nodiscard]] static bool prepareForInstantiate( + FrontendContext* fc, CompilationAtomCache& atomCache, + const CompilationStencil& stencil, CompilationGCOutput& gcOutput); + + [[nodiscard]] static bool instantiateStencils( + JSContext* cx, CompilationInput& input, const CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + // Decode the special self-hosted stencil + [[nodiscard]] bool instantiateSelfHostedAtoms( + JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const; + [[nodiscard]] JSScript* instantiateSelfHostedTopLevelForRealm( + JSContext* cx, CompilationInput& input); + [[nodiscard]] JSFunction* instantiateSelfHostedLazyFunction( + JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index, + Handle name); + [[nodiscard]] bool delazifySelfHostedFunction(JSContext* cx, + CompilationAtomCache& atomCache, + ScriptIndexRange range, + HandleFunction fun); + + [[nodiscard]] bool serializeStencils(JSContext* cx, CompilationInput& input, + JS::TranscodeBuffer& buf, + bool* succeededOut = nullptr) const; + [[nodiscard]] bool deserializeStencils( + FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + const JS::TranscodeRange& range, bool* succeededOut = nullptr); + + // To avoid any misuses, make sure this is neither copyable or assignable. + CompilationStencil(const CompilationStencil&) = delete; + CompilationStencil(CompilationStencil&&) = delete; + CompilationStencil& operator=(const CompilationStencil&) = delete; + CompilationStencil& operator=(CompilationStencil&&) = delete; +#ifdef DEBUG + ~CompilationStencil() { + // We can mix UniquePtr<..> and RefPtr<..>. This asserts that a UniquePtr + // does not delete a reference-counted stencil. + MOZ_ASSERT(!refCount); + } +#endif + + static inline ScriptStencilIterable functionScriptStencils( + const CompilationStencil& stencil, CompilationGCOutput& gcOutput); + + void setFunctionKey(BaseScript* lazy) { + functionKey = lazy->extent().toFunctionKey(); + } + + inline size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + + const ParserAtomSpan& parserAtomsSpan() const { return parserAtomData; } + + bool isModule() const; + + bool hasMultipleReference() const { return refCount > 1; } + + bool hasOwnedBorrow() const { + return storageType == StorageType::OwnedExtensible; + } + + ExtensibleCompilationStencil* takeOwnedBorrow() { + MOZ_ASSERT(!hasMultipleReference()); + MOZ_ASSERT(hasOwnedBorrow()); + return ownedBorrowStencil.release(); + } + +#ifdef DEBUG + void assertNoExternalDependency() const; +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::JSONPrinter& json) const; + void dumpFields(js::JSONPrinter& json) const; + + void dumpAtom(TaggedParserAtomIndex index) const; +#endif +}; + +// The top level struct of stencil specialized for extensible case. +// Used as the temporary storage during compilation, an the compilation output. +// +// All not-owning pointer fields point the internal LifoAlloc. +// +// See CompilationStencil for each field's description. +// +// Also see SMDOC in Stencil.h for more info. +struct ExtensibleCompilationStencil { + bool canLazilyParse = false; + + using FunctionKey = SourceExtent::FunctionKey; + + FunctionKey functionKey = SourceExtent::NullFunctionKey; + + // Data pointed by other fields are allocated in this LifoAlloc, + // and moved to `CompilationStencil.alloc`. + LifoAlloc alloc; + + RefPtr source; + + // NOTE: We reserve a modest amount of inline storage in order to reduce + // allocations in the most common delazification cases. These common + // cases have one script and scope, as well as a handful of gcthings. + // For complex pages this covers about 75% of delazifications. + + Vector scriptData; + Vector scriptExtra; + + Vector gcThingData; + + Vector scopeData; + Vector scopeNames; + + Vector regExpData; + Vector bigIntData; + Vector objLiteralData; + + // Table of parser atoms for this compilation. + ParserAtomsTable parserAtoms; + + SharedDataContainer sharedData; + + RefPtr moduleMetadata; + + RefPtr asmJS; + + explicit ExtensibleCompilationStencil(ScriptSource* source); + + explicit ExtensibleCompilationStencil(CompilationInput& input); + ExtensibleCompilationStencil(const JS::ReadOnlyCompileOptions& options, + RefPtr source); + + ExtensibleCompilationStencil(ExtensibleCompilationStencil&& other) noexcept + : canLazilyParse(other.canLazilyParse), + functionKey(other.functionKey), + alloc(CompilationStencil::LifoAllocChunkSize), + source(std::move(other.source)), + scriptData(std::move(other.scriptData)), + scriptExtra(std::move(other.scriptExtra)), + gcThingData(std::move(other.gcThingData)), + scopeData(std::move(other.scopeData)), + scopeNames(std::move(other.scopeNames)), + regExpData(std::move(other.regExpData)), + bigIntData(std::move(other.bigIntData)), + objLiteralData(std::move(other.objLiteralData)), + parserAtoms(std::move(other.parserAtoms)), + sharedData(std::move(other.sharedData)), + moduleMetadata(std::move(other.moduleMetadata)), + asmJS(std::move(other.asmJS)) { + alloc.steal(&other.alloc); + parserAtoms.fixupAlloc(alloc); + } + + ExtensibleCompilationStencil& operator=( + ExtensibleCompilationStencil&& other) noexcept { + MOZ_ASSERT(alloc.isEmpty()); + + canLazilyParse = other.canLazilyParse; + functionKey = other.functionKey; + source = std::move(other.source); + scriptData = std::move(other.scriptData); + scriptExtra = std::move(other.scriptExtra); + gcThingData = std::move(other.gcThingData); + scopeData = std::move(other.scopeData); + scopeNames = std::move(other.scopeNames); + regExpData = std::move(other.regExpData); + bigIntData = std::move(other.bigIntData); + objLiteralData = std::move(other.objLiteralData); + parserAtoms = std::move(other.parserAtoms); + sharedData = std::move(other.sharedData); + moduleMetadata = std::move(other.moduleMetadata); + asmJS = std::move(other.asmJS); + + alloc.steal(&other.alloc); + parserAtoms.fixupAlloc(alloc); + + return *this; + } + + void setFunctionKey(const SourceExtent& extent) { + functionKey = extent.toFunctionKey(); + } + + bool isInitialStencil() const { + return functionKey == SourceExtent::NullFunctionKey; + } + + // Steal CompilationStencil content. + [[nodiscard]] bool steal(FrontendContext* fc, + RefPtr&& other); + + // Clone ExtensibleCompilationStencil content. + [[nodiscard]] bool cloneFrom(FrontendContext* fc, + const CompilationStencil& other); + [[nodiscard]] bool cloneFrom(FrontendContext* fc, + const ExtensibleCompilationStencil& other); + + private: + template + [[nodiscard]] bool cloneFromImpl(FrontendContext* fc, const Stencil& other); + + public: + const ParserAtomVector& parserAtomsSpan() const { + return parserAtoms.entries(); + } + + bool isModule() const; + + inline size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + +#ifdef DEBUG + void assertNoExternalDependency() const; +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); + + void dumpAtom(TaggedParserAtomIndex index); +#endif +}; + +// The internal state of the compilation. +struct MOZ_RAII CompilationState : public ExtensibleCompilationStencil { + Directives directives; + + ScopeContext scopeContext; + + UsedNameTracker usedNames; + + // LifoAlloc scope used by Parser for allocating AST etc. + // + // NOTE: This is not used for ExtensibleCompilationStencil.alloc. + LifoAllocScope& parserAllocScope; + + CompilationInput& input; + CompilationSyntaxParseCache previousParseCache; + + // The number of functions that *will* have bytecode. + // This doesn't count top-level non-function script. + // + // This should be counted while parsing, and should be passed to + // SharedDataContainer.prepareStorageFor *before* start emitting bytecode. + size_t nonLazyFunctionCount = 0; + + // End of fields. + + CompilationState(FrontendContext* fc, LifoAllocScope& parserAllocScope, + CompilationInput& input); + + bool init(FrontendContext* fc, ScopeBindingCache* scopeCache, + InheritThis inheritThis = InheritThis::No, + JSObject* enclosingEnv = nullptr) { + if (!scopeContext.init(fc, input, parserAtoms, scopeCache, inheritThis, + enclosingEnv)) { + return false; + } + + // gcThings is later used by the full parser initialization. + if (input.isDelazifying()) { + InputScript lazy = input.lazyOuterScript(); + auto& atomCache = input.atomCache; + if (!previousParseCache.init(fc, alloc, parserAtoms, atomCache, lazy)) { + return false; + } + } + + return true; + } + + // Track the state of key allocations and roll them back as parts of parsing + // get retried. This ensures iteration during stencil instantiation does not + // encounter discarded frontend state. + struct CompilationStatePosition { + // Temporarily share this token struct with CompilationState. + size_t scriptDataLength = 0; + + size_t asmJSCount = 0; + }; + + bool prepareSharedDataStorage(FrontendContext* fc); + + CompilationStatePosition getPosition(); + void rewind(const CompilationStatePosition& pos); + + // When parsing arrow function, parameter is parsed twice, and if there are + // functions inside parameter expression, stencils will be created for them. + // + // Those functions exist only for lazy parsing. + // Mark them "ghost", so that they don't affect other parts. + // + // See GHOST_FUNCTION in FunctionFlags.h for more details. + void markGhost(const CompilationStatePosition& pos); + + // Allocate space for `length` gcthings, and return the address of the + // first element to `cursor` to initialize on the caller. + bool allocateGCThingsUninitialized(FrontendContext* fc, + ScriptIndex scriptIndex, size_t length, + TaggedScriptThingIndex** cursor); + + bool appendScriptStencilAndData(FrontendContext* fc); + + bool appendGCThings(FrontendContext* fc, ScriptIndex scriptIndex, + mozilla::Span things); +}; + +// A temporary CompilationStencil instance that borrows +// ExtensibleCompilationStencil data. +// Ensure that this instance does not outlive the ExtensibleCompilationStencil. +class MOZ_STACK_CLASS BorrowingCompilationStencil : public CompilationStencil { + public: + explicit BorrowingCompilationStencil( + ExtensibleCompilationStencil& extensibleStencil); +}; + +// Size of dynamic data. Ignores Spans (unless their contents are in the +// LifoAlloc) and RefPtrs since we are not the unique owner. +inline size_t CompilationStencil::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + if (ownedBorrowStencil) { + return ownedBorrowStencil->sizeOfIncludingThis(mallocSizeOf); + } + + size_t moduleMetadataSize = + moduleMetadata ? moduleMetadata->sizeOfIncludingThis(mallocSizeOf) : 0; + size_t asmJSSize = asmJS ? asmJS->sizeOfIncludingThis(mallocSizeOf) : 0; + + return alloc.sizeOfExcludingThis(mallocSizeOf) + + sharedData.sizeOfExcludingThis(mallocSizeOf) + moduleMetadataSize + + asmJSSize; +} + +inline size_t ExtensibleCompilationStencil::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t moduleMetadataSize = + moduleMetadata ? moduleMetadata->sizeOfIncludingThis(mallocSizeOf) : 0; + size_t asmJSSize = asmJS ? asmJS->sizeOfIncludingThis(mallocSizeOf) : 0; + + return alloc.sizeOfExcludingThis(mallocSizeOf) + + scriptData.sizeOfExcludingThis(mallocSizeOf) + + scriptExtra.sizeOfExcludingThis(mallocSizeOf) + + gcThingData.sizeOfExcludingThis(mallocSizeOf) + + scopeData.sizeOfExcludingThis(mallocSizeOf) + + scopeNames.sizeOfExcludingThis(mallocSizeOf) + + regExpData.sizeOfExcludingThis(mallocSizeOf) + + bigIntData.sizeOfExcludingThis(mallocSizeOf) + + objLiteralData.sizeOfExcludingThis(mallocSizeOf) + + parserAtoms.sizeOfExcludingThis(mallocSizeOf) + + sharedData.sizeOfExcludingThis(mallocSizeOf) + moduleMetadataSize + + asmJSSize; +} + +// The output of GC allocation from stencil. +struct CompilationGCOutput { + // The resulting outermost script for the compilation powered + // by this CompilationStencil. + JSScript* script = nullptr; + + // The resulting module object if there is one. + ModuleObject* module = nullptr; + + // A Rooted vector to handle tracing of JSFunction* and Atoms within. + // + // If the top level script isn't a function, the item at TopLevelIndex is + // nullptr. + JS::GCVector functions; + + // References to scopes are controlled via AbstractScopePtr, which holds onto + // an index (and CompilationStencil reference). + JS::GCVector scopes; + + // The result ScriptSourceObject. This is unused in delazifying parses. + ScriptSourceObject* sourceObject = nullptr; + + private: + // If we are only instantiating part of a stencil, we can reduce allocations + // by setting a base index and reserving only the vector capacity we need. + // This applies to both the `functions` and `scopes` arrays. These fields are + // initialized by `ensureReservedWithBaseIndex` which also reserves the vector + // sizes appropriately. + // + // Note: These are only used for self-hosted delazification currently. + ScriptIndex functionsBaseIndex{}; + ScopeIndex scopesBaseIndex{}; + + // End of fields. + + public: + CompilationGCOutput() = default; + + // Helper to access the `functions` vector. The NoBaseIndex version is used if + // the caller never uses a base index. + JSFunction*& getFunction(ScriptIndex index) { + return functions[index - functionsBaseIndex]; + } + JSFunction*& getFunctionNoBaseIndex(ScriptIndex index) { + MOZ_ASSERT(!functionsBaseIndex); + return functions[index]; + } + + // Helper accessors for the `scopes` vector. + js::Scope*& getScope(ScopeIndex index) { + return scopes[index - scopesBaseIndex]; + } + js::Scope*& getScopeNoBaseIndex(ScopeIndex index) { + MOZ_ASSERT(!scopesBaseIndex); + return scopes[index]; + } + js::Scope* getScopeNoBaseIndex(ScopeIndex index) const { + MOZ_ASSERT(!scopesBaseIndex); + return scopes[index]; + } + + // Reserve output vector capacity. This may be called before instantiate to do + // allocations ahead of time (off thread). The stencil instantiation code will + // also run this to ensure the vectors are ready. + [[nodiscard]] bool ensureReserved(FrontendContext* fc, + size_t scriptDataLength, + size_t scopeDataLength) { + if (!functions.reserve(scriptDataLength)) { + ReportOutOfMemory(fc); + return false; + } + if (!scopes.reserve(scopeDataLength)) { + ReportOutOfMemory(fc); + return false; + } + return true; + } + + // A variant of `ensureReserved` that sets a base index for the function and + // scope arrays. This is used when instantiating only a subset of the stencil. + // Currently this only applies to self-hosted delazification. The ranges + // include the start index and exclude the limit index. + [[nodiscard]] bool ensureReservedWithBaseIndex(FrontendContext* fc, + ScriptIndex scriptStart, + ScriptIndex scriptLimit, + ScopeIndex scopeStart, + ScopeIndex scopeLimit) { + this->functionsBaseIndex = scriptStart; + this->scopesBaseIndex = scopeStart; + + return ensureReserved(fc, scriptLimit - scriptStart, + scopeLimit - scopeStart); + } + + // Size of dynamic data. Note that GC data is counted by GC and not here. + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return functions.sizeOfExcludingThis(mallocSizeOf) + + scopes.sizeOfExcludingThis(mallocSizeOf); + } + + void trace(JSTracer* trc); +}; + +// Iterator over functions that make up a CompilationStencil. This abstracts +// over the parallel arrays in stencil and gc-output that use the same index +// system. +class ScriptStencilIterable { + public: + class ScriptAndFunction { + public: + const ScriptStencil& script; + const ScriptStencilExtra* scriptExtra; + JSFunction* function; + ScriptIndex index; + + ScriptAndFunction() = delete; + ScriptAndFunction(const ScriptStencil& script, + const ScriptStencilExtra* scriptExtra, + JSFunction* function, ScriptIndex index) + : script(script), + scriptExtra(scriptExtra), + function(function), + index(index) {} + }; + + class Iterator { + size_t index_ = 0; + const CompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + Iterator(const CompilationStencil& stencil, CompilationGCOutput& gcOutput, + size_t index) + : index_(index), stencil_(stencil), gcOutput_(gcOutput) { + MOZ_ASSERT(index == stencil.scriptData.size()); + } + + public: + explicit Iterator(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) { + skipTopLevelNonFunction(); + } + + Iterator operator++() { + next(); + assertFunction(); + return *this; + } + + void next() { + MOZ_ASSERT(index_ < stencil_.scriptData.size()); + index_++; + } + + void assertFunction() { + if (index_ < stencil_.scriptData.size()) { + MOZ_ASSERT(stencil_.scriptData[index_].isFunction()); + } + } + + void skipTopLevelNonFunction() { + MOZ_ASSERT(index_ == 0); + if (stencil_.scriptData.size()) { + if (!stencil_.scriptData[0].isFunction()) { + next(); + assertFunction(); + } + } + } + + bool operator!=(const Iterator& other) const { + return index_ != other.index_; + } + + ScriptAndFunction operator*() { + ScriptIndex index = ScriptIndex(index_); + const ScriptStencil& script = stencil_.scriptData[index]; + const ScriptStencilExtra* scriptExtra = nullptr; + if (stencil_.isInitialStencil()) { + scriptExtra = &stencil_.scriptExtra[index]; + } + return ScriptAndFunction(script, scriptExtra, + gcOutput_.getFunctionNoBaseIndex(index), index); + } + + static Iterator end(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + return Iterator(stencil, gcOutput, stencil.scriptData.size()); + } + }; + + const CompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + explicit ScriptStencilIterable(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) {} + + Iterator begin() const { return Iterator(stencil_, gcOutput_); } + + Iterator end() const { return Iterator::end(stencil_, gcOutput_); } +}; + +inline ScriptStencilIterable CompilationStencil::functionScriptStencils( + const CompilationStencil& stencil, CompilationGCOutput& gcOutput) { + return ScriptStencilIterable(stencil, gcOutput); +} + +// Merge CompilationStencil for delazification into initial +// ExtensibleCompilationStencil. +struct CompilationStencilMerger { + private: + using FunctionKey = SourceExtent::FunctionKey; + + // The stencil for the initial compilation. + // Delazifications are merged into this. + // + // If any failure happens during merge operation, this field is reset to + // nullptr. + UniquePtr initial_; + + // A Map from function key to the ScriptIndex in the initial stencil. + using FunctionKeyToScriptIndexMap = + HashMap, + js::SystemAllocPolicy>; + FunctionKeyToScriptIndexMap functionKeyToInitialScriptIndex_; + + [[nodiscard]] bool buildFunctionKeyToIndex(FrontendContext* fc); + + ScriptIndex getInitialScriptIndexFor( + const CompilationStencil& delazification) const; + + // A map from delazification's ParserAtomIndex to + // initial's TaggedParserAtomIndex + using AtomIndexMap = Vector; + + [[nodiscard]] bool buildAtomIndexMap(FrontendContext* fc, + const CompilationStencil& delazification, + AtomIndexMap& atomIndexMap); + + public: + CompilationStencilMerger() = default; + + // Set the initial stencil and prepare for merging. + [[nodiscard]] bool setInitial( + FrontendContext* fc, UniquePtr&& initial); + + // Merge the delazification stencil into the initial stencil. + [[nodiscard]] bool addDelazification( + FrontendContext* fc, const CompilationStencil& delazification); + + ExtensibleCompilationStencil& getResult() const { return *initial_; } + UniquePtr takeResult() { + return std::move(initial_); + } +}; + +const ScopeStencil& ScopeStencilRef::scope() const { + return context_.scopeData[scopeIndex_]; +} + +InputScope InputScope::enclosing() const { + return scope_.match( + [](const Scope* ptr) { + // This may return a nullptr Scope pointer. + return InputScope(ptr->enclosing()); + }, + [](const ScopeStencilRef& ref) { + if (ref.scope().hasEnclosing()) { + return InputScope(ref.context_, ref.scope().enclosing()); + } + return InputScope(nullptr); + }); +} + +FunctionFlags InputScope::functionFlags() const { + return scope_.match( + [](const Scope* ptr) { + JSFunction* fun = ptr->as().canonicalFunction(); + return fun->flags(); + }, + [](const ScopeStencilRef& ref) { + MOZ_ASSERT(ref.scope().isFunction()); + ScriptIndex scriptIndex = ref.scope().functionIndex(); + ScriptStencil& data = ref.context_.scriptData[scriptIndex]; + return data.functionFlags; + }); +} + +ImmutableScriptFlags InputScope::immutableFlags() const { + return scope_.match( + [](const Scope* ptr) { + JSFunction* fun = ptr->as().canonicalFunction(); + return fun->baseScript()->immutableFlags(); + }, + [](const ScopeStencilRef& ref) { + MOZ_ASSERT(ref.scope().isFunction()); + ScriptIndex scriptIndex = ref.scope().functionIndex(); + ScriptStencilExtra& extra = ref.context_.scriptExtra[scriptIndex]; + return extra.immutableFlags; + }); +} + +MemberInitializers InputScope::getMemberInitializers() const { + return scope_.match( + [](const Scope* ptr) { + JSFunction* fun = ptr->as().canonicalFunction(); + return fun->baseScript()->getMemberInitializers(); + }, + [](const ScopeStencilRef& ref) { + MOZ_ASSERT(ref.scope().isFunction()); + ScriptIndex scriptIndex = ref.scope().functionIndex(); + ScriptStencilExtra& extra = ref.context_.scriptExtra[scriptIndex]; + return extra.memberInitializers(); + }); +} + +const ScriptStencil& ScriptStencilRef::scriptData() const { + return context_.scriptData[scriptIndex_]; +} + +const ScriptStencilExtra& ScriptStencilRef::scriptExtra() const { + return context_.scriptExtra[scriptIndex_]; +} + +} // namespace frontend +} // namespace js + +#endif // frontend_CompilationStencil_h -- cgit v1.2.3