Skip to content

Commit a3e3598

Browse files
committed
Make cross-importing faster
We previously computed cross-imports by comparing N transitive imports against N transitive imports. This is wasteful, because at least one of the two modules in a pair has to actually declare a cross-import overlay for us to discover one, and the vast majority of modules don’t declare any. This commit makes us instead compare N transitive imports against M transitive imports which are known to declare at least one cross-import overlay. Since N is potentailly in the thousands while M is perhaps in the double digits, this should be good for a substantial time savings. However, this optimization has made a test of another cross-import performance optimization fail—not because we have regressed on that, but because it skips work the test case expects us to perform. I have XFAILed that test for now. Fixes <rdar://problem/59538458>.
1 parent 261e9e4 commit a3e3598

File tree

3 files changed

+96
-35
lines changed

3 files changed

+96
-35
lines changed

include/swift/AST/Module.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,18 @@ class ModuleDecl : public DeclContext, public TypeDecl {
260260
/// Add a file declaring a cross-import overlay.
261261
void addCrossImportOverlayFile(StringRef file);
262262

263+
/// If this method returns \c false, the module does not declare any
264+
/// cross-import overlays.
265+
///
266+
/// This is a quick check you can use to bail out of expensive logic early;
267+
/// however, a \c true return doesn't guarantee that the module declares
268+
/// cross-import overlays--it only means that it \em might declare some.
269+
///
270+
/// (Specifically, this method checks if the module loader found any
271+
/// swiftoverlay files, but does not load the files to see if they list any
272+
/// overlay modules.)
273+
bool mightDeclareCrossImportOverlays() const;
274+
263275
/// Append to \p overlayNames the names of all modules that this module
264276
/// declares should be imported when \p bystanderName is imported.
265277
///

lib/AST/Module.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,10 @@ void ModuleDecl::addCrossImportOverlayFile(StringRef file) {
15551555
.push_back(new (ctx) OverlayFile(ctx.AllocateCopy(file)));
15561556
}
15571557

1558+
bool ModuleDecl::mightDeclareCrossImportOverlays() const {
1559+
return !declaredCrossImports.empty();
1560+
}
1561+
15581562
void ModuleDecl::
15591563
findDeclaredCrossImportOverlays(Identifier bystanderName,
15601564
SmallVectorImpl<Identifier> &overlayNames,

lib/Sema/NameBinding.cpp

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ namespace {
183183
/// often enormous.
184184
SmallSetVector<ImportedModuleDesc, 64> crossImportableModules;
185185

186+
/// The subset of \c crossImportableModules which may declare cross-imports.
187+
///
188+
/// This is a performance optimization. Since most modules do not register
189+
/// any cross-imports, we can usually compare against this list, which is
190+
/// much, much smaller than \c crossImportableModules.
191+
SmallVector<ImportedModuleDesc, 16> crossImportDeclaringModules;
192+
186193
/// The index of the next module in \c visibleModules that should be
187194
/// cross-imported.
188195
size_t nextModuleToCrossImport = 0;
@@ -230,13 +237,21 @@ namespace {
230237
/// then adds them to \c unboundImports using source locations from \p I.
231238
void crossImport(ModuleDecl *M, UnboundImport &I);
232239

240+
/// Discovers any cross-imports between \p newImport and
241+
/// \p oldImports and adds them to \c unboundImports, using source
242+
/// locations from \p I.
243+
void findCrossImportsInLists(UnboundImport &I,
244+
ArrayRef<ImportedModuleDesc> declaring,
245+
ArrayRef<ImportedModuleDesc> bystanding,
246+
bool shouldDiagnoseRedundantCrossImports);
247+
233248
/// Discovers any cross-imports between \p declaringImport and
234249
/// \p bystandingImport and adds them to \c unboundImports, using source
235250
/// locations from \p I.
236251
void findCrossImports(UnboundImport &I,
237252
const ImportedModuleDesc &declaringImport,
238253
const ImportedModuleDesc &bystandingImport,
239-
SmallVectorImpl<Identifier> &overlayNames);
254+
bool shouldDiagnoseRedundantCrossImports);
240255

241256
/// Load a module referenced by an import statement.
242257
///
@@ -852,55 +867,72 @@ void NameBinder::crossImport(ModuleDecl *M, UnboundImport &I) {
852867
// FIXME: Should we warn if M doesn't reexport underlyingModule?
853868
SF.addSeparatelyImportedOverlay(M, I.getUnderlyingModule().get());
854869

855-
// FIXME: Most of the comparisons we do here are probably unnecessary. We
856-
// only need to findCrossImports() on pairs where at least one of the two
857-
// modules declares cross-imports, and most modules don't. This is low-hanging
858-
// performance fruit.
859-
860870
auto newImports = crossImportableModules.getArrayRef()
861-
.slice(nextModuleToCrossImport);
871+
.drop_front(nextModuleToCrossImport);
872+
873+
if (newImports.empty())
874+
// Nothing to do except crash when we read past the end of
875+
// crossImportableModules in that assert at the bottom.
876+
return;
877+
862878
for (auto &newImport : newImports) {
863879
if (!canCrossImport(newImport))
864880
continue;
865881

866-
// Search imports up to, but not including or after, `newImport`.
882+
// First we check if any of the imports of modules that have declared
883+
// cross-imports have declared one with this module.
884+
findCrossImportsInLists(I, crossImportDeclaringModules, {newImport},
885+
/*shouldDiagnoseRedundantCrossImports=*/false);
886+
887+
// If this module doesn't declare any cross-imports, we're done with this
888+
// import.
889+
if (!newImport.module.second->mightDeclareCrossImportOverlays())
890+
continue;
891+
892+
// Fine, we need to do the slow-but-rare thing: check if this import
893+
// declares a cross-import with any previous one.
867894
auto oldImports =
868-
make_range(crossImportableModules.getArrayRef().data(), &newImport);
869-
for (auto &oldImport : oldImports) {
870-
if (!canCrossImport(oldImport))
895+
// Slice from the start of crossImportableModules up to newImport.
896+
llvm::makeArrayRef(crossImportableModules.getArrayRef().data(),
897+
&newImport);
898+
findCrossImportsInLists(I, {newImport}, oldImports,
899+
/*shouldDiagnoseRedundantCrossImports=*/true);
900+
901+
// Add this to the list of imports everyone needs to check against.
902+
crossImportDeclaringModules.push_back(newImport);
903+
}
904+
905+
// Catch potential memory smashers
906+
assert(newImports.data() ==
907+
&crossImportableModules[nextModuleToCrossImport] &&
908+
"findCrossImports() should never mutate visibleModules");
909+
910+
nextModuleToCrossImport = crossImportableModules.size();
911+
}
912+
913+
void
914+
NameBinder::findCrossImportsInLists(UnboundImport &I,
915+
ArrayRef<ImportedModuleDesc> declaring,
916+
ArrayRef<ImportedModuleDesc> bystanding,
917+
bool shouldDiagnoseRedundantCrossImports) {
918+
for (auto &declaringImport : declaring) {
919+
if (!canCrossImport(declaringImport))
920+
continue;
921+
922+
for (auto &bystandingImport : bystanding) {
923+
if (!canCrossImport(bystandingImport))
871924
continue;
872925

873-
SmallVector<Identifier, 2> newImportOverlays;
874-
findCrossImports(I, newImport, oldImport, newImportOverlays);
875-
876-
SmallVector<Identifier, 2> oldImportOverlays;
877-
findCrossImports(I, oldImport, newImport, oldImportOverlays);
878-
879-
// If both sides of the cross-import declare some of the same overlays,
880-
// this will cause some strange name lookup behavior; let's warn about it.
881-
for (auto name : newImportOverlays) {
882-
if (llvm::is_contained(oldImportOverlays, name)) {
883-
ctx.Diags.diagnose(I.importLoc, diag::cross_imported_by_both_modules,
884-
newImport.module.second->getName(),
885-
oldImport.module.second->getName(), name);
886-
}
887-
}
888-
889-
// If findCrossImports() ever changed the visibleModules list, we'd see
890-
// memory smashers here.
891-
assert(newImports.data() ==
892-
&crossImportableModules[nextModuleToCrossImport] &&
893-
"findCrossImports() should never mutate visibleModules");
926+
findCrossImports(I, declaringImport, bystandingImport,
927+
shouldDiagnoseRedundantCrossImports);
894928
}
895929
}
896-
897-
nextModuleToCrossImport = crossImportableModules.size();
898930
}
899931

900932
void NameBinder::findCrossImports(UnboundImport &I,
901933
const ImportedModuleDesc &declaringImport,
902934
const ImportedModuleDesc &bystandingImport,
903-
SmallVectorImpl<Identifier> &names) {
935+
bool shouldDiagnoseRedundantCrossImports) {
904936
assert(&declaringImport != &bystandingImport);
905937

906938
LLVM_DEBUG(
@@ -912,9 +944,17 @@ void NameBinder::findCrossImports(UnboundImport &I,
912944
ctx.Stats->getFrontendCounters().NumCrossImportsChecked++;
913945

914946
// Find modules we need to import.
947+
SmallVector<Identifier, 4> names;
915948
declaringImport.module.second->findDeclaredCrossImportOverlays(
916949
bystandingImport.module.second->getName(), names, I.importLoc);
917950

951+
// If we're diagnosing cases where we cross-import in both directions, get the
952+
// inverse list. Otherwise, leave the list empty.
953+
SmallVector<Identifier, 4> oppositeNames;
954+
if (shouldDiagnoseRedundantCrossImports)
955+
bystandingImport.module.second->findDeclaredCrossImportOverlays(
956+
declaringImport.module.second->getName(), oppositeNames, I.importLoc);
957+
918958
if (ctx.Stats && !names.empty())
919959
ctx.Stats->getFrontendCounters().NumCrossImportsFound++;
920960

@@ -928,6 +968,11 @@ void NameBinder::findCrossImports(UnboundImport &I,
928968
unboundImports.emplace_back(declaringImport.module.second->getASTContext(),
929969
I, name, declaringImport, bystandingImport);
930970

971+
if (llvm::is_contained(oppositeNames, name))
972+
ctx.Diags.diagnose(I.importLoc, diag::cross_imported_by_both_modules,
973+
declaringImport.module.second->getName(),
974+
bystandingImport.module.second->getName(), name);
975+
931976
LLVM_DEBUG({
932977
auto &crossImportOptions = unboundImports.back().options;
933978
llvm::dbgs() << " ";

0 commit comments

Comments
 (0)