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