Skip to content

Commit c989823

Browse files
committed
[Dependency Scanning] Specify dependency inputs of Swift module dependencies on the command line
Do this by computing a transitive closure on the computed dependency graph, relying on the fact that it is a DAG. The used algorithm is: ``` for each v ∈ V { T(v) = { v } } for v ∈ V in reverse topological order { for each (v, w) ∈ E { T(v) = T(v) ∪ T(w) } } ```
1 parent c1599fe commit c989823

File tree

7 files changed

+305
-70
lines changed

7 files changed

+305
-70
lines changed

include/swift/AST/ModuleDependencies.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class SwiftInterfaceModuleDependenciesStorage :
150150

151151
/// The Swift frontend invocation arguments to build the Swift module from the
152152
/// interface.
153-
const std::vector<std::string> buildCommandLine;
153+
std::vector<std::string> buildCommandLine;
154154

155155
/// The hash value that will be used for the generated module
156156
const std::string contextHash;
@@ -186,6 +186,10 @@ class SwiftInterfaceModuleDependenciesStorage :
186186
static bool classof(const ModuleDependencyInfoStorageBase *base) {
187187
return base->dependencyKind == ModuleDependencyKind::SwiftInterface;
188188
}
189+
190+
void updateCommandLine(const std::vector<std::string> &newCommandLine) {
191+
buildCommandLine = newCommandLine;
192+
}
189193
};
190194

191195
/// Describes the dependencies of a Swift module
@@ -433,6 +437,11 @@ class ModuleDependencyInfo {
433437
storage->resolvedModuleDependencies.assign(dependencyIDs.begin(), dependencyIDs.end());
434438
}
435439

440+
void updateCommandLine(const std::vector<std::string> &newCommandLine) {
441+
assert(isSwiftInterfaceModule() && "Can only update command line on Swift interface dependency");
442+
cast<SwiftInterfaceModuleDependenciesStorage>(storage.get())->updateCommandLine(newCommandLine);
443+
}
444+
436445
bool isResolved() const {
437446
return storage->resolved;
438447
}
@@ -752,4 +761,17 @@ class ModuleDependenciesCache {
752761

753762
} // namespace swift
754763

764+
namespace std {
765+
template <>
766+
struct hash<swift::ModuleDependencyID> {
767+
using UnderlyingKindType = std::underlying_type<swift::ModuleDependencyKind>::type;
768+
std::size_t operator()(const swift::ModuleDependencyID &id) const {
769+
auto underlyingKindValue = static_cast<UnderlyingKindType>(id.second);
770+
771+
return (hash<string>()(id.first) ^
772+
(hash<UnderlyingKindType>()(underlyingKindValue)));
773+
}
774+
};
775+
} // namespace std
776+
755777
#endif /* SWIFT_AST_MODULE_DEPENDENCIES_H */

include/swift/Frontend/ModuleInterfaceLoader.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ class SearchPathOptions;
129129
class CompilerInvocation;
130130

131131
/// A ModuleLoader that loads explicitly built Swift modules specified via
132-
/// -swift-module-file or modules found in
132+
/// -swift-module-file or modules found in a provided
133+
/// -explicit-swift-module-map-file JSON input.
133134
class ExplicitSwiftModuleLoader: public SerializedModuleLoaderBase {
134135
explicit ExplicitSwiftModuleLoader(ASTContext &ctx, DependencyTracker *tracker,
135136
ModuleLoadingMode loadMode,

lib/DependencyScan/ModuleDependencyCacheSerialization.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -656,19 +656,6 @@ struct llvm::DenseMapInfo<ModuleIdentifierArrayKind> {
656656
}
657657
};
658658

659-
namespace std {
660-
template <>
661-
struct hash<ModuleDependencyID> {
662-
using UnderlyingKindType = std::underlying_type<ModuleDependencyKind>::type;
663-
std::size_t operator()(const ModuleDependencyID &id) const {
664-
auto underlyingKindValue = static_cast<UnderlyingKindType>(id.second);
665-
666-
return (hash<string>()(id.first) ^
667-
(hash<UnderlyingKindType>()(underlyingKindValue)));
668-
}
669-
};
670-
} // namespace std
671-
672659
namespace swift {
673660

674661
class ModuleDependenciesCacheSerializer {

lib/DependencyScan/ScanDependencies.cpp

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "swift/Strings.h"
3737
#include "clang/Basic/Module.h"
3838
#include "llvm/ADT/SetVector.h"
39+
#include "llvm/ADT/SetOperations.h"
3940
#include "llvm/ADT/StringMap.h"
4041
#include "llvm/ADT/StringRef.h"
4142
#include "llvm/ADT/StringSet.h"
@@ -47,6 +48,7 @@
4748
#include <set>
4849
#include <string>
4950
#include <sstream>
51+
#include <algorithm>
5052

5153
using namespace swift;
5254
using namespace swift::dependencies;
@@ -157,6 +159,152 @@ static void findAllImportedClangModules(ASTContext &ctx, StringRef moduleName,
157159
}
158160
}
159161

162+
// Get all dependencies's IDs of this module from its cached
163+
// ModuleDependencyInfo
164+
static ArrayRef<ModuleDependencyID>
165+
getDependencies(const ModuleDependencyID &moduleID,
166+
const ModuleDependenciesCache &cache) {
167+
const auto &optionalModuleInfo =
168+
cache.findDependency(moduleID.first, moduleID.second);
169+
assert(optionalModuleInfo.has_value());
170+
return optionalModuleInfo.value()->getModuleDependencies();
171+
}
172+
173+
/// Implements a topological sort via recursion and reverse postorder DFS.
174+
/// Does not bother handling cycles, relying on a DAG guarantee by the client.
175+
static std::vector<ModuleDependencyID>
176+
computeTopologicalSortOfExplicitDependencies(
177+
const ModuleDependencyIDSetVector &allModules,
178+
const ModuleDependenciesCache &cache) {
179+
// Must be explicitly-typed to allow recursion
180+
std::function<void(const ModuleDependencyID &,
181+
std::stack<ModuleDependencyID> &,
182+
std::unordered_set<ModuleDependencyID> &,
183+
std::vector<ModuleDependencyID> &)>
184+
visit;
185+
visit = [&visit, &cache](const ModuleDependencyID &moduleID,
186+
std::stack<ModuleDependencyID> &stack,
187+
std::unordered_set<ModuleDependencyID> &visited,
188+
std::vector<ModuleDependencyID> &result) {
189+
// Mark this node as visited -- we are done if it already was.
190+
if (!visited.insert(moduleID).second)
191+
return;
192+
193+
// Otherwise, visit each adjacent node.
194+
for (const auto &succID : getDependencies(moduleID, cache)) {
195+
// We don't worry if successor is already in this current stack,
196+
// since that would mean we have found a cycle, which should not
197+
// be possible because we checked for cycles earlier.
198+
stack.push(succID);
199+
visit(succID, stack, visited, result);
200+
auto top = stack.top();
201+
stack.pop();
202+
assert(top == succID);
203+
}
204+
205+
// Add to the result.
206+
result.push_back(moduleID);
207+
};
208+
209+
std::unordered_set<ModuleDependencyID> visited;
210+
std::vector<ModuleDependencyID> result;
211+
std::stack<ModuleDependencyID> stack;
212+
for (const auto &modID : allModules) {
213+
assert(stack.empty());
214+
stack.push(modID);
215+
visit(modID, stack, visited, result);
216+
auto top = stack.top();
217+
stack.pop();
218+
assert(top == modID);
219+
}
220+
221+
std::reverse(result.begin(), result.end());
222+
return result;
223+
}
224+
225+
/// For each module in the graph, compute a set of all its dependencies
226+
/// direct *and* transitive.
227+
static std::unordered_map<ModuleDependencyID,
228+
std::set<ModuleDependencyID>>
229+
computeTransitiveClosureOfExplicitDependencies(
230+
const std::vector<ModuleDependencyID> &topologicallySortedModuleList,
231+
const ModuleDependenciesCache &cache) {
232+
std::unordered_map<ModuleDependencyID, std::set<ModuleDependencyID>>
233+
result;
234+
for (const auto &modID : topologicallySortedModuleList)
235+
result[modID] = {modID};
236+
237+
// Traverse the set of modules in reverse topological order, assimilating
238+
// transitive closures
239+
for (auto it = topologicallySortedModuleList.rbegin(),
240+
end = topologicallySortedModuleList.rend();
241+
it != end; ++it) {
242+
const auto &modID = *it;
243+
auto &modReachableSet = result[modID];
244+
for (const auto &succID : getDependencies(modID, cache)) {
245+
const auto &succReachableSet = result[succID];
246+
llvm::set_union(modReachableSet, succReachableSet);
247+
}
248+
}
249+
return result;
250+
}
251+
252+
static void
253+
resolveExplicitModuleInputs(ModuleDependencyID moduleID,
254+
const ModuleDependencyInfo &resolvingDepInfo,
255+
const std::set<ModuleDependencyID> &dependencies,
256+
ModuleDependenciesCache &cache) {
257+
auto resolvingInterfaceDepDetails =
258+
resolvingDepInfo.getAsSwiftInterfaceModule();
259+
assert(resolvingInterfaceDepDetails &&
260+
"Expected Swift Interface dependency.");
261+
262+
auto commandLine = resolvingInterfaceDepDetails->buildCommandLine;
263+
for (const auto &depModuleID : dependencies) {
264+
const auto optionalDepInfo =
265+
cache.findDependency(depModuleID.first, depModuleID.second);
266+
assert(optionalDepInfo.has_value());
267+
const auto depInfo = optionalDepInfo.value();
268+
switch (depModuleID.second) {
269+
case swift::ModuleDependencyKind::SwiftInterface: {
270+
auto interfaceDepDetails = depInfo->getAsSwiftInterfaceModule();
271+
assert(interfaceDepDetails && "Expected Swift Interface dependency.");
272+
commandLine.push_back("-swift-module-file=" + depModuleID.first + "=" +
273+
interfaceDepDetails->moduleOutputPath);
274+
} break;
275+
case swift::ModuleDependencyKind::SwiftBinary: {
276+
auto binaryDepDetails = depInfo->getAsSwiftBinaryModule();
277+
assert(binaryDepDetails && "Expected Swift Binary Module dependency.");
278+
commandLine.push_back("-swift-module-file=" + depModuleID.first + "=" +
279+
binaryDepDetails->compiledModulePath);
280+
} break;
281+
case swift::ModuleDependencyKind::SwiftPlaceholder: {
282+
auto placeholderDetails = depInfo->getAsPlaceholderDependencyModule();
283+
assert(placeholderDetails && "Expected Swift Placeholder dependency.");
284+
commandLine.push_back("-swift-module-file=" + depModuleID.first + "=" +
285+
placeholderDetails->compiledModulePath);
286+
} break;
287+
case swift::ModuleDependencyKind::Clang: {
288+
auto clangDepDetails = depInfo->getAsClangModule();
289+
assert(binaryDepDetails && "Expected Clang Module dependency.");
290+
commandLine.push_back("-Xcc");
291+
commandLine.push_back("-fmodule-file=" + depModuleID.first + "=" +
292+
clangDepDetails->pcmOutputPath);
293+
commandLine.push_back("-Xcc");
294+
commandLine.push_back("-fmodule-map-file=" +
295+
clangDepDetails->moduleMapFile);
296+
} break;
297+
default:
298+
llvm_unreachable("Unhandled dependency kind.");
299+
}
300+
}
301+
302+
// Update the dependency in the cache with the modified command-line.
303+
auto dependencyInfoCopy = resolvingDepInfo;
304+
dependencyInfoCopy.updateCommandLine(commandLine);
305+
cache.updateDependency(moduleID, dependencyInfoCopy);
306+
}
307+
160308
/// Resolve the direct dependencies of the given module.
161309
static ArrayRef<ModuleDependencyID>
162310
resolveDirectDependencies(CompilerInstance &instance, ModuleDependencyID module,
@@ -1475,8 +1623,29 @@ swift::dependencies::performModuleScan(CompilerInstance &instance,
14751623
if (diagnoseCycle(instance, cache, mainModuleID, ASTDelegate))
14761624
return std::make_error_code(std::errc::not_supported);
14771625

1626+
// Resolve Swift dependency command-line arguments
1627+
// Must happen after cycle detection, because it relies on
1628+
// the DAG property of the dependency graph.
1629+
auto topoSortedModuleList =
1630+
computeTopologicalSortOfExplicitDependencies(allModules, cache);
1631+
auto moduleTransitiveClosures =
1632+
computeTransitiveClosureOfExplicitDependencies(topoSortedModuleList,
1633+
cache);
1634+
for (const auto &dependencyClosure : moduleTransitiveClosures) {
1635+
auto &modID = dependencyClosure.first;
1636+
// For main module or binary modules, no command-line to resolve.
1637+
// For Clang modules, their dependencies are resolved by the clang Scanner
1638+
// itself for us.
1639+
if (modID.second != ModuleDependencyKind::SwiftInterface)
1640+
continue;
1641+
auto optionalDeps = cache.findDependency(modID.first, modID.second);
1642+
assert(optionalDeps.has_value());
1643+
auto deps = optionalDeps.value();
1644+
resolveExplicitModuleInputs(modID, *deps, dependencyClosure.second, cache);
1645+
}
1646+
14781647
auto dependencyGraph = generateFullDependencyGraph(
1479-
instance, cache, ASTDelegate, allModules.getArrayRef());
1648+
instance, cache, ASTDelegate, topoSortedModuleList);
14801649
// Update the dependency tracker.
14811650
if (auto depTracker = instance.getDependencyTracker()) {
14821651
for (auto module : allModules) {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: mkdir -p %t/clang-module-cache
3+
4+
// RUN: %target-swift-frontend -scan-dependencies -module-cache-path %t/clang-module-cache %s -o %t/deps.json -I %S/Inputs/CHeaders -I %S/Inputs/Swift -emit-dependencies -emit-dependencies-path %t/deps.d -import-objc-header %S/Inputs/CHeaders/Bridging.h -swift-version 4
5+
// Check the contents of the JSON output
6+
// RUN: %FileCheck %s < %t/deps.json
7+
8+
// REQUIRES: executable_test
9+
// REQUIRES: objc_interop
10+
11+
12+
import F
13+
14+
// CHECK: "mainModuleName": "deps"
15+
16+
/// --------Main module
17+
// CHECK-LABEL: "modulePath": "deps.swiftmodule",
18+
// CHECK-NEXT: sourceFiles
19+
// CHECK-NEXT: explicit-swift-dependencies.swift
20+
// CHECK-NEXT: ],
21+
// CHECK-NEXT: "directDependencies": [
22+
// CHECK-DAG: "swift": "F"
23+
// CHECK-DAG: "swift": "Swift"
24+
// CHECK-DAG: "swift": "SwiftOnoneSupport"
25+
// CHECK-DAG: "swift": "_Concurrency"
26+
// CHECK-DAG: "clang": "_SwiftConcurrencyShims"
27+
// CHECK-DAG: "swift": "_StringProcessing"
28+
// CHECK: ],
29+
30+
/// --------Swift module F
31+
// CHECK: "modulePath": "{{.*}}{{/|\\}}F-{{.*}}.swiftmodule",
32+
// CHECK: directDependencies
33+
// CHECK-NEXT: {
34+
// CHECK-DAG: "clang": "F"
35+
// CHECK-DAG: "swift": "Swift"
36+
// CHECK-DAG: "swift": "SwiftOnoneSupport"
37+
38+
39+
// CHECK: "commandLine": [
40+
// CHECK-NEXT: "-frontend",
41+
// CHECK-NEXT: "-compile-module-from-interface",
42+
// CHECK-NEXT: "-target",
43+
// ...
44+
// CHECK: "-explicit-interface-module-build",
45+
// CHECK-NEXT: "-disable-implicit-swift-modules",
46+
// CHECK-NEXT: "-Xcc",
47+
// CHECK-NEXT: "-fno-implicit-modules",
48+
// CHECK-NEXT: "-Xcc",
49+
// CHECK-NEXT: "-fno-implicit-module-maps",
50+
// CHECK-NEXT: "-o",
51+
// CHECK-NEXT: "{{.*}}{{/|\\}}F-{{.*}}.swiftmodule"
52+
// CHECK-DAG: "-swift-module-file=Swift={{.*}}{{/|\\}}Swift-{{.*}}.swiftmodule"
53+
// CHECK-DAG: "-swift-module-file=SwiftOnoneSupport={{.*}}{{/|\\}}SwiftOnoneSupport-{{.*}}.swiftmodule"
54+
// CHECK-DAG: "-fmodule-file=F={{.*}}{{/|\\}}F-{{.*}}.pcm",
55+
// CHECK-DAG: "-fmodule-file=SwiftShims={{.*}}{{/|\\}}SwiftShims-{{.*}}.pcm",

0 commit comments

Comments
 (0)