Skip to content

Commit 4fb7abc

Browse files
[ExplicitModule] Fix canImport lookup for swift explicit module build
Previously, canImport lookup is not completely working with explicit module due to two issues: * For clang modules, canImport check still do a full modulemap lookup which is repeated work from scanner. For caching builds, this lookup cannot be performed because all modulemap and search path are dropped after scanning. * For swift module, if the canImport module was never actually imported later, this canImport check will fail during the actual compilation, causing different dependencies in the actual compilation. To fix the problem, first unified the lookup method for clang and swift module, which will only lookup the module dependencies reported by scanner to determine if `canImport` succeed or not. Secondly, add all the successful `canImport` check modules into the dependency of the current module so this information can be used during actual compilation. Note the behavior change here is that if a module is only checked in `canImport` but never imported still needs to be built. Comparing to implicit module build, this can bring in additional clang modules if they are only check inside `canImport` but should not increase work for swift modules (where binary module needs to be on disk anyway) or the most common usecase for `canImport` which is to check the same module before importing. rdar://121082031
1 parent 517d187 commit 4fb7abc

File tree

8 files changed

+226
-23
lines changed

8 files changed

+226
-23
lines changed

include/swift/AST/ASTContext.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ class ASTContext final {
395395
/// Cache of module names that fail the 'canImport' test in this context.
396396
mutable llvm::StringSet<> FailedModuleImportNames;
397397

398+
/// Cache of module names that passed the 'canImport' test. This cannot be
399+
/// mutable since it needs to be queried for dependency discovery.
400+
llvm::StringSet<> SucceededModuleImportNames;
401+
398402
/// Set if a `-module-alias` was passed. Used to store mapping between module aliases and
399403
/// their corresponding real names, and vice versa for a reverse lookup, which is needed to check
400404
/// if the module names appearing in source files are aliases or real names.
@@ -1202,9 +1206,19 @@ class ASTContext final {
12021206
bool canImportModule(ImportPath::Module ModulePath,
12031207
llvm::VersionTuple version = llvm::VersionTuple(),
12041208
bool underlyingVersion = false);
1205-
bool canImportModule(ImportPath::Module ModulePath,
1206-
llvm::VersionTuple version = llvm::VersionTuple(),
1207-
bool underlyingVersion = false) const;
1209+
1210+
/// Check whether the module with a given name can be imported without
1211+
/// importing it. This is a const method that won't remember the outcome so
1212+
/// repeated check of the same module will induce full cost and won't count
1213+
/// as the dependency for current module.
1214+
bool testImportModule(ImportPath::Module ModulePath,
1215+
llvm::VersionTuple version = llvm::VersionTuple(),
1216+
bool underlyingVersion = false) const;
1217+
1218+
/// \returns a set of names from all successfully canImport module checks.
1219+
const llvm::StringSet<> &getSuccessfulCanImportCheckNames() const {
1220+
return SucceededModuleImportNames;
1221+
}
12081222

12091223
/// \returns a module with a given name that was already loaded. If the
12101224
/// module was not loaded, returns nullptr.

lib/AST/ASTContext.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,6 +2318,10 @@ bool ASTContext::canImportModuleImpl(ImportPath::Module ModuleName,
23182318
return false;
23192319

23202320
if (version.empty()) {
2321+
// If this module has already been checked successfully, it is importable.
2322+
if (SucceededModuleImportNames.count(ModuleNameStr))
2323+
return true;
2324+
23212325
// If this module has already been successfully imported, it is importable.
23222326
if (getLoadedModule(ModuleName) != nullptr)
23232327
return true;
@@ -2375,12 +2379,19 @@ bool ASTContext::canImportModuleImpl(ImportPath::Module ModuleName,
23752379
bool ASTContext::canImportModule(ImportPath::Module ModuleName,
23762380
llvm::VersionTuple version,
23772381
bool underlyingVersion) {
2378-
return canImportModuleImpl(ModuleName, version, underlyingVersion, true);
2382+
if (!canImportModuleImpl(ModuleName, version, underlyingVersion, true))
2383+
return false;
2384+
2385+
// If inserted successfully, add that to success list as dependency.
2386+
SmallString<64> FullModuleName;
2387+
ModuleName.getString(FullModuleName);
2388+
SucceededModuleImportNames.insert(FullModuleName.str());
2389+
return true;
23792390
}
23802391

2381-
bool ASTContext::canImportModule(ImportPath::Module ModuleName,
2382-
llvm::VersionTuple version,
2383-
bool underlyingVersion) const {
2392+
bool ASTContext::testImportModule(ImportPath::Module ModuleName,
2393+
llvm::VersionTuple version,
2394+
bool underlyingVersion) const {
23842395
return canImportModuleImpl(ModuleName, version, underlyingVersion, false);
23852396
}
23862397

lib/ClangImporter/ClangImporter.cpp

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,11 +2016,8 @@ bool ClangImporter::canImportModule(ImportPath::Module modulePath,
20162016
ModuleVersionInfo *versionInfo,
20172017
bool isTestableDependencyLookup) {
20182018
// Look up the top-level module to see if it exists.
2019-
auto &clangHeaderSearch = Impl.getClangPreprocessor().getHeaderSearchInfo();
20202019
auto topModule = modulePath.front();
2021-
clang::Module *clangModule = clangHeaderSearch.lookupModule(
2022-
topModule.Item.str(), /*ImportLoc=*/clang::SourceLocation(),
2023-
/*AllowSearch=*/true, /*AllowExtraModuleMapSearch=*/true);
2020+
clang::Module *clangModule = Impl.lookupModule(topModule.Item.str());
20242021
if (!clangModule) {
20252022
return false;
20262023
}
@@ -2045,11 +2042,8 @@ bool ClangImporter::canImportModule(ImportPath::Module modulePath,
20452042
// this.
20462043
if (!clangModule && component.Item.str() == "Private" &&
20472044
(&component) == (&modulePath.getRaw()[1])) {
2048-
clangModule = clangHeaderSearch.lookupModule(
2049-
(topModule.Item.str() + "_Private").str(),
2050-
/*ImportLoc=*/clang::SourceLocation(),
2051-
/*AllowSearch=*/true,
2052-
/*AllowExtraModuleMapSearch=*/true);
2045+
clangModule =
2046+
Impl.lookupModule((topModule.Item.str() + "_Private").str());
20532047
}
20542048
if (!clangModule || !clangModule->isAvailable(lo, ti, r, mh, m)) {
20552049
return false;
@@ -2073,6 +2067,39 @@ bool ClangImporter::canImportModule(ImportPath::Module modulePath,
20732067
return true;
20742068
}
20752069

2070+
clang::Module *
2071+
ClangImporter::Implementation::lookupModule(StringRef moduleName) {
2072+
auto &clangHeaderSearch = getClangPreprocessor().getHeaderSearchInfo();
2073+
if (getClangASTContext().getLangOpts().ImplicitModules)
2074+
return clangHeaderSearch.lookupModule(
2075+
moduleName, /*ImportLoc=*/clang::SourceLocation(),
2076+
/*AllowSearch=*/true, /*AllowExtraModuleMapSearch=*/true);
2077+
2078+
// Explicit module. Try load from modulemap.
2079+
auto &PP = Instance->getPreprocessor();
2080+
auto &MM = PP.getHeaderSearchInfo().getModuleMap();
2081+
auto loadFromMM = [&]() -> clang::Module * {
2082+
auto *II = PP.getIdentifierInfo(moduleName);
2083+
if (auto clangModule = MM.getCachedModuleLoad(*II))
2084+
return *clangModule;
2085+
return nullptr;
2086+
};
2087+
// Check if it is already loaded.
2088+
if (auto *clangModule = loadFromMM())
2089+
return clangModule;
2090+
2091+
// If not, try load it.
2092+
auto &PrebuiltModules = Instance->getHeaderSearchOpts().PrebuiltModuleFiles;
2093+
auto moduleFile = PrebuiltModules.find(moduleName);
2094+
if (moduleFile == PrebuiltModules.end())
2095+
return nullptr;
2096+
2097+
if (!Instance->loadModuleFile(moduleFile->second))
2098+
return nullptr; // error loading, return not found.
2099+
// Lookup again.
2100+
return loadFromMM();
2101+
}
2102+
20762103
ModuleDecl *ClangImporter::Implementation::loadModuleClang(
20772104
SourceLoc importLoc, ImportPath::Module path) {
20782105
auto &clangHeaderSearch = getClangPreprocessor().getHeaderSearchInfo();

lib/ClangImporter/ImporterImpl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
860860
ModuleDecl *loadModuleDWARF(SourceLoc importLoc,
861861
ImportPath::Module path);
862862

863+
/// Lookup a clang module.
864+
clang::Module *lookupModule(StringRef moduleName);
865+
863866
public:
864867
/// Load a module using either method.
865868
ModuleDecl *loadModule(SourceLoc importLoc,

lib/DependencyScan/ModuleDependencyScanner.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,18 @@ ModuleDependencyScanner::getMainModuleDependencyInfo(
428428
for (auto fileUnit : mainModule->getFiles()) {
429429
auto sf = dyn_cast<SourceFile>(fileUnit);
430430
if (!sf)
431-
continue;
431+
continue;
432432

433433
mainDependencies.addModuleImport(*sf, alreadyAddedModules);
434434
}
435+
436+
// Add all the successful canImport checks from the ASTContext as part of
437+
// the dependency since only mainModule can have `canImport` check. This
438+
// needs to happen after visiting all the top-level decls from all
439+
// SourceFiles.
440+
for (auto &Module :
441+
mainModule->getASTContext().getSuccessfulCanImportCheckNames())
442+
mainDependencies.addModuleImport(Module.first(), &alreadyAddedModules);
435443
}
436444

437445
return mainDependencies;

lib/Frontend/Frontend.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,14 +1107,14 @@ bool CompilerInstance::canImportSwiftConcurrency() const {
11071107
ImportPath::Module::Builder builder(
11081108
getASTContext().getIdentifier(SWIFT_CONCURRENCY_NAME));
11091109
auto modulePath = builder.get();
1110-
return getASTContext().canImportModule(modulePath);
1110+
return getASTContext().testImportModule(modulePath);
11111111
}
11121112

11131113
bool CompilerInstance::canImportSwiftConcurrencyShims() const {
11141114
ImportPath::Module::Builder builder(
11151115
getASTContext().getIdentifier(SWIFT_CONCURRENCY_SHIMS_NAME));
11161116
auto modulePath = builder.get();
1117-
return getASTContext().canImportModule(modulePath);
1117+
return getASTContext().testImportModule(modulePath);
11181118
}
11191119

11201120
void CompilerInstance::verifyImplicitStringProcessingImport() {
@@ -1129,7 +1129,7 @@ bool CompilerInstance::canImportSwiftStringProcessing() const {
11291129
ImportPath::Module::Builder builder(
11301130
getASTContext().getIdentifier(SWIFT_STRING_PROCESSING_NAME));
11311131
auto modulePath = builder.get();
1132-
return getASTContext().canImportModule(modulePath);
1132+
return getASTContext().testImportModule(modulePath);
11331133
}
11341134

11351135
void CompilerInstance::verifyImplicitBacktracingImport() {
@@ -1144,15 +1144,16 @@ bool CompilerInstance::canImportSwiftBacktracing() const {
11441144
ImportPath::Module::Builder builder(
11451145
getASTContext().getIdentifier(SWIFT_BACKTRACING_NAME));
11461146
auto modulePath = builder.get();
1147-
return getASTContext().canImportModule(modulePath);
1147+
return getASTContext().testImportModule(modulePath);
11481148
}
11491149

11501150
bool CompilerInstance::canImportCxxShim() const {
11511151
ImportPath::Module::Builder builder(
11521152
getASTContext().getIdentifier(CXX_SHIM_NAME));
11531153
auto modulePath = builder.get();
1154-
return getASTContext().canImportModule(modulePath) &&
1155-
!Invocation.getFrontendOptions().InputsAndOutputs.hasModuleInterfaceOutputPath();
1154+
return getASTContext().testImportModule(modulePath) &&
1155+
!Invocation.getFrontendOptions()
1156+
.InputsAndOutputs.hasModuleInterfaceOutputPath();
11561157
}
11571158

11581159
bool CompilerInstance::supportCaching() const {

test/CAS/can-import.swift

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// rdar://119964830 Temporarily disabling in Linux
2+
// UNSUPPORTED: OS=linux-gnu
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: split-file %s %t
6+
7+
// RUN: %target-swift-frontend -scan-dependencies -module-name Test -module-cache-path %t/clang-module-cache -O \
8+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
9+
// RUN: %t/main.swift -o %t/deps.json -swift-version 5 -cache-compile-job -cas-path %t/cas -I %t/include
10+
11+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps.json clang:A > %t/A.cmd
12+
// RUN: %swift_frontend_plain @%t/A.cmd
13+
14+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps.json clang:B > %t/B.cmd
15+
// RUN: %swift_frontend_plain @%t/B.cmd
16+
17+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps.json clang:SwiftShims > %t/SwiftShims.cmd
18+
// RUN: %swift_frontend_plain @%t/SwiftShims.cmd
19+
20+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps.json Swift > %t/Swift.cmd
21+
// RUN: %swift_frontend_plain @%t/Swift.cmd
22+
23+
// RUN: %S/Inputs/SwiftDepsExtractor.py %t/deps.json Swift moduleCacheKey | tr -d '\n' > %t/Swift.key
24+
// RUN: %S/Inputs/SwiftDepsExtractor.py %t/deps.json clang:SwiftShims moduleCacheKey | tr -d '\n' > %t/Shims.key
25+
// RUN: %S/Inputs/SwiftDepsExtractor.py %t/deps.json clang:A moduleCacheKey | tr -d '\n' > %t/A.key
26+
// RUN: %S/Inputs/SwiftDepsExtractor.py %t/deps.json clang:B moduleCacheKey | tr -d '\n' > %t/B.key
27+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps.json MyApp > %t/MyApp.cmd
28+
29+
// RUN: echo "[{" > %/t/map.json
30+
// RUN: echo "\"moduleName\": \"Swift\"," >> %/t/map.json
31+
// RUN: echo "\"modulePath\": \"Swift.swiftmodule\"," >> %/t/map.json
32+
// RUN: echo -n "\"moduleCacheKey\": " >> %/t/map.json
33+
// RUN: cat %t/Swift.key >> %/t/map.json
34+
// RUN: echo "," >> %/t/map.json
35+
// RUN: echo "\"isFramework\": false" >> %/t/map.json
36+
// RUN: echo "}," >> %/t/map.json
37+
// RUN: echo "{" >> %/t/map.json
38+
// RUN: echo "\"moduleName\": \"SwiftShims\"," >> %/t/map.json
39+
// RUN: echo "\"clangModulePath\": \"SwiftShims.pcm\"," >> %/t/map.json
40+
// RUN: echo -n "\"clangModuleCacheKey\": " >> %/t/map.json
41+
// RUN: cat %t/Shims.key >> %/t/map.json
42+
// RUN: echo "," >> %/t/map.json
43+
// RUN: echo "\"isFramework\": false" >> %/t/map.json
44+
// RUN: echo "}," >> %/t/map.json
45+
// RUN: echo "{" >> %/t/map.json
46+
// RUN: echo "\"moduleName\": \"A\"," >> %/t/map.json
47+
// RUN: echo "\"clangModulePath\": \"A.pcm\"," >> %/t/map.json
48+
// RUN: echo -n "\"clangModuleCacheKey\": " >> %/t/map.json
49+
// RUN: cat %t/A.key >> %/t/map.json
50+
// RUN: echo "," >> %/t/map.json
51+
// RUN: echo "\"isFramework\": false" >> %/t/map.json
52+
// RUN: echo "}," >> %/t/map.json
53+
// RUN: echo "{" >> %/t/map.json
54+
// RUN: echo "\"moduleName\": \"B\"," >> %/t/map.json
55+
// RUN: echo "\"clangModulePath\": \"B.pcm\"," >> %/t/map.json
56+
// RUN: echo -n "\"clangModuleCacheKey\": " >> %/t/map.json
57+
// RUN: cat %t/B.key >> %/t/map.json
58+
// RUN: echo "," >> %/t/map.json
59+
// RUN: echo "\"isFramework\": false" >> %/t/map.json
60+
// RUN: echo "}]" >> %/t/map.json
61+
// RUN: llvm-cas --cas %t/cas --make-blob --data %t/map.json > %t/map.casid
62+
63+
// RUN: %target-swift-frontend \
64+
// RUN: -typecheck -cache-compile-job -cas-path %t/cas \
65+
// RUN: -swift-version 5 -disable-implicit-swift-modules \
66+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
67+
// RUN: -module-name MyApp -explicit-swift-module-map-file @%t/map.casid \
68+
// RUN: %t/main.swift @%t/MyApp.cmd
69+
70+
//--- main.swift
71+
#if canImport(A.Sub)
72+
import A.Sub
73+
#endif
74+
75+
#if canImport(A.Missing)
76+
import A.Missing
77+
#endif
78+
79+
#if canImport(B) // never actually import B
80+
func b() {}
81+
#endif
82+
83+
func useA() {
84+
a()
85+
b()
86+
}
87+
88+
//--- include/module.modulemap
89+
module A {
90+
module Sub {
91+
header "sub.h"
92+
export *
93+
}
94+
}
95+
96+
module B {
97+
header "B.h"
98+
export *
99+
}
100+
101+
//--- include/sub.h
102+
void a(void);
103+
104+
//--- include/B.h
105+
void notused(void);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -scan-dependencies -module-cache-path %t/clang-module-cache %s -module-name Test -o %t/deps.json -I %S/Inputs/CHeaders -I %S/Inputs/Swift -swift-version 4
3+
4+
// RUN: %{python} %S/../CAS/Inputs/SwiftDepsExtractor.py %t/deps.json Test directDependencies | %FileCheck %s
5+
6+
// CHECK-DAG: "clang": "C"
7+
// CHECK-DAG: "swift": "A"
8+
// CHECK-DAG: "swift": "F"
9+
// CHECK-NOT: "swift": "G"
10+
// CHECK-NOT: "swift": "B"
11+
// CHECK-NOT: "Missing"
12+
13+
#if canImport(Missing)
14+
import G
15+
#endif
16+
17+
#if canImport(C)
18+
import A
19+
#else
20+
import B
21+
#endif
22+
23+
#if !canImport(F)
24+
import Missing
25+
#endif
26+
27+
// B is not dependency
28+
#if false && canImport(B)
29+
import Missing
30+
#endif
31+
32+
// B is short circuited
33+
#if canImport(C) || canImport(B)
34+
#endif

0 commit comments

Comments
 (0)