Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Commit 859b87f

Browse files
committed
Move The Last Pieces for Cross-Module Incremental Builds
We're going to play a dirty, dirty trick - but it'll make our users' lives better in the end so stick with me here. In order to build up an incremental compilation, we need two sources of dependency information: 1) "Priors" - Swiftdeps with dependency information from the past build(s) 2) "Posteriors" - Swiftdeps with dependencies from after we rebuild the file or module or whatever With normal swift files built in incremental mode, the priors are given by the swiftdeps files which are generated parallel to a swift file and usually placed in the build directory alongside the object files. Because we have entries in the output file map, we can always know where these swiftdeps files are. The priors are integrated by the driver and then the build is scheduled. As the build runs and jobs complete, their swiftdeps are reloaded and re-integrated. The resulting changes are then traversed and more jobs are scheduled if necessary. These give us the posteriors we desire. A module flips this on its head. The swiftdeps information serialized in a module functions as the *posterior* since the driver consuming the module has no way of knowing how to rebuild the module, and because its dependencies are, for all intents and purposes, fixed in time. The missing piece of the puzzle is the priors. That is, we need some way of knowing what the "past" interface of the module looked like so we can compare it to the "present" interface. Moreover, we need to always know where to look for these priors. We solve this problem by serializing a file alongside the build record: the "external" build record. This is given by a... creative encoding of multiple source file dependency graphs into a single source file dependency graph. The rough structure of this is: SourceFile => interface <BUILD_RECORD>.external | - Incremental External Dependency => interface <MODULE_1>.swiftmodule | | - <dependency> ... | | - <dependency> ... | | - <dependency> ... | - Incremental External Dependency => interface <MODULE_2>.swiftmodule | | - <dependency> ... | | - <dependency> ... | - Incremental External Dependency => interface <MODULE_3>.swiftmodule | - ... Sorta, `cat`'ing a bunch of source file dependency graphs together but with incremental external dependency nodes acting as glue. Now for the trick: We have to unpack this structure and integrate it to get our priors. This is easy. The tricky bit comes in integrate itself. Because the top-level source file node points directly at the external build record, not the original swift modules that defined these dependency nodes, we swap the key it wants to use (the external build record) for the incremental external dependency acting as the "parent" of the dependency node. We do this by following the arc we carefully laid down in the structure above. For rdar://69595010 Goes a long way towards rdar://48955139, rdar://64238133
1 parent 903fd71 commit 859b87f

File tree

7 files changed

+158
-8
lines changed

7 files changed

+158
-8
lines changed

include/swift/Basic/FileTypes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ TYPE("llvm-bc", LLVM_BC, "bc", "")
6161
TYPE("diagnostics", SerializedDiagnostics, "dia", "")
6262
TYPE("objc-header", ObjCHeader, "h", "")
6363
TYPE("swift-dependencies", SwiftDeps, "swiftdeps", "")
64+
TYPE("external-swift-dependencies", ExternalSwiftDeps, "swiftdeps.external", "")
6465
TYPE("swift-ranges", SwiftRanges, "swiftranges", "")
6566
TYPE("compiled-source", CompiledSource, "compiledsource", "")
6667
TYPE("remap", Remapping, "remap", "")

include/swift/Driver/Compilation.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,17 @@ class Compilation {
508508
/// \sa types::isPartOfSwiftCompilation
509509
const char *getAllSourcesPath() const;
510510

511+
/// Retrieve the path to the external swift deps file.
512+
///
513+
/// For cross-module incremental builds, this file contains the dependencies
514+
/// from all the modules integrated over the prior build.
515+
///
516+
/// Currently this patch is relative to the build record, but we may want
517+
/// to allow the output file map to customize this at some point.
518+
std::string getExternalSwiftDepsFilePath() const {
519+
return CompilationRecordPath + ".external";
520+
}
521+
511522
/// Asks the Compilation to perform the Jobs which it knows about.
512523
///
513524
/// \param TQ The TaskQueue used to schedule jobs for execution.

lib/Basic/FileTypes.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ bool file_types::isTextual(ID Id) {
100100
case file_types::TY_SerializedDiagnostics:
101101
case file_types::TY_ClangModuleFile:
102102
case file_types::TY_SwiftDeps:
103+
case file_types::TY_ExternalSwiftDeps:
103104
case file_types::TY_SwiftRanges:
104105
case file_types::TY_CompiledSource:
105106
case file_types::TY_Nothing:
@@ -145,6 +146,7 @@ bool file_types::isAfterLLVM(ID Id) {
145146
case file_types::TY_SerializedDiagnostics:
146147
case file_types::TY_ClangModuleFile:
147148
case file_types::TY_SwiftDeps:
149+
case file_types::TY_ExternalSwiftDeps:
148150
case file_types::TY_SwiftRanges:
149151
case file_types::TY_CompiledSource:
150152
case file_types::TY_Nothing:
@@ -197,6 +199,7 @@ bool file_types::isPartOfSwiftCompilation(ID Id) {
197199
case file_types::TY_SerializedDiagnostics:
198200
case file_types::TY_ClangModuleFile:
199201
case file_types::TY_SwiftDeps:
202+
case file_types::TY_ExternalSwiftDeps:
200203
case file_types::TY_SwiftRanges:
201204
case file_types::TY_CompiledSource:
202205
case file_types::TY_Nothing:

lib/Driver/Compilation.cpp

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "swift/AST/DiagnosticEngine.h"
1616
#include "swift/AST/DiagnosticsDriver.h"
1717
#include "swift/AST/FineGrainedDependencies.h"
18+
#include "swift/AST/FineGrainedDependencyFormat.h"
1819
#include "swift/Basic/OutputFileMap.h"
1920
#include "swift/Basic/Program.h"
2021
#include "swift/Basic/STLExtras.h"
@@ -1185,6 +1186,30 @@ namespace driver {
11851186
return ExternallyDependentJobs;
11861187
}
11871188

1189+
using ChangeSet = fine_grained_dependencies::ModuleDepGraph::Changes::value_type;
1190+
static void
1191+
pruneChangeSetFromExternalDependency(ChangeSet &changes) {
1192+
// The changeset includes detritus from the graph that gets consed up
1193+
// in \c writePriorDependencyGraph. We need to ignore the fake
1194+
// source file provides nodes and the fake incremental external
1195+
// dependencies linked to them.
1196+
swift::erase_if(
1197+
changes, [&](fine_grained_dependencies::ModuleDepGraphNode *node) {
1198+
if (node->getKey().getKind() ==
1199+
fine_grained_dependencies::NodeKind::sourceFileProvide ||
1200+
node->getKey().getKind() ==
1201+
fine_grained_dependencies::NodeKind::
1202+
incrementalExternalDepend) {
1203+
return true;
1204+
}
1205+
if (node->getKey().getAspect() ==
1206+
fine_grained_dependencies::DeclAspect::implementation) {
1207+
return true;
1208+
}
1209+
return !node->getIsProvides();
1210+
});
1211+
}
1212+
11881213
SmallVector<const Job *, 16>
11891214
collectIncrementalExternallyDependentJobsFromDependencyGraph(
11901215
const bool forRanges) {
@@ -1196,6 +1221,17 @@ namespace driver {
11961221
}
11971222
};
11981223

1224+
// Load our priors, which are always adjacent to the build record. We
1225+
// don't care if this load succeeds or not. If it fails, and we succeed at
1226+
// integrating one of the external files below, the changeset will be the
1227+
// entire module!
1228+
const auto *externalPriorJob = Comp.addExternalJob(
1229+
std::make_unique<Job>(Comp.getDerivedOutputFileMap(),
1230+
Comp.getExternalSwiftDepsFilePath()));
1231+
getFineGrainedDepGraph(forRanges).loadFromPath(
1232+
externalPriorJob, Comp.getExternalSwiftDepsFilePath(),
1233+
Comp.getDiags());
1234+
11991235
for (auto external : getFineGrainedDepGraph(forRanges)
12001236
.getIncrementalExternalDependencies()) {
12011237
llvm::sys::fs::file_status depStatus;
@@ -1231,20 +1267,23 @@ namespace driver {
12311267
// code's internal invariants.
12321268
const auto *externalJob = Comp.addExternalJob(
12331269
std::make_unique<Job>(Comp.getDerivedOutputFileMap(), external));
1234-
auto subChanges =
1270+
auto maybeChanges =
12351271
getFineGrainedDepGraph(forRanges).loadFromSwiftModuleBuffer(
12361272
externalJob, *buffer.get(), Comp.getDiags());
12371273

12381274
// If the incremental dependency graph failed to load, fall back to
12391275
// treating this as plain external job.
1240-
if (!subChanges.hasValue()) {
1276+
if (!maybeChanges.hasValue()) {
12411277
fallbackToExternalBehavior(external);
12421278
continue;
12431279
}
12441280

1245-
for (auto *CMD :
1246-
getFineGrainedDepGraph(forRanges)
1247-
.findJobsToRecompileWhenNodesChange(subChanges.getValue())) {
1281+
// Prune away the detritus from the build record.
1282+
auto &changes = maybeChanges.getValue();
1283+
pruneChangeSetFromExternalDependency(changes);
1284+
1285+
for (auto *CMD : getFineGrainedDepGraph(forRanges)
1286+
.findJobsToRecompileWhenNodesChange(changes)) {
12481287
if (CMD == externalJob) {
12491288
continue;
12501289
}
@@ -1861,6 +1900,76 @@ static void writeCompilationRecord(StringRef path, StringRef argsHash,
18611900
}
18621901
}
18631902

1903+
using SourceFileDepGraph = swift::fine_grained_dependencies::SourceFileDepGraph;
1904+
1905+
/// Render out the unified module dependency graph to the given \p path, which
1906+
/// is expected to be a path relative to the build record.
1907+
static void withPriorDependencyGraph(StringRef path,
1908+
const Compilation::Result &result,
1909+
llvm::function_ref<void(SourceFileDepGraph &&)> cont) {
1910+
// Building a source file dependency graph from the module dependency graph
1911+
// is a strange task on its face because a source file dependency graph is
1912+
// usually built for exactly one file. However, the driver is going to use
1913+
// some encoding tricks to get the dependencies for each incremental external
1914+
// dependency into one big file. Note that these tricks
1915+
// are undone in \c pruneChangeSetFromExternalDependency, so if you modify
1916+
// this you need to go fix that algorithm up as well. This is a diagrammatic
1917+
// view of the structure of the dependencies this function builds:
1918+
//
1919+
// SourceFile => interface <BUILD_RECORD>.external
1920+
// | - Incremetal External Dependency => interface <MODULE_1>.swiftmodule
1921+
// | | - <dependency> ...
1922+
// | | - <dependency> ...
1923+
// | | - <dependency> ...
1924+
// | - Incremetal External Dependency => interface <MODULE_2>.swiftmodule
1925+
// | | - <dependency> ...
1926+
// | | - <dependency> ...
1927+
// | - Incremetal External Dependency => interface <MODULE_3>.swiftmodule
1928+
// | - ...
1929+
//
1930+
// Where each <dependency> node has an arc back to its parent swiftmodule.
1931+
// That swiftmodule, in turn, takes the form of as an incremental external
1932+
// dependency. This formulation allows us to easily discern the original
1933+
// swiftmodule that a <dependency> came from just by examining that arc. This
1934+
// is used in integrate to "move" the <dependency> from the build record to
1935+
// the swiftmodule by swapping the key it uses.
1936+
using namespace swift::fine_grained_dependencies;
1937+
SourceFileDepGraph g;
1938+
const auto &resultModuleGraph = result.depGraph;
1939+
// Create the key for the entire external build record.
1940+
auto fileKey =
1941+
DependencyKey::createKeyForWholeSourceFile(DeclAspect::interface, path);
1942+
auto fileNodePair = g.findExistingNodePairOrCreateAndAddIfNew(fileKey, None);
1943+
for (StringRef incrExternalDep :
1944+
resultModuleGraph.getIncrementalExternalDependencies()) {
1945+
// Now make a node for each incremental external dependency.
1946+
auto interfaceKey =
1947+
DependencyKey(NodeKind::incrementalExternalDepend,
1948+
DeclAspect::interface, "", incrExternalDep.str());
1949+
auto ifaceNode = g.findExistingNodeOrCreateIfNew(interfaceKey, None,
1950+
false /* = !isProvides */);
1951+
resultModuleGraph.forEachNodeInJob(incrExternalDep, [&](const auto *node) {
1952+
// Reject
1953+
// 1) Implementation nodes: We don't care about the interface nodes
1954+
// for cross-module dependencies because the client cannot observe it
1955+
// by definition.
1956+
// 2) Source file nodes: we're about to define our own.
1957+
if (!node->getKey().isInterface() ||
1958+
node->getKey().getKind() == NodeKind::sourceFileProvide) {
1959+
return;
1960+
}
1961+
assert(node->getIsProvides() &&
1962+
"Found a node in module depdendencies that is not a provides!");
1963+
auto *newNode = new SourceFileDepGraphNode(
1964+
node->getKey(), node->getFingerprint(), /*isProvides*/ true);
1965+
g.addNode(newNode);
1966+
g.addArc(ifaceNode, newNode);
1967+
});
1968+
g.addArc(fileNodePair.getInterface(), ifaceNode);
1969+
}
1970+
return cont(std::move(g));
1971+
}
1972+
18641973
static void writeInputJobsToFilelist(llvm::raw_fd_ostream &out, const Job *job,
18651974
const file_types::ID infoType) {
18661975
// FIXME: Duplicated from ToolChains.cpp.
@@ -1960,6 +2069,15 @@ Compilation::performJobsImpl(std::unique_ptr<TaskQueue> &&TQ) {
19602069
auto result = std::move(State).takeResult();
19612070
writeCompilationRecord(CompilationRecordPath, ArgsHash, BuildStartTime,
19622071
InputInfo);
2072+
if (EnableCrossModuleIncrementalBuild) {
2073+
// Write out our priors adjacent to the build record so we can pick
2074+
// the up in a subsequent build.
2075+
withPriorDependencyGraph(getExternalSwiftDepsFilePath(), result,
2076+
[&](SourceFileDepGraph &&g) {
2077+
writeFineGrainedDependencyGraphToPath(
2078+
Diags, getExternalSwiftDepsFilePath(), g);
2079+
});
2080+
}
19632081
return result;
19642082
} else {
19652083
return std::move(State).takeResult();

lib/Driver/Driver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,6 +2039,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
20392039
case file_types::TY_ObjCHeader:
20402040
case file_types::TY_ClangModuleFile:
20412041
case file_types::TY_SwiftDeps:
2042+
case file_types::TY_ExternalSwiftDeps:
20422043
case file_types::TY_SwiftRanges:
20432044
case file_types::TY_CompiledSource:
20442045
case file_types::TY_Remapping:

lib/Driver/FineGrainedDependencyDriverGraph.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,15 +287,29 @@ ModuleDepGraph::Changes ModuleDepGraph::integrate(const SourceFileDepGraph &g,
287287

288288
g.forEachNode([&](const SourceFileDepGraphNode *integrand) {
289289
const auto &key = integrand->getKey();
290-
291-
auto preexistingMatch = findPreexistingMatch(swiftDepsOfJob, integrand);
290+
StringRef realSwiftDepsPath = swiftDepsOfJob;
291+
// If we're doing a cross-module incremental build, we'll see these
292+
// `.external` "swiftdeps" files. See \c writePriorDependencyGraph for
293+
// the structure of the graph we're traversing here. Essentially, follow
294+
// the arc laid down there to discover the file path for the swiftmodule
295+
// where this dependency node originally came from.
296+
if (swiftDepsOfJob.endswith(file_types::getExtension(file_types::TY_ExternalSwiftDeps)) &&
297+
integrand->getKey().getKind() != NodeKind::sourceFileProvide) {
298+
integrand->forEachDefIDependUpon([&](size_t seqNum) {
299+
auto &external = g.getNode(seqNum)->getKey();
300+
if (external.getKind() == NodeKind::incrementalExternalDepend) {
301+
realSwiftDepsPath = external.getName();
302+
}
303+
});
304+
}
305+
auto preexistingMatch = findPreexistingMatch(realSwiftDepsPath, integrand);
292306
if (preexistingMatch.hasValue() &&
293307
preexistingMatch.getValue().first == LocationOfPreexistingNode::here)
294308
disappearedNodes.erase(key); // Node was and still is. Do not erase it.
295309

296310
Optional<NullablePtr<ModuleDepGraphNode>> newNodeOrChangedNode =
297311
integrateSourceFileDepGraphNode(g, integrand, preexistingMatch,
298-
swiftDepsOfJob);
312+
realSwiftDepsPath);
299313

300314
if (!newNodeOrChangedNode)
301315
changedNodes = None;

lib/Driver/ToolChains.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ const char *ToolChain::JobContext::computeFrontendModeForCompile() const {
619619
case file_types::TY_ObjCHeader:
620620
case file_types::TY_Image:
621621
case file_types::TY_SwiftDeps:
622+
case file_types::TY_ExternalSwiftDeps:
622623
case file_types::TY_SwiftRanges:
623624
case file_types::TY_CompiledSource:
624625
case file_types::TY_ModuleTrace:
@@ -881,6 +882,7 @@ ToolChain::constructInvocation(const BackendJobAction &job,
881882
case file_types::TY_ObjCHeader:
882883
case file_types::TY_Image:
883884
case file_types::TY_SwiftDeps:
885+
case file_types::TY_ExternalSwiftDeps:
884886
case file_types::TY_SwiftRanges:
885887
case file_types::TY_CompiledSource:
886888
case file_types::TY_Remapping:

0 commit comments

Comments
 (0)