Skip to content

Commit 630d3be

Browse files
committed
[JSC] Populate Module resolution cache only when it is star-resolution / indirect-resolution
https://bugs.webkit.org/show_bug.cgi?id=313619 rdar://175826413 Reviewed by Yijia Huang. m_exportEntries is already offering a hashmap for Local and Namespace resolutions. The cache is only useful when we have star-resolution / indirect-resolution since they are costly. We can stop populating a cache for Local / Namespace and we can cache only when star-resolution / indirect-resolution resolutions are found. * Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp: (JSC::AbstractModuleRecord::resolveImport): (JSC::AbstractModuleRecord::resolveExportImpl): (JSC::AbstractModuleRecord::resolveExport): (JSC::AbstractModuleRecord::getModuleNamespace): Canonical link: https://commits.webkit.org/312416@main
1 parent e3d5d98 commit 630d3be

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

‎Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp‎

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ auto AbstractModuleRecord::resolveImport(JSGlobalObject* globalObject, const Ide
224224

225225
AbstractModuleRecord* importedModule = hostResolveImportedModule(globalObject, importEntry.moduleRequest);
226226
RETURN_IF_EXCEPTION(scope, Resolution::error());
227-
return importedModule->resolveExport(globalObject, importEntry.importName);
227+
228+
RELEASE_AND_RETURN(scope, importedModule->resolveExport(globalObject, importEntry.importName));
228229
}
229230

230231
struct AbstractModuleRecord::ResolveQuery {
@@ -544,7 +545,7 @@ auto AbstractModuleRecord::resolveExportImpl(JSGlobalObject* globalObject, const
544545
// To summarize the observations.
545546
//
546547
// 1. The starting point is always cacheable.
547-
// 2. A module that has resolved a local binding is always cacheable.
548+
// 2. A module that has resolved a local binding is always cacheable. But since they are in exportEntries, we do not need a cache.
548549
// 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable.
549550
// 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result.
550551
// 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching.
@@ -650,19 +651,17 @@ auto AbstractModuleRecord::resolveExportImpl(JSGlobalObject* globalObject, const
650651
if (!moduleRecord->starExportEntries().isEmpty())
651652
foundStarLinks = true;
652653

653-
// 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result.
654-
if (!foundStarLinks) {
655-
if (std::optional<Resolution> cachedResolution = moduleRecord->tryGetCachedResolution(query.exportName.get())) {
656-
if (!mergeToCurrentTop(*cachedResolution))
657-
return Resolution::ambiguous();
658-
continue;
659-
}
660-
}
661-
662654
const std::optional<ExportEntry> optionalExportEntry = moduleRecord->tryGetExportEntry(query.exportName.get());
663655
if (!optionalExportEntry) {
664-
// If there is no matched exported binding in the current module,
665-
// we need to look into the stars.
656+
// If there is no matched exported binding in the current module, we need to look
657+
// into the stars. We don't probe m_resolutionCache here: the only writer that can
658+
// populate (moduleRecord, exportName) while exportEntries has no match for exportName
659+
// is the root-cache write (rule #1), which only fires when star traversal produced
660+
// Resolved - which in turn requires moduleRecord to have non-empty starExportEntries.
661+
// starExportEntries is immutable after parse, so by the time we reach this point
662+
// foundStarLinks is already true (set above) whenever a cached entry could exist -
663+
// making any probe here dead. The top-level resolveExport fast path still benefits
664+
// from the rule #1 cache write.
666665
bool success = resolveNonLocal(task.query);
667666
EXCEPTION_ASSERT(!scope.exception() || !success);
668667
if (!success)
@@ -675,14 +674,21 @@ auto AbstractModuleRecord::resolveExportImpl(JSGlobalObject* globalObject, const
675674
case ExportEntry::Type::Local: {
676675
ASSERT(!exportEntry.localName.isNull());
677676
Resolution resolution { Resolution::Type::Resolved, moduleRecord, exportEntry.localName };
678-
// 2. A module that has resolved a local binding is always cacheable.
679-
cacheResolutionForQuery(query, resolution);
680677
if (!mergeToCurrentTop(resolution))
681678
return Resolution::ambiguous();
682679
continue;
683680
}
684681

685682
case ExportEntry::Type::Indirect: {
683+
// 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result.
684+
if (!foundStarLinks) {
685+
if (std::optional<Resolution> cachedResolution = moduleRecord->tryGetCachedResolution(query.exportName.get())) {
686+
if (!mergeToCurrentTop(*cachedResolution))
687+
return Resolution::ambiguous();
688+
continue;
689+
}
690+
}
691+
686692
AbstractModuleRecord* importedModuleRecord = moduleRecord->hostResolveImportedModule(globalObject, exportEntry.moduleName);
687693
RETURN_IF_EXCEPTION(scope, Resolution::error());
688694

@@ -698,10 +704,7 @@ auto AbstractModuleRecord::resolveExportImpl(JSGlobalObject* globalObject, const
698704
case ExportEntry::Type::Namespace: {
699705
AbstractModuleRecord* importedModuleRecord = moduleRecord->hostResolveImportedModule(globalObject, exportEntry.moduleName);
700706
RETURN_IF_EXCEPTION(scope, Resolution::error());
701-
702707
Resolution resolution { Resolution::Type::Resolved, importedModuleRecord, vm.propertyNames->starNamespacePrivateName };
703-
// 2. A module that has resolved a module namespace binding is always cacheable.
704-
cacheResolutionForQuery(query, resolution);
705708
if (!mergeToCurrentTop(resolution))
706709
return Resolution::ambiguous();
707710
continue;
@@ -758,10 +761,35 @@ auto AbstractModuleRecord::resolveExportImpl(JSGlobalObject* globalObject, const
758761

759762
auto AbstractModuleRecord::resolveExport(JSGlobalObject* globalObject, const Identifier& exportName) -> Resolution
760763
{
761-
// Look up the cached resolution first before entering the resolving loop, since the loop setup takes some cost.
762-
if (std::optional<Resolution> cachedResolution = tryGetCachedResolution(exportName.impl()))
763-
return *cachedResolution;
764-
return resolveExportImpl(globalObject, ResolveQuery(this, exportName.impl()));
764+
VM& vm = globalObject->vm();
765+
auto scope = DECLARE_THROW_SCOPE(vm);
766+
767+
// Local / Namespace exports are trivially derivable from m_exportEntries.
768+
// m_resolutionCache only holds results that actually amortise costly traversals, Indirect resolutions and star-resolved results.
769+
if (const auto optionalExportEntry = tryGetExportEntry(exportName.impl())) {
770+
const ExportEntry& entry = *optionalExportEntry;
771+
switch (entry.type) {
772+
case ExportEntry::Type::Local:
773+
ASSERT(!entry.localName.isNull());
774+
return Resolution { Resolution::Type::Resolved, this, entry.localName };
775+
case ExportEntry::Type::Namespace: {
776+
AbstractModuleRecord* importedModuleRecord = hostResolveImportedModule(globalObject, entry.moduleName);
777+
RETURN_IF_EXCEPTION(scope, Resolution::error());
778+
return Resolution { Resolution::Type::Resolved, importedModuleRecord, vm.propertyNames->starNamespacePrivateName };
779+
}
780+
case ExportEntry::Type::Indirect:
781+
if (std::optional<Resolution> cachedResolution = tryGetCachedResolution(exportName.impl()))
782+
return *cachedResolution;
783+
break;
784+
}
785+
} else if (!starExportEntries().isEmpty()) {
786+
// When there is no matching export entry, cache can exist only when we found star-resolved results.
787+
// Thus, if there is no star export entries, cache never exists.
788+
if (std::optional<Resolution> cachedResolution = tryGetCachedResolution(exportName.impl()))
789+
return *cachedResolution;
790+
}
791+
792+
RELEASE_AND_RETURN(scope, resolveExportImpl(globalObject, ResolveQuery(this, exportName.impl())));
765793
}
766794

767795
JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject* globalObject)
@@ -788,6 +816,7 @@ JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject
788816
Resolutions uniqueBindings;
789817
IdentifierSet rootShadowedNames;
790818
IdentifierSet slowPathNames;
819+
Vector<std::pair<Identifier, Resolution>> resolutions;
791820

792821
UncheckedKeyHashSet<AbstractModuleRecord*> exportStarSet;
793822
Vector<AbstractModuleRecord*, 8> pendingModules;
@@ -801,15 +830,21 @@ JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject
801830

802831
for (const auto& pair : moduleRecord->exportEntries()) {
803832
const ExportEntry& exportEntry = pair.value;
804-
if (!isRoot && vm.propertyNames->defaultKeyword == exportEntry.exportName)
805-
continue;
806833
SUPPRESS_UNCOUNTED_LOCAL auto* exportName = exportEntry.exportName.impl();
807834
// ResolveExport returns at root's own Local/Indirect/Namespace entry before
808835
// consulting star exports, so star-reachable bindings cannot affect those names.
809-
if (!isRoot && rootShadowedNames.contains(exportName))
810-
continue;
811-
if (slowPathNames.contains(exportName))
812-
continue;
836+
if (isRoot)
837+
rootShadowedNames.add(exportName);
838+
else {
839+
if (vm.propertyNames->defaultKeyword == exportEntry.exportName)
840+
continue;
841+
// Both sets do not include exportName during the root iteration (root is always the first
842+
// module popped from pendingModules), so we only need to probe them for non-root.
843+
if (rootShadowedNames.contains(exportName))
844+
continue;
845+
if (slowPathNames.contains(exportName))
846+
continue;
847+
}
813848

814849
Resolution candidate;
815850
switch (exportEntry.type) {
@@ -823,17 +858,17 @@ JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject
823858
break;
824859
}
825860
case ExportEntry::Type::Indirect:
826-
if (isRoot)
827-
rootShadowedNames.add(exportName);
828-
else
861+
if (!isRoot)
829862
uniqueBindings.remove(exportName);
830863
slowPathNames.add(exportName);
831864
continue;
832865
}
833866

834867
if (isRoot) {
835-
rootShadowedNames.add(exportName);
836-
uniqueBindings.add(exportName, candidate);
868+
// Root's own Local / Namespace are served by resolveExport's m_exportEntries
869+
// fast path, so they never need to sit in uniqueBindings or the cache. Emit
870+
// them directly so the cache-write loop below can skip the owned-name probe.
871+
resolutions.append({ Identifier::fromUid(vm, exportName), candidate });
837872
continue;
838873
}
839874

@@ -851,9 +886,10 @@ JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(JSGlobalObject
851886
}
852887
}
853888

854-
Vector<std::pair<Identifier, Resolution>> resolutions;
855-
resolutions.reserveInitialCapacity(uniqueBindings.size() + slowPathNames.size());
889+
resolutions.reserveCapacity(resolutions.size() + uniqueBindings.size() + slowPathNames.size());
856890
for (auto& pair : uniqueBindings) {
891+
// Every entry here arrived via a star-export edge (root's own names were emitted
892+
// during the walk), so the cache is always useful.
857893
cacheResolution(pair.key.get(), pair.value);
858894
resolutions.append({ Identifier::fromUid(vm, pair.key.get()), pair.value });
859895
}

0 commit comments

Comments
 (0)