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
#include "frontend/Stencil.h"
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/Assertions.h" // MOZ_RELEASE_ASSERT
#include "mozilla/CheckedInt.h" // mozilla::CheckedInt
#include "mozilla/Maybe.h" // mozilla::Maybe
#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull
#include "mozilla/PodOperations.h" // mozilla::PodCopy
#include "mozilla/RefPtr.h" // RefPtr
#include "mozilla/ScopeExit.h" // mozilla::ScopeExit
#include "mozilla/Sprintf.h" // SprintfLiteral
#include <algorithm> // std::fill
#include <string.h> // strlen
#include "ds/LifoAlloc.h" // LifoAlloc
#include "frontend/AbstractScopePtr.h" // ScopeIndex
#include "frontend/BytecodeCompiler.h" // CompileGlobalScriptToStencil, InstantiateStencils, CanLazilyParse, ParseModuleToStencil
#include "frontend/BytecodeSection.h" // EmitScriptThingsVector
#include "frontend/CompilationStencil.h" // CompilationStencil, CompilationState, ExtensibleCompilationStencil, CompilationGCOutput, CompilationStencilMerger
#include "frontend/FrontendContext.h"
#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate
#include "frontend/ParserAtom.h" // ParserAtom, ParserAtomIndex, TaggedParserAtomIndex, ParserAtomsTable, Length{1,2,3}StaticParserString, InstantiateMarkedAtoms, InstantiateMarkedAtomsAsPermanent, GetWellKnownAtom
#include "frontend/ScopeBindingCache.h" // ScopeBindingCache
#include "frontend/SharedContext.h"
#include "frontend/StencilXdr.h" // XDRStencilEncoder, XDRStencilDecoder
#include "gc/AllocKind.h" // gc::AllocKind
#include "gc/Tracer.h" // TraceNullableRoot
#include "js/CallArgs.h" // JSNative
#include "js/CompileOptions.h" // JS::DecodeOptions, JS::ReadOnlyDecodeOptions
#include "js/experimental/CompileScript.h" // JS::PrepareForInstantiate
#include "js/experimental/JSStencil.h" // JS::Stencil
#include "js/GCAPI.h" // JS::AutoCheckCannotGC
#include "js/Printer.h" // js::Fprinter
#include "js/RealmOptions.h" // JS::RealmBehaviors
#include "js/RootingAPI.h" // Rooted
#include "js/Transcoding.h" // JS::TranscodeBuffer
#include "js/Utility.h" // js_malloc, js_calloc, js_free
#include "js/Value.h" // ObjectValue
#include "js/WasmModule.h" // JS::WasmModule
#include "vm/BigIntType.h" // ParseBigIntLiteral, BigInt::createFromInt64
#include "vm/BindingKind.h" // BindingKind
#include "vm/EnvironmentObject.h"
#include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind
#include "vm/JSContext.h" // JSContext
#include "vm/JSFunction.h" // JSFunction, GetFunctionPrototype, NewFunctionWithProto
#include "vm/JSObject.h" // JSObject, TenuredObject
#include "vm/JSONPrinter.h" // js::JSONPrinter
#include "vm/JSScript.h" // BaseScript, JSScript
#include "vm/Realm.h" // JS::Realm
#include "vm/RegExpObject.h" // js::RegExpObject
#include "vm/Scope.h" // Scope, *Scope, ScopeKind::*, ScopeKindString, ScopeIter, ScopeKindIsCatch, BindingIter, GetScopeDataTrailingNames, SizeOfParserScopeData
#include "vm/ScopeKind.h" // ScopeKind
#include "vm/SelfHosting.h" // SetClonedSelfHostedFunctionName
#include "vm/StaticStrings.h"
#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum
#include "vm/StringType.h" // JSAtom, js::CopyChars
#include "wasm/AsmJS.h" // InstantiateAsmJS
#include "vm/EnvironmentObject-inl.h" // JSObject::enclosingEnvironment
#include "vm/JSFunction-inl.h" // JSFunction::create
using namespace js;
using namespace js::frontend;
// These 2 functions are used to write the same code with lambda using auto
// arguments. The auto argument type is set by the Variant.match function of the
// InputScope variant. Thus dispatching to either a Scope* or to a
// ScopeStencilRef. This function can then be used as a way to specialize the
// code within the lambda without duplicating the code.
//
// Identically, an InputName is constructed using the scope type and the
// matching binding name type. This way, functions which are called by this
// lambda can manipulate an InputName and do not have to be duplicated.
//
// for (InputScopeIter si(...); si; si++) {
// si.scope().match([](auto& scope) {
// for (auto bi = InputBindingIter(scope); bi; bi++) {
// InputName name(scope, bi.name());
// }
// });
// }
static js::BindingIter InputBindingIter(Scope* ptr) {
return js::BindingIter(ptr);
}
static ParserBindingIter InputBindingIter(const ScopeStencilRef& ref) {
return ParserBindingIter(ref);
}
static ParserBindingIter InputBindingIter(const FakeStencilGlobalScope&) {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No bindings on empty global.");
}
InputName InputScript::displayAtom() const {
return script_.match(
[](BaseScript* ptr) {
return InputName(ptr, ptr->function()->fullDisplayAtom());
},
[](const ScriptStencilRef& ref) {
return InputName(ref, ref.scriptData().functionAtom);
});
}
TaggedParserAtomIndex InputName::internInto(FrontendContext* fc,
ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache) {
return variant_.match(
[&](JSAtom* ptr) -> TaggedParserAtomIndex {
return parserAtoms.internJSAtom(fc, atomCache, ptr);
},
[&](NameStencilRef& ref) -> TaggedParserAtomIndex {
return parserAtoms.internExternalParserAtomIndex(fc, ref.context_,
ref.atomIndex_);
});
}
bool InputName::isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache,
TaggedParserAtomIndex other,
JSAtom** otherCached) const {
return variant_.match(
[&](const JSAtom* ptr) -> bool {
if (ptr->hash() != parserAtoms.hash(other)) {
return false;
}
// JSAtom variant is used only on the main thread delazification,
// where JSContext is always available.
JSContext* cx = fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
if (!*otherCached) {
// TODO-Stencil:
// Here, we convert our name into a JSAtom*, and hard-crash on failure
// to allocate. This conversion should not be required as we should be
// able to iterate up snapshotted scope chains that use parser atoms.
//
// This will be fixed when the enclosing scopes are snapshotted.
//
AutoEnterOOMUnsafeRegion oomUnsafe;
*otherCached = parserAtoms.toJSAtom(cx, fc, other, atomCache);
if (!*otherCached) {
oomUnsafe.crash("InputName::isEqualTo");
}
} else {
MOZ_ASSERT(atomCache.getExistingAtomAt(cx, other) == *otherCached);
}
return ptr == *otherCached;
},
[&](const NameStencilRef& ref) -> bool {
return parserAtoms.isEqualToExternalParserAtomIndex(other, ref.context_,
ref.atomIndex_);
});
}
GenericAtom::GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache,
TaggedParserAtomIndex index)
: ref(EmitterName(fc, parserAtoms, atomCache, index)) {
hash = parserAtoms.hash(index);
}
GenericAtom::GenericAtom(const CompilationStencil& context,
TaggedParserAtomIndex index)
: ref(StencilName{context, index}) {
if (index.isParserAtomIndex()) {
ParserAtom* atom = context.parserAtomData[index.toParserAtomIndex()];
hash = atom->hash();
} else {
hash = index.staticOrWellKnownHash();
}
}
GenericAtom::GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index)
: GenericAtom(scope.context_, index) {}
BindingHasher<TaggedParserAtomIndex>::Lookup::Lookup(ScopeStencilRef& scope_ref,
const GenericAtom& other)
: keyStencil(scope_ref.context_), other(other) {}
bool GenericAtom::operator==(const GenericAtom& other) const {
return ref.match(
[&other](const EmitterName& name) -> bool {
return other.ref.match(
[&name](const EmitterName& other) -> bool {
// We never have multiple Emitter context at the same time.
MOZ_ASSERT(name.fc == other.fc);
MOZ_ASSERT(&name.parserAtoms == &other.parserAtoms);
MOZ_ASSERT(&name.atomCache == &other.atomCache);
return name.index == other.index;
},
[&name](const StencilName& other) -> bool {
return name.parserAtoms.isEqualToExternalParserAtomIndex(
name.index, other.stencil, other.index);
},
[&name](JSAtom* other) -> bool {
// JSAtom variant is used only on the main thread delazification,
// where JSContext is always available.
JSContext* cx = name.fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
AutoEnterOOMUnsafeRegion oomUnsafe;
JSAtom* namePtr = name.parserAtoms.toJSAtom(
cx, name.fc, name.index, name.atomCache);
if (!namePtr) {
oomUnsafe.crash("GenericAtom(EmitterName == JSAtom*)");
}
return namePtr == other;
});
},
[&other](const StencilName& name) -> bool {
return other.ref.match(
[&name](const EmitterName& other) -> bool {
return other.parserAtoms.isEqualToExternalParserAtomIndex(
other.index, name.stencil, name.index);
},
[&name](const StencilName& other) -> bool {
// Technically it is possible to have multiple stencils, but in
// this particular case let's assume we never encounter a case
// where we are comparing names from different stencils.
//
// The reason this assumption is safe today is that we are only
// using this in the context of a stencil-delazification, where
// the only StencilNames are coming from the CompilationStencil
// provided to CompilationInput::initFromStencil.
MOZ_ASSERT(&name.stencil == &other.stencil);
return name.index == other.index;
},
[](JSAtom* other) -> bool {
MOZ_CRASH("Never used.");
return false;
});
},
[&other](JSAtom* name) -> bool {
return other.ref.match(
[&name](const EmitterName& other) -> bool {
// JSAtom variant is used only on the main thread delazification,
// where JSContext is always available.
JSContext* cx = other.fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
AutoEnterOOMUnsafeRegion oomUnsafe;
JSAtom* otherPtr = other.parserAtoms.toJSAtom(
cx, other.fc, other.index, other.atomCache);
if (!otherPtr) {
oomUnsafe.crash("GenericAtom(JSAtom* == EmitterName)");
}
return name == otherPtr;
},
[](const StencilName& other) -> bool {
MOZ_CRASH("Never used.");
return false;
},
[&name](JSAtom* other) -> bool { return name == other; });
});
}
#ifdef DEBUG
template <typename SpanT, typename VecT>
void AssertBorrowingSpan(const SpanT& span, const VecT& vec) {
MOZ_ASSERT(span.size() == vec.length());
MOZ_ASSERT(span.data() == vec.begin());
}
#endif
bool ScopeBindingCache::canCacheFor(Scope* ptr) {
MOZ_CRASH("Unexpected scope chain type: Scope*");
}
bool ScopeBindingCache::canCacheFor(ScopeStencilRef ref) {
MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}
bool ScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}
BindingMap<JSAtom*>* ScopeBindingCache::createCacheFor(Scope* ptr) {
MOZ_CRASH("Unexpected scope chain type: Scope*");
}
BindingMap<JSAtom*>* ScopeBindingCache::lookupScope(Scope* ptr,
CacheGeneration gen) {
MOZ_CRASH("Unexpected scope chain type: Scope*");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
ScopeStencilRef ref) {
MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
ScopeStencilRef ref, CacheGeneration gen) {
MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
const FakeStencilGlobalScope& ref) {
MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
const FakeStencilGlobalScope& ref, CacheGeneration gen) {
MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}
bool NoScopeBindingCache::canCacheFor(Scope* ptr) { return false; }
bool NoScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return false; }
bool NoScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
return false;
}
bool RuntimeScopeBindingCache::canCacheFor(Scope* ptr) { return true; }
BindingMap<JSAtom*>* RuntimeScopeBindingCache::createCacheFor(Scope* ptr) {
BaseScopeData* dataPtr = ptr->rawData();
BindingMap<JSAtom*> bindingCache;
if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
return nullptr;
}
return lookupScope(ptr, cacheGeneration);
}
BindingMap<JSAtom*>* RuntimeScopeBindingCache::lookupScope(
Scope* ptr, CacheGeneration gen) {
MOZ_ASSERT(gen == cacheGeneration);
BaseScopeData* dataPtr = ptr->rawData();
auto valuePtr = scopeMap.lookup(dataPtr);
if (!valuePtr) {
return nullptr;
}
return &valuePtr->value();
}
bool StencilScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return true; }
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
ScopeStencilRef ref) {
#ifdef DEBUG
AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
#endif
auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
BindingMap<TaggedParserAtomIndex> bindingCache;
if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
return nullptr;
}
return lookupScope(ref, 1);
}
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
ScopeStencilRef ref, CacheGeneration gen) {
#ifdef DEBUG
AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
#endif
auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
auto ptr = scopeMap.lookup(dataPtr);
if (!ptr) {
return nullptr;
}
return &ptr->value();
}
static AbstractBaseScopeData<TaggedParserAtomIndex>
moduleGlobalAbstractScopeData;
bool StencilScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
return true;
}
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
const FakeStencilGlobalScope& ref) {
auto* dataPtr = &moduleGlobalAbstractScopeData;
BindingMap<TaggedParserAtomIndex> bindingCache;
if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
return nullptr;
}
return lookupScope(ref, 1);
}
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
const FakeStencilGlobalScope& ref, CacheGeneration gen) {
auto* dataPtr = &moduleGlobalAbstractScopeData;
auto ptr = scopeMap.lookup(dataPtr);
if (!ptr) {
return nullptr;
}
return &ptr->value();
}
bool ScopeContext::init(FrontendContext* fc, CompilationInput& input,
ParserAtomsTable& parserAtoms,
ScopeBindingCache* scopeCache, InheritThis inheritThis,
JSObject* enclosingEnv) {
// Record the scopeCache to be used while looking up NameLocation bindings.
this->scopeCache = scopeCache;
scopeCacheGen = scopeCache->getCurrentGeneration();
InputScope maybeNonDefaultEnclosingScope(
input.maybeNonDefaultEnclosingScope());
// If this eval is in response to Debugger.Frame.eval, we may have an
// incomplete scope chain. In order to provide a better debugging experience,
// we inspect the (optional) environment chain to determine it's enclosing
// FunctionScope if there is one. If there is no such scope, we use the
// orignal scope provided.
//
// NOTE: This is used to compute the ThisBinding kind and to allow access to
// private fields and methods, while other contextual information only
// uses the actual scope passed to the compile.
auto effectiveScope =
determineEffectiveScope(maybeNonDefaultEnclosingScope, enclosingEnv);
if (inheritThis == InheritThis::Yes) {
computeThisBinding(effectiveScope);
computeThisEnvironment(maybeNonDefaultEnclosingScope);
}
computeInScope(maybeNonDefaultEnclosingScope);
cacheEnclosingScope(input.enclosingScope);
if (input.target == CompilationInput::CompilationTarget::Eval) {
if (!cacheEnclosingScopeBindingForEval(fc, input, parserAtoms)) {
return false;
}
if (!cachePrivateFieldsForEval(fc, input, enclosingEnv, effectiveScope,
parserAtoms)) {
return false;
}
}
return true;
}
void ScopeContext::computeThisEnvironment(const InputScope& enclosingScope) {
uint32_t envCount = 0;
for (InputScopeIter si(enclosingScope); si; si++) {
if (si.kind() == ScopeKind::Function) {
// Arrow function inherit the "this" environment of the enclosing script,
// so continue ignore them.
if (!si.scope().isArrow()) {
allowNewTarget = true;
if (si.scope().allowSuperProperty()) {
allowSuperProperty = true;
enclosingThisEnvironmentHops = envCount;
}
if (si.scope().isClassConstructor()) {
memberInitializers =
si.scope().useMemberInitializers()
? mozilla::Some(si.scope().getMemberInitializers())
: mozilla::Some(MemberInitializers::Empty());
MOZ_ASSERT(memberInitializers->valid);
} else {
if (si.scope().isSyntheticFunction()) {
allowArguments = false;
}
}
if (si.scope().isDerivedClassConstructor()) {
allowSuperCall = true;
}
// Found the effective "this" environment, so stop.
return;
}
}
if (si.scope().hasEnvironment()) {
envCount++;
}
}
}
void ScopeContext::computeThisBinding(const InputScope& scope) {
// Inspect the scope-chain.
for (InputScopeIter si(scope); si; si++) {
if (si.kind() == ScopeKind::Module) {
thisBinding = ThisBinding::Module;
return;
}
if (si.kind() == ScopeKind::Function) {
// Arrow functions don't have their own `this` binding.
if (si.scope().isArrow()) {
continue;
}
// Derived class constructors (and their nested arrow functions and evals)
// use ThisBinding::DerivedConstructor, which ensures TDZ checks happen
// when accessing |this|.
if (si.scope().isDerivedClassConstructor()) {
thisBinding = ThisBinding::DerivedConstructor;
} else {
thisBinding = ThisBinding::Function;
}
return;
}
}
thisBinding = ThisBinding::Global;
}
void ScopeContext::computeInScope(const InputScope& enclosingScope) {
for (InputScopeIter si(enclosingScope); si; si++) {
if (si.kind() == ScopeKind::ClassBody) {
inClass = true;
}
if (si.kind() == ScopeKind::With) {
inWith = true;
}
}
}
void ScopeContext::cacheEnclosingScope(const InputScope& enclosingScope) {
if (enclosingScope.isNull()) {
return;
}
enclosingScopeEnvironmentChainLength =
enclosingScope.environmentChainLength();
enclosingScopeKind = enclosingScope.kind();
if (enclosingScopeKind == ScopeKind::Function) {
enclosingScopeIsArrow = enclosingScope.isArrow();
}
enclosingScopeHasEnvironment = enclosingScope.hasEnvironment();
#ifdef DEBUG
hasNonSyntacticScopeOnChain =
enclosingScope.hasOnChain(ScopeKind::NonSyntactic);
// This computes a general answer for the query "does the enclosing scope
// have a function scope that needs a home object?", but it's only asserted
// if the parser parses eval body that contains `super` that needs a home
// object.
for (InputScopeIter si(enclosingScope); si; si++) {
if (si.kind() == ScopeKind::Function) {
if (si.scope().isArrow()) {
continue;
}
if (si.scope().allowSuperProperty() && si.scope().needsHomeObject()) {
hasFunctionNeedsHomeObjectOnChain = true;
}
break;
}
}
#endif
// Pre-fill the scope cache by iterating over all the names. Stop iterating
// as soon as we find a scope which already has a filled scope cache.
AutoEnterOOMUnsafeRegion oomUnsafe;
for (InputScopeIter si(enclosingScope); si; si++) {
// If the current scope already exists, then there is no need to go deeper
// as the scope which are encoded after this one should already be present
// in the cache.
bool hasScopeCache = si.scope().match([&](auto& scope_ref) -> bool {
MOZ_ASSERT(scopeCache->canCacheFor(scope_ref));
return scopeCache->lookupScope(scope_ref, scopeCacheGen);
});
if (hasScopeCache) {
return;
}
bool hasEnv = si.hasSyntacticEnvironment();
auto setCatchAll = [&](NameLocation loc) {
return si.scope().match([&](auto& scope_ref) {
using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
return;
}
bindingMapPtr->catchAll.emplace(loc);
});
};
auto createEmpty = [&]() {
return si.scope().match([&](auto& scope_ref) {
using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
return;
}
});
};
switch (si.kind()) {
case ScopeKind::Function:
if (hasEnv) {
if (si.scope().funHasExtensibleScope()) {
setCatchAll(NameLocation::Dynamic());
return;
}
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->createCacheFor(scope_ref));
using Lookup =
typename std::remove_pointer_t<BindingMapPtr>::Lookup;
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: "
"scopeCache->createCacheFor");
return;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
NameLocation loc = bi.nameLocation();
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
continue;
}
auto ctxFreeKey = bi.name();
GenericAtom ctxKey(scope_ref, ctxFreeKey);
Lookup ctxLookup(scope_ref, ctxKey);
if (!bindingMapPtr->hashMap.put(ctxLookup, ctxFreeKey, loc)) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
return;
}
}
});
} else {
createEmpty();
}
break;
case ScopeKind::StrictEval:
case ScopeKind::FunctionBodyVar:
case ScopeKind::Lexical:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
case ScopeKind::FunctionLexical:
case ScopeKind::ClassBody:
if (hasEnv) {
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->createCacheFor(scope_ref));
using Lookup =
typename std::remove_pointer_t<BindingMapPtr>::Lookup;
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: "
"scopeCache->createCacheFor");
return;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
NameLocation loc = bi.nameLocation();
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
continue;
}
auto ctxFreeKey = bi.name();
GenericAtom ctxKey(scope_ref, ctxFreeKey);
Lookup ctxLookup(scope_ref, ctxKey);
if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
return;
}
}
});
} else {
createEmpty();
}
break;
case ScopeKind::Module:
// This case is used only when delazifying a function inside
// module.
// Initial compilation of module doesn't have enlcosing scope.
if (hasEnv) {
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->createCacheFor(scope_ref));
using Lookup =
typename std::remove_pointer_t<BindingMapPtr>::Lookup;
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: "
"scopeCache->createCacheFor");
return;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
// Imports are on the environment but are indirect
// bindings and must be accessed dynamically instead of
// using an EnvironmentCoordinate.
NameLocation loc = bi.nameLocation();
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate &&
loc.kind() != NameLocation::Kind::Import) {
continue;
}
auto ctxFreeKey = bi.name();
GenericAtom ctxKey(scope_ref, ctxFreeKey);
Lookup ctxLookup(scope_ref, ctxKey);
if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
return;
}
}
});
} else {
createEmpty();
}
break;
case ScopeKind::Eval:
// As an optimization, if the eval doesn't have its own var
// environment and its immediate enclosing scope is a global
// scope, all accesses are global.
if (!hasEnv) {
ScopeKind kind = si.scope().enclosing().kind();
if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
setCatchAll(NameLocation::Global(BindingKind::Var));
return;
}
}
setCatchAll(NameLocation::Dynamic());
return;
case ScopeKind::Global:
setCatchAll(NameLocation::Global(BindingKind::Var));
return;
case ScopeKind::With:
case ScopeKind::NonSyntactic:
setCatchAll(NameLocation::Dynamic());
return;
case ScopeKind::WasmInstance:
case ScopeKind::WasmFunction:
MOZ_CRASH("No direct eval inside wasm functions");
}
}
MOZ_CRASH("Malformed scope chain");
}
// Given an input scope, possibly refine this to a more precise scope.
// This is used during eval in the debugger to provide the appropriate scope and
// ThisBinding kind and environment, which is key to making private field eval
// work correctly.
//
// The trick here is that an eval may have a non-syntatic scope but nevertheless
// have an 'interesting' environment which can be traversed to find the
// appropriate scope the the eval to function as desired. See the diagram below.
//
// Eval Scope Eval Env Frame Env Frame Scope
// ============ ============= ========= =============
//
// NonSyntactic
// |
// v
// null DebugEnvProxy LexicalScope
// | |
// v v
// DebugEnvProxy --> CallObj --> FunctionScope
// | | |
// v v v
// ... ... ...
//
InputScope ScopeContext::determineEffectiveScope(InputScope& scope,
JSObject* environment) {
MOZ_ASSERT(effectiveScopeHops == 0);
// If the scope-chain is non-syntactic, we may still determine a more precise
// effective-scope to use instead.
if (environment && scope.hasOnChain(ScopeKind::NonSyntactic)) {
JSObject* env = environment;
while (env) {
// Look at target of any DebugEnvironmentProxy, but be sure to use
// enclosingEnvironment() of the proxy itself.
JSObject* unwrapped = env;
if (env->is<DebugEnvironmentProxy>()) {
unwrapped = &env->as<DebugEnvironmentProxy>().environment();
#ifdef DEBUG
enclosingEnvironmentIsDebugProxy_ = true;
#endif
}
if (unwrapped->is<CallObject>()) {
JSFunction* callee = &unwrapped->as<CallObject>().callee();
return InputScope(callee->nonLazyScript()->bodyScope());
}
env = env->enclosingEnvironment();
effectiveScopeHops++;
}
}
return scope;
}
static uint32_t DepthOfNearestVarScopeForDirectEval(const InputScope& scope) {
uint32_t depth = 0;
if (scope.isNull()) {
return depth;
}
for (InputScopeIter si(scope); si; si++) {
depth++;
switch (si.scope().kind()) {
case ScopeKind::Function:
case ScopeKind::FunctionBodyVar:
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
return depth;
default:
break;
}
}
return depth;
}
bool ScopeContext::cacheEnclosingScopeBindingForEval(
FrontendContext* fc, CompilationInput& input,
ParserAtomsTable& parserAtoms) {
enclosingLexicalBindingCache_.emplace();
uint32_t varScopeDepth =
DepthOfNearestVarScopeForDirectEval(input.enclosingScope);
uint32_t depth = 0;
for (InputScopeIter si(input.enclosingScope); si; si++) {
bool success = si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
switch (bi.kind()) {
case BindingKind::Let: {
// Annex B.3.5 allows redeclaring simple (non-destructured)
// catch parameters with var declarations.
bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch;
if (!annexB35Allowance) {
auto kind = ScopeKindIsCatch(si.kind())
? EnclosingLexicalBindingKind::CatchParameter
: EnclosingLexicalBindingKind::Let;
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding, kind)) {
return false;
}
}
break;
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
case BindingKind::Using:
break;
#endif
case BindingKind::Const: {
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::Const)) {
return false;
}
break;
}
case BindingKind::Synthetic: {
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::Synthetic)) {
return false;
}
break;
}
case BindingKind::PrivateMethod: {
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::PrivateMethod)) {
return false;
}
break;
}
case BindingKind::Import:
case BindingKind::FormalParameter:
case BindingKind::Var:
case BindingKind::NamedLambdaCallee:
break;
}
}
return true;
});
if (!success) {
return false;
}
if (++depth == varScopeDepth) {
break;
}
}
return true;
}
bool ScopeContext::addToEnclosingLexicalBindingCache(
FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache, InputName& name,
EnclosingLexicalBindingKind kind) {
TaggedParserAtomIndex parserName =
name.internInto(fc, parserAtoms, atomCache);
if (!parserName) {
return false;
}
// Same lexical binding can appear multiple times across scopes.
//
// enclosingLexicalBindingCache_ map is used for detecting conflicting
// `var` binding, and inner binding should be reported in the error.
//
// cacheEnclosingScopeBindingForEval iterates from inner scope, and
// inner-most binding is added to the map first.
//
// Do not overwrite the value with outer bindings.
auto p = enclosingLexicalBindingCache_->lookupForAdd(parserName);
if (!p) {
if (!enclosingLexicalBindingCache_->add(p, parserName, kind)) {
ReportOutOfMemory(fc);
return false;
}
}
return true;
}
static bool IsPrivateField(Scope*, JSAtom* atom) {
MOZ_ASSERT(atom->length() > 0);
JS::AutoCheckCannotGC nogc;
if (atom->hasLatin1Chars()) {
return atom->latin1Chars(nogc)[0] == '#';
}
return atom->twoByteChars(nogc)[0] == '#';
}
static bool IsPrivateField(ScopeStencilRef& scope, TaggedParserAtomIndex atom) {
if (atom.isParserAtomIndex()) {
const CompilationStencil& context = scope.context_;
ParserAtom* parserAtom = context.parserAtomData[atom.toParserAtomIndex()];
return parserAtom->isPrivateName();
}
#ifdef DEBUG
if (atom.isWellKnownAtomId()) {
const auto& info = GetWellKnownAtomInfo(atom.toWellKnownAtomId());
// #constructor is a well-known term, but it is invalid private name.
MOZ_ASSERT(!(info.length > 1 && info.content[0] == '#'));
} else if (atom.isLength2StaticParserString()) {
char content[2];
ParserAtomsTable::getLength2Content(atom.toLength2StaticParserString(),
content);
// # character is not part of the allowed character of static strings.
MOZ_ASSERT(content[0] != '#');
}
#endif
return false;
}
static bool IsPrivateField(const FakeStencilGlobalScope&,
TaggedParserAtomIndex) {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No private fields on empty global.");
}
bool ScopeContext::cachePrivateFieldsForEval(FrontendContext* fc,
CompilationInput& input,
JSObject* enclosingEnvironment,
const InputScope& effectiveScope,
ParserAtomsTable& parserAtoms) {
effectiveScopePrivateFieldCache_.emplace();
// We compute an environment coordinate relative to the effective scope
// environment. In order to safely consume these environment coordinates,
// we re-map them to include the hops to get the to the effective scope:
// see EmitterScope::lookupPrivate
uint32_t hops = effectiveScopeHops;
for (InputScopeIter si(effectiveScope); si; si++) {
if (si.scope().kind() == ScopeKind::ClassBody) {
uint32_t slots = 0;
bool success = si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
if (bi.kind() == BindingKind::PrivateMethod ||
(bi.kind() == BindingKind::Synthetic &&
IsPrivateField(scope_ref, bi.name()))) {
InputName binding(scope_ref, bi.name());
auto parserName =
binding.internInto(fc, parserAtoms, input.atomCache);
if (!parserName) {
return false;
}
NameLocation loc = NameLocation::DebugEnvironmentCoordinate(
bi.kind(), hops, slots);
if (!effectiveScopePrivateFieldCache_->put(parserName, loc)) {
ReportOutOfMemory(fc);
return false;
}
}
slots++;
}
return true;
});
if (!success) {
return false;
}
}
// Hops is only consumed by GetAliasedDebugVar, which uses this to
// traverse the debug environment chain. See the [SMDOC] for Debug
// Environment Chain, which explains why we don't check for
// isEnvironment when computing hops here (basically, debug proxies
// pretend all scopes have environments, even if they were actually
// optimized out).
hops++;
}
return true;
}
#ifdef DEBUG
static bool NameIsOnEnvironment(FrontendContext* fc,
ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache,
InputScope& scope, TaggedParserAtomIndex name) {
JSAtom* jsname = nullptr;
return scope.match([&](auto& scope_ref) {
if (std::is_same_v<decltype(scope_ref), FakeStencilGlobalScope&>) {
// This condition is added to handle the FakeStencilGlobalScope which is
// used to emulate the global object when delazifying while executing, and
// which is not provided by the Stencil.
return true;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
// If found, the name must already be on the environment or an import,
// or else there is a bug in the closed-over name analysis in the
// Parser.
InputName binding(scope_ref, bi.name());
if (binding.isEqualTo(fc, parserAtoms, atomCache, name, &jsname)) {
BindingLocation::Kind kind = bi.location().kind();
if (bi.hasArgumentSlot()) {
// The following is equivalent to
// functionScope.script()->functionAllowsParameterRedeclaration()
if (scope.hasMappedArgsObj()) {
// Check for duplicate positional formal parameters.
using InputBindingIter = decltype(bi);
for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
bi2++) {
InputName binding2(scope_ref, bi2.name());
if (binding2.isEqualTo(fc, parserAtoms, atomCache, name,
&jsname)) {
kind = bi2.location().kind();
}
}
}
}
return kind == BindingLocation::Kind::Global ||
kind == BindingLocation::Kind::Environment ||
kind == BindingLocation::Kind::Import;
}
}
// If not found, assume it's on the global or dynamically accessed.
return true;
});
}
#endif
NameLocation ScopeContext::searchInEnclosingScope(FrontendContext* fc,
CompilationInput& input,
ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name) {
MOZ_ASSERT(input.target ==
CompilationInput::CompilationTarget::Delazification ||
input.target == CompilationInput::CompilationTarget::Eval);
MOZ_ASSERT(scopeCache);
if (scopeCacheGen != scopeCache->getCurrentGeneration()) {
return searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
}
#ifdef DEBUG
// Catch assertion failures in the NoCache variant before looking at the
// cached content.
NameLocation expect =
searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
#endif
NameLocation found =
searchInEnclosingScopeWithCache(fc, input, parserAtoms, name);
MOZ_ASSERT(expect == found);
return found;
}
NameLocation ScopeContext::searchInEnclosingScopeWithCache(
FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name) {
MOZ_ASSERT(input.target ==
CompilationInput::CompilationTarget::Delazification ||
input.target == CompilationInput::CompilationTarget::Eval);
// Generic atom of the looked up name.
GenericAtom genName(fc, parserAtoms, input.atomCache, name);
mozilla::Maybe<NameLocation> found;
// Number of enclosing scope we walked over.
uint8_t hops = 0;
for (InputScopeIter si(input.enclosingScope); si; si++) {
MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
name));
// If the result happens to be in the cached content of the scope that we
// are iterating over, then return it.
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->lookupScope(scope_ref, scopeCacheGen));
BindingMapPtr bindingMapPtr =
scopeCache->lookupScope(scope_ref, scopeCacheGen);
MOZ_ASSERT(bindingMapPtr);
auto& bindingMap = *bindingMapPtr;
if (bindingMap.catchAll.isSome()) {
found = bindingMap.catchAll;
return;
}
// The scope_ref is given as argument to know where to lookup the key
// index of the hash table if the names have to be compared.
using Lookup = typename std::remove_pointer_t<BindingMapPtr>::Lookup;
Lookup ctxName(scope_ref, genName);
auto ptr = bindingMap.hashMap.lookup(ctxName);
if (!ptr) {
return;
}
found.emplace(ptr->value());
});
if (found.isSome()) {
// Cached entries do not store the number of hops, as it might be reused
// by multiple inner functions, which might different number of hops.
found = found.map([&hops](NameLocation loc) {
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
return loc;
}
return loc.addHops(hops);
});
return found.value();
}
bool hasEnv = si.hasSyntacticEnvironment();
if (hasEnv) {
MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
hops++;
}
}
MOZ_CRASH("Malformed scope chain");
}
NameLocation ScopeContext::searchInEnclosingScopeNoCache(
FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name) {
MOZ_ASSERT(input.target ==
CompilationInput::CompilationTarget::Delazification ||
input.target == CompilationInput::CompilationTarget::Eval);
// Cached JSAtom equivalent of the TaggedParserAtomIndex `name` argument.
JSAtom* jsname = nullptr;
// NameLocation which contains relative locations to access `name`.
mozilla::Maybe<NameLocation> result;
// Number of enclosing scoep we walked over.
uint8_t hops = 0;
for (InputScopeIter si(input.enclosingScope); si; si++) {
MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
name));
bool hasEnv = si.hasSyntacticEnvironment();
switch (si.kind()) {
case ScopeKind::Function:
if (hasEnv) {
if (si.scope().funHasExtensibleScope()) {
return NameLocation::Dynamic();
}
si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
InputName binding(scope_ref, bi.name());
if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
&jsname)) {
continue;
}
BindingLocation bindLoc = bi.location();
// hasMappedArgsObj == script.functionAllowsParameterRedeclaration
if (bi.hasArgumentSlot() && si.scope().hasMappedArgsObj()) {
// Check for duplicate positional formal parameters.
using InputBindingIter = decltype(bi);
for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
bi2++) {
if (bi.name() == bi2.name()) {
bindLoc = bi2.location();
}
}
}
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
result.emplace(NameLocation::EnvironmentCoordinate(
bi.kind(), hops, bindLoc.slot()));
return;
}
});
}
break;
case ScopeKind::StrictEval:
case ScopeKind::FunctionBodyVar:
case ScopeKind::Lexical:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
case ScopeKind::FunctionLexical:
case ScopeKind::ClassBody:
if (hasEnv) {
si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
InputName binding(scope_ref, bi.name());
if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
&jsname)) {
continue;
}
// The name must already have been marked as closed
// over. If this assertion is hit, there is a bug in the
// name analysis.
BindingLocation bindLoc = bi.location();
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
result.emplace(NameLocation::EnvironmentCoordinate(
bi.kind(), hops, bindLoc.slot()));
return;
}
});
}
break;
case ScopeKind::Module:
// This case is used only when delazifying a function inside
// module.
// Initial compilation of module doesn't have enlcosing scope.
if (hasEnv) {
si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
InputName binding(scope_ref, bi.name());
if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
&jsname)) {
continue;
}
BindingLocation bindLoc = bi.location();
// Imports are on the environment but are indirect
// bindings and must be accessed dynamically instead of
// using an EnvironmentCoordinate.
if (bindLoc.kind() == BindingLocation::Kind::Import) {
MOZ_ASSERT(si.kind() == ScopeKind::Module);
result.emplace(NameLocation::Import());
return;
}
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
result.emplace(NameLocation::EnvironmentCoordinate(
bi.kind(), hops, bindLoc.slot()));
return;
}
});
}
break;
case ScopeKind::Eval:
// As an optimization, if the eval doesn't have its own var
// environment and its immediate enclosing scope is a global
// scope, all accesses are global.
if (!hasEnv) {
ScopeKind kind = si.scope().enclosing().kind();
if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
return NameLocation::Global(BindingKind::Var);
}
}
return NameLocation::Dynamic();
case ScopeKind::Global:
return NameLocation::Global(BindingKind::Var);
case ScopeKind::With:
case ScopeKind::NonSyntactic:
return NameLocation::Dynamic();
case ScopeKind::WasmInstance:
case ScopeKind::WasmFunction:
MOZ_CRASH("No direct eval inside wasm functions");
}
if (result.isSome()) {
return result.value();
}
if (hasEnv) {
MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
hops++;
}
}
MOZ_CRASH("Malformed scope chain");
}
mozilla::Maybe<ScopeContext::EnclosingLexicalBindingKind>
ScopeContext::lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name) {
auto p = enclosingLexicalBindingCache_->lookup(name);
if (!p) {
return mozilla::Nothing();
}
return mozilla::Some(p->value());
}
bool ScopeContext::effectiveScopePrivateFieldCacheHas(
TaggedParserAtomIndex name) {
return effectiveScopePrivateFieldCache_->has(name);
}
mozilla::Maybe<NameLocation> ScopeContext::getPrivateFieldLocation(
TaggedParserAtomIndex name) {
// The locations returned by this method are only valid for
// traversing debug environments.
//
// See the comment in cachePrivateFieldsForEval
MOZ_ASSERT(enclosingEnvironmentIsDebugProxy_);
auto p = effectiveScopePrivateFieldCache_->lookup(name);
if (!p) {
return mozilla::Nothing();
}
return mozilla::Some(p->value());
}
bool CompilationInput::initScriptSource(FrontendContext* fc) {
source = do_AddRef(fc->getAllocator()->new_<ScriptSource>());
if (!source) {
return false;
}
return source->initFromOptions(fc, options);
}
bool CompilationInput::initForStandaloneFunctionInNonSyntacticScope(
FrontendContext* fc, Handle<Scope*> functionEnclosingScope) {
MOZ_ASSERT(!functionEnclosingScope->as<GlobalScope>().isSyntactic());
target = CompilationTarget::StandaloneFunctionInNonSyntacticScope;
if (!initScriptSource(fc)) {
return false;
}
enclosingScope = InputScope(functionEnclosingScope);
return true;
}
FunctionSyntaxKind CompilationInput::functionSyntaxKind() const {
if (functionFlags().isClassConstructor()) {
if (functionFlags().hasBaseScript() && isDerivedClassConstructor()) {
return FunctionSyntaxKind::DerivedClassConstructor;
}
return FunctionSyntaxKind::ClassConstructor;
}
if (functionFlags().isMethod()) {
if (functionFlags().hasBaseScript() && isSyntheticFunction()) {
// return FunctionSyntaxKind::FieldInitializer;
MOZ_ASSERT_UNREACHABLE(
"Lazy parsing of class field initializers not supported (yet)");
}
return FunctionSyntaxKind::Method;
}
if (functionFlags().isGetter()) {
return FunctionSyntaxKind::Getter;
}
if (functionFlags().isSetter()) {
return FunctionSyntaxKind::Setter;
}
if (functionFlags().isArrow()) {
return FunctionSyntaxKind::Arrow;
}
return FunctionSyntaxKind::Statement;
}
bool CompilationInput::internExtraBindings(FrontendContext* fc,
ParserAtomsTable& parserAtoms) {
MOZ_ASSERT(hasExtraBindings());
for (auto& bindingInfo : *maybeExtraBindings_) {
if (bindingInfo.isShadowed) {
continue;
}
const char* chars = bindingInfo.nameChars.get();
auto index = parserAtoms.internUtf8(
fc, reinterpret_cast<const mozilla::Utf8Unit*>(chars), strlen(chars));
if (!index) {
return false;
}
bindingInfo.nameIndex = index;
}
return true;
}
void InputScope::trace(JSTracer* trc) {
using ScopePtr = Scope*;
if (scope_.is<ScopePtr>()) {
ScopePtr* ptrAddr = &scope_.as<ScopePtr>();
TraceNullableRoot(trc, ptrAddr, "compilation-input-scope");
}
}
void InputScript::trace(JSTracer* trc) {
using ScriptPtr = BaseScript*;
if (script_.is<ScriptPtr>()) {
ScriptPtr* ptrAddr = &script_.as<ScriptPtr>();
TraceNullableRoot(trc, ptrAddr, "compilation-input-lazy");
}
}
void CompilationInput::trace(JSTracer* trc) {
atomCache.trace(trc);
lazy_.trace(trc);
enclosingScope.trace(trc);
}
bool CompilationSyntaxParseCache::init(FrontendContext* fc, LifoAlloc& alloc,
ParserAtomsTable& parseAtoms,
CompilationAtomCache& atomCache,
const InputScript& lazy) {
if (!copyFunctionInfo(fc, parseAtoms, atomCache, lazy)) {
return false;
}
bool success = lazy.raw().match([&](auto& ref) {
if (!copyScriptInfo(fc, alloc, parseAtoms, atomCache, ref)) {
return false;
}
if (!copyClosedOverBindings(fc, alloc, parseAtoms, atomCache, ref)) {
return false;
}
return true;
});
if (!success) {
return false;
}
#ifdef DEBUG
isInitialized = true;
#endif
return true;
}
bool CompilationSyntaxParseCache::copyFunctionInfo(
FrontendContext* fc, ParserAtomsTable& parseAtoms,
CompilationAtomCache& atomCache, const InputScript& lazy) {
InputName name = lazy.displayAtom();
if (!name.isNull()) {
displayAtom_ = name.internInto(fc, parseAtoms, atomCache);
if (!displayAtom_) {
return false;
}
}
funExtra_.immutableFlags = lazy.immutableFlags();
funExtra_.extent = lazy.extent();
if (funExtra_.useMemberInitializers()) {
funExtra_.setMemberInitializers(lazy.getMemberInitializers());
}
return true;
}
bool CompilationSyntaxParseCache::copyScriptInfo(
FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
CompilationAtomCache& atomCache, BaseScript* lazy) {
using GCThingsSpan = mozilla::Span<TaggedScriptThingIndex>;
using ScriptDataSpan = mozilla::Span<ScriptStencil>;
using ScriptExtraSpan = mozilla::Span<ScriptStencilExtra>;
cachedGCThings_ = GCThingsSpan(nullptr);
cachedScriptData_ = ScriptDataSpan(nullptr);
cachedScriptExtra_ = ScriptExtraSpan(nullptr);
auto gcthings = lazy->gcthings();
size_t length = gcthings.Length();
if (length == 0) {
return true;
}
// Reduce the length to the first element which is not a function.
for (size_t i = 0; i < length; i++) {
gc::Cell* cell = gcthings[i].asCell();
if (!cell || !cell->is<JSObject>()) {
length = i;
break;
}
MOZ_ASSERT(cell->as<JSObject>()->is<JSFunction>());
}
TaggedScriptThingIndex* gcThingsData =
alloc.newArrayUninitialized<TaggedScriptThingIndex>(length);
ScriptStencil* scriptData =
alloc.newArrayUninitialized<ScriptStencil>(length);
ScriptStencilExtra* scriptExtra =
alloc.newArrayUninitialized<ScriptStencilExtra>(length);
if (!gcThingsData || !scriptData || !scriptExtra) {
ReportOutOfMemory(fc);
return false;
}
for (size_t i = 0; i < length; i++) {
gc::Cell* cell = gcthings[i].asCell();
JSFunction* fun = &cell->as<JSObject>()->as<JSFunction>();
gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i));
new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil();
ScriptStencil& data = scriptData[i];
new (mozilla::KnownNotNull, &scriptExtra[i]) ScriptStencilExtra();
ScriptStencilExtra& extra = scriptExtra[i];
if (fun->fullDisplayAtom()) {
TaggedParserAtomIndex displayAtom =
parseAtoms.internJSAtom(fc, atomCache, fun->fullDisplayAtom());
if (!displayAtom) {
return false;
}
data.functionAtom = displayAtom;
}
data.functionFlags = fun->flags();
BaseScript* lazy = fun->baseScript();
extra.immutableFlags = lazy->immutableFlags();
extra.extent = lazy->extent();
// Info derived from parent compilation should not be set yet for our inner
// lazy functions. Instead that info will be updated when we finish our
// compilation.
MOZ_ASSERT(lazy->hasEnclosingScript());
}
cachedGCThings_ = GCThingsSpan(gcThingsData, length);
cachedScriptData_ = ScriptDataSpan(scriptData, length);
cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length);
return true;
}
bool CompilationSyntaxParseCache::copyScriptInfo(
FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) {
using GCThingsSpan = mozilla::Span<TaggedScriptThingIndex>;
using ScriptDataSpan = mozilla::Span<ScriptStencil>;
using ScriptExtraSpan = mozilla::Span<ScriptStencilExtra>;
cachedGCThings_ = GCThingsSpan(nullptr);
cachedScriptData_ = ScriptDataSpan(nullptr);
cachedScriptExtra_ = ScriptExtraSpan(nullptr);
size_t offset = lazy.scriptData().gcThingsOffset.index;
size_t length = lazy.scriptData().gcThingsLength;
if (length == 0) {
return true;
}
// Reduce the length to the first element which is not a function.
for (size_t i = offset; i < offset + length; i++) {
if (!lazy.context_.gcThingData[i].isFunction()) {
length = i - offset;
break;
}
}
TaggedScriptThingIndex* gcThingsData =
alloc.newArrayUninitialized<TaggedScriptThingIndex>(length);
ScriptStencil* scriptData =
alloc.newArrayUninitialized<ScriptStencil>(length);
ScriptStencilExtra* scriptExtra =
alloc.newArrayUninitialized<ScriptStencilExtra>(length);
if (!gcThingsData || !scriptData || !scriptExtra) {
ReportOutOfMemory(fc);
return false;
}
for (size_t i = 0; i < length; i++) {
ScriptStencilRef inner{lazy.context_,
lazy.context_.gcThingData[i + offset].toFunction()};
gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i));
new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil();
ScriptStencil& data = scriptData[i];
ScriptStencilExtra& extra = scriptExtra[i];
InputName name{inner, inner.scriptData().functionAtom};
if (!name.isNull()) {
auto displayAtom = name.internInto(fc, parseAtoms, atomCache);
if (!displayAtom) {
return false;
}
data.functionAtom = displayAtom;
}
data.functionFlags = inner.scriptData().functionFlags;
extra = inner.scriptExtra();
}
cachedGCThings_ = GCThingsSpan(gcThingsData, length);
cachedScriptData_ = ScriptDataSpan(scriptData, length);
cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length);
return true;
}
bool CompilationSyntaxParseCache::copyClosedOverBindings(
FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
CompilationAtomCache& atomCache, BaseScript* lazy) {
using ClosedOverBindingsSpan = mozilla::Span<TaggedParserAtomIndex>;
closedOverBindings_ = ClosedOverBindingsSpan(nullptr);
// The gcthings() array contains the inner function list followed by the
// closed-over bindings data. Skip the inner function list, as it is already
// cached in cachedGCThings_. See also: BaseScript::CreateLazy.
size_t start = cachedGCThings_.Length();
auto gcthings = lazy->gcthings();
size_t length = gcthings.Length();
MOZ_ASSERT(start <= length);
if (length - start == 0) {
return true;
}
TaggedParserAtomIndex* closedOverBindings =
alloc.newArrayUninitialized<TaggedParserAtomIndex>(length - start);
if (!closedOverBindings) {
ReportOutOfMemory(fc);
return false;
}
for (size_t i = start; i < length; i++) {
gc::Cell* cell = gcthings[i].asCell();
if (!cell) {
closedOverBindings[i - start] = TaggedParserAtomIndex::null();
continue;
}
MOZ_ASSERT(cell->as<JSString>()->isAtom());
auto name = static_cast<JSAtom*>(cell);
auto parserAtom = parseAtoms.internJSAtom(fc, atomCache, name);
if (!parserAtom) {
return false;
}
closedOverBindings[i - start] = parserAtom;
}
closedOverBindings_ =
ClosedOverBindingsSpan(closedOverBindings, length - start);
return true;
}
bool CompilationSyntaxParseCache::copyClosedOverBindings(
FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) {
using ClosedOverBindingsSpan = mozilla::Span<TaggedParserAtomIndex>;
closedOverBindings_ = ClosedOverBindingsSpan(nullptr);
// The gcthings array contains the inner function list followed by the
// closed-over bindings data. Skip the inner function list, as it is already
// cached in cachedGCThings_. See also: BaseScript::CreateLazy.
size_t offset = lazy.scriptData().gcThingsOffset.index;
size_t length = lazy.scriptData().gcThingsLength;
size_t start = cachedGCThings_.Length();
MOZ_ASSERT(start <= length);
if (length - start == 0) {
return true;
}
length -= start;
start += offset;
// Atoms from the lazy.context (CompilationStencil) are not registered in the
// the parseAtoms table. Thus we create a new span which will contain all the
// interned atoms.
TaggedParserAtomIndex* closedOverBindings =
alloc.newArrayUninitialized<TaggedParserAtomIndex>(length);
if (!closedOverBindings) {
ReportOutOfMemory(fc);
return false;
}
for (size_t i = 0; i < length; i++) {
auto gcThing = lazy.context_.gcThingData[i + start];
if (gcThing.isNull()) {
closedOverBindings[i] = TaggedParserAtomIndex::null();
continue;
}
MOZ_ASSERT(gcThing.isAtom());
InputName name(lazy, gcThing.toAtom());
auto parserAtom = name.internInto(fc, parseAtoms, atomCache);
if (!parserAtom) {
return false;
}
closedOverBindings[i] = parserAtom;
}
closedOverBindings_ = ClosedOverBindingsSpan(closedOverBindings, length);
return true;
}
template <typename T>
PreAllocateableGCArray<T>::~PreAllocateableGCArray() {
if (elems_) {
js_free(elems_);
elems_ = nullptr;
}
}
template <typename T>
bool PreAllocateableGCArray<T>::allocate(size_t length) {
MOZ_ASSERT(empty());
length_ = length;
if (isInline()) {
inlineElem_ = nullptr;
return true;
}
elems_ = reinterpret_cast<T*>(js_calloc(sizeof(T) * length_));
if (!elems_) {
return false;
}
return true;
}
template <typename T>
bool PreAllocateableGCArray<T>::allocateWith(T init, size_t length) {
MOZ_ASSERT(empty());
length_ = length;
if (isInline()) {
inlineElem_ = init;
return true;
}
elems_ = reinterpret_cast<T*>(js_malloc(sizeof(T) * length_));
if (!elems_) {
return false;
}
std::fill(elems_, elems_ + length_, init);
return true;
}
template <typename T>
void PreAllocateableGCArray<T>::steal(Preallocated&& buffer) {
MOZ_ASSERT(empty());
length_ = buffer.length_;
buffer.length_ = 0;
if (isInline()) {
inlineElem_ = nullptr;
return;
}
elems_ = reinterpret_cast<T*>(buffer.elems_);
buffer.elems_ = nullptr;
#ifdef DEBUG
for (size_t i = 0; i < length_; i++) {
MOZ_ASSERT(elems_[i] == nullptr);
}
#endif
}
template <typename T>
void PreAllocateableGCArray<T>::trace(JSTracer* trc) {
if (empty()) {
return;
}
if (isInline()) {
TraceNullableRoot(trc, &inlineElem_, "PreAllocateableGCArray::inlineElem_");
return;
}
for (size_t i = 0; i < length_; i++) {
TraceNullableRoot(trc, &elems_[i], "PreAllocateableGCArray::elems_");
}
}
template <typename T>
PreAllocateableGCArray<T>::Preallocated::~Preallocated() {
if (elems_) {
js_free(elems_);
elems_ = nullptr;
}
}
template <typename T>
bool PreAllocateableGCArray<T>::Preallocated::allocate(size_t length) {
MOZ_ASSERT(empty());
length_ = length;
if (isInline()) {
return true;
}
elems_ = reinterpret_cast<uintptr_t*>(js_calloc(sizeof(uintptr_t) * length_));
if (!elems_) {
return false;
}
return true;
}
template struct js::frontend::PreAllocateableGCArray<JSFunction*>;
template struct js::frontend::PreAllocateableGCArray<js::Scope*>;
void CompilationAtomCache::trace(JSTracer* trc) { atoms_.trace(trc); }
void CompilationGCOutput::trace(JSTracer* trc) {
TraceNullableRoot(trc, &script, "compilation-gc-output-script");
TraceNullableRoot(trc, &module, "compilation-gc-output-module");
TraceNullableRoot(trc, &sourceObject, "compilation-gc-output-source");
functions.trace(trc);
scopes.trace(trc);
}
RegExpObject* RegExpStencil::createRegExp(
JSContext* cx, const CompilationAtomCache& atomCache) const {
Rooted<JSAtom*> atom(cx, atomCache.getExistingAtomAt(cx, atom_));
return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject);
}
RegExpObject* RegExpStencil::createRegExpAndEnsureAtom(
JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache) const {
Rooted<JSAtom*> atom(cx, parserAtoms.toJSAtom(cx, fc, atom_, atomCache));
if (!atom) {
return nullptr;
}
return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject);
}
AbstractScopePtr ScopeStencil::enclosing(
CompilationState& compilationState) const {
if (hasEnclosing()) {
return AbstractScopePtr(compilationState, enclosing());
}
return AbstractScopePtr::compilationEnclosingScope(compilationState);
}
Scope* ScopeStencil::enclosingExistingScope(
const CompilationInput& input, const CompilationGCOutput& gcOutput) const {
if (hasEnclosing()) {
Scope* result = gcOutput.getScopeNoBaseIndex(enclosing());
MOZ_ASSERT(result, "Scope must already exist to use this method");
return result;
}
// When creating a scope based on the input and a gc-output, we assume that
// the scope stencil that we are looking at has not been merged into another
// stencil, and thus that we still have the compilation input of the stencil.
//
// Otherwise, if this was in the case of an input generated from a Stencil
// instead of live-gc values, we would not know its associated gcOutput as it
// might not even have one yet.
return input.enclosingScope.variant().as<Scope*>();
}
Scope* ScopeStencil::createScope(JSContext* cx, CompilationInput& input,
CompilationGCOutput& gcOutput,
BaseParserScopeData* baseScopeData) const {
Rooted<Scope*> enclosingScope(cx, enclosingExistingScope(input, gcOutput));
return createScope(cx, input.atomCache, enclosingScope, baseScopeData);
}
Scope* ScopeStencil::createScope(JSContext* cx, CompilationAtomCache& atomCache,
Handle<Scope*> enclosingScope,
BaseParserScopeData* baseScopeData) const {
switch (kind()) {
case ScopeKind::Function: {
using ScopeType = FunctionScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, CallObject>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::FunctionLexical: {
using ScopeType = LexicalScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, BlockLexicalEnvironmentObject>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::ClassBody: {
using ScopeType = ClassBodyScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, BlockLexicalEnvironmentObject>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::FunctionBodyVar: {
using ScopeType = VarScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, VarEnvironmentObject>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::Global:
case ScopeKind::NonSyntactic: {
using ScopeType = GlobalScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, std::nullptr_t>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::Eval:
case ScopeKind::StrictEval: {
using ScopeType = EvalScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, VarEnvironmentObject>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::Module: {
using ScopeType = ModuleScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, ModuleEnvironmentObject>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::With: {
using ScopeType = WithScope;
MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
return createSpecificScope<ScopeType, std::nullptr_t>(
cx, atomCache, enclosingScope, baseScopeData);
}
case ScopeKind::WasmFunction:
case ScopeKind::WasmInstance: {
// ScopeStencil does not support WASM
break;
}
}
MOZ_CRASH();
}
bool CompilationState::prepareSharedDataStorage(FrontendContext* fc) {
size_t allScriptCount = scriptData.length();
size_t nonLazyScriptCount = nonLazyFunctionCount;
if (!scriptData[0].isFunction()) {
nonLazyScriptCount++;
}
return sharedData.prepareStorageFor(fc, nonLazyScriptCount, allScriptCount);
}
static bool CreateLazyScript(JSContext* cx,
const CompilationAtomCache& atomCache,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput,
const ScriptStencil& script,
const ScriptStencilExtra& scriptExtra,
ScriptIndex scriptIndex, HandleFunction function) {
Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
size_t ngcthings = script.gcThingsLength;
Rooted<BaseScript*> lazy(
cx, BaseScript::CreateRawLazy(cx, ngcthings, function, sourceObject,
scriptExtra.extent,
scriptExtra.immutableFlags));
if (!lazy) {
return false;
}
if (ngcthings) {
if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput,
script.gcthings(stencil),
lazy->gcthingsForInit())) {
return false;
}
}
if (scriptExtra.useMemberInitializers()) {
lazy->setMemberInitializers(scriptExtra.memberInitializers());
}
function->initScript(lazy);
return true;
}
// Parser-generated functions with the same prototype will share the same shape.
// By computing the correct values up front, we can save a lot of time in the
// Object creation code. For simplicity, we focus only on plain synchronous
// functions which are by far the most common.
//
// NOTE: Keep this in sync with `js::NewFunctionWithProto`.
static JSFunction* CreateFunctionFast(JSContext* cx,
CompilationAtomCache& atomCache,
Handle<SharedShape*> shape,
const ScriptStencil& script,
const ScriptStencilExtra& scriptExtra) {
MOZ_ASSERT(
!scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync));
MOZ_ASSERT(!scriptExtra.immutableFlags.hasFlag(
ImmutableScriptFlagsEnum::IsGenerator));
MOZ_ASSERT(!script.functionFlags.isAsmJSNative());
FunctionFlags flags = script.functionFlags;
gc::AllocKind allocKind = flags.isExtended()
? gc::AllocKind::FUNCTION_EXTENDED
: gc::AllocKind::FUNCTION;
JSFunction* fun = JSFunction::create(cx, allocKind, gc::Heap::Tenured, shape);
if (!fun) {
return nullptr;
}
fun->setArgCount(scriptExtra.nargs);
fun->setFlags(flags);
fun->initScript(nullptr);
fun->initEnvironment(nullptr);
if (script.functionAtom) {
JSAtom* atom = atomCache.getExistingAtomAt(cx, script.functionAtom);
MOZ_ASSERT(atom);
fun->initAtom(atom);
}
#ifdef DEBUG
fun->assertFunctionKindIntegrity();
#endif
return fun;
}
static JSFunction* CreateFunction(JSContext* cx,
CompilationAtomCache& atomCache,
const CompilationStencil& stencil,
const ScriptStencil& script,
const ScriptStencilExtra& scriptExtra,
ScriptIndex functionIndex) {
GeneratorKind generatorKind =
scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsGenerator)
? GeneratorKind::Generator
: GeneratorKind::NotGenerator;
FunctionAsyncKind asyncKind =
scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync)
? FunctionAsyncKind::AsyncFunction
: FunctionAsyncKind::SyncFunction;
// Determine the new function's proto. This must be done for singleton
// functions.
RootedObject proto(cx);
if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
return nullptr;
}
gc::AllocKind allocKind = script.functionFlags.isExtended()
? gc::AllocKind::FUNCTION_EXTENDED
: gc::AllocKind::FUNCTION;
bool isAsmJS = script.functionFlags.isAsmJSNative();
JSNative maybeNative = isAsmJS ? InstantiateAsmJS : nullptr;
Rooted<JSAtom*> displayAtom(cx);
if (script.functionAtom) {
displayAtom.set(atomCache.getExistingAtomAt(cx, script.functionAtom));
MOZ_ASSERT(displayAtom);
}
RootedFunction fun(
cx, NewFunctionWithProto(cx, maybeNative, scriptExtra.nargs,
script.functionFlags, nullptr, displayAtom,
proto, allocKind, TenuredObject));
if (!fun) {
return nullptr;
}
if (isAsmJS) {
RefPtr<const JS::WasmModule> asmJS =
stencil.asmJS->moduleMap.lookup(functionIndex)->value();
JSObject* moduleObj = asmJS->createObjectForAsmJS(cx);
if (!moduleObj) {
return nullptr;
}
fun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT,
ObjectValue(*moduleObj));
}
return fun;
}
static bool InstantiateAtoms(JSContext* cx, FrontendContext* fc,
CompilationAtomCache& atomCache,
const CompilationStencil& stencil) {
return InstantiateMarkedAtoms(cx, fc, stencil.parserAtomData, atomCache);
}
static bool InstantiateScriptSourceObject(JSContext* cx,
const JS::InstantiateOptions& options,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
MOZ_ASSERT(stencil.source);
gcOutput.sourceObject = ScriptSourceObject::create(cx, stencil.source.get());
if (!gcOutput.sourceObject) {
return false;
}
Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
if (!ScriptSourceObject::initFromOptions(cx, sourceObject, options)) {
return false;
}
return true;
}
// Instantiate ModuleObject. Further initialization is done after the associated
// BaseScript is instantiated in InstantiateTopLevel.
static bool InstantiateModuleObject(JSContext* cx, FrontendContext* fc,
CompilationAtomCache& atomCache,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
MOZ_ASSERT(stencil.isModule());
gcOutput.module = ModuleObject::create(cx);
if (!gcOutput.module) {
return false;
}
Rooted<ModuleObject*> module(cx, gcOutput.module);
return stencil.moduleMetadata->initModule(cx, fc, atomCache, module);
}
// Instantiate JSFunctions for each FunctionBox.
static bool InstantiateFunctions(JSContext* cx, FrontendContext* fc,
CompilationAtomCache& atomCache,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
using ImmutableFlags = ImmutableScriptFlagsEnum;
MOZ_ASSERT(gcOutput.functions.length() == stencil.scriptData.size());
// Most JSFunctions will be have the same Shape so we can compute it now to
// allow fast object creation. Generators / Async will use the slow path
// instead.
Rooted<SharedShape*> functionShape(
cx, GlobalObject::getFunctionShapeWithDefaultProto(
cx, /* extended = */ false));
if (!functionShape) {
return false;
}
Rooted<SharedShape*> extendedShape(
cx, GlobalObject::getFunctionShapeWithDefaultProto(
cx, /* extended = */ true));
if (!extendedShape) {
return false;
}
for (auto item :
CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
const auto& scriptStencil = item.script;
const auto& scriptExtra = (*item.scriptExtra);
auto index = item.index;
MOZ_ASSERT(!item.function);
// Plain functions can use a fast path.
bool useFastPath =
!scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsAsync) &&
!scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsGenerator) &&
!scriptStencil.functionFlags.isAsmJSNative();
JSFunction* fun;
if (useFastPath) {
Handle<SharedShape*> shape = scriptStencil.functionFlags.isExtended()
? extendedShape
: functionShape;
fun =
CreateFunctionFast(cx, atomCache, shape, scriptStencil, scriptExtra);
} else {
fun = CreateFunction(cx, atomCache, stencil, scriptStencil, scriptExtra,
index);
}
if (!fun) {
return false;
}
// Self-hosted functions may have a canonical name to use when instantiating
// into other realms.
if (scriptStencil.hasSelfHostedCanonicalName()) {
JSAtom* canonicalName = atomCache.getExistingAtomAt(
cx, scriptStencil.selfHostedCanonicalName());
fun->setAtom(canonicalName);
}
gcOutput.getFunctionNoBaseIndex(index) = fun;
}
return true;
}
// Instantiate Scope for each ScopeStencil.
//
// This should be called after InstantiateFunctions, given FunctionScope needs
// associated JSFunction pointer, and also should be called before
// InstantiateScriptStencils, given JSScript needs Scope pointer in gc things.
static bool InstantiateScopes(JSContext* cx, CompilationInput& input,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
// While allocating Scope object from ScopeStencil, Scope object for the
// enclosing Scope should already be allocated.
//
// Enclosing scope of ScopeStencil can be either ScopeStencil or Scope*
// pointer.
//
// If the enclosing scope is ScopeStencil, it's guaranteed to be earlier
// element in stencil.scopeData, because enclosing_ field holds
// index into it, and newly created ScopeStencil is pushed back to the array.
//
// If the enclosing scope is Scope*, it's CompilationInput.enclosingScope.
MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size());
size_t scopeCount = stencil.scopeData.size();
for (size_t i = 0; i < scopeCount; i++) {
Scope* scope = stencil.scopeData[i].createScope(cx, input, gcOutput,
stencil.scopeNames[i]);
if (!scope) {
return false;
}
gcOutput.scopes[i] = scope;
}
return true;
}
// Instantiate js::BaseScripts from ScriptStencils for inner functions of the
// compilation. Note that standalone functions and functions being delazified
// are handled below with other top-levels.
static bool InstantiateScriptStencils(JSContext* cx,
CompilationAtomCache& atomCache,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
MOZ_ASSERT(stencil.isInitialStencil());
Rooted<JSFunction*> fun(cx);
for (auto item :
CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
auto& scriptStencil = item.script;
auto* scriptExtra = item.scriptExtra;
fun = item.function;
auto index = item.index;
if (scriptStencil.hasSharedData()) {
// If the function was not referenced by enclosing script's bytecode, we
// do not generate a BaseScript for it. For example, `(function(){});`.
//
// `wasEmittedByEnclosingScript` is false also for standalone
// functions. They are handled in InstantiateTopLevel.
if (!scriptStencil.wasEmittedByEnclosingScript()) {
continue;
}
RootedScript script(
cx, JSScript::fromStencil(cx, atomCache, stencil, gcOutput, index));
if (!script) {
return false;
}
if (scriptStencil.allowRelazify()) {
MOZ_ASSERT(script->isRelazifiable());
script->setAllowRelazify();
}
} else if (scriptStencil.functionFlags.isAsmJSNative()) {
MOZ_ASSERT(fun->isAsmJSNative());
} else {
MOZ_ASSERT(fun->isIncomplete());
if (!CreateLazyScript(cx, atomCache, stencil, gcOutput, scriptStencil,
*scriptExtra, index, fun)) {
return false;
}
}
}
return true;
}
// Instantiate the Stencil for the top-level script of the compilation. This
// includes standalone functions and functions being delazified.
static bool InstantiateTopLevel(JSContext* cx, CompilationInput& input,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
const ScriptStencil& scriptStencil =
stencil.scriptData[CompilationStencil::TopLevelIndex];
// Top-level asm.js does not generate a JSScript.
if (scriptStencil.functionFlags.isAsmJSNative()) {
return true;
}
MOZ_ASSERT(scriptStencil.hasSharedData());
MOZ_ASSERT(stencil.sharedData.get(CompilationStencil::TopLevelIndex));
if (!stencil.isInitialStencil()) {
MOZ_ASSERT(input.lazyOuterBaseScript());
RootedScript script(cx,
JSScript::CastFromLazy(input.lazyOuterBaseScript()));
if (!JSScript::fullyInitFromStencil(cx, input.atomCache, stencil, gcOutput,
script,
CompilationStencil::TopLevelIndex)) {
return false;
}
if (scriptStencil.allowRelazify()) {
MOZ_ASSERT(script->isRelazifiable());
script->setAllowRelazify();
}
gcOutput.script = script;
return true;
}
gcOutput.script =
JSScript::fromStencil(cx, input.atomCache, stencil, gcOutput,
CompilationStencil::TopLevelIndex);
if (!gcOutput.script) {
return false;
}
if (scriptStencil.allowRelazify()) {
MOZ_ASSERT(gcOutput.script->isRelazifiable());
gcOutput.script->setAllowRelazify();
}
const ScriptStencilExtra& scriptExtra =
stencil.scriptExtra[CompilationStencil::TopLevelIndex];
// Finish initializing the ModuleObject if needed.
if (scriptExtra.isModule()) {
RootedScript script(cx, gcOutput.script);
Rooted<ModuleObject*> module(cx, gcOutput.module);
script->outermostScope()->as<ModuleScope>().initModule(module);
module->initScriptSlots(script);
if (!ModuleObject::createEnvironment(cx, module)) {
return false;
}
if (!ModuleObject::Freeze(cx, module)) {
return false;
}
}
return true;
}
// When a function is first referenced by enclosing script's bytecode, we need
// to update it with information determined by the BytecodeEmitter. This applies
// to both initial and delazification parses. The functions being update may or
// may not have bytecode at this point.
static void UpdateEmittedInnerFunctions(JSContext* cx,
CompilationAtomCache& atomCache,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
for (auto item :
CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
auto& scriptStencil = item.script;
auto& fun = item.function;
if (!scriptStencil.wasEmittedByEnclosingScript()) {
continue;
}
if (scriptStencil.functionFlags.isAsmJSNative() ||
fun->baseScript()->hasBytecode()) {
// Non-lazy inner functions don't use the enclosingScope_ field.
MOZ_ASSERT(!scriptStencil.hasLazyFunctionEnclosingScopeIndex());
} else {
// Apply updates from FunctionEmitter::emitLazy().
BaseScript* script = fun->baseScript();
ScopeIndex index = scriptStencil.lazyFunctionEnclosingScopeIndex();
Scope* scope = gcOutput.getScopeNoBaseIndex(index);
script->setEnclosingScope(scope);
// Inferred and Guessed names are computed by BytecodeEmitter and so may
// need to be applied to existing JSFunctions during delazification.
if (fun->fullDisplayAtom() == nullptr) {
JSAtom* funcAtom = nullptr;
if (scriptStencil.functionFlags.hasInferredName() ||
scriptStencil.functionFlags.hasGuessedAtom()) {
funcAtom =
atomCache.getExistingAtomAt(cx, scriptStencil.functionAtom);
MOZ_ASSERT(funcAtom);
}
if (scriptStencil.functionFlags.hasInferredName()) {
fun->setInferredName(funcAtom);
}
if (scriptStencil.functionFlags.hasGuessedAtom()) {
fun->setGuessedAtom(funcAtom);
}
}
}
}
}
// During initial parse we must link lazy-functions-inside-lazy-functions to
// their enclosing script.
static void LinkEnclosingLazyScript(const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
for (auto item :
CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
auto& scriptStencil = item.script;
auto& fun = item.function;
if (!scriptStencil.functionFlags.hasBaseScript()) {
continue;
}
if (!fun->baseScript()) {
continue;
}
if (fun->baseScript()->hasBytecode()) {
continue;
}
BaseScript* script = fun->baseScript();
MOZ_ASSERT(!script->hasBytecode());
for (auto inner : script->gcthings()) {
if (!inner.is<JSObject>()) {
continue;
}
JSFunction* innerFun = &inner.as<JSObject>().as<JSFunction>();
MOZ_ASSERT(innerFun->hasBaseScript(),
"inner function should have base script");
if (!innerFun->hasBaseScript()) {
continue;
}
// Check for the case that the inner function has the base script flag,
// but still doesn't have the actual base script pointer.
// `baseScript` method asserts the pointer itself, so no extra MOZ_ASSERT
// here.
if (!innerFun->baseScript()) {
continue;
}
innerFun->setEnclosingLazyScript(script);
}
}
}
#ifdef DEBUG
// Some fields aren't used in delazification, given the target functions and
// scripts are already instantiated, but they still should match.
static void AssertDelazificationFieldsMatch(const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
for (auto item :
CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
auto& scriptStencil = item.script;
auto* scriptExtra = item.scriptExtra;
auto& fun = item.function;
MOZ_ASSERT(scriptExtra == nullptr);
// Names are updated by UpdateInnerFunctions.
constexpr uint16_t HAS_INFERRED_NAME =
uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME);
constexpr uint16_t HAS_GUESSED_ATOM =
uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM);
constexpr uint16_t MUTABLE_FLAGS =
uint16_t(FunctionFlags::Flags::MUTABLE_FLAGS);
constexpr uint16_t acceptableDifferenceForFunction =
HAS_INFERRED_NAME | HAS_GUESSED_ATOM | MUTABLE_FLAGS;
MOZ_ASSERT((fun->flags().toRaw() | acceptableDifferenceForFunction) ==
(scriptStencil.functionFlags.toRaw() |
acceptableDifferenceForFunction));
// Delazification shouldn't delazify inner scripts.
MOZ_ASSERT_IF(item.index == CompilationStencil::TopLevelIndex,
scriptStencil.hasSharedData());
MOZ_ASSERT_IF(item.index > CompilationStencil::TopLevelIndex,
!scriptStencil.hasSharedData());
}
}
#endif // DEBUG
// When delazifying, use the existing JSFunctions. The initial and delazifying
// parse are required to generate the same sequence of functions for lazy
// parsing to work at all.
static void FunctionsFromExistingLazy(CompilationInput& input,
CompilationGCOutput& gcOutput) {
MOZ_ASSERT(!gcOutput.functions[0]);
size_t instantiatedFunIndex = 0;
gcOutput.functions[instantiatedFunIndex++] = input.function();
for (JS::GCCellPtr elem : input.lazyOuterBaseScript()->gcthings()) {
if (!elem.is<JSObject>()) {
continue;
}
JSFunction* fun = &elem.as<JSObject>().as<JSFunction>();
gcOutput.functions[instantiatedFunIndex++] = fun;
}
}
void CompilationStencil::borrowFromExtensibleCompilationStencil(
ExtensibleCompilationStencil& extensibleStencil) {
canLazilyParse = extensibleStencil.canLazilyParse;
functionKey = extensibleStencil.functionKey;
// Borrow the vector content as span.
scriptData = extensibleStencil.scriptData;
scriptExtra = extensibleStencil.scriptExtra;
gcThingData = extensibleStencil.gcThingData;
scopeData = extensibleStencil.scopeData;
scopeNames = extensibleStencil.scopeNames;
regExpData = extensibleStencil.regExpData;
bigIntData = extensibleStencil.bigIntData;
objLiteralData = extensibleStencil.objLiteralData;
// Borrow the parser atoms as span.
parserAtomData = extensibleStencil.parserAtoms.entries_;
// Borrow container.
sharedData.setBorrow(&extensibleStencil.sharedData);
// Share ref-counted data.
source = extensibleStencil.source;
asmJS = extensibleStencil.asmJS;
moduleMetadata = extensibleStencil.moduleMetadata;
}
#ifdef DEBUG
void CompilationStencil::assertBorrowingFromExtensibleCompilationStencil(
const ExtensibleCompilationStencil& extensibleStencil) const {
MOZ_ASSERT(canLazilyParse == extensibleStencil.canLazilyParse);
MOZ_ASSERT(functionKey == extensibleStencil.functionKey);
AssertBorrowingSpan(scriptData, extensibleStencil.scriptData);
AssertBorrowingSpan(scriptExtra, extensibleStencil.scriptExtra);
AssertBorrowingSpan(gcThingData, extensibleStencil.gcThingData);
AssertBorrowingSpan(scopeData, extensibleStencil.scopeData);
AssertBorrowingSpan(scopeNames, extensibleStencil.scopeNames);
AssertBorrowingSpan(regExpData, extensibleStencil.regExpData);
AssertBorrowingSpan(bigIntData, extensibleStencil.bigIntData);
AssertBorrowingSpan(objLiteralData, extensibleStencil.objLiteralData);
AssertBorrowingSpan(parserAtomData, extensibleStencil.parserAtoms.entries_);
MOZ_ASSERT(sharedData.isBorrow());
MOZ_ASSERT(sharedData.asBorrow() == &extensibleStencil.sharedData);
MOZ_ASSERT(source == extensibleStencil.source);
MOZ_ASSERT(asmJS == extensibleStencil.asmJS);
MOZ_ASSERT(moduleMetadata == extensibleStencil.moduleMetadata);
}
#endif
CompilationStencil::CompilationStencil(
UniquePtr<ExtensibleCompilationStencil>&& extensibleStencil)
: alloc(LifoAllocChunkSize, js::BackgroundMallocArena) {
ownedBorrowStencil = std::move(extensibleStencil);
storageType = StorageType::OwnedExtensible;
borrowFromExtensibleCompilationStencil(*ownedBorrowStencil);
#ifdef DEBUG
assertNoExternalDependency();
#endif
}
/* static */
bool CompilationStencil::instantiateStencils(JSContext* cx,
CompilationInput& input,
const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
AutoReportFrontendContext fc(cx);
if (!prepareForInstantiate(&fc, input.atomCache, stencil, gcOutput)) {
return false;
}
return instantiateStencilAfterPreparation(cx, input, stencil, gcOutput);
}
/* static */
bool CompilationStencil::instantiateStencilAfterPreparation(
JSContext* cx, CompilationInput& input, const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
// Distinguish between the initial (possibly lazy) compile and any subsequent
// delazification compiles. Delazification will update existing GC things.
bool isInitialParse = stencil.isInitialStencil();
MOZ_ASSERT(stencil.isInitialStencil() == input.isInitialStencil());
// Assert the consistency between the compile option and the target global.
MOZ_ASSERT_IF(cx->realm()->behaviors().discardSource(),
!stencil.canLazilyParse);
CompilationAtomCache& atomCache = input.atomCache;
const JS::InstantiateOptions options(input.options);
// Phase 1: Instantiate JSAtom/JSStrings.
AutoReportFrontendContext fc(cx);
if (!InstantiateAtoms(cx, &fc, atomCache, stencil)) {
return false;
}
// Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions.
if (isInitialParse) {
if (!InstantiateScriptSourceObject(cx, options, stencil, gcOutput)) {
return false;
}
if (stencil.moduleMetadata) {
// The enclosing script of a module is always the global scope. Fetch the
// scope of the current global and update input data.
MOZ_ASSERT(input.enclosingScope.isNull());
input.enclosingScope = InputScope(&cx->global()->emptyGlobalScope());
MOZ_ASSERT(input.enclosingScope.environmentChainLength() ==
ModuleScope::EnclosingEnvironmentChainLength);
if (!InstantiateModuleObject(cx, &fc, atomCache, stencil, gcOutput)) {
return false;
}
}
if (!InstantiateFunctions(cx, &fc, atomCache, stencil, gcOutput)) {
return false;
}
} else {
MOZ_ASSERT(
stencil.scriptData[CompilationStencil::TopLevelIndex].isFunction());
// FunctionKey is used when caching to map a delazification stencil to a
// specific lazy script. It is not used by instantiation, but we should
// ensure it is correctly defined.
MOZ_ASSERT(stencil.functionKey == input.extent().toFunctionKey());
FunctionsFromExistingLazy(input, gcOutput);
MOZ_ASSERT(gcOutput.functions.length() == stencil.scriptData.size());
#ifdef DEBUG
AssertDelazificationFieldsMatch(stencil, gcOutput);
#endif
}
// Phase 3: Instantiate js::Scopes.
if (!InstantiateScopes(cx, input, stencil, gcOutput)) {
return false;
}
// Phase 4: Instantiate (inner) BaseScripts.
if (isInitialParse) {
if (!InstantiateScriptStencils(cx, atomCache, stencil, gcOutput)) {
return false;
}
}
// Phase 5: Finish top-level handling
if (!InstantiateTopLevel(cx, input, stencil, gcOutput)) {
return false;
}
// !! Must be infallible from here forward !!
// Phase 6: Update lazy scripts.
if (stencil.canLazilyParse) {
UpdateEmittedInnerFunctions(cx, atomCache, stencil, gcOutput);
if (isInitialParse) {
LinkEnclosingLazyScript(stencil, gcOutput);
}
}
return true;
}
// The top-level self-hosted script is created and executed in each realm that
// needs it. While the stencil has a gcthings list for the various top-level
// functions, we use special machinery to create them on demand. So instead we
// use a placeholder JSFunction that should never be called.
static bool SelfHostedDummyFunction(JSContext* cx, unsigned argc,
JS::Value* vp) {
MOZ_CRASH("Self-hosting top-level should not use functions directly");
}
bool CompilationStencil::instantiateSelfHostedAtoms(
JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const {
MOZ_ASSERT(isInitialStencil());
// We must instantiate atoms during startup so they can be made permanent
// across multiple runtimes.
AutoReportFrontendContext fc(cx);
return InstantiateMarkedAtomsAsPermanent(cx, &fc, atomSet, parserAtomData,
atomCache);
}
JSScript* CompilationStencil::instantiateSelfHostedTopLevelForRealm(
JSContext* cx, CompilationInput& input) {
MOZ_ASSERT(isInitialStencil());
Rooted<CompilationGCOutput> gcOutput(cx);
gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx);
if (!gcOutput.get().sourceObject) {
return nullptr;
}
// The top-level script has ScriptIndex references in its gcthings list, but
// we do not want to instantiate those functions here since they are instead
// created on demand from the stencil. Create a dummy function and populate
// the functions array of the CompilationGCOutput with references to it.
RootedFunction dummy(
cx, NewNativeFunction(cx, SelfHostedDummyFunction, 0, nullptr));
if (!dummy) {
return nullptr;
}
if (!gcOutput.get().functions.allocateWith(dummy, scriptData.size())) {
ReportOutOfMemory(cx);
return nullptr;
}
if (!InstantiateTopLevel(cx, input, *this, gcOutput.get())) {
return nullptr;
}
return gcOutput.get().script;
}
JSFunction* CompilationStencil::instantiateSelfHostedLazyFunction(
JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index,
Handle<JSAtom*> name) {
MOZ_ASSERT(cx->zone()->suppressAllocationMetadataBuilder);
GeneratorKind generatorKind = scriptExtra[index].immutableFlags.hasFlag(
ImmutableScriptFlagsEnum::IsGenerator)
? GeneratorKind::Generator
: GeneratorKind::NotGenerator;
FunctionAsyncKind asyncKind = scriptExtra[index].immutableFlags.hasFlag(
ImmutableScriptFlagsEnum::IsAsync)
? FunctionAsyncKind::AsyncFunction
: FunctionAsyncKind::SyncFunction;
Rooted<JSAtom*> funName(cx);
if (scriptData[index].hasSelfHostedCanonicalName()) {
// SetCanonicalName was used to override the name.
funName = atomCache.getExistingAtomAt(
cx, scriptData[index].selfHostedCanonicalName());
} else if (name) {
// Our caller has a name it wants to use.
funName = name;
} else {
MOZ_ASSERT(scriptData[index].functionAtom);
funName = atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom);
}
RootedObject proto(cx);
if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
return nullptr;
}
RootedObject env(cx, &cx->global()->lexicalEnvironment());
RootedFunction fun(
cx,
NewFunctionWithProto(cx, nullptr, scriptExtra[index].nargs,
scriptData[index].functionFlags, env, funName, proto,
gc::AllocKind::FUNCTION_EXTENDED, TenuredObject));
if (!fun) {
return nullptr;
}
fun->initSelfHostedLazyScript(&cx->runtime()->selfHostedLazyScript.ref());
JSAtom* selfHostedName =
atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom);
SetClonedSelfHostedFunctionName(fun, selfHostedName->asPropertyName());
return fun;
}
bool CompilationStencil::delazifySelfHostedFunction(
JSContext* cx, CompilationAtomCache& atomCache, ScriptIndexRange range,
HandleFunction fun) {
// Determine the equivalent ScopeIndex range by looking at the outermost scope
// of the scripts defining the range. Take special care if this is the last
// script in the list.
auto getOutermostScope = [this](ScriptIndex scriptIndex) -> ScopeIndex {
MOZ_ASSERT(scriptData[scriptIndex].hasSharedData());
auto gcthings = scriptData[scriptIndex].gcthings(*this);
return gcthings[GCThingIndex::outermostScopeIndex()].toScope();
};
ScopeIndex scopeIndex = getOutermostScope(range.start);
ScopeIndex scopeLimit = (range.limit < scriptData.size())
? getOutermostScope(range.limit)
: ScopeIndex(scopeData.size());
// Prepare to instantiate by allocating the output arrays. We also set a base
// index to avoid allocations in most cases.
AutoReportFrontendContext fc(cx);
Rooted<CompilationGCOutput> gcOutput(cx);
if (!gcOutput.get().ensureAllocatedWithBaseIndex(
&fc, range.start, range.limit, scopeIndex, scopeLimit)) {
return false;
}
// Phase 1: Instantiate JSAtoms.
// NOTE: The self-hosted atoms are all "permanent" and the
// CompilationAtomCache is already stored on the JSRuntime.
// Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions.
// Get the corresponding ScriptSourceObject to use in current realm.
gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx);
if (!gcOutput.get().sourceObject) {
return false;
}
size_t instantiatedFunIndex = 0;
// Delazification target function.
gcOutput.get().functions[instantiatedFunIndex++] = fun;
// Allocate inner functions. Self-hosted functions do not allocate these with
// the initial function.
for (size_t i = range.start + 1; i < range.limit; i++) {
JSFunction* innerFun = CreateFunction(cx, atomCache, *this, scriptData[i],
scriptExtra[i], ScriptIndex(i));
if (!innerFun) {
return false;
}
gcOutput.get().functions[instantiatedFunIndex++] = innerFun;
}
// Phase 3: Instantiate js::Scopes.
// NOTE: When the enclosing scope is not a stencil, directly use the
// `emptyGlobalScope` instead of reading from CompilationInput. This is
// a special case for self-hosted delazification that allows us to reuse
// the CompilationInput between different realms.
size_t instantiatedScopeIndex = 0;
for (size_t i = scopeIndex; i < scopeLimit; i++) {
ScopeStencil& data = scopeData[i];
Rooted<Scope*> enclosingScope(
cx, data.hasEnclosing() ? gcOutput.get().getScope(data.enclosing())
: &cx->global()->emptyGlobalScope());
js::Scope* scope =
data.createScope(cx, atomCache, enclosingScope, scopeNames[i]);
if (!scope) {
return false;
}
gcOutput.get().scopes[instantiatedScopeIndex++] = scope;
}
// Phase 4: Instantiate (inner) BaseScripts.
ScriptIndex innerStart(range.start + 1);
for (size_t i = innerStart; i < range.limit; i++) {
if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(),
ScriptIndex(i))) {
return false;
}
}
// Phase 5: Finish top-level handling
// NOTE: We do not have a `CompilationInput` handy here, so avoid using the
// `InstantiateTopLevel` helper and directly create the JSScript. Our
// caller also handles the `AllowRelazify` flag for us since self-hosted
// delazification is a special case.
if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(),
range.start)) {
return false;
}
// Phase 6: Update lazy scripts.
// NOTE: Self-hosting is always fully parsed so there is nothing to do here.
return true;
}
/* static */
bool CompilationStencil::prepareForInstantiate(
FrontendContext* fc, CompilationAtomCache& atomCache,
const CompilationStencil& stencil, CompilationGCOutput& gcOutput) {
// Allocate the `gcOutput` arrays.
if (!gcOutput.ensureAllocated(fc, stencil.scriptData.size(),
stencil.scopeData.size())) {
return false;
}
return atomCache.allocate(fc, stencil.parserAtomData.size());
}
/* static */
bool CompilationStencil::prepareForInstantiate(
FrontendContext* fc, const CompilationStencil& stencil,
PreallocatedCompilationGCOutput& gcOutput) {
return gcOutput.allocate(fc, stencil.scriptData.size(),
stencil.scopeData.size());
}
bool JS::PrepareForInstantiate(JS::FrontendContext* fc, JS::Stencil& stencil,
JS::InstantiationStorage& storage) {
if (!storage.gcOutput_) {
storage.gcOutput_ =
fc->getAllocator()
->new_<js::frontend::PreallocatedCompilationGCOutput>();
if (!storage.gcOutput_) {
return false;
}
}
return CompilationStencil::prepareForInstantiate(fc, *stencil.getInitial(),
*storage.gcOutput_);
}
ExtensibleCompilationStencil::ExtensibleCompilationStencil(ScriptSource* source)
: alloc(CompilationStencil::LifoAllocChunkSize, js::BackgroundMallocArena),
source(source),
parserAtoms(alloc) {}
ExtensibleCompilationStencil::ExtensibleCompilationStencil(
CompilationInput& input)
: canLazilyParse(CanLazilyParse(input.options)),
alloc(CompilationStencil::LifoAllocChunkSize, js::BackgroundMallocArena),
source(input.source),
parserAtoms(alloc) {}
ExtensibleCompilationStencil::ExtensibleCompilationStencil(
const JS::ReadOnlyCompileOptions& options, RefPtr<ScriptSource> source)
: canLazilyParse(CanLazilyParse(options)),
alloc(CompilationStencil::LifoAllocChunkSize, js::BackgroundMallocArena),
source(std::move(source)),
parserAtoms(alloc) {}
CompilationState::CompilationState(FrontendContext* fc,
LifoAllocScope& parserAllocScope,
CompilationInput& input)
: ExtensibleCompilationStencil(input),
directives(input.options.forceStrictMode()),
usedNames(fc),
parserAllocScope(parserAllocScope),
input(input) {}
BorrowingCompilationStencil::BorrowingCompilationStencil(
ExtensibleCompilationStencil& extensibleStencil)
: CompilationStencil(extensibleStencil.source) {
storageType = StorageType::Borrowed;
borrowFromExtensibleCompilationStencil(extensibleStencil);
}
SharedDataContainer::~SharedDataContainer() {
if (isEmpty()) {
// Nothing to do.
} else if (isSingle()) {
asSingle()->Release();
} else if (isVector()) {
js_delete(asVector());
} else if (isMap()) {
js_delete(asMap());
} else {
MOZ_ASSERT(isBorrow());
// Nothing to do.
}
}
bool SharedDataContainer::initVector(FrontendContext* fc) {
MOZ_ASSERT(isEmpty());
auto* vec = js_new<SharedDataVector>();
if (!vec) {
ReportOutOfMemory(fc);
return false;
}
data_ = uintptr_t(vec) | VectorTag;
return true;
}
bool SharedDataContainer::initMap(FrontendContext* fc) {
MOZ_ASSERT(isEmpty());
auto* map = js_new<SharedDataMap>();
if (!map) {
ReportOutOfMemory(fc);
return false;
}
data_ = uintptr_t(map) | MapTag;
return true;
}
bool SharedDataContainer::prepareStorageFor(FrontendContext* fc,
size_t nonLazyScriptCount,
size_t allScriptCount) {
MOZ_ASSERT(isEmpty());
if (nonLazyScriptCount <= 1) {
MOZ_ASSERT(isSingle());
return true;
}
// If the ratio of scripts with bytecode is small, allocating the Vector
// storage with the number of all scripts isn't space-efficient.
// In that case use HashMap instead.
//
// In general, we expect either all scripts to contain bytecode (priviledge
// and self-hosted), or almost none to (eg standard lazy parsing output).
constexpr size_t thresholdRatio = 8;
bool useHashMap = nonLazyScriptCount < allScriptCount / thresholdRatio;
if (useHashMap) {
if (!initMap(fc)) {
return false;
}
if (!asMap()->reserve(nonLazyScriptCount)) {
ReportOutOfMemory(fc);
return false;
}
} else {
if (!initVector(fc)) {
return false;
}
if (!asVector()->resize(allScriptCount)) {
ReportOutOfMemory(fc);
return false;
}
}
return true;
}
bool SharedDataContainer::cloneFrom(FrontendContext* fc,
const SharedDataContainer& other) {
MOZ_ASSERT(isEmpty());
if (other.isBorrow()) {
return cloneFrom(fc, *other.asBorrow());
}
if (other.isSingle()) {
// As we clone, we add an extra reference.
RefPtr<SharedImmutableScriptData> ref(other.asSingle());
setSingle(ref.forget());
} else if (other.isVector()) {
if (!initVector(fc)) {
return false;
}
if (!asVector()->appendAll(*other.asVector())) {
ReportOutOfMemory(fc);
return false;
}
} else if (other.isMap()) {
if (!initMap(fc)) {
return false;
}
auto& otherMap = *other.asMap();
if (!asMap()->reserve(otherMap.count())) {
ReportOutOfMemory(fc);
return false;
}
auto& map = *asMap();
for (auto iter = otherMap.iter(); !iter.done(); iter.next()) {
auto& entry = iter.get();
map.putNewInfallible(entry.key(), entry.value());
}
}
return true;
}
js::SharedImmutableScriptData* SharedDataContainer::get(
ScriptIndex index) const {
if (isSingle()) {
if (index == CompilationStencil::TopLevelIndex) {
return asSingle();
}
return nullptr;
}
if (isVector()) {
auto& vec = *asVector();
if (index.index < vec.length()) {
return vec[index];
}
return nullptr;
}
if (isMap()) {
auto& map = *asMap();
auto p = map.lookup(index);
if (p) {
return p->value();
}
return nullptr;
}
MOZ_ASSERT(isBorrow());
return asBorrow()->get(index);
}
bool SharedDataContainer::convertFromSingleToMap(FrontendContext* fc) {
MOZ_ASSERT(isSingle());
// Use a temporary container so that on OOM we do not break the stencil.
SharedDataContainer other;
if (!other.initMap(fc)) {
return false;
}
if (!other.asMap()->putNew(CompilationStencil::TopLevelIndex, asSingle())) {
ReportOutOfMemory(fc);
return false;
}
std::swap(data_, other.data_);
return true;
}
bool SharedDataContainer::addAndShare(FrontendContext* fc, ScriptIndex index,
js::SharedImmutableScriptData* data) {
MOZ_ASSERT(!isBorrow());
if (isSingle()) {
MOZ_ASSERT(index == CompilationStencil::TopLevelIndex);
RefPtr<SharedImmutableScriptData> ref(data);
if (!SharedImmutableScriptData::shareScriptData(fc, ref)) {
return false;
}
setSingle(ref.forget());
return true;
}
if (isVector()) {
auto& vec = *asVector();
// Resized by SharedDataContainer::prepareStorageFor.
vec[index] = data;
return SharedImmutableScriptData::shareScriptData(fc, vec[index]);
}
MOZ_ASSERT(isMap());
auto& map = *asMap();
// Reserved by SharedDataContainer::prepareStorageFor.
map.putNewInfallible(index, data);
auto p = map.lookup(index);
MOZ_ASSERT(p);
return SharedImmutableScriptData::shareScriptData(fc, p->value());
}
bool SharedDataContainer::addExtraWithoutShare(
FrontendContext* fc, ScriptIndex index,
js::SharedImmutableScriptData* data) {
MOZ_ASSERT(!isEmpty());
if (isSingle()) {
if (!convertFromSingleToMap(fc)) {
return false;
}
}
if (isVector()) {
// SharedDataContainer::prepareStorageFor allocates space for all scripts.
(*asVector())[index] = data;
return true;
}
MOZ_ASSERT(isMap());
// SharedDataContainer::prepareStorageFor doesn't allocate space for
// delazification, and this can fail.
if (!asMap()->putNew(index, data)) {
ReportOutOfMemory(fc);
return false;
}
return true;
}
#ifdef DEBUG
void CompilationStencil::assertNoExternalDependency() const {
if (ownedBorrowStencil) {
ownedBorrowStencil->assertNoExternalDependency();
assertBorrowingFromExtensibleCompilationStencil(*ownedBorrowStencil);
return;
}
MOZ_ASSERT_IF(!scriptData.empty(), alloc.contains(scriptData.data()));
MOZ_ASSERT_IF(!scriptExtra.empty(), alloc.contains(scriptExtra.data()));
MOZ_ASSERT_IF(!scopeData.empty(), alloc.contains(scopeData.data()));
MOZ_ASSERT_IF(!scopeNames.empty(), alloc.contains(scopeNames.data()));
for (const auto* data : scopeNames) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
MOZ_ASSERT_IF(!regExpData.empty(), alloc.contains(regExpData.data()));
MOZ_ASSERT_IF(!bigIntData.empty(), alloc.contains(bigIntData.data()));
for (const auto& data : bigIntData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
MOZ_ASSERT_IF(!objLiteralData.empty(), alloc.contains(objLiteralData.data()));
for (const auto& data : objLiteralData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
MOZ_ASSERT_IF(!parserAtomData.empty(), alloc.contains(parserAtomData.data()));
for (const auto* data : parserAtomData) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
MOZ_ASSERT(!sharedData.isBorrow());
}
void ExtensibleCompilationStencil::assertNoExternalDependency() const {
for (const auto& data : bigIntData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
for (const auto& data : objLiteralData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
for (const auto* data : scopeNames) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
for (const auto* data : parserAtoms.entries()) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
MOZ_ASSERT(!sharedData.isBorrow());
}
#endif // DEBUG
template <typename T, typename VectorT>
[[nodiscard]] bool CopySpanToVector(FrontendContext* fc, VectorT& vec,
mozilla::Span<T>& span) {
auto len = span.size();
if (len == 0) {
return true;
}
if (!vec.append(span.data(), len)) {
js::ReportOutOfMemory(fc);
return false;
}
return true;
}
template <typename T, typename IntoSpanT, size_t Inline, typename AllocPolicy>
[[nodiscard]] bool CopyToVector(FrontendContext* fc,
mozilla::Vector<T, Inline, AllocPolicy>& vec,
const IntoSpanT& source) {
mozilla::Span<const T> span = source;
return CopySpanToVector(fc, vec, span);
}
// Span and Vector do not share the same method names.
template <typename T, size_t Inline, typename AllocPolicy>
size_t GetLength(const mozilla::Vector<T, Inline, AllocPolicy>& vec) {
return vec.length();
}
template <typename T>
size_t GetLength(const mozilla::Span<T>& span) {
return span.Length();
}
// Copy scope names from `src` into `alloc`, and returns the allocated data.
BaseParserScopeData* CopyScopeData(FrontendContext* fc, LifoAlloc& alloc,
ScopeKind kind,
const BaseParserScopeData* src) {
MOZ_ASSERT(kind != ScopeKind::With);
size_t dataSize = SizeOfParserScopeData(kind, src->length);
auto* dest = static_cast<BaseParserScopeData*>(alloc.alloc(dataSize));
if (!dest) {
js::ReportOutOfMemory(fc);
return nullptr;
}
memcpy(dest, src, dataSize);
return dest;
}
template <typename Stencil>
bool ExtensibleCompilationStencil::cloneFromImpl(FrontendContext* fc,
const Stencil& other) {
MOZ_ASSERT(alloc.isEmpty());
canLazilyParse = other.canLazilyParse;
functionKey = other.functionKey;
if (!CopyToVector(fc, scriptData, other.scriptData)) {
return false;
}
if (!CopyToVector(fc, scriptExtra, other.scriptExtra)) {
return false;
}
if (!CopyToVector(fc, gcThingData, other.gcThingData)) {
return false;
}
size_t scopeSize = GetLength(other.scopeData);
if (!CopyToVector(fc, scopeData, other.scopeData)) {
return false;
}
if (!scopeNames.reserve(scopeSize)) {
js::ReportOutOfMemory(fc);
return false;
}
for (size_t i = 0; i < scopeSize; i++) {
if (other.scopeNames[i]) {
BaseParserScopeData* data = CopyScopeData(
fc, alloc, other.scopeData[i].kind(), other.scopeNames[i]);
if (!data) {
return false;
}
scopeNames.infallibleEmplaceBack(data);
} else {
scopeNames.infallibleEmplaceBack(nullptr);
}
}
if (!CopyToVector(fc, regExpData, other.regExpData)) {
return false;
}
// If CompilationStencil has external dependency, peform deep copy.
size_t bigIntSize = GetLength(other.bigIntData);
if (!bigIntData.resize(bigIntSize)) {
js::ReportOutOfMemory(fc);
return false;
}
for (size_t i = 0; i < bigIntSize; i++) {
if (!bigIntData[i].init(fc, alloc, other.bigIntData[i])) {
return false;
}
}
size_t objLiteralSize = GetLength(other.objLiteralData);
if (!objLiteralData.reserve(objLiteralSize)) {
js::ReportOutOfMemory(fc);
return false;
}
for (const auto& data : other.objLiteralData) {
size_t length = data.code().size();
auto* code = alloc.newArrayUninitialized<uint8_t>(length);
if (!code) {
js::ReportOutOfMemory(fc);
return false;
}
memcpy(code, data.code().data(), length);
objLiteralData.infallibleEmplaceBack(code, length, data.kind(),
data.flags(), data.propertyCount());
}
// Regardless of whether CompilationStencil has external dependency or not,
// ParserAtoms should be interned, to populate internal HashMap.
for (const auto* entry : other.parserAtomsSpan()) {
if (!entry) {
if (!parserAtoms.addPlaceholder(fc)) {
return false;
}
continue;
}
auto index = parserAtoms.internExternalParserAtom(fc, entry);
if (!index) {
return false;
}
}
// We copy the stencil and increment the reference count of each
// SharedImmutableScriptData.
if (!sharedData.cloneFrom(fc, other.sharedData)) {
return false;
}
// Note: moduleMetadata and asmJS are known after the first parse, and are
// not mutated by any delazifications later on. Thus we can safely increment
// the reference counter and keep these as-is.
moduleMetadata = other.moduleMetadata;
asmJS = other.asmJS;
#ifdef DEBUG
assertNoExternalDependency();
#endif
return true;
}
bool ExtensibleCompilationStencil::cloneFrom(FrontendContext* fc,
const CompilationStencil& other) {
return cloneFromImpl(fc, other);
}
bool ExtensibleCompilationStencil::cloneFrom(
FrontendContext* fc, const ExtensibleCompilationStencil& other) {
return cloneFromImpl(fc, other);
}
bool ExtensibleCompilationStencil::steal(FrontendContext* fc,
RefPtr<CompilationStencil>&& other) {
MOZ_ASSERT(alloc.isEmpty());
using StorageType = CompilationStencil::StorageType;
StorageType storageType = other->storageType;
if (other->hasMultipleReference()) {
storageType = StorageType::Borrowed;
}
if (storageType == StorageType::OwnedExtensible) {
auto& otherExtensible = other->ownedBorrowStencil;
canLazilyParse = otherExtensible->canLazilyParse;
functionKey = otherExtensible->functionKey;
alloc.steal(&otherExtensible->alloc);
source = std::move(otherExtensible->source);
scriptData = std::move(otherExtensible->scriptData);
scriptExtra = std::move(otherExtensible->scriptExtra);
gcThingData = std::move(otherExtensible->gcThingData);
scopeData = std::move(otherExtensible->scopeData);
scopeNames = std::move(otherExtensible->scopeNames);
regExpData = std::move(otherExtensible->regExpData);
bigIntData = std::move(otherExtensible->bigIntData);
objLiteralData = std::move(otherExtensible->objLiteralData);
parserAtoms = std::move(otherExtensible->parserAtoms);
parserAtoms.fixupAlloc(alloc);
sharedData = std::move(otherExtensible->sharedData);
moduleMetadata = std::move(otherExtensible->moduleMetadata);
asmJS = std::move(otherExtensible->asmJS);
#ifdef DEBUG
assertNoExternalDependency();
#endif
return true;
}
if (storageType == StorageType::Borrowed) {
return cloneFrom(fc, *other);
}
MOZ_ASSERT(storageType == StorageType::Owned);
canLazilyParse = other->canLazilyParse;
functionKey = other->functionKey;
#ifdef DEBUG
other->assertNoExternalDependency();
MOZ_ASSERT(!other->hasMultipleReference());
#endif
// If CompilationStencil has no external dependency,
// steal LifoAlloc and perform shallow copy.
alloc.steal(&other->alloc);
if (!CopySpanToVector(fc, scriptData, other->scriptData)) {
return false;
}
if (!CopySpanToVector(fc, scriptExtra, other->scriptExtra)) {
return false;
}
if (!CopySpanToVector(fc, gcThingData, other->gcThingData)) {
return false;
}
if (!CopySpanToVector(fc, scopeData, other->scopeData)) {
return false;
}
if (!CopySpanToVector(fc, scopeNames, other->scopeNames)) {
return false;
}
if (!CopySpanToVector(fc, regExpData, other->regExpData)) {
return false;
}
if (!CopySpanToVector(fc, bigIntData, other->bigIntData)) {
return false;
}
if (!CopySpanToVector(fc, objLiteralData, other->objLiteralData)) {
return false;
}
// Regardless of whether CompilationStencil has external dependency or not,
// ParserAtoms should be interned, to populate internal HashMap.
for (const auto* entry : other->parserAtomData) {
if (!entry) {
if (!parserAtoms.addPlaceholder(fc)) {
return false;
}
continue;
}
auto index = parserAtoms.internExternalParserAtom(fc, entry);
if (!index) {
return false;
}
}
sharedData = std::move(other->sharedData);
moduleMetadata = std::move(other->moduleMetadata);
asmJS = std::move(other->asmJS);
#ifdef DEBUG
assertNoExternalDependency();
#endif
return true;
}
bool CompilationStencil::isModule() const {
return scriptExtra[CompilationStencil::TopLevelIndex].isModule();
}
bool ExtensibleCompilationStencil::isModule() const {
return scriptExtra[CompilationStencil::TopLevelIndex].isModule();
}
InitialStencilAndDelazifications::~InitialStencilAndDelazifications() {
MOZ_ASSERT(refCount_ == 0);
for (size_t i = 0; i < delazifications_.length(); i++) {
CompilationStencil* delazification = delazifications_[i].exchange(nullptr);
if (delazification) {
delazification->Release();
}
}
}
void InitialStencilAndDelazifications::AddRef() { refCount_++; }
void InitialStencilAndDelazifications::Release() {
MOZ_RELEASE_ASSERT(refCount_ > 0);
if (--refCount_ == 0) {
js_delete(this);
}
}
bool InitialStencilAndDelazifications::init(FrontendContext* fc,
const CompilationStencil* initial) {
MOZ_ASSERT(initial->isInitialStencil());
initial_ = initial;
if (!canLazilyParse()) {
// If the initial stencil is known to be fully-parsed, delazification
// never happens, and the delazifications_ vector and the
// functionKeyToInitialScriptIndex_ map is never used.
return true;
}
if (!delazifications_.resize(initial_->scriptData.size())) {
ReportOutOfMemory(fc);
return false;
}
return functionKeyToInitialScriptIndex_.init(fc, initial_);
}
const CompilationStencil* InitialStencilAndDelazifications::getInitial() const {
return initial_.get();
}
const CompilationStencil* InitialStencilAndDelazifications::getDelazificationAt(
size_t functionIndex) const {
MOZ_ASSERT(canLazilyParse());
MOZ_ASSERT(functionIndex > 0);
return delazifications_[functionIndex - 1];
}
const CompilationStencil*
InitialStencilAndDelazifications::getDelazificationFor(
const SourceExtent& extent) const {
MOZ_ASSERT(canLazilyParse());
auto maybeIndex =
functionKeyToInitialScriptIndex_.get(extent.toFunctionKey());
MOZ_ASSERT(maybeIndex,
"The extent parameter should be for a function inside the script");
return getDelazificationAt(*maybeIndex);
}
const CompilationStencil* InitialStencilAndDelazifications::storeDelazification(
RefPtr<CompilationStencil>&& delazification) {
MOZ_ASSERT(!delazification->hasMultipleReference());
MOZ_ASSERT(canLazilyParse());
auto maybeIndex =
functionKeyToInitialScriptIndex_.get(delazification->functionKey);
MOZ_ASSERT(maybeIndex);
size_t functionIndex = *maybeIndex;
CompilationStencil* raw = delazification.forget().take();
if (delazifications_[functionIndex - 1].compareExchange(nullptr, raw)) {
return raw;
}
raw->Release();
return delazifications_[functionIndex - 1];
}
CompilationStencil* InitialStencilAndDelazifications::getMerged(
FrontendContext* fc) const {
MOZ_ASSERT(canLazilyParse());
UniquePtr<ExtensibleCompilationStencil> extensibleStencil(
fc->getAllocator()->new_<ExtensibleCompilationStencil>(initial_->source));
if (!extensibleStencil) {
return nullptr;
}
if (!extensibleStencil->cloneFrom(fc, *initial_)) {
return nullptr;
}
CompilationStencilMerger merger;
if (!merger.setInitial(fc, std::move(extensibleStencil))) {
return nullptr;
}
for (const auto& delazification : delazifications_) {
if (!delazification) {
continue;
}
// NOTE: The delazifications_ vector can be modified by other threads
// during the iteration.
// The enclosing delazification's is not guaranteed to be iterated
// over in this iteration.
// If the enclosing function wasn't merged, all inner functions are
// ignored inside maybeAddDelazification.
if (!merger.maybeAddDelazification(fc, *delazification)) {
return nullptr;
}
}
UniquePtr<ExtensibleCompilationStencil> merged = merger.takeResult();
return fc->getAllocator()->new_<CompilationStencil>(std::move(merged));
}
/* static */
bool InitialStencilAndDelazifications::instantiateStencils(
JSContext* cx, CompilationInput& input,
InitialStencilAndDelazifications& stencils, CompilationGCOutput& gcOutput) {
if (!CompilationStencil::instantiateStencils(cx, input, *stencils.initial_,
gcOutput)) {
return false;
}
if (input.options.populateDelazificationCache()) {
RefPtr<InitialStencilAndDelazifications> stencilsPtr = &stencils;
ScriptSourceObject* sso = gcOutput.script->sourceObject();
MOZ_ASSERT(!sso->maybeGetStencils());
if (!stencils.getInitial()->asmJS) {
sso->setStencils(stencilsPtr.forget());
sso->setSharingDelazifications();
}
}
// At this point, gcOutput.script contains the top-level script, and
// gcOutput.functions[i] contains i-th function, where 0-th item is
// always nullptr.
// gcOutput.functions[i]->baseScript() is either JSScript or lazy script.
for (size_t i = 0, length = stencils.delazifications_.length(); i < length;
i++) {
const auto& delazification = stencils.delazifications_[i];
if (!delazification) {
continue;
}
ScriptIndex scriptIndex = ScriptIndex(i + 1);
JS::Rooted<JSFunction*> fun(cx, gcOutput.functions[scriptIndex]);
if (!fun->baseScript()->isReadyForDelazification()) {
// NOTE: The delazifications_ vector can be modified by other threads
// during the iteration.
// The enclosing delazification's is not guaranteed to be iterated
// over in this iteration.
// If the enclosing function wasn't instantiated, ignore all inner
// functions.
continue;
}
JS::Rooted<CompilationInput> inputForFunc(cx,
CompilationInput(input.options));
inputForFunc.get().initFromLazy(cx, fun->baseScript(), input.source);
// TODO: The preparation can be shared across iterations.
JS::Rooted<CompilationGCOutput> gcOutputForFunc(cx);
if (!CompilationStencil::instantiateStencils(
cx, inputForFunc.get(), *delazification, gcOutputForFunc.get())) {
return false;
}
}
return true;
}
size_t InitialStencilAndDelazifications::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
size_t size = 0;
if (initial_) {
// The initial stencil can be shared between multiple owners, but
// in most case this instance is considered as the main owner, in term
// of the memory reporting.
size += initial_->sizeOfExcludingThis(mallocSizeOf);
}
size += delazifications_.sizeOfExcludingThis(mallocSizeOf);
for (const auto& delazification : delazifications_) {
if (!delazification) {
continue;
}
// Delazifications are exclusively owned by this instance.
size += (*delazification).sizeOfExcludingThis(mallocSizeOf);
}
size += functionKeyToInitialScriptIndex_.sizeOfExcludingThis(mallocSizeOf);
return size;
}
mozilla::Span<TaggedScriptThingIndex> ScriptStencil::gcthings(
const CompilationStencil& stencil) const {
return stencil.gcThingData.Subspan(gcThingsOffset, gcThingsLength);
}
bool BigIntStencil::initFromChars(FrontendContext* fc, LifoAlloc& alloc,
mozilla::Span<const char16_t> buf) {
MOZ_ASSERT(ParseBigInt64Literal(buf).isNothing(),
"int64-sized BigInts are stored inline");
size_t length = buf.size();
MOZ_ASSERT(length > 0);
char16_t* p = alloc.template newArrayUninitialized<char16_t>(length);
if (!p) {
ReportOutOfMemory(fc);
return false;
}
mozilla::PodCopy(p, buf.data(), length);
bigInt_ = mozilla::AsVariant(mozilla::Span(p, length));
return true;
}
bool BigIntStencil::init(FrontendContext* fc, LifoAlloc& alloc,
mozilla::Span<const char16_t> buf) {
if (auto int64 = ParseBigInt64Literal(buf)) {
bigInt_ = mozilla::AsVariant(*int64);
return true;
}
return initFromChars(fc, alloc, buf);
}
bool BigIntStencil::init(FrontendContext* fc, LifoAlloc& alloc,
const BigIntStencil& other) {
if (other.bigInt_.is<int64_t>()) {
bigInt_ = other.bigInt_;
return true;
}
return initFromChars(fc, alloc, other.source());
}
BigInt* BigIntStencil::createBigInt(JSContext* cx) const {
return bigInt_.match(
[cx](mozilla::Span<char16_t> source) {
return js::ParseBigIntLiteral(cx, source);
},
[cx](int64_t int64) {
// BigInts are stored in the script's data vector and therefore need to
// be allocated in the tenured heap.
constexpr gc::Heap heap = gc::Heap::Tenured;
return BigInt::createFromInt64(cx, int64, heap);
});
}
bool BigIntStencil::isZero() const {
return bigInt_.match([](mozilla::Span<char16_t>) { return false; },
[](int64_t int64) { return int64 == 0; });
}
bool BigIntStencil::inplaceNegate() {
return bigInt_.match([](mozilla::Span<char16_t>) { return false; },
[](int64_t& int64) {
auto negated = -mozilla::CheckedInt<int64_t>{int64};
if (!negated.isValid()) {
return false;
}
int64 = negated.value();
return true;
});
}
bool BigIntStencil::inplaceBitNot() {
return bigInt_.match([](mozilla::Span<char16_t>) { return false; },
[](int64_t& int64) {
int64 = ~int64;
return true;
});
}
#ifdef DEBUG
bool BigIntStencil::isContainedIn(const LifoAlloc& alloc) const {
return bigInt_.match(
[&alloc](mozilla::Span<char16_t> source) {
return alloc.contains(source.data());
},
[](int64_t) { return true; });
}
#endif
#if defined(DEBUG) || defined(JS_JITSPEW)
void frontend::DumpTaggedParserAtomIndex(js::JSONPrinter& json,
TaggedParserAtomIndex taggedIndex,
const CompilationStencil* stencil) {
if (taggedIndex.isParserAtomIndex()) {
json.property("tag", "AtomIndex");
auto index = taggedIndex.toParserAtomIndex();
if (stencil && stencil->parserAtomData[index]) {
GenericPrinter& out = json.beginStringProperty("atom");
stencil->parserAtomData[index]->dumpCharsNoQuote(out);
json.endString();
} else {
json.property("index", size_t(index));
}
return;
}
if (taggedIndex.isWellKnownAtomId()) {
json.property("tag", "WellKnown");
auto index = taggedIndex.toWellKnownAtomId();
switch (index) {
case WellKnownAtomId::empty_:
json.property("atom", "");
break;
# define CASE_(name, _) case WellKnownAtomId::name:
FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_)
# undef CASE_
# define CASE_(name, _) case WellKnownAtomId::name:
JS_FOR_EACH_PROTOTYPE(CASE_)
# undef CASE_
# define CASE_(name) case WellKnownAtomId::name:
JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_)
# undef CASE_
{
GenericPrinter& out = json.beginStringProperty("atom");
ParserAtomsTable::dumpCharsNoQuote(out, index);
json.endString();
break;
}
default:
// This includes tiny WellKnownAtomId atoms, which is invalid.
json.property("index", size_t(index));
break;
}
return;
}
if (taggedIndex.isLength1StaticParserString()) {
json.property("tag", "Length1Static");
auto index = taggedIndex.toLength1StaticParserString();
GenericPrinter& out = json.beginStringProperty("atom");
ParserAtomsTable::dumpCharsNoQuote(out, index);
json.endString();
return;
}
if (taggedIndex.isLength2StaticParserString()) {
json.property("tag", "Length2Static");
auto index = taggedIndex.toLength2StaticParserString();
GenericPrinter& out = json.beginStringProperty("atom");
ParserAtomsTable::dumpCharsNoQuote(out, index);
json.endString();
return;
}
if (taggedIndex.isLength3StaticParserString()) {
json.property("tag", "Length3Static");
auto index = taggedIndex.toLength3StaticParserString();
GenericPrinter& out = json.beginStringProperty("atom");
ParserAtomsTable::dumpCharsNoQuote(out, index);
json.endString();
return;
}
MOZ_ASSERT(taggedIndex.isNull());
json.property("tag", "null");
}
void frontend::DumpTaggedParserAtomIndexNoQuote(
GenericPrinter& out, TaggedParserAtomIndex taggedIndex,
const CompilationStencil* stencil) {
if (taggedIndex.isParserAtomIndex()) {
auto index = taggedIndex.toParserAtomIndex();
if (stencil && stencil->parserAtomData[index]) {
stencil->parserAtomData[index]->dumpCharsNoQuote(out);
} else {
out.printf("AtomIndex#%zu", size_t(index));
}
return;
}
if (taggedIndex.isWellKnownAtomId()) {
auto index = taggedIndex.toWellKnownAtomId();
switch (index) {
case WellKnownAtomId::empty_:
out.put("#<zero-length name>");
break;
# define CASE_(name, _) case WellKnownAtomId::name:
FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_)
# undef CASE_
# define CASE_(name, _) case WellKnownAtomId::name:
JS_FOR_EACH_PROTOTYPE(CASE_)
# undef CASE_
# define CASE_(name) case WellKnownAtomId::name:
JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_)
# undef CASE_
{
ParserAtomsTable::dumpCharsNoQuote(out, index);
break;
}
default:
// This includes tiny WellKnownAtomId atoms, which is invalid.
out.printf("WellKnown#%zu", size_t(index));
break;
}
return;
}
if (taggedIndex.isLength1StaticParserString()) {
auto index = taggedIndex.toLength1StaticParserString();
ParserAtomsTable::dumpCharsNoQuote(out, index);
return;
}
if (taggedIndex.isLength2StaticParserString()) {
auto index = taggedIndex.toLength2StaticParserString();
ParserAtomsTable::dumpCharsNoQuote(out, index);
return;
}
if (taggedIndex.isLength3StaticParserString()) {
auto index = taggedIndex.toLength3StaticParserString();
ParserAtomsTable::dumpCharsNoQuote(out, index);
return;
}
MOZ_ASSERT(taggedIndex.isNull());
out.put("#<null name>");
}
void RegExpStencil::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json, nullptr);
}
void RegExpStencil::dump(js::JSONPrinter& json,
const CompilationStencil* stencil) const {
json.beginObject();
dumpFields(json, stencil);
json.endObject();
}
void RegExpStencil::dumpFields(js::JSONPrinter& json,
const CompilationStencil* stencil) const {
json.beginObjectProperty("pattern");
DumpTaggedParserAtomIndex(json, atom_, stencil);
json.endObject();
GenericPrinter& out = json.beginStringProperty("flags");
if (flags().global()) {
out.put("g");
}
if (flags().ignoreCase()) {
out.put("i");
}
if (flags().multiline()) {
out.put("m");
}
if (flags().dotAll()) {
out.put("s");
}
if (flags().unicode()) {
out.put("u");
}
if (flags().sticky()) {
out.put("y");
}
json.endStringProperty();
}
void BigIntStencil::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json);
}
void BigIntStencil::dump(js::JSONPrinter& json) const {
GenericPrinter& out = json.beginString();
dumpCharsNoQuote(out);
json.endString();
}
void BigIntStencil::dumpCharsNoQuote(GenericPrinter& out) const {
bigInt_.match(
[&out](mozilla::Span<char16_t> source) {
for (char16_t c : source) {
out.putChar(char(c));
}
},
[&out](int64_t int64) { out.printf("%" PRId64, int64); });
}
void ScopeStencil::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json, nullptr, nullptr);
}
void ScopeStencil::dump(js::JSONPrinter& json,
const BaseParserScopeData* baseScopeData,
const CompilationStencil* stencil) const {
json.beginObject();
dumpFields(json, baseScopeData, stencil);
json.endObject();
}
void ScopeStencil::dumpFields(js::JSONPrinter& json,
const BaseParserScopeData* baseScopeData,
const CompilationStencil* stencil) const {
json.property("kind", ScopeKindString(kind_));
if (hasEnclosing()) {
json.formatProperty("enclosing", "ScopeIndex(%zu)", size_t(enclosing()));
}
json.property("firstFrameSlot", firstFrameSlot_);
if (hasEnvironmentShape()) {
json.formatProperty("numEnvironmentSlots", "%zu",
size_t(numEnvironmentSlots_));
}
if (isFunction()) {
json.formatProperty("functionIndex", "ScriptIndex(%zu)",
size_t(functionIndex_));
}
json.beginListProperty("flags");
if (flags_ & HasEnclosing) {
json.value("HasEnclosing");
}
if (flags_ & HasEnvironmentShape) {
json.value("HasEnvironmentShape");
}
if (flags_ & IsArrow) {
json.value("IsArrow");
}
json.endList();
if (!baseScopeData) {
return;
}
json.beginObjectProperty("data");
mozilla::Span<const ParserBindingName> trailingNames;
switch (kind_) {
case ScopeKind::Function: {
const auto* data =
static_cast<const FunctionScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
json.property("hasParameterExprs", data->slotInfo.hasParameterExprs());
json.property("nonPositionalFormalStart",
data->slotInfo.nonPositionalFormalStart);
json.property("varStart", data->slotInfo.varStart);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::FunctionBodyVar: {
const auto* data =
static_cast<const VarScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::FunctionLexical: {
const auto* data =
static_cast<const LexicalScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
json.property("constStart", data->slotInfo.constStart);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::ClassBody: {
const auto* data =
static_cast<const ClassBodyScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
json.property("privateMethodStart", data->slotInfo.privateMethodStart);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::With: {
break;
}
case ScopeKind::Eval:
case ScopeKind::StrictEval: {
const auto* data =
static_cast<const EvalScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::Global:
case ScopeKind::NonSyntactic: {
const auto* data =
static_cast<const GlobalScope::ParserData*>(baseScopeData);
json.property("letStart", data->slotInfo.letStart);
json.property("constStart", data->slotInfo.constStart);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::Module: {
const auto* data =
static_cast<const ModuleScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
json.property("varStart", data->slotInfo.varStart);
json.property("letStart", data->slotInfo.letStart);
json.property("constStart", data->slotInfo.constStart);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::WasmInstance: {
const auto* data =
static_cast<const WasmInstanceScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
json.property("globalsStart", data->slotInfo.globalsStart);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
case ScopeKind::WasmFunction: {
const auto* data =
static_cast<const WasmFunctionScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
trailingNames = GetScopeDataTrailingNames(data);
break;
}
default: {
MOZ_CRASH("Unexpected ScopeKind");
break;
}
}
if (!trailingNames.empty()) {
char index[64];
json.beginObjectProperty("trailingNames");
for (size_t i = 0; i < trailingNames.size(); i++) {
const auto& name = trailingNames[i];
SprintfLiteral(index, "%zu", i);
json.beginObjectProperty(index);
json.boolProperty("closedOver", name.closedOver());
json.boolProperty("isTopLevelFunction", name.isTopLevelFunction());
json.beginObjectProperty("name");
DumpTaggedParserAtomIndex(json, name.name(), stencil);
json.endObject();
json.endObject();
}
json.endObject();
}
json.endObject();
}
static void DumpModuleRequestVectorItems(
js::JSONPrinter& json, const StencilModuleMetadata::RequestVector& requests,
const CompilationStencil* stencil) {
for (const auto& request : requests) {
json.beginObject();
if (request.specifier) {
json.beginObjectProperty("specifier");
DumpTaggedParserAtomIndex(json, request.specifier, stencil);
json.endObject();
}
json.endObject();
}
}
static void DumpModuleEntryVectorItems(
js::JSONPrinter& json, const StencilModuleMetadata::EntryVector& entries,
const CompilationStencil* stencil) {
for (const auto& entry : entries) {
json.beginObject();
if (entry.moduleRequest) {
json.property("moduleRequest", entry.moduleRequest.value());
}
if (entry.localName) {
json.beginObjectProperty("localName");
DumpTaggedParserAtomIndex(json, entry.localName, stencil);
json.endObject();
}
if (entry.importName) {
json.beginObjectProperty("importName");
DumpTaggedParserAtomIndex(json, entry.importName, stencil);
json.endObject();
}
if (entry.exportName) {
json.beginObjectProperty("exportName");
DumpTaggedParserAtomIndex(json, entry.exportName, stencil);
json.endObject();
}
// TODO: Dump assertions.
json.endObject();
}
}
void StencilModuleMetadata::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json, nullptr);
}
void StencilModuleMetadata::dump(js::JSONPrinter& json,
const CompilationStencil* stencil) const {
json.beginObject();
dumpFields(json, stencil);
json.endObject();
}
void StencilModuleMetadata::dumpFields(
js::JSONPrinter& json, const CompilationStencil* stencil) const {
json.beginListProperty("moduleRequests");
DumpModuleRequestVectorItems(json, moduleRequests, stencil);
json.endList();
json.beginListProperty("requestedModules");
DumpModuleEntryVectorItems(json, requestedModules, stencil);
json.endList();
json.beginListProperty("importEntries");
DumpModuleEntryVectorItems(json, importEntries, stencil);
json.endList();
json.beginListProperty("localExportEntries");
DumpModuleEntryVectorItems(json, localExportEntries, stencil);
json.endList();
json.beginListProperty("indirectExportEntries");
DumpModuleEntryVectorItems(json, indirectExportEntries, stencil);
json.endList();
json.beginListProperty("starExportEntries");
DumpModuleEntryVectorItems(json, starExportEntries, stencil);
json.endList();
json.beginListProperty("functionDecls");
for (const auto& index : functionDecls) {
json.value("ScriptIndex(%zu)", size_t(index));
}
json.endList();
json.boolProperty("isAsync", isAsync);
}
void js::DumpImmutableScriptFlags(js::JSONPrinter& json,
ImmutableScriptFlags immutableFlags) {
for (uint32_t i = 1; i; i = i << 1) {
if (uint32_t(immutableFlags) & i) {
switch (ImmutableScriptFlagsEnum(i)) {
case ImmutableScriptFlagsEnum::IsForEval:
json.value("IsForEval");
break;
case ImmutableScriptFlagsEnum::IsModule:
json.value("IsModule");
break;
case ImmutableScriptFlagsEnum::IsFunction:
json.value("IsFunction");
break;
case ImmutableScriptFlagsEnum::SelfHosted:
json.value("SelfHosted");
break;
case ImmutableScriptFlagsEnum::ForceStrict:
json.value("ForceStrict");
break;
case ImmutableScriptFlagsEnum::HasNonSyntacticScope:
json.value("HasNonSyntacticScope");
break;
case ImmutableScriptFlagsEnum::NoScriptRval:
json.value("NoScriptRval");
break;
case ImmutableScriptFlagsEnum::TreatAsRunOnce:
json.value("TreatAsRunOnce");
break;
case ImmutableScriptFlagsEnum::Strict:
json.value("Strict");
break;
case ImmutableScriptFlagsEnum::HasModuleGoal:
json.value("HasModuleGoal");
break;
case ImmutableScriptFlagsEnum::HasInnerFunctions:
json.value("HasInnerFunctions");
break;
case ImmutableScriptFlagsEnum::HasDirectEval:
json.value("HasDirectEval");
break;
case ImmutableScriptFlagsEnum::BindingsAccessedDynamically:
json.value("BindingsAccessedDynamically");
break;
case ImmutableScriptFlagsEnum::HasCallSiteObj:
json.value("HasCallSiteObj");
break;
case ImmutableScriptFlagsEnum::IsAsync:
json.value("IsAsync");
break;
case ImmutableScriptFlagsEnum::IsGenerator:
json.value("IsGenerator");
break;
case ImmutableScriptFlagsEnum::FunHasExtensibleScope:
json.value("FunHasExtensibleScope");
break;
case ImmutableScriptFlagsEnum::FunctionHasThisBinding:
json.value("FunctionHasThisBinding");
break;
case ImmutableScriptFlagsEnum::NeedsHomeObject:
json.value("NeedsHomeObject");
break;
case ImmutableScriptFlagsEnum::IsDerivedClassConstructor:
json.value("IsDerivedClassConstructor");
break;
case ImmutableScriptFlagsEnum::IsSyntheticFunction:
json.value("IsSyntheticFunction");
break;
case ImmutableScriptFlagsEnum::UseMemberInitializers:
json.value("UseMemberInitializers");
break;
case ImmutableScriptFlagsEnum::HasRest:
json.value("HasRest");
break;
case ImmutableScriptFlagsEnum::NeedsFunctionEnvironmentObjects:
json.value("NeedsFunctionEnvironmentObjects");
break;
case ImmutableScriptFlagsEnum::FunctionHasExtraBodyVarScope:
json.value("FunctionHasExtraBodyVarScope");
break;
case ImmutableScriptFlagsEnum::ShouldDeclareArguments:
json.value("ShouldDeclareArguments");
break;
case ImmutableScriptFlagsEnum::NeedsArgsObj:
json.value("NeedsArgsObj");
break;
case ImmutableScriptFlagsEnum::HasMappedArgsObj:
json.value("HasMappedArgsObj");
break;
case ImmutableScriptFlagsEnum::IsInlinableLargeFunction:
json.value("IsInlinableLargeFunction");
break;
case ImmutableScriptFlagsEnum::FunctionHasNewTargetBinding:
json.value("FunctionHasNewTargetBinding");
break;
case ImmutableScriptFlagsEnum::UsesArgumentsIntrinsics:
json.value("UsesArgumentsIntrinsics");
break;
default:
json.value("Unknown(%x)", i);
break;
}
}
}
}
void js::DumpFunctionFlagsItems(js::JSONPrinter& json,
FunctionFlags functionFlags) {
switch (functionFlags.kind()) {
case FunctionFlags::FunctionKind::NormalFunction:
json.value("NORMAL_KIND");
break;
case FunctionFlags::FunctionKind::AsmJS:
json.value("ASMJS_KIND");
break;
case FunctionFlags::FunctionKind::Wasm:
json.value("WASM_KIND");
break;
case FunctionFlags::FunctionKind::Arrow:
json.value("ARROW_KIND");
break;
case FunctionFlags::FunctionKind::Method:
json.value("METHOD_KIND");
break;
case FunctionFlags::FunctionKind::ClassConstructor:
json.value("CLASSCONSTRUCTOR_KIND");
break;
case FunctionFlags::FunctionKind::Getter:
json.value("GETTER_KIND");
break;
case FunctionFlags::FunctionKind::Setter:
json.value("SETTER_KIND");
break;
default:
json.value("Unknown(%x)", uint8_t(functionFlags.kind()));
break;
}
static_assert(FunctionFlags::FUNCTION_KIND_MASK == 0x0007,
"FunctionKind should use the lowest 3 bits");
for (uint16_t i = 1 << 3; i; i = i << 1) {
if (functionFlags.toRaw() & i) {
switch (FunctionFlags::Flags(i)) {
case FunctionFlags::Flags::EXTENDED:
json.value("EXTENDED");
break;
case FunctionFlags::Flags::SELF_HOSTED:
json.value("SELF_HOSTED");
break;
case FunctionFlags::Flags::BASESCRIPT:
json.value("BASESCRIPT");
break;
case FunctionFlags::Flags::SELFHOSTLAZY:
json.value("SELFHOSTLAZY");
break;
case FunctionFlags::Flags::CONSTRUCTOR:
json.value("CONSTRUCTOR");
break;
case FunctionFlags::Flags::LAZY_ACCESSOR_NAME:
json.value("LAZY_ACCESSOR_NAME");
break;
case FunctionFlags::Flags::LAMBDA:
json.value("LAMBDA");
break;
case FunctionFlags::Flags::NATIVE_JIT_ENTRY:
json.value("NATIVE_JIT_ENTRY");
break;
case FunctionFlags::Flags::HAS_INFERRED_NAME:
json.value("HAS_INFERRED_NAME");
break;
case FunctionFlags::Flags::HAS_GUESSED_ATOM:
json.value("HAS_GUESSED_ATOM");
break;
case FunctionFlags::Flags::RESOLVED_NAME:
json.value("RESOLVED_NAME");
break;
case FunctionFlags::Flags::RESOLVED_LENGTH:
json.value("RESOLVED_LENGTH");
break;
case FunctionFlags::Flags::GHOST_FUNCTION:
json.value("GHOST_FUNCTION");
break;
default:
json.value("Unknown(%x)", i);
break;
}
}
}
}
static void DumpScriptThing(js::JSONPrinter& json,
const CompilationStencil* stencil,
TaggedScriptThingIndex thing) {
switch (thing.tag()) {
case TaggedScriptThingIndex::Kind::ParserAtomIndex:
case TaggedScriptThingIndex::Kind::WellKnown:
json.beginObject();
json.property("type", "Atom");
DumpTaggedParserAtomIndex(json, thing.toAtom(), stencil);
json.endObject();
break;
case TaggedScriptThingIndex::Kind::Null:
json.nullValue();
break;
case TaggedScriptThingIndex::Kind::BigInt:
json.value("BigIntIndex(%zu)", size_t(thing.toBigInt()));
break;
case TaggedScriptThingIndex::Kind::ObjLiteral:
json.value("ObjLiteralIndex(%zu)", size_t(thing.toObjLiteral()));
break;
case TaggedScriptThingIndex::Kind::RegExp:
json.value("RegExpIndex(%zu)", size_t(thing.toRegExp()));
break;
case TaggedScriptThingIndex::Kind::Scope:
json.value("ScopeIndex(%zu)", size_t(thing.toScope()));
break;
case TaggedScriptThingIndex::Kind::Function:
json.value("ScriptIndex(%zu)", size_t(thing.toFunction()));
break;
case TaggedScriptThingIndex::Kind::EmptyGlobalScope:
json.value("EmptyGlobalScope");
break;
}
}
void ScriptStencil::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json, nullptr);
}
void ScriptStencil::dump(js::JSONPrinter& json,
const CompilationStencil* stencil) const {
json.beginObject();
dumpFields(json, stencil);
json.endObject();
}
void ScriptStencil::dumpFields(js::JSONPrinter& json,
const CompilationStencil* stencil) const {
json.formatProperty("gcThingsOffset", "CompilationGCThingIndex(%u)",
gcThingsOffset.index);
json.property("gcThingsLength", gcThingsLength);
if (stencil) {
json.beginListProperty("gcThings");
for (const auto& thing : gcthings(*stencil)) {
DumpScriptThing(json, stencil, thing);
}
json.endList();
}
json.beginListProperty("flags");
if (flags_ & WasEmittedByEnclosingScriptFlag) {
json.value("WasEmittedByEnclosingScriptFlag");
}
if (flags_ & AllowRelazifyFlag) {
json.value("AllowRelazifyFlag");
}
if (flags_ & HasSharedDataFlag) {
json.value("HasSharedDataFlag");
}
if (flags_ & HasLazyFunctionEnclosingScopeIndexFlag) {
json.value("HasLazyFunctionEnclosingScopeIndexFlag");
}
json.endList();
if (isFunction()) {
json.beginObjectProperty("functionAtom");
DumpTaggedParserAtomIndex(json, functionAtom, stencil);
json.endObject();
json.beginListProperty("functionFlags");
DumpFunctionFlagsItems(json, functionFlags);
json.endList();
if (hasLazyFunctionEnclosingScopeIndex()) {
json.formatProperty("lazyFunctionEnclosingScopeIndex", "ScopeIndex(%zu)",
size_t(lazyFunctionEnclosingScopeIndex()));
}
if (hasSelfHostedCanonicalName()) {
json.beginObjectProperty("selfHostCanonicalName");
DumpTaggedParserAtomIndex(json, selfHostedCanonicalName(), stencil);
json.endObject();
}
}
}
void ScriptStencilExtra::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json);
}
void ScriptStencilExtra::dump(js::JSONPrinter& json) const {
json.beginObject();
dumpFields(json);
json.endObject();
}
void ScriptStencilExtra::dumpFields(js::JSONPrinter& json) const {
json.beginListProperty("immutableFlags");
DumpImmutableScriptFlags(json, immutableFlags);
json.endList();
json.beginObjectProperty("extent");
json.property("sourceStart", extent.sourceStart);
json.property("sourceEnd", extent.sourceEnd);
json.property("toStringStart", extent.toStringStart);
json.property("toStringEnd", extent.toStringEnd);
json.property("lineno", extent.lineno);
json.property("column", extent.column.oneOriginValue());
json.endObject();
json.property("memberInitializers", memberInitializers_);
json.property("nargs", nargs);
}
void SharedDataContainer::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json);
}
void SharedDataContainer::dump(js::JSONPrinter& json) const {
json.beginObject();
dumpFields(json);
json.endObject();
}
void SharedDataContainer::dumpFields(js::JSONPrinter& json) const {
if (isEmpty()) {
json.nullProperty("ScriptIndex(0)");
return;
}
if (isSingle()) {
json.formatProperty("ScriptIndex(0)", "u8[%zu]",
asSingle()->immutableDataLength());
return;
}
if (isVector()) {
auto& vec = *asVector();
char index[64];
for (size_t i = 0; i < vec.length(); i++) {
SprintfLiteral(index, "ScriptIndex(%zu)", i);
if (vec[i]) {
json.formatProperty(index, "u8[%zu]", vec[i]->immutableDataLength());
} else {
json.nullProperty(index);
}
}
return;
}
if (isMap()) {
auto& map = *asMap();
char index[64];
for (auto iter = map.iter(); !iter.done(); iter.next()) {
SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index);
json.formatProperty(index, "u8[%zu]",
iter.get().value()->immutableDataLength());
}
return;
}
MOZ_ASSERT(isBorrow());
asBorrow()->dumpFields(json);
}
struct DumpOptionsFields {
js::JSONPrinter& json;
void operator()(const char* name, JS::AsmJSOption value) {
const char* valueStr = nullptr;
switch (value) {
case JS::AsmJSOption::Enabled:
valueStr = "JS::AsmJSOption::Enabled";
break;
case JS::AsmJSOption::DisabledByAsmJSPref:
valueStr = "JS::AsmJSOption::DisabledByAsmJSPref";
break;
case JS::AsmJSOption::DisabledByLinker:
valueStr = "JS::AsmJSOption::DisabledByLinker";
break;
case JS::AsmJSOption::DisabledByNoWasmCompiler:
valueStr = "JS::AsmJSOption::DisabledByNoWasmCompiler";
break;
case JS::AsmJSOption::DisabledByDebugger:
valueStr = "JS::AsmJSOption::DisabledByDebugger";
break;
}
json.property(name, valueStr);
}
void operator()(const char* name, JS::DelazificationOption value) {
const char* valueStr = nullptr;
switch (value) {
# define SelectValueStr_(Strategy) \
case JS::DelazificationOption::Strategy: \
valueStr = "JS::DelazificationOption::" #Strategy; \
break;
FOREACH_DELAZIFICATION_STRATEGY(SelectValueStr_)
# undef SelectValueStr_
}
json.property(name, valueStr);
}
void operator()(const char* name, char16_t* value) {}
void operator()(const char* name, bool value) { json.property(name, value); }
void operator()(const char* name, uint32_t value) {
json.property(name, value);
}
void operator()(const char* name, uint64_t value) {
json.property(name, value);
}
void operator()(const char* name, const char* value) {
if (value) {
json.property(name, value);
return;
}
json.nullProperty(name);
}
void operator()(const char* name, JS::ConstUTF8CharsZ value) {
if (value) {
json.property(name, value.c_str());
return;
}
json.nullProperty(name);
}
};
static void DumpOptionsFields(js::JSONPrinter& json,
const JS::ReadOnlyCompileOptions& options) {
struct DumpOptionsFields printer {
json
};
options.dumpWith(printer);
}
static void DumpInputScopeFields(js::JSONPrinter& json,
const InputScope& scope) {
json.property("kind", ScopeKindString(scope.kind()));
InputScope enclosing = scope.enclosing();
if (enclosing.isNull()) {
json.nullProperty("enclosing");
} else {
json.beginObjectProperty("enclosing");
DumpInputScopeFields(json, enclosing);
json.endObject();
}
}
static void DumpInputScriptFields(js::JSONPrinter& json,
const InputScript& script) {
json.beginObjectProperty("extent");
{
SourceExtent extent = script.extent();
json.property("sourceStart", extent.sourceStart);
json.property("sourceEnd", extent.sourceEnd);
json.property("toStringStart", extent.toStringStart);
json.property("toStringEnd", extent.toStringEnd);
json.property("lineno", extent.lineno);
json.property("column", extent.column.oneOriginValue());
}
json.endObject();
json.beginListProperty("immutableFlags");
DumpImmutableScriptFlags(json, script.immutableFlags());
json.endList();
json.beginListProperty("functionFlags");
DumpFunctionFlagsItems(json, script.functionFlags());
json.endList();
json.property("hasPrivateScriptData", script.hasPrivateScriptData());
InputScope scope = script.enclosingScope();
if (scope.isNull()) {
json.nullProperty("enclosingScope");
} else {
json.beginObjectProperty("enclosingScope");
DumpInputScopeFields(json, scope);
json.endObject();
}
if (script.useMemberInitializers()) {
json.property("memberInitializers",
script.getMemberInitializers().serialize());
}
}
void CompilationInput::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json);
out.put("\n");
}
void CompilationInput::dump(js::JSONPrinter& json) const {
json.beginObject();
dumpFields(json);
json.endObject();
}
void CompilationInput::dumpFields(js::JSONPrinter& json) const {
const char* targetStr = nullptr;
switch (target) {
case CompilationTarget::Global:
targetStr = "CompilationTarget::Global";
break;
case CompilationTarget::SelfHosting:
targetStr = "CompilationTarget::SelfHosting";
break;
case CompilationTarget::StandaloneFunction:
targetStr = "CompilationTarget::StandaloneFunction";
break;
case CompilationTarget::StandaloneFunctionInNonSyntacticScope:
targetStr = "CompilationTarget::StandaloneFunctionInNonSyntacticScope";
break;
case CompilationTarget::Eval:
targetStr = "CompilationTarget::Eval";
break;
case CompilationTarget::Module:
targetStr = "CompilationTarget::Module";
break;
case CompilationTarget::Delazification:
targetStr = "CompilationTarget::Delazification";
break;
}
json.property("target", targetStr);
json.beginObjectProperty("options");
DumpOptionsFields(json, options);
json.endObject();
if (lazy_.isNull()) {
json.nullProperty("lazy_");
} else {
json.beginObjectProperty("lazy_");
DumpInputScriptFields(json, lazy_);
json.endObject();
}
if (enclosingScope.isNull()) {
json.nullProperty("enclosingScope");
} else {
json.beginObjectProperty("enclosingScope");
DumpInputScopeFields(json, enclosingScope);
json.endObject();
}
// TODO: Support printing the atomCache and the source fields.
}
void CompilationStencil::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json);
out.put("\n");
}
void CompilationStencil::dump(js::JSONPrinter& json) const {
json.beginObject();
dumpFields(json);
json.endObject();
}
void CompilationStencil::dumpFields(js::JSONPrinter& json) const {
char index[64];
json.beginObjectProperty("scriptData");
for (size_t i = 0; i < scriptData.size(); i++) {
SprintfLiteral(index, "ScriptIndex(%zu)", i);
json.beginObjectProperty(index);
scriptData[i].dumpFields(json, this);
json.endObject();
}
json.endObject();
json.beginObjectProperty("scriptExtra");
for (size_t i = 0; i < scriptExtra.size(); i++) {
SprintfLiteral(index, "ScriptIndex(%zu)", i);
json.beginObjectProperty(index);
scriptExtra[i].dumpFields(json);
json.endObject();
}
json.endObject();
json.beginObjectProperty("scopeData");
MOZ_ASSERT(scopeData.size() == scopeNames.size());
for (size_t i = 0; i < scopeData.size(); i++) {
SprintfLiteral(index, "ScopeIndex(%zu)", i);
json.beginObjectProperty(index);
scopeData[i].dumpFields(json, scopeNames[i], this);
json.endObject();
}
json.endObject();
json.beginObjectProperty("sharedData");
sharedData.dumpFields(json);
json.endObject();
json.beginObjectProperty("regExpData");
for (size_t i = 0; i < regExpData.size(); i++) {
SprintfLiteral(index, "RegExpIndex(%zu)", i);
json.beginObjectProperty(index);
regExpData[i].dumpFields(json, this);
json.endObject();
}
json.endObject();
json.beginObjectProperty("bigIntData");
for (size_t i = 0; i < bigIntData.size(); i++) {
SprintfLiteral(index, "BigIntIndex(%zu)", i);
GenericPrinter& out = json.beginStringProperty(index);
bigIntData[i].dumpCharsNoQuote(out);
json.endStringProperty();
}
json.endObject();
json.beginObjectProperty("objLiteralData");
for (size_t i = 0; i < objLiteralData.size(); i++) {
SprintfLiteral(index, "ObjLiteralIndex(%zu)", i);
json.beginObjectProperty(index);
objLiteralData[i].dumpFields(json, this);
json.endObject();
}
json.endObject();
if (moduleMetadata) {
json.beginObjectProperty("moduleMetadata");
moduleMetadata->dumpFields(json, this);
json.endObject();
}
json.beginObjectProperty("asmJS");
if (asmJS) {
for (auto iter = asmJS->moduleMap.iter(); !iter.done(); iter.next()) {
SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index);
json.formatProperty(index, "asm.js");
}
}
json.endObject();
}
void CompilationStencil::dumpAtom(TaggedParserAtomIndex index) const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
json.beginObject();
DumpTaggedParserAtomIndex(json, index, this);
json.endObject();
}
void ExtensibleCompilationStencil::dump() {
frontend::BorrowingCompilationStencil borrowingStencil(*this);
borrowingStencil.dump();
}
void ExtensibleCompilationStencil::dump(js::JSONPrinter& json) {
frontend::BorrowingCompilationStencil borrowingStencil(*this);
borrowingStencil.dump(json);
}
void ExtensibleCompilationStencil::dumpFields(js::JSONPrinter& json) {
frontend::BorrowingCompilationStencil borrowingStencil(*this);
borrowingStencil.dumpFields(json);
}
void ExtensibleCompilationStencil::dumpAtom(TaggedParserAtomIndex index) {
frontend::BorrowingCompilationStencil borrowingStencil(*this);
borrowingStencil.dumpAtom(index);
}
void InitialStencilAndDelazifications::dump() const {
js::Fprinter out(stderr);
js::JSONPrinter json(out);
dump(json);
out.put("\n");
}
void InitialStencilAndDelazifications::dump(js::JSONPrinter& json) const {
json.beginObject();
dumpFields(json);
json.endObject();
}
void InitialStencilAndDelazifications::dumpFields(js::JSONPrinter& json) const {
if (initial_) {
json.beginObjectProperty("initial_");
initial_->dumpFields(json);
json.endObject();
} else {
json.nullProperty("initial_");
}
for (size_t i = 0; i < delazifications_.length(); i++) {
const CompilationStencil* delazification = delazifications_[i];
char index[64];
SprintfLiteral(index, "ScriptIndex(%zu)", i + 1);
if (delazification) {
json.beginObjectProperty(index);
delazification->dumpFields(json);
json.endObject();
} else {
json.nullProperty(index);
}
}
}
#endif // defined(DEBUG) || defined(JS_JITSPEW)
JSString* CompilationAtomCache::getExistingStringAt(
ParserAtomIndex index) const {
MOZ_RELEASE_ASSERT(atoms_.length() >= index);
return atoms_[index];
}
JSString* CompilationAtomCache::getExistingStringAt(
JSContext* cx, TaggedParserAtomIndex taggedIndex) const {
if (taggedIndex.isParserAtomIndex()) {
auto index = taggedIndex.toParserAtomIndex();
return getExistingStringAt(index);
}
if (taggedIndex.isWellKnownAtomId()) {
auto index = taggedIndex.toWellKnownAtomId();
return GetWellKnownAtom(cx, index);
}
if (taggedIndex.isLength1StaticParserString()) {
auto index = taggedIndex.toLength1StaticParserString();
return cx->staticStrings().getUnit(char16_t(index));
}
if (taggedIndex.isLength2StaticParserString()) {
auto index = taggedIndex.toLength2StaticParserString();
return cx->staticStrings().getLength2FromIndex(size_t(index));
}
MOZ_ASSERT(taggedIndex.isLength3StaticParserString());
auto index = taggedIndex.toLength3StaticParserString();
return cx->staticStrings().getUint(uint32_t(index));
}
JSString* CompilationAtomCache::getStringAt(ParserAtomIndex index) const {
if (size_t(index) >= atoms_.length()) {
return nullptr;
}
return atoms_[index];
}
JSAtom* CompilationAtomCache::getExistingAtomAt(ParserAtomIndex index) const {
return &getExistingStringAt(index)->asAtom();
}
JSAtom* CompilationAtomCache::getExistingAtomAt(
JSContext* cx, TaggedParserAtomIndex taggedIndex) const {
return &getExistingStringAt(cx, taggedIndex)->asAtom();
}
JSAtom* CompilationAtomCache::getAtomAt(ParserAtomIndex index) const {
if (size_t(index) >= atoms_.length()) {
return nullptr;
}
if (!atoms_[index]) {
return nullptr;
}
return &atoms_[index]->asAtom();
}
bool CompilationAtomCache::hasAtomAt(ParserAtomIndex index) const {
if (size_t(index) >= atoms_.length()) {
return false;
}
return !!atoms_[index];
}
bool CompilationAtomCache::setAtomAt(FrontendContext* fc, ParserAtomIndex index,
JSString* atom) {
if (size_t(index) < atoms_.length()) {
atoms_[index] = atom;
return true;
}
if (!atoms_.resize(size_t(index) + 1)) {
ReportOutOfMemory(fc);
return false;
}
atoms_[index] = atom;
return true;
}
bool CompilationAtomCache::allocate(FrontendContext* fc, size_t length) {
MOZ_ASSERT(length >= atoms_.length());
if (length == atoms_.length()) {
return true;
}
if (!atoms_.resize(length)) {
ReportOutOfMemory(fc);
return false;
}
return true;
}
void CompilationAtomCache::stealBuffer(AtomCacheVector& atoms) {
atoms_ = std::move(atoms);
// Destroy elements, without unreserving.
atoms_.clear();
}
void CompilationAtomCache::releaseBuffer(AtomCacheVector& atoms) {
atoms = std::move(atoms_);
}
bool CompilationState::allocateGCThingsUninitialized(
FrontendContext* fc, ScriptIndex scriptIndex, size_t length,
TaggedScriptThingIndex** cursor) {
MOZ_ASSERT(gcThingData.length() <= UINT32_MAX);
auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length());
if (length > INDEX_LIMIT) {
ReportAllocationOverflow(fc);
return false;
}
uint32_t gcThingsLength = length;
if (!gcThingData.growByUninitialized(length)) {
js::ReportOutOfMemory(fc);
return false;
}
if (gcThingData.length() > UINT32_MAX) {
ReportAllocationOverflow(fc);
return false;
}
ScriptStencil& script = scriptData[scriptIndex];
script.gcThingsOffset = gcThingsOffset;
script.gcThingsLength = gcThingsLength;
*cursor = gcThingData.begin() + gcThingsOffset;
return true;
}
bool CompilationState::appendScriptStencilAndData(FrontendContext* fc) {
if (!scriptData.emplaceBack()) {
js::ReportOutOfMemory(fc);
return false;
}
if (isInitialStencil()) {
if (!scriptExtra.emplaceBack()) {
scriptData.popBack();
MOZ_ASSERT(scriptData.length() == scriptExtra.length());
js::ReportOutOfMemory(fc);
return false;
}
}
return true;
}
bool CompilationState::appendGCThings(
FrontendContext* fc, ScriptIndex scriptIndex,
mozilla::Span<const TaggedScriptThingIndex> things) {
MOZ_ASSERT(gcThingData.length() <= UINT32_MAX);
auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length());
if (things.size() > INDEX_LIMIT) {
ReportAllocationOverflow(fc);
return false;
}
uint32_t gcThingsLength = uint32_t(things.size());
if (!gcThingData.append(things.data(), things.size())) {
js::ReportOutOfMemory(fc);
return false;
}
if (gcThingData.length() > UINT32_MAX) {
ReportAllocationOverflow(fc);
return false;
}
ScriptStencil& script = scriptData[scriptIndex];
script.gcThingsOffset = gcThingsOffset;
script.gcThingsLength = gcThingsLength;
return true;
}
CompilationState::CompilationStatePosition CompilationState::getPosition() {
return CompilationStatePosition{scriptData.length(),
asmJS ? asmJS->moduleMap.count() : 0};
}
void CompilationState::rewind(
const CompilationState::CompilationStatePosition& pos) {
if (asmJS && asmJS->moduleMap.count() != pos.asmJSCount) {
for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) {
asmJS->moduleMap.remove(ScriptIndex(i));
}
MOZ_ASSERT(asmJS->moduleMap.count() == pos.asmJSCount);
}
// scriptExtra is empty for delazification.
if (scriptExtra.length()) {
MOZ_ASSERT(scriptExtra.length() == scriptData.length());
scriptExtra.shrinkTo(pos.scriptDataLength);
}
scriptData.shrinkTo(pos.scriptDataLength);
}
void CompilationState::markGhost(
const CompilationState::CompilationStatePosition& pos) {
for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) {
scriptData[i].setIsGhost();
}
}
ScriptIndex CompilationStencilMerger::getInitialScriptIndexFor(
const CompilationStencil& delazification) const {
auto maybeIndex =
functionKeyToInitialScriptIndex_.get(delazification.functionKey);
MOZ_ASSERT(maybeIndex);
return *maybeIndex;
}
template <typename T>
bool FunctionKeyToScriptIndexMap::init(FrontendContext* fc,
const T& scriptExtra,
size_t scriptExtraSize) {
if (!map_.reserve(scriptExtraSize - 1)) {
ReportOutOfMemory(fc);
return false;
}
for (size_t i = 1; i < scriptExtraSize; i++) {
const auto& extra = scriptExtra[i];
auto key = extra.extent.toFunctionKey();
// There can be multiple ScriptStencilExtra with same extent if
// the function is parsed multiple times because of rewind for
// arrow function, and in that case the last one's index should be used.
// Overwrite with the last one.
//
// Already reserved above, but OOMTest can hit failure mode in
// HashTable::add.
if (!map_.put(key, ScriptIndex(i))) {
ReportOutOfMemory(fc);
return false;
}
}
return true;
}
bool FunctionKeyToScriptIndexMap::init(FrontendContext* fc,
const CompilationStencil* initial) {
return init(fc, initial->scriptExtra, initial->scriptExtra.size());
}
bool FunctionKeyToScriptIndexMap::init(
FrontendContext* fc, const ExtensibleCompilationStencil* initial) {
return init(fc, initial->scriptExtra, initial->scriptExtra.length());
}
mozilla::Maybe<ScriptIndex> FunctionKeyToScriptIndexMap::get(
FunctionKey key) const {
auto p = map_.readonlyThreadsafeLookup(key);
if (!p) {
return mozilla::Nothing();
}
return mozilla::Some(p->value());
}
size_t FunctionKeyToScriptIndexMap::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
return map_.shallowSizeOfExcludingThis(mallocSizeOf);
}
bool CompilationStencilMerger::buildAtomIndexMap(
FrontendContext* fc, const CompilationStencil& delazification,
AtomIndexMap& atomIndexMap) {
uint32_t atomCount = delazification.parserAtomData.size();
if (!atomIndexMap.reserve(atomCount)) {
ReportOutOfMemory(fc);
return false;
}
for (const auto& atom : delazification.parserAtomData) {
auto mappedIndex = initial_->parserAtoms.internExternalParserAtom(fc, atom);
if (!mappedIndex) {
return false;
}
atomIndexMap.infallibleAppend(mappedIndex);
}
return true;
}
bool CompilationStencilMerger::setInitial(
FrontendContext* fc, UniquePtr<ExtensibleCompilationStencil>&& initial) {
MOZ_ASSERT(!initial_);
initial_ = std::move(initial);
return functionKeyToInitialScriptIndex_.init(fc, initial_.get());
}
template <typename GCThingIndexMapFunc, typename AtomIndexMapFunc,
typename ScopeIndexMapFunc>
static void MergeScriptStencil(ScriptStencil& dest, const ScriptStencil& src,
GCThingIndexMapFunc mapGCThingIndex,
AtomIndexMapFunc mapAtomIndex,
ScopeIndexMapFunc mapScopeIndex,
bool isTopLevel) {
// If this function was lazy, all inner functions should have been lazy.
MOZ_ASSERT(!dest.hasSharedData());
// If the inner lazy function is skipped, gcThingsLength is empty.
if (src.gcThingsLength) {
dest.gcThingsOffset = mapGCThingIndex(src.gcThingsOffset);
dest.gcThingsLength = src.gcThingsLength;
}
if (src.functionAtom) {
dest.functionAtom = mapAtomIndex(src.functionAtom);
}
if (!dest.hasLazyFunctionEnclosingScopeIndex() &&
src.hasLazyFunctionEnclosingScopeIndex()) {
// Both enclosing function and this function were lazy, and
// now enclosing function is non-lazy and this function is still lazy.
dest.setLazyFunctionEnclosingScopeIndex(
mapScopeIndex(src.lazyFunctionEnclosingScopeIndex()));
} else if (dest.hasLazyFunctionEnclosingScopeIndex() &&
!src.hasLazyFunctionEnclosingScopeIndex()) {
// The enclosing function was non-lazy and this function was lazy, and
// now this function is non-lazy.
dest.resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge();
} else {
// The enclosing function is still lazy.
MOZ_ASSERT(!dest.hasLazyFunctionEnclosingScopeIndex());
MOZ_ASSERT(!src.hasLazyFunctionEnclosingScopeIndex());
}
#ifdef DEBUG
uint16_t BASESCRIPT = uint16_t(FunctionFlags::Flags::BASESCRIPT);
uint16_t HAS_INFERRED_NAME =
uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME);
uint16_t HAS_GUESSED_ATOM = uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM);
uint16_t acceptableDifferenceForLazy = HAS_INFERRED_NAME | HAS_GUESSED_ATOM;
uint16_t acceptableDifferenceForNonLazy =
BASESCRIPT | HAS_INFERRED_NAME | HAS_GUESSED_ATOM;
MOZ_ASSERT_IF(
isTopLevel,
(dest.functionFlags.toRaw() | acceptableDifferenceForNonLazy) ==
(src.functionFlags.toRaw() | acceptableDifferenceForNonLazy));
// NOTE: Currently we don't delazify inner functions.
MOZ_ASSERT_IF(!isTopLevel,
(dest.functionFlags.toRaw() | acceptableDifferenceForLazy) ==
(src.functionFlags.toRaw() | acceptableDifferenceForLazy));
#endif // DEBUG
dest.functionFlags = src.functionFlags;
// Other flags.
if (src.wasEmittedByEnclosingScript()) {
// NOTE: the top-level function of the delazification have
// src.wasEmittedByEnclosingScript() == false, and that shouldn't
// be copied.
dest.setWasEmittedByEnclosingScript();
}
if (src.allowRelazify()) {
dest.setAllowRelazify();
}
if (src.hasSharedData()) {
dest.setHasSharedData();
}
}
bool CompilationStencilMerger::addDelazification(
FrontendContext* fc, const CompilationStencil& delazification) {
MOZ_ASSERT(initial_);
auto delazifiedFunctionIndex = getInitialScriptIndexFor(delazification);
auto& destFun = initial_->scriptData[delazifiedFunctionIndex];
if (destFun.hasSharedData()) {
// If the function was already non-lazy, it means the following happened:
// A. delazified twice within single collecting delazifications
// 1. this function is lazily parsed
// 2. collecting delazifications is started
// 3. this function is delazified, encoded, and merged
// 4. this function is relazified
// 5. this function is delazified, encoded, and merged
//
// B. delazified twice across decode
// 1. this function is lazily parsed
// 2. collecting delazifications is started
// 3. this function is delazified, encoded, and merged
// 4. collecting delazifications is finished
// 5. decoded
// 6. collecting delazifications is started
// here, this function is non-lazy
// 7. this function is relazified
// 8. this function is delazified, encoded, and merged
//
// A can happen with public API.
//
// B cannot happen with public API, but can happen if incremental
// encoding at step B.6 is explicitly started by internal function.
// See Evaluate and StartCollectingDelazifications in js/src/shell/js.cpp.
return true;
}
// If any failure happens, the initial stencil is left in the broken state.
// Immediately discard it.
auto failureCase = mozilla::MakeScopeExit([&] { initial_.reset(); });
mozilla::Maybe<ScopeIndex> functionEnclosingScope;
if (destFun.hasLazyFunctionEnclosingScopeIndex()) {
// lazyFunctionEnclosingScopeIndex_ can be Nothing if this is
// top-level function.
functionEnclosingScope =
mozilla::Some(destFun.lazyFunctionEnclosingScopeIndex());
}
// A map from ParserAtomIndex in delazification to TaggedParserAtomIndex
// in initial_.
AtomIndexMap atomIndexMap;
if (!buildAtomIndexMap(fc, delazification, atomIndexMap)) {
return false;
}
auto mapAtomIndex = [&](TaggedParserAtomIndex index) {
if (index.isParserAtomIndex()) {
return atomIndexMap[index.toParserAtomIndex()];
}
return index;
};
size_t gcThingOffset = initial_->gcThingData.length();
size_t regExpOffset = initial_->regExpData.length();
size_t bigIntOffset = initial_->bigIntData.length();
size_t objLiteralOffset = initial_->objLiteralData.length();
size_t scopeOffset = initial_->scopeData.length();
// Map delazification's ScriptIndex to initial's ScriptIndex.
//
// The lazy function's gcthings list stores inner function's ScriptIndex.
// The n-th gcthing holds the ScriptIndex of the (n+1)-th script in
// delazification.
//
// NOTE: Currently we don't delazify inner functions.
auto lazyFunctionGCThingsOffset = destFun.gcThingsOffset;
auto mapScriptIndex = [&](ScriptIndex index) {
if (index == CompilationStencil::TopLevelIndex) {
return delazifiedFunctionIndex;
}
return initial_->gcThingData[lazyFunctionGCThingsOffset + index.index - 1]
.toFunction();
};
// Map other delazification's indices into initial's indices.
auto mapGCThingIndex = [&](CompilationGCThingIndex offset) {
return CompilationGCThingIndex(gcThingOffset + offset.index);
};
auto mapRegExpIndex = [&](RegExpIndex index) {
return RegExpIndex(regExpOffset + index.index);
};
auto mapBigIntIndex = [&](BigIntIndex index) {
return BigIntIndex(bigIntOffset + index.index);
};
auto mapObjLiteralIndex = [&](ObjLiteralIndex index) {
return ObjLiteralIndex(objLiteralOffset + index.index);
};
auto mapScopeIndex = [&](ScopeIndex index) {
return ScopeIndex(scopeOffset + index.index);
};
// Append gcThingData, with mapping TaggedScriptThingIndex.
if (!initial_->gcThingData.append(delazification.gcThingData.data(),
delazification.gcThingData.size())) {
js::ReportOutOfMemory(fc);
return false;
}
for (size_t i = gcThingOffset; i < initial_->gcThingData.length(); i++) {
auto& index = initial_->gcThingData[i];
if (index.isNull()) {
// Nothing to do.
} else if (index.isAtom()) {
index = TaggedScriptThingIndex(mapAtomIndex(index.toAtom()));
} else if (index.isBigInt()) {
index = TaggedScriptThingIndex(mapBigIntIndex(index.toBigInt()));
} else if (index.isObjLiteral()) {
index = TaggedScriptThingIndex(mapObjLiteralIndex(index.toObjLiteral()));
} else if (index.isRegExp()) {
index = TaggedScriptThingIndex(mapRegExpIndex(index.toRegExp()));
} else if (index.isScope()) {
index = TaggedScriptThingIndex(mapScopeIndex(index.toScope()));
} else if (index.isFunction()) {
index = TaggedScriptThingIndex(mapScriptIndex(index.toFunction()));
} else {
MOZ_ASSERT(index.isEmptyGlobalScope());
// Nothing to do
}
}
// Append regExpData, with mapping RegExpStencil.atom_.
if (!initial_->regExpData.append(delazification.regExpData.data(),
delazification.regExpData.size())) {
js::ReportOutOfMemory(fc);
return false;
}
for (size_t i = regExpOffset; i < initial_->regExpData.length(); i++) {
auto& data = initial_->regExpData[i];
data.atom_ = mapAtomIndex(data.atom_);
}
// Append bigIntData, with copying BigIntStencil.bigInt_.
if (!initial_->bigIntData.reserve(bigIntOffset +
delazification.bigIntData.size())) {
js::ReportOutOfMemory(fc);
return false;
}
for (const auto& data : delazification.bigIntData) {
initial_->bigIntData.infallibleEmplaceBack();
if (!initial_->bigIntData.back().init(fc, initial_->alloc, data)) {
return false;
}
}
// Append objLiteralData, with copying ObjLiteralStencil.code_, and mapping
// TaggedParserAtomIndex in it.
if (!initial_->objLiteralData.reserve(objLiteralOffset +
delazification.objLiteralData.size())) {
js::ReportOutOfMemory(fc);
return false;
}
for (const auto& data : delazification.objLiteralData) {
size_t length = data.code().size();
auto* code = initial_->alloc.newArrayUninitialized<uint8_t>(length);
if (!code) {
js::ReportOutOfMemory(fc);
return false;
}
memcpy(code, data.code().data(), length);
ObjLiteralModifier modifier(mozilla::Span(code, length));
modifier.mapAtom(mapAtomIndex);
initial_->objLiteralData.infallibleEmplaceBack(
code, length, data.kind(), data.flags(), data.propertyCount());
}
// Append scopeData, with mapping indices in ScopeStencil fields.
// And append scopeNames, with copying the entire data, and mapping
// trailingNames.
if (!initial_->scopeData.reserve(scopeOffset +
delazification.scopeData.size())) {
js::ReportOutOfMemory(fc);
return false;
}
if (!initial_->scopeNames.reserve(scopeOffset +
delazification.scopeNames.size())) {
js::ReportOutOfMemory(fc);
return false;
}
for (size_t i = 0; i < delazification.scopeData.size(); i++) {
const auto& srcData = delazification.scopeData[i];
const auto* srcNames = delazification.scopeNames[i];
mozilla::Maybe<ScriptIndex> functionIndex = mozilla::Nothing();
if (srcData.isFunction()) {
// Inner functions should be in the same order as initial, beginning from
// the delazification's index.
functionIndex = mozilla::Some(mapScriptIndex(srcData.functionIndex()));
}
BaseParserScopeData* destNames = nullptr;
if (srcNames) {
destNames = CopyScopeData(fc, initial_->alloc, srcData.kind(), srcNames);
if (!destNames) {
return false;
}
auto trailingNames =
GetParserScopeDataTrailingNames(srcData.kind(), destNames);
for (auto& name : trailingNames) {
if (name.name()) {
name.updateNameAfterStencilMerge(mapAtomIndex(name.name()));
}
}
}
initial_->scopeData.infallibleEmplaceBack(
srcData.kind(),
srcData.hasEnclosing()
? mozilla::Some(mapScopeIndex(srcData.enclosing()))
: functionEnclosingScope,
srcData.firstFrameSlot(),
srcData.hasEnvironmentShape()
? mozilla::Some(srcData.numEnvironmentSlots())
: mozilla::Nothing(),
functionIndex, srcData.isArrow());
initial_->scopeNames.infallibleEmplaceBack(destNames);
}
// Add delazified function's shared data.
//
// NOTE: Currently we don't delazify inner functions.
if (!initial_->sharedData.addExtraWithoutShare(
fc, delazifiedFunctionIndex,
delazification.sharedData.get(CompilationStencil::TopLevelIndex))) {
return false;
}
// Update scriptData, with mapping indices in ScriptStencil fields.
for (uint32_t i = 0; i < delazification.scriptData.size(); i++) {
auto destIndex = mapScriptIndex(ScriptIndex(i));
MergeScriptStencil(initial_->scriptData[destIndex],
delazification.scriptData[i], mapGCThingIndex,
mapAtomIndex, mapScopeIndex,
i == CompilationStencil::TopLevelIndex);
}
// WARNING: moduleMetadata and asmJS fields are known at script/module
// top-level parsing, any mutation made in this function should be reflected
// to ExtensibleCompilationStencil::steal and CompilationStencil::clone.
// Function shouldn't be a module.
MOZ_ASSERT(!delazification.moduleMetadata);
// asm.js shouldn't appear inside delazification, given asm.js forces
// full-parse.
MOZ_ASSERT(!delazification.asmJS);
failureCase.release();
return true;
}
bool CompilationStencilMerger::maybeAddDelazification(
FrontendContext* fc, const CompilationStencil& delazification) {
auto delazifiedFunctionIndex = getInitialScriptIndexFor(delazification);
auto& destFun = initial_->scriptData[delazifiedFunctionIndex];
if (!destFun.hasLazyFunctionEnclosingScopeIndex()) {
// The enclosing function is still lazy, and this inner function cannot
// be added.
return true;
}
return addDelazification(fc, delazification);
}
void CompilationStencil::AddRef() { refCount_++; }
void CompilationStencil::Release() {
MOZ_RELEASE_ASSERT(refCount_ > 0);
if (--refCount_ == 0) {
js_delete(this);
}
}
void JS::StencilAddRef(JS::Stencil* stencil) { stencil->AddRef(); }
void JS::StencilRelease(JS::Stencil* stencil) { stencil->Release(); }
JS_PUBLIC_API JSScript* JS::InstantiateGlobalStencil(
JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil,
JS::InstantiationStorage* storage) {
MOZ_ASSERT_IF(storage, storage->isValid());
CompileOptions compileOptions(cx);
options.copyTo(compileOptions);
Rooted<CompilationInput> input(cx, CompilationInput(compileOptions));
Rooted<CompilationGCOutput> gcOutput(cx);
if (storage) {
gcOutput.get().steal(std::move(*storage->gcOutput_));
}
if (!InstantiateStencils(cx, input.get(), *stencil, gcOutput.get())) {
return nullptr;
}
return gcOutput.get().script;
}
JS_PUBLIC_API bool JS::StencilIsBorrowed(JS::Stencil* stencil) {
return stencil->getInitial()->storageType ==
CompilationStencil::StorageType::Borrowed;
}
JS_PUBLIC_API JSObject* JS::InstantiateModuleStencil(
JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil,
JS::InstantiationStorage* storage) {
MOZ_ASSERT_IF(storage, storage->isValid());
CompileOptions compileOptions(cx);
options.copyTo(compileOptions);
compileOptions.setModule();
Rooted<CompilationInput> input(cx, CompilationInput(compileOptions));
Rooted<CompilationGCOutput> gcOutput(cx);
if (storage) {
gcOutput.get().steal(std::move(*storage->gcOutput_));
}
if (!InstantiateStencils(cx, input.get(), *stencil, gcOutput.get())) {
return nullptr;
}
return gcOutput.get().module;
}
JS_PUBLIC_API size_t JS::SizeOfStencil(Stencil* stencil,
mozilla::MallocSizeOf mallocSizeOf) {
return stencil->sizeOfIncludingThis(mallocSizeOf);
}
JS::InstantiationStorage::~InstantiationStorage() {
if (gcOutput_) {
js_delete(gcOutput_);
gcOutput_ = nullptr;
}
}