Skip to content

Commit 746de8b

Browse files
Merge pull request #70974 from cachemeifyoucan/eng/PR-121082031
[ExplicitModule] Fix `canImport` lookup for swift explicit module build
2 parents e5742be + 4fb7abc commit 746de8b

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
@@ -1119,14 +1119,14 @@ bool CompilerInstance::canImportSwiftConcurrency() const {
11191119
ImportPath::Module::Builder builder(
11201120
getASTContext().getIdentifier(SWIFT_CONCURRENCY_NAME));
11211121
auto modulePath = builder.get();
1122-
return getASTContext().canImportModule(modulePath);
1122+
return getASTContext().testImportModule(modulePath);
11231123
}
11241124

11251125
bool CompilerInstance::canImportSwiftConcurrencyShims() const {
11261126
ImportPath::Module::Builder builder(
11271127
getASTContext().getIdentifier(SWIFT_CONCURRENCY_SHIMS_NAME));
11281128
auto modulePath = builder.get();
1129-
return getASTContext().canImportModule(modulePath);
1129+
return getASTContext().testImportModule(modulePath);
11301130
}
11311131

11321132
void CompilerInstance::verifyImplicitStringProcessingImport() {
@@ -1141,7 +1141,7 @@ bool CompilerInstance::canImportSwiftStringProcessing() const {
11411141
ImportPath::Module::Builder builder(
11421142
getASTContext().getIdentifier(SWIFT_STRING_PROCESSING_NAME));
11431143
auto modulePath = builder.get();
1144-
return getASTContext().canImportModule(modulePath);
1144+
return getASTContext().testImportModule(modulePath);
11451145
}
11461146

11471147
void CompilerInstance::verifyImplicitBacktracingImport() {
@@ -1156,15 +1156,16 @@ bool CompilerInstance::canImportSwiftBacktracing() const {
11561156
ImportPath::Module::Builder builder(
11571157
getASTContext().getIdentifier(SWIFT_BACKTRACING_NAME));
11581158
auto modulePath = builder.get();
1159-
return getASTContext().canImportModule(modulePath);
1159+
return getASTContext().testImportModule(modulePath);
11601160
}
11611161

11621162
bool CompilerInstance::canImportCxxShim() const {
11631163
ImportPath::Module::Builder builder(
11641164
getASTContext().getIdentifier(CXX_SHIM_NAME));
11651165
auto modulePath = builder.get();
1166-
return getASTContext().canImportModule(modulePath) &&
1167-
!Invocation.getFrontendOptions().InputsAndOutputs.hasModuleInterfaceOutputPath();
1166+
return getASTContext().testImportModule(modulePath) &&
1167+
!Invocation.getFrontendOptions()
1168+
.InputsAndOutputs.hasModuleInterfaceOutputPath();
11681169
}
11691170

11701171
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)