Skip to content

Commit 014bb1c

Browse files
committed
Don’t count submodules when cross-importing
It turns out that, if you pull in any nontrivial module, there are thousands of submodules and none of them could possibly have a cross-import overlay. Avoid evaluating them.
1 parent 1228619 commit 014bb1c

File tree

3 files changed

+62
-8
lines changed

3 files changed

+62
-8
lines changed

include/swift/Basic/Statistics.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ FRONTEND_STATISTIC(Parse, NumIterableDeclContextParsed)
187187
/// Number of conformances that were deserialized by this frontend job.
188188
FRONTEND_STATISTIC(Sema, NumConformancesDeserialized)
189189

190+
/// Number of pairs of modules we've checked for cross-imports.
191+
FRONTEND_STATISTIC(Sema, NumCrossImportsChecked)
192+
193+
/// Number of pairs of modules we've actually found cross-imports for.
194+
FRONTEND_STATISTIC(Sema, NumCrossImportsFound)
195+
190196
/// Number of constraint-solving scopes created in the typechecker, while
191197
/// solving expression type constraints. A rough proxy for "how much work the
192198
/// expression typechecker did".

lib/Sema/NameBinding.cpp

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,16 @@ namespace {
146146
/// Imports which still need their scoped imports validated.
147147
SmallVector<BoundImport, 16> unvalidatedImports;
148148

149-
/// All imported modules, including by re-exports.
150-
SmallSetVector<ImportedModuleDesc, 16> visibleModules;
149+
// crossImportableModules is usually relatively small (~hundreds max) and
150+
// keeping it in order is convenient, so we use a SmallSetVector for it.
151+
// visibleModules is much larger and we don't care about its order, so we
152+
// use a DenseSet instead.
153+
154+
/// All imported modules, including by re-exports, and including submodules.
155+
llvm::DenseSet<ImportedModuleDesc> visibleModules;
156+
157+
/// visibleModules but without the submoduless.
158+
SmallSetVector<ImportedModuleDesc, 64> crossImportableModules;
151159

152160
/// The index of the next module in \c visibleModules that should be
153161
/// cross-imported.
@@ -815,14 +823,20 @@ void NameBinder::crossImport(ModuleDecl *M, UnboundImport &I) {
815823
// FIXME: Should we warn if M doesn't reexport underlyingModule?
816824
SF.addSeparatelyImportedOverlay(M, I.underlyingModule.get());
817825

818-
auto newImports = visibleModules.getArrayRef().slice(nextModuleToCrossImport);
826+
// FIXME: Most of the comparisons we do here are probably unnecessary. We
827+
// only need to findCrossImports() on pairs where at least one of the two
828+
// modules declares cross-imports, and most modules don't. This is low-hanging
829+
// performance fruit.
830+
831+
auto newImports = crossImportableModules.getArrayRef()
832+
.slice(nextModuleToCrossImport);
819833
for (auto &newImport : newImports) {
820834
if (!canCrossImport(newImport))
821835
continue;
822836

823837
// Search imports up to, but not including or after, `newImport`.
824838
auto oldImports =
825-
make_range(visibleModules.getArrayRef().data(), &newImport);
839+
make_range(crossImportableModules.getArrayRef().data(), &newImport);
826840
for (auto &oldImport : oldImports) {
827841
if (!canCrossImport(oldImport))
828842
continue;
@@ -845,12 +859,13 @@ void NameBinder::crossImport(ModuleDecl *M, UnboundImport &I) {
845859

846860
// If findCrossImports() ever changed the visibleModules list, we'd see
847861
// memory smashers here.
848-
assert(newImports.data() == &visibleModules[nextModuleToCrossImport] &&
862+
assert(newImports.data() ==
863+
&crossImportableModules[nextModuleToCrossImport] &&
849864
"findCrossImports() should never mutate visibleModules");
850865
}
851866
}
852867

853-
nextModuleToCrossImport = visibleModules.size();
868+
nextModuleToCrossImport = crossImportableModules.size();
854869
}
855870

856871
void NameBinder::findCrossImports(UnboundImport &I,
@@ -864,10 +879,16 @@ void NameBinder::findCrossImports(UnboundImport &I,
864879
<< declaringImport.module.second->getName() << "' -> '"
865880
<< bystandingImport.module.second->getName() << "'\n");
866881

882+
if (ctx.Stats)
883+
ctx.Stats->getFrontendCounters().NumCrossImportsChecked++;
884+
867885
// Find modules we need to import.
868886
declaringImport.module.second->findDeclaredCrossImportOverlays(
869887
bystandingImport.module.second->getName(), names, I.importLoc);
870888

889+
if (ctx.Stats && !names.empty())
890+
ctx.Stats->getFrontendCounters().NumCrossImportsFound++;
891+
871892
// Add import statements.
872893
for (auto &name : names) {
873894
// If we are actually compiling part of this overlay, don't try to load the
@@ -890,6 +911,11 @@ void NameBinder::findCrossImports(UnboundImport &I,
890911
}
891912
}
892913

914+
static bool isSubmodule(ModuleDecl* M) {
915+
auto clangMod = M->findUnderlyingClangModule();
916+
return clangMod && clangMod->Parent;
917+
}
918+
893919
void NameBinder::addVisibleModules(ImportedModuleDesc importDesc) {
894920
// FIXME: namelookup::getAllImports() doesn't quite do what we need (mainly
895921
// w.r.t. scoped imports), but it seems like we could extend it to do so, and
@@ -903,7 +929,8 @@ void NameBinder::addVisibleModules(ImportedModuleDesc importDesc) {
903929
// If they are both scoped, and they are *differently* scoped, this import
904930
// cannot possibly expose anything new. Skip it.
905931
if (!importDesc.module.first.empty() && !nextImport.first.empty() &&
906-
importDesc.module.first != nextImport.first)
932+
!ModuleDecl::isSameAccessPath(importDesc.module.first,
933+
nextImport.first))
907934
continue;
908935

909936
// Drop this module into the ImportDesc so we treat it as imported with the
@@ -912,9 +939,14 @@ void NameBinder::addVisibleModules(ImportedModuleDesc importDesc) {
912939

913940
// If we've already imported it, we've also already imported its
914941
// imports.
915-
if (!visibleModules.insert(importDesc))
942+
if (!visibleModules.insert(importDesc).second)
916943
continue;
917944

945+
// We don't cross-import submodules, so we shouldn't add them to the
946+
// visibility set. (However, we do consider their reexports.)
947+
if(!isSubmodule(importDesc.module.second))
948+
crossImportableModules.insert(importDesc);
949+
918950
// Add the module's re-exports to worklist.
919951
nextImport.second->getImportedModules(importsWorklist,
920952
ModuleDecl::ImportFilterKind::Public);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Make sure we don't consider submodules, because there are...a lot of them.
2+
3+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -enable-cross-import-overlays -Xllvm -debug-only=swift-name-binding %s 2>%t.txt
4+
// RUN: %FileCheck %s < %t.txt
5+
// RUN: %FileCheck --check-prefix=NEGATIVE %s < %t.txt
6+
7+
import Darwin
8+
import ctypes
9+
10+
// CHECK-DAG: Discovering cross-imports for 'ctypes' -> 'Darwin'
11+
// CHECK-DAG: Discovering cross-imports for 'Darwin' -> 'ctypes'
12+
13+
// NEGATIVE-NOT: Discovering cross-imports for 'bits' -> '{{.*}}'
14+
// NEGATIVE-NOT: Discovering cross-imports for '{{.*}}' -> 'bits'
15+
// NEGATIVE-NOT: Discovering cross-imports for 'MacTypes' -> '{{.*}}'
16+
// NEGATIVE-NOT: Discovering cross-imports for '{{.*}}' -> 'MacTypes'

0 commit comments

Comments
 (0)