Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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, MOZ_RELEASE_ASSERT, MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE
#include "mozilla/Atomics.h" // mozilla::Atomic
#include "mozilla/Attributes.h" // MOZ_RAII, MOZ_STACK_CLASS
#include "mozilla/HashTable.h" // mozilla::HashMap, mozilla::DefaultHasher
#include "mozilla/Maybe.h" // mozilla::Maybe
#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf
#include "mozilla/RefPtr.h" // RefPtr
#include "mozilla/Span.h" // mozilla::Span
#include "mozilla/Variant.h" // mozilla::Variant
#include <algorithm> // std::swap
#include <stddef.h> // size_t
#include <stdint.h> // uint32_t, uintptr_t
#include <type_traits> // std::is_pointer_v
#include <utility> // std::forward, std::move
#include "ds/LifoAlloc.h" // LifoAlloc, LifoAllocScope
#include "frontend/FrontendContext.h" // FrontendContext
#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
#include "frontend/NameAnalysisTypes.h" // NameLocation
#include "frontend/ParserAtom.h" // ParserAtomsTable, ParserAtomIndex, TaggedParserAtomIndex, ParserAtomSpan
#include "frontend/ScopeIndex.h" // ScopeIndex
#include "frontend/ScriptIndex.h" // ScriptIndex
#include "frontend/SharedContext.h" // ThisBinding, InheritThis, Directives
#include "frontend/Stencil.h" // ScriptStencil, ScriptStencilExtra, ScopeStencil, RegExpStencil, BigIntStencil, ObjLiteralStencil, BaseParserScopeData, StencilModuleMetadata
#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
#include "frontend/UsedNameTracker.h" // UsedNameTracker
#include "js/AllocPolicy.h" // SystemAllocPolicy, ReportOutOfMemory
#include "js/GCVector.h" // JS::GCVector
#include "js/RefCounted.h" // AtomicRefCounted
#include "js/RootingAPI.h" // JS::Handle
#include "js/Transcoding.h" // JS::TranscodeBuffer, JS::TranscodeRange
#include "js/UniquePtr.h" // js::UniquePtr
#include "js/Vector.h" // Vector
#include "js/WasmModule.h" // JS::WasmModule
#include "vm/FunctionFlags.h" // FunctionFlags
#include "vm/GlobalObject.h" // GlobalObject
#include "vm/JSContext.h" // JSContext
#include "vm/JSFunction.h" // JSFunction
#include "vm/JSScript.h" // BaseScript, ScriptSource, SourceExtent
#include "vm/Realm.h" // JSContext::global
#include "vm/Scope.h" // Scope, ModuleScope
#include "vm/ScopeKind.h" // ScopeKind
#include "vm/SharedStencil.h" // ImmutableScriptFlags, MemberInitializers, SharedImmutableScriptData, RO_IMMUTABLE_SCRIPT_FLAGS
class JSAtom;
class JSFunction;
class JSObject;
class JSString;
class JSTracer;
namespace JS {
class JS_PUBLIC_API ReadOnlyCompileOptions;
}
namespace js {
class AtomSet;
class JSONPrinter;
class ModuleObject;
namespace frontend {
struct CompilationInput;
struct CompilationStencil;
struct CompilationGCOutput;
struct PreallocatedCompilationGCOutput;
class ScriptStencilIterable;
struct InputName;
class ScopeBindingCache;
// When delazifying modules' inner functions, the actual global scope is used.
// However, when doing a delazification the global scope is not available. We
// use this dummy type to be a placeholder to be used as part of the InputScope
// variants to mimic what the Global scope would be used for.
struct FakeStencilGlobalScope {};
// 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<Scope*, ScopeStencilRef, FakeStencilGlobalScope>;
InputScopeStorage scope_;
public:
// Create an InputScope given an instantiated scope.
explicit InputScope(Scope* ptr) : scope_(ptr) {}
// Create an InputScope for a global.
explicit InputScope(FakeStencilGlobalScope global) : scope_(global) {}
// 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 <typename Matcher>
decltype(auto) match(Matcher&& matcher) const& {
return scope_.match(std::forward<Matcher>(matcher));
}
template <typename Matcher>
decltype(auto) match(Matcher&& matcher) & {
return scope_.match(std::forward<Matcher>(matcher));
}
bool isNull() const {
return scope_.match(
[](const Scope* ptr) { return !ptr; },
[](const ScopeStencilRef& ref) { return !ref.scopeIndex_.isValid(); },
[](const FakeStencilGlobalScope&) { return false; });
}
ScopeKind kind() const {
return scope_.match(
[](const Scope* ptr) { return ptr->kind(); },
[](const ScopeStencilRef& ref) { return ref.scope().kind(); },
[](const FakeStencilGlobalScope&) { return ScopeKind::Global; });
};
bool hasEnvironment() const {
return scope_.match(
[](const Scope* ptr) { return ptr->hasEnvironment(); },
[](const ScopeStencilRef& ref) { return ref.scope().hasEnvironment(); },
[](const FakeStencilGlobalScope&) {
// See Scope::hasEnvironment
return true;
});
};
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.kind() == ScopeKind::Module &&
kind == ScopeKind::Global) {
return true;
}
if (!scope.hasEnclosing()) {
break;
}
new (&it) ScopeStencilRef{ref.context_, scope.enclosing()};
}
return false;
},
[=](const FakeStencilGlobalScope&) {
return kind == ScopeKind::Global;
});
}
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.kind() == ScopeKind::Module) {
// Stencil do not encode the Global scope, as it used to be
// assumed to already exists. As moving delazification off-thread,
// we need to materialize a fake-stencil version of the Global
// Scope.
MOZ_ASSERT(!scope.hasEnclosing());
length += js::ModuleScope::EnclosingEnvironmentChainLength;
}
if (!scope.hasEnclosing()) {
break;
}
new (&it) ScopeStencilRef{ref.context_, scope.enclosing()};
}
return length;
},
[=](const FakeStencilGlobalScope&) {
// Stencil-based delazification needs to calculate
// environmentChainLength where the global is not available.
//
// The FakeStencilGlobalScope is used to represent what the global
// would be if we had access to it while delazifying.
return uint32_t(js::ModuleScope::EnclosingEnvironmentChainLength);
});
}
void trace(JSTracer* trc);
bool isStencil() const { return !scope_.is<Scope*>(); };
// 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<BaseScript*, ScriptStencilRef>;
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<InputScopeIter> 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<JSAtom*, NameStencilRef>;
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}) {}
// Dummy for empty global.
InputName(const FakeStencilGlobalScope&, TaggedParserAtomIndex)
: variant_(static_cast<JSAtom*>(nullptr)) {}
// 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> memberInitializers = {};
enum class EnclosingLexicalBindingKind {
Let,
Const,
CatchParameter,
Synthetic,
PrivateMethod,
};
using EnclosingLexicalBindingCache =
mozilla::HashMap<TaggedParserAtomIndex, EnclosingLexicalBindingKind,
TaggedParserAtomIndexHasher>;
// Cache of enclosing lexical bindings.
// Used only for eval.
mozilla::Maybe<EnclosingLexicalBindingCache> 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<TaggedParserAtomIndex, NameLocation,
TaggedParserAtomIndexHasher>;
// Cache of enclosing class's private fields.
// Used only for eval.
mozilla::Maybe<EffectiveScopePrivateFieldCache>
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<EnclosingLexicalBindingKind>
lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name);
NameLocation searchInEnclosingScope(FrontendContext* fc,
CompilationInput& input,
ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name);
bool effectiveScopePrivateFieldCacheHas(TaggedParserAtomIndex name);
mozilla::Maybe<NameLocation> 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<JSString*, 0, js::SystemAllocPolicy>;
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);
}
};
// Information associated with an extra binding provided to a global script.
// See frontend::CompileGlobalScriptWithExtraBindings.
struct ExtraBindingInfo {
// UTF-8 encoded name of the binding.
UniqueChars nameChars;
TaggedParserAtomIndex nameIndex;
// If the binding conflicts with global variable or global lexical variable,
// the binding is shadowed.
bool isShadowed = false;
ExtraBindingInfo(UniqueChars&& nameChars, bool isShadowed)
: nameChars(std::move(nameChars)), isShadowed(isShadowed) {}
};
using ExtraBindingInfoVector =
js::Vector<ExtraBindingInfo, 0, js::SystemAllocPolicy>;
// 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);
// Extra bindings for the global script.
ExtraBindingInfoVector* maybeExtraBindings_ = nullptr;
public:
RefPtr<ScriptSource> 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 initForGlobalWithExtraBindings(
FrontendContext* fc, ExtraBindingInfoVector* maybeExtraBindings) {
MOZ_ASSERT(maybeExtraBindings);
target = CompilationTarget::Global;
maybeExtraBindings_ = maybeExtraBindings;
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, JS::Handle<Scope*> functionEnclosingScope);
bool initForEval(FrontendContext* fc, JS::Handle<Scope*> 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<BaseScript*>(); }
// The JSFunction* is needed when instantiating a lazy function.
// See FunctionsFromExistingLazy.
JSFunction* function() const {
return lazy_.raw().as<BaseScript*>()->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; }
bool hasExtraBindings() const { return !!maybeExtraBindings_; }
ExtraBindingInfoVector& extraBindings() { return *maybeExtraBindings_; }
const ExtraBindingInfoVector& extraBindings() const {
return *maybeExtraBindings_;
}
bool internExtraBindings(FrontendContext* fc, ParserAtomsTable& parserAtoms);
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<TaggedScriptThingIndex> 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<ScriptStencil> cachedScriptData_;
mozilla::Span<ScriptStencilExtra> 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<TaggedParserAtomIndex> 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<TaggedParserAtomIndex> 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 =
mozilla::HashMap<ScriptIndex, RefPtr<const JS::WasmModule>,
mozilla::DefaultHasher<ScriptIndex>,
js::SystemAllocPolicy>;
struct StencilAsmJSContainer
: public js::AtomicRefCounted<StencilAsmJSContainer> {
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<RefPtr<js::SharedImmutableScriptData>, 0, js::SystemAllocPolicy>;
using SharedDataVectorPtr = SharedDataVector*;
using SharedDataMap =
mozilla::HashMap<ScriptIndex, RefPtr<js::SharedImmutableScriptData>,
mozilla::DefaultHasher<ScriptIndex>,
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<SharedImmutableScriptData>&& data) {
MOZ_ASSERT(isEmpty());
data_ = reinterpret_cast<uintptr_t>(data.take());
MOZ_ASSERT(isSingle());
MOZ_ASSERT(!isEmpty());
}
void setBorrow(SharedDataContainer* sharedData) {
MOZ_ASSERT(isEmpty());
data_ = reinterpret_cast<uintptr_t>(sharedData) | BorrowTag;
MOZ_ASSERT(isBorrow());
}
SingleSharedDataPtr asSingle() const {
MOZ_ASSERT(isSingle());
MOZ_ASSERT(!isEmpty());
static_assert(SingleTag == 0);
return reinterpret_cast<SingleSharedDataPtr>(data_);
}
SharedDataVectorPtr asVector() const {
MOZ_ASSERT(isVector());
return reinterpret_cast<SharedDataVectorPtr>(data_ & ~TagMask);
}
SharedDataMapPtr asMap() const {
MOZ_ASSERT(isMap());
return reinterpret_cast<SharedDataMapPtr>(data_ & ~TagMask);
}
SharedDataContainer* asBorrow() const {
MOZ_ASSERT(isBorrow());
return reinterpret_cast<SharedDataContainer*>(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<T>, or RefPtr<T>. 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<uintptr_t> refCount{0};
private:
// On-heap ExtensibleCompilationStencil that this CompilationStencil owns,
// and this CompilationStencil borrows each data from.
UniquePtr<ExtensibleCompilationStencil> 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<ScriptSource> 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<ScriptStencil> scriptData;
// Immutable data computed during initial compilation and never updated during
// delazification.
mozilla::Span<ScriptStencilExtra> scriptExtra;
mozilla::Span<TaggedScriptThingIndex> 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<ScopeStencil> scopeData;
mozilla::Span<BaseParserScopeData*> scopeNames;
// Hold onto the RegExpStencil, BigIntStencil, and ObjLiteralStencil that are
// allocated during parse to ensure correct destruction.
mozilla::Span<RegExpStencil> regExpData;
mozilla::Span<BigIntStencil> bigIntData;
mozilla::Span<ObjLiteralStencil> 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<StencilModuleMetadata> moduleMetadata;
// AsmJS modules generated by parsing. These scripts are never lazy and
// therefore only generated during initial parse.
RefPtr<StencilAsmJSContainer> 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<ExtensibleCompilationStencil>&& 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 prepareForInstantiate(
FrontendContext* fc, const CompilationStencil& stencil,
PreallocatedCompilationGCOutput& 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,
JS::Handle<JSAtom*> name);
[[nodiscard]] bool delazifySelfHostedFunction(JSContext* cx,
CompilationAtomCache& atomCache,
ScriptIndexRange range,
JS::Handle<JSFunction*> 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<ScriptSource> 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<ScriptStencil, 1, js::SystemAllocPolicy> scriptData;
Vector<ScriptStencilExtra, 0, js::SystemAllocPolicy> scriptExtra;
Vector<TaggedScriptThingIndex, 8, js::SystemAllocPolicy> gcThingData;
Vector<ScopeStencil, 1, js::SystemAllocPolicy> scopeData;
Vector<BaseParserScopeData*, 1, js::SystemAllocPolicy> scopeNames;
Vector<RegExpStencil, 0, js::SystemAllocPolicy> regExpData;
Vector<BigIntStencil, 0, js::SystemAllocPolicy> bigIntData;
Vector<ObjLiteralStencil, 0, js::SystemAllocPolicy> objLiteralData;
// Table of parser atoms for this compilation.
ParserAtomsTable parserAtoms;
SharedDataContainer sharedData;
RefPtr<StencilModuleMetadata> moduleMetadata;
RefPtr<StencilAsmJSContainer> asmJS;
explicit ExtensibleCompilationStencil(ScriptSource* source);
explicit ExtensibleCompilationStencil(CompilationInput& input);
ExtensibleCompilationStencil(const JS::ReadOnlyCompileOptions& options,
RefPtr<ScriptSource> 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<CompilationStencil>&& other);
// Clone ExtensibleCompilationStencil content.
[[nodiscard]] bool cloneFrom(FrontendContext* fc,
const CompilationStencil& other);
[[nodiscard]] bool cloneFrom(FrontendContext* fc,
const ExtensibleCompilationStencil& other);
private:
template <typename Stencil>
[[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<const TaggedScriptThingIndex> 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;
}
// A PreAllocateableGCArray is an array of GC thing pointers.
//
// The array's internal buffer can be allocated ahead of time, possibly off
// main thread.
template <typename T>
struct PreAllocateableGCArray {
private:
size_t length_ = 0;
// Inline element for the case when length_ == 1.
T inlineElem_;
// Heap-allocated elements for the case when length_ > 1;
T* elems_ = nullptr;
public:
struct Preallocated {
private:
size_t length_ = 0;
uintptr_t* elems_ = nullptr;
friend struct PreAllocateableGCArray<T>;
public:
Preallocated() = default;
~Preallocated();
bool empty() const { return length_ == 0; }
size_t length() const { return length_; }
private:
bool isInline() const { return length_ == 1; }
public:
bool allocate(size_t length);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return sizeof(uintptr_t) * length_;
}
};
PreAllocateableGCArray() {
static_assert(std::is_pointer_v<T>,
"PreAllocateableGCArray element must be a pointer");
}
~PreAllocateableGCArray();
bool empty() const { return length_ == 0; }
size_t length() const { return length_; }
private:
bool isInline() const { return length_ == 1; }
public:
bool allocate(size_t length);
bool allocateWith(T init, size_t length);
// Steal pre-allocated buffer.
void steal(Preallocated&& buffer);
T& operator[](size_t index) {
MOZ_ASSERT(index < length_);
if (isInline()) {
return inlineElem_;
}
return elems_[index];
}
const T& operator[](size_t index) const {
MOZ_ASSERT(index < length_);
if (isInline()) {
return inlineElem_;
}
return elems_[index];
}
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
if (!elems_) {
return 0;
}
return sizeof(T) * length_;
}
void trace(JSTracer* trc);
};
struct CompilationGCOutput;
// Pre-allocated storage for CompilationGCOutput.
struct PreallocatedCompilationGCOutput {
private:
PreAllocateableGCArray<JSFunction*>::Preallocated functions;
PreAllocateableGCArray<js::Scope*>::Preallocated scopes;
friend struct CompilationGCOutput;
public:
PreallocatedCompilationGCOutput() = default;
[[nodiscard]] bool allocate(FrontendContext* fc, size_t scriptDataLength,
size_t scopeDataLength) {
if (!functions.allocate(scriptDataLength)) {
ReportOutOfMemory(fc);
return false;
}
if (!scopes.allocate(scopeDataLength)) {
ReportOutOfMemory(fc);
return false;
}
return true;
}
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return functions.sizeOfExcludingThis(mallocSizeOf) +
scopes.sizeOfExcludingThis(mallocSizeOf);
}
};
// 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;
// An array to handle tracing of JSFunction* and Atoms within.
//
// If the top level script isn't a function, the item at TopLevelIndex is
// nullptr.
PreAllocateableGCArray<JSFunction*> functions;
// References to scopes are controlled via AbstractScopePtr, which holds onto
// an index (and CompilationStencil reference).
PreAllocateableGCArray<js::Scope*> 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 allocating only the array elements we need.
// This applies to both the `functions` and `scopes` arrays. These fields are
// initialized by `ensureAllocatedWithBaseIndex` which also allocates the
// array 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` array. 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` array.
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];
}
// Allocate output arrays.
[[nodiscard]] bool ensureAllocated(FrontendContext* fc,
size_t scriptDataLength,
size_t scopeDataLength) {
if (functions.empty()) {
if (!functions.allocate(scriptDataLength)) {
ReportOutOfMemory(fc);
return false;
}
}
if (scopes.empty()) {
if (!scopes.allocate(scopeDataLength)) {
ReportOutOfMemory(fc);
return false;
}
}
return true;
}
// Steal output arrays' buffer.
void steal(PreallocatedCompilationGCOutput&& pre) {
functions.steal(std::move(pre.functions));
scopes.steal(std::move(pre.scopes));
}
// A variant of `ensureAllocated` 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 ensureAllocatedWithBaseIndex(FrontendContext* fc,
ScriptIndex scriptStart,
ScriptIndex scriptLimit,
ScopeIndex scopeStart,
ScopeIndex scopeLimit) {
this->functionsBaseIndex = scriptStart;
this->scopesBaseIndex = scopeStart;
return ensureAllocated(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<ExtensibleCompilationStencil> initial_;
// A Map from function key to the ScriptIndex in the initial stencil.
using FunctionKeyToScriptIndexMap =
mozilla::HashMap<FunctionKey, ScriptIndex,
mozilla::DefaultHasher<FunctionKey>,
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<TaggedParserAtomIndex, 0, js::SystemAllocPolicy>;
[[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<ExtensibleCompilationStencil>&& initial);
// Merge the delazification stencil into the initial stencil.
[[nodiscard]] bool addDelazification(
FrontendContext* fc, const CompilationStencil& delazification);
ExtensibleCompilationStencil& getResult() const { return *initial_; }
UniquePtr<ExtensibleCompilationStencil> 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());
}
// The global scope is not known by the Stencil, while parsing inner
// functions from Stencils where they are known at the execution using
// the GlobalScope.
if (ref.scope().kind() == ScopeKind::Module) {
return InputScope(FakeStencilGlobalScope{});
}
return InputScope(nullptr);
},
[](const FakeStencilGlobalScope&) { return InputScope(nullptr); });
}
FunctionFlags InputScope::functionFlags() const {
return scope_.match(
[](const Scope* ptr) {
JSFunction* fun = ptr->as<FunctionScope>().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;
},
[](const FakeStencilGlobalScope&) -> FunctionFlags {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No functionFlags on global.");
});
}
ImmutableScriptFlags InputScope::immutableFlags() const {
return scope_.match(
[](const Scope* ptr) {
JSFunction* fun = ptr->as<FunctionScope>().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;
},
[](const FakeStencilGlobalScope&) -> ImmutableScriptFlags {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No immutableFlags on global.");
});
}
MemberInitializers InputScope::getMemberInitializers() const {
return scope_.match(
[](const Scope* ptr) {
JSFunction* fun = ptr->as<FunctionScope>().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 FakeStencilGlobalScope&) -> MemberInitializers {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
"No getMemberInitializers on global.");
});
}
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