/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// 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);
}
// JSAtom variant is used only on the main thread delazification, // where JSContext is always available.
JSContext* cx = fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
if (!*otherCached) { // TODO-Stencil: // Here, we convert our name into a JSAtom*, and hard-crash on failure // to allocate. This conversion should not be required as we should be // able to iterate up snapshotted scope chains that use parser atoms. // // This will be fixed when the enclosing scopes are snapshotted. // // See bug 1690277.
AutoEnterOOMUnsafeRegion oomUnsafe;
*otherCached = parserAtoms.toJSAtom(cx, fc, other, atomCache); if (!*otherCached) {
oomUnsafe.crash("InputName::isEqualTo");
}
} else {
MOZ_ASSERT(atomCache.getExistingAtomAt(cx, other) == *otherCached);
} return ptr == *otherCached;
},
[&](const NameStencilRef& ref) -> bool { return parserAtoms.isEqualToExternalParserAtomIndex(other, ref.context_,
ref.atomIndex_);
});
}
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."); returnfalse;
});
},
[&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."); returnfalse;
},
[&name](JSAtom* other) -> bool { return name == other; });
});
}
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();
// 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)) { returnfalse;
} if (!cachePrivateFieldsForEval(fc, input, enclosingEnv, effectiveScope,
parserAtoms)) { returnfalse;
}
}
returntrue;
}
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;
}
// 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());
}
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)) { returnfalse;
}
} break;
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT // TODO: Optimize cache population for `using` bindings. (Bug 1899502) case BindingKind::Using: break; #endif case BindingKind::Const: {
InputName binding(scope_ref, bi.name()); if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::Const)) { returnfalse;
} break;
}
case BindingKind::Synthetic: {
InputName binding(scope_ref, bi.name()); if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::Synthetic)) { returnfalse;
} break;
}
case BindingKind::PrivateMethod: {
InputName binding(scope_ref, bi.name()); if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::PrivateMethod)) { returnfalse;
} break;
}
case BindingKind::Import: case BindingKind::FormalParameter: case BindingKind::Var: case BindingKind::NamedLambdaCallee: break;
}
} returntrue;
}); if (!success) { returnfalse;
}
// 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); returnfalse;
}
}
#ifdef DEBUG if (atom.isWellKnownAtomId()) { constauto& info = GetWellKnownAtomInfo(atom.toWellKnownAtomId()); // #constructor is a well-known term, but it is invalid private name.
MOZ_ASSERT(!(info.length > 1 && info.content[0] == '#'));
} elseif (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
// 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) { returnfalse;
}
NameLocation loc = NameLocation::DebugEnvironmentCoordinate(
bi.kind(), hops, slots);
if (!effectiveScopePrivateFieldCache_->put(parserName, loc)) {
ReportOutOfMemory(fc); returnfalse;
}
}
slots++;
} returntrue;
}); if (!success) { returnfalse;
}
}
// 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++;
}
returntrue;
}
#ifdef DEBUG staticbool 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. returntrue;
} 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();
}
}
}
}
#ifdef DEBUG // Catch assertion failures in the NoCache variant before looking at the // cached content.
NameLocation expect =
searchInEnclosingScopeNoCache(fc, input, parserAtoms, name); #endif
// 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();
}
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;
}
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");
}
mozilla::Maybe<ScopeContext::EnclosingLexicalBindingKind>
ScopeContext::lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name) { auto p = enclosingLexicalBindingCache_->lookup(name); if (!p) { return mozilla::Nothing();
}
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());
}
auto gcthings = lazy->gcthings();
size_t length = gcthings.Length(); if (length == 0) { returntrue;
}
// 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>());
}
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];
// 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());
}
// 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;
}
}
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) { returnfalse;
}
data.functionAtom = displayAtom;
}
data.functionFlags = inner.scriptData().functionFlags;
// 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) { returntrue;
}
// 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) { returntrue;
}
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); returnfalse;
}
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) { returnfalse;
}
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*>();
}
if (ngcthings) { if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput,
script.gcthings(stencil),
lazy->gcthingsForInit())) { returnfalse;
}
}
if (scriptExtra.useMemberInitializers()) {
lazy->setMemberInitializers(scriptExtra.memberInitializers());
}
function->initScript(lazy);
returntrue;
}
// 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());
// Determine the new function's proto. This must be done for singleton // functions.
RootedObject proto(cx); if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) { return nullptr;
}
gcOutput.sourceObject = ScriptSourceObject::create(cx, stencil.source.get()); if (!gcOutput.sourceObject) { returnfalse;
}
Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject); if (!ScriptSourceObject::initFromOptions(cx, sourceObject, options)) { returnfalse;
}
returntrue;
}
// Instantiate ModuleObject. Further initialization is done after the associated // BaseScript is instantiated in InstantiateTopLevel. staticbool InstantiateModuleObject(JSContext* cx, FrontendContext* fc,
CompilationAtomCache& atomCache, const CompilationStencil& stencil,
CompilationGCOutput& gcOutput) {
MOZ_ASSERT(stencil.isModule());
gcOutput.module = ModuleObject::create(cx); if (!gcOutput.module) { returnfalse;
}
// 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) { returnfalse;
}
for (auto item :
CompilationStencil::functionScriptStencils(stencil, gcOutput)) { constauto& scriptStencil = item.script; constauto& 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();
// 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;
}
returntrue;
}
// 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. staticbool 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) { returnfalse;
}
gcOutput.scopes[i] = scope;
}
returntrue;
}
// 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. staticbool 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;
}
// Instantiate the Stencil for the top-level script of the compilation. This // includes standalone functions and functions being delazified. staticbool 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()) { returntrue;
}
if (!ModuleObject::createEnvironment(cx, module)) { returnfalse;
}
if (!ModuleObject::Freeze(cx, module)) { returnfalse;
}
}
returntrue;
}
// 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. staticvoid 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. staticvoid 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;
}
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. staticvoid 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;
// 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. staticvoid FunctionsFromExistingLazy(CompilationInput& input,
CompilationGCOutput& gcOutput) {
MOZ_ASSERT(!gcOutput.functions[0]);
/* 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);
// Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions. if (isInitialParse) { if (!InstantiateScriptSourceObject(cx, options, stencil, gcOutput)) { returnfalse;
}
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)) { returnfalse;
}
}
// 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());
if (isInitialParse) {
LinkEnclosingLazyScript(stencil, gcOutput);
}
}
returntrue;
}
// 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. staticbool SelfHostedDummyFunction(JSContext* cx, unsigned argc,
JS::Value* vp) {
MOZ_CRASH("Self-hosting top-level should not use functions directly");
}
// 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);
}
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;
}
Rooted<JSAtom*> funName(cx); if (scriptData[index].hasSelfHostedCanonicalName()) { // SetCanonicalName was used to override the name.
funName = atomCache.getExistingAtomAt(
cx, scriptData[index].selfHostedCanonicalName());
} elseif (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);
}
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)) { returnfalse;
}
// Phase 1: Instantiate JSAtoms. // NOTE: The self-hosted atoms are all "permanent" and the // CompilationAtomCache is already stored on the JSRuntime.
// Get the corresponding ScriptSourceObject to use in current realm.
gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx); if (!gcOutput.get().sourceObject) { returnfalse;
}
// 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) { returnfalse;
}
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());
// 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))) { returnfalse;
}
}
// 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)) { returnfalse;
}
// Phase 6: Update lazy scripts. // NOTE: Self-hosting is always fully parsed so there is nothing to do here.
if (nonLazyScriptCount <= 1) {
MOZ_ASSERT(isSingle()); returntrue;
}
// 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)) { returnfalse;
} if (!asMap()->reserve(nonLazyScriptCount)) {
ReportOutOfMemory(fc); returnfalse;
}
} else { if (!initVector(fc)) { returnfalse;
} if (!asVector()->resize(allScriptCount)) {
ReportOutOfMemory(fc); returnfalse;
}
}
if (isSingle()) { if (!convertFromSingleToMap(fc)) { returnfalse;
}
}
if (isVector()) { // SharedDataContainer::prepareStorageFor allocates space for all scripts.
(*asVector())[index] = data; returntrue;
}
MOZ_ASSERT(isMap()); // SharedDataContainer::prepareStorageFor doesn't allocate space for // delazification, and this can fail. if (!asMap()->putNew(index, data)) {
ReportOutOfMemory(fc); returnfalse;
} returntrue;
}
#ifdef DEBUG void CompilationStencil::assertNoExternalDependency() const { if (ownedBorrowStencil) {
ownedBorrowStencil->assertNoExternalDependency();
MOZ_ASSERT_IF(!bigIntData.empty(), alloc.contains(bigIntData.data())); for (constauto& data : bigIntData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
MOZ_ASSERT_IF(!objLiteralData.empty(), alloc.contains(objLiteralData.data())); for (constauto& data : objLiteralData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
MOZ_ASSERT_IF(!parserAtomData.empty(), alloc.contains(parserAtomData.data())); for (constauto* data : parserAtomData) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
MOZ_ASSERT(!sharedData.isBorrow());
}
void ExtensibleCompilationStencil::assertNoExternalDependency() const { for (constauto& data : bigIntData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
for (constauto& data : objLiteralData) {
MOZ_ASSERT(data.isContainedIn(alloc));
}
for (constauto* data : scopeNames) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
for (constauto* data : parserAtoms.entries()) {
MOZ_ASSERT_IF(data, alloc.contains(data));
}
if (!CopyToVector(fc, scriptData, other.scriptData)) { returnfalse;
}
if (!CopyToVector(fc, scriptExtra, other.scriptExtra)) { returnfalse;
}
if (!CopyToVector(fc, gcThingData, other.gcThingData)) { returnfalse;
}
size_t scopeSize = GetLength(other.scopeData); if (!CopyToVector(fc, scopeData, other.scopeData)) { returnfalse;
} if (!scopeNames.reserve(scopeSize)) {
js::ReportOutOfMemory(fc); returnfalse;
} 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) { returnfalse;
}
scopeNames.infallibleEmplaceBack(data);
} else {
scopeNames.infallibleEmplaceBack(nullptr);
}
}
if (!CopyToVector(fc, regExpData, other.regExpData)) { returnfalse;
}
// If CompilationStencil has external dependency, peform deep copy.
size_t bigIntSize = GetLength(other.bigIntData); if (!bigIntData.resize(bigIntSize)) {
js::ReportOutOfMemory(fc); returnfalse;
} for (size_t i = 0; i < bigIntSize; i++) { if (!bigIntData[i].init(fc, alloc, other.bigIntData[i])) { returnfalse;
}
}
size_t objLiteralSize = GetLength(other.objLiteralData); if (!objLiteralData.reserve(objLiteralSize)) {
js::ReportOutOfMemory(fc); returnfalse;
} for (constauto& data : other.objLiteralData) {
size_t length = data.code().size(); auto* code = alloc.newArrayUninitialized<uint8_t>(length); if (!code) {
js::ReportOutOfMemory(fc); returnfalse;
}
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 (constauto* entry : other.parserAtomsSpan()) { if (!entry) { if (!parserAtoms.addPlaceholder(fc)) { returnfalse;
} continue;
}
auto index = parserAtoms.internExternalParserAtom(fc, entry); if (!index) { returnfalse;
}
}
// We copy the stencil and increment the reference count of each // SharedImmutableScriptData. if (!sharedData.cloneFrom(fc, other.sharedData)) { returnfalse;
}
// 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;
// If CompilationStencil has no external dependency, // steal LifoAlloc and perform shallow copy.
alloc.steal(&other->alloc);
if (!CopySpanToVector(fc, scriptData, other->scriptData)) { returnfalse;
}
if (!CopySpanToVector(fc, scriptExtra, other->scriptExtra)) { returnfalse;
}
if (!CopySpanToVector(fc, gcThingData, other->gcThingData)) { returnfalse;
}
if (!CopySpanToVector(fc, scopeData, other->scopeData)) { returnfalse;
} if (!CopySpanToVector(fc, scopeNames, other->scopeNames)) { returnfalse;
}
if (!CopySpanToVector(fc, regExpData, other->regExpData)) { returnfalse;
}
if (!CopySpanToVector(fc, bigIntData, other->bigIntData)) { returnfalse;
}
if (!CopySpanToVector(fc, objLiteralData, other->objLiteralData)) { returnfalse;
}
// Regardless of whether CompilationStencil has external dependency or not, // ParserAtoms should be interned, to populate internal HashMap. for (constauto* entry : other->parserAtomData) { if (!entry) { if (!parserAtoms.addPlaceholder(fc)) { returnfalse;
} continue;
}
auto index = parserAtoms.internExternalParserAtom(fc, entry); if (!index) { returnfalse;
}
}
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. returntrue;
}
if (!delazifications_.resize(initial_->scriptData.size())) {
ReportOutOfMemory(fc); returnfalse;
}
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);
}
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 (constauto& 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;
}
}
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++) { constauto& 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;
}
// TODO: The preparation can be shared across iterations.
JS::Rooted<CompilationGCOutput> gcOutputForFunc(cx); if (!CompilationStencil::instantiateStencils(
cx, inputForFunc.get(), *delazification, gcOutputForFunc.get())) { returnfalse;
}
}
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);
}
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);
});
}
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;
}
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_
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;
}
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");
}
case ScopeKind::FunctionBodyVar: { constauto* data = static_cast<const VarScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: case ScopeKind::FunctionLexical: { constauto* data = static_cast<const LexicalScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
json.property("constStart", data->slotInfo.constStart);
case ScopeKind::Eval: case ScopeKind::StrictEval: { constauto* data = static_cast<const EvalScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
case ScopeKind::Global: case ScopeKind::NonSyntactic: { constauto* data = static_cast<const GlobalScope::ParserData*>(baseScopeData);
json.property("letStart", data->slotInfo.letStart);
json.property("constStart", data->slotInfo.constStart);
case ScopeKind::WasmFunction: { constauto* data = static_cast<const WasmFunctionScope::ParserData*>(baseScopeData);
json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
if (!trailingNames.empty()) { char index[64];
json.beginObjectProperty("trailingNames"); for (size_t i = 0; i < trailingNames.size(); i++) { constauto& name = trailingNames[i];
SprintfLiteral(index, "%zu", i);
json.beginObjectProperty(index);
json.beginListProperty("functionDecls"); for (constauto& 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;
}
}
}
}
staticvoid 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;
}
}
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));
}
for (size_t i = 1; i < scriptExtraSize; i++) { constauto& 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); returnfalse;
}
}
template <typename GCThingIndexMapFunc, typename AtomIndexMapFunc, typename ScopeIndexMapFunc> staticvoid 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()));
} elseif (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());
}
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();
}
}
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. returntrue;
}
// 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)) { returnfalse;
} auto mapAtomIndex = [&](TaggedParserAtomIndex index) { if (index.isParserAtomIndex()) { return atomIndexMap[index.toParserAtomIndex()];
}
// 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;
}
// 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); returnfalse;
} if (!initial_->scopeNames.reserve(scopeOffset +
delazification.scopeNames.size())) {
js::ReportOutOfMemory(fc); returnfalse;
} for (size_t i = 0; i < delazification.scopeData.size(); i++) { constauto& srcData = delazification.scopeData[i]; constauto* 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) { returnfalse;
} auto trailingNames =
GetParserScopeDataTrailingNames(srcData.kind(), destNames); for (auto& name : trailingNames) { if (name.name()) {
name.updateNameAfterStencilMerge(mapAtomIndex(name.name()));
}
}
}
// 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))) { returnfalse;
}
// 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);
if (!destFun.hasLazyFunctionEnclosingScopeIndex()) { // The enclosing function is still lazy, and this inner function cannot // be added. returntrue;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.200Bemerkung:
(vorverarbeitet am 2026-04-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.