mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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
801 lines
30 KiB
C++
801 lines
30 KiB
C++
//===--- FineGrainedDependencyGraph.cpp ------------------------------------==//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/Driver/FineGrainedDependencyDriverGraph.h"
|
|
// Next two includes needed for reporting errors opening dot file for writing.
|
|
#include "swift/AST/DiagnosticEngine.h"
|
|
#include "swift/AST/DiagnosticsFrontend.h"
|
|
#include "swift/AST/FileSystem.h"
|
|
#include "swift/Basic/PrettyStackTrace.h"
|
|
#include "swift/Basic/ReferenceDependencyKeys.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/Basic/Statistic.h"
|
|
#include "swift/Demangling/Demangle.h"
|
|
#include "swift/Driver/Job.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/SourceMgr.h"
|
|
#include "llvm/Support/YAMLParser.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <unordered_set>
|
|
|
|
// Definitions for the portion fine-grained dependency system used by the
|
|
// driver.
|
|
|
|
using namespace swift;
|
|
|
|
using namespace swift::fine_grained_dependencies;
|
|
using namespace swift::driver;
|
|
|
|
//==============================================================================
|
|
// MARK: Affordances to unit tests
|
|
//==============================================================================
|
|
|
|
LLVM_ATTRIBUTE_UNUSED
|
|
std::vector<const Job *>
|
|
ModuleDepGraph::printJobsForDebugging(const std::vector<const Job *> &jobs) {
|
|
llvm::errs() << "\nprintForDebugging: ";
|
|
for (auto *j : jobs) {
|
|
const auto swiftDeps =
|
|
j->getOutput().getAdditionalOutputForType(file_types::TY_SwiftDeps);
|
|
assert(!swiftDeps.empty());
|
|
llvm::errs() << "job" << swiftDeps << ", ";
|
|
}
|
|
llvm::errs() << "\n";
|
|
return jobs;
|
|
}
|
|
//==============================================================================
|
|
// MARK: Interfacing to Compilation
|
|
//==============================================================================
|
|
|
|
ModuleDepGraph::Changes ModuleDepGraph::loadFromPath(const Job *Cmd,
|
|
StringRef path,
|
|
DiagnosticEngine &diags) {
|
|
FrontendStatsTracer tracer(stats, "fine-grained-dependencies-loadFromPath");
|
|
PrettyStackTraceStringAction stackTrace("loading fine-grained dependency graph", path);
|
|
|
|
if (driverDotFileBasePath.empty()) {
|
|
driverDotFileBasePath = path;
|
|
llvm::sys::path::remove_filename(driverDotFileBasePath);
|
|
llvm::sys::path::append(driverDotFileBasePath, "driver");
|
|
}
|
|
|
|
auto buffer = llvm::MemoryBuffer::getFile(path);
|
|
if (!buffer)
|
|
return None;
|
|
auto r = loadFromBuffer(Cmd, *buffer.get(), diags);
|
|
assert(path == getSwiftDeps(Cmd) && "Should be reading the job's swiftdeps");
|
|
assert(!r || !nodeMap[path.str()].empty() &&
|
|
"Must have a node for the whole file");
|
|
return r;
|
|
}
|
|
|
|
/// Returns None for error or a set of changed keys
|
|
ModuleDepGraph::Changes
|
|
ModuleDepGraph::loadFromBuffer(const Job *job, llvm::MemoryBuffer &buffer,
|
|
DiagnosticEngine &diags) {
|
|
Optional<SourceFileDepGraph> sourceFileDepGraph =
|
|
SourceFileDepGraph::loadFromBuffer(buffer);
|
|
if (!sourceFileDepGraph)
|
|
return None;
|
|
return loadFromSourceFileDepGraph(job, sourceFileDepGraph.getValue(), diags);
|
|
}
|
|
|
|
ModuleDepGraph::Changes ModuleDepGraph::loadFromSourceFileDepGraph(
|
|
const Job *job, const SourceFileDepGraph &sourceFileDepGraph,
|
|
DiagnosticEngine &diags) {
|
|
registerJob(job);
|
|
auto changes = integrate(sourceFileDepGraph, getSwiftDeps(job));
|
|
|
|
if (verifyFineGrainedDependencyGraphAfterEveryImport)
|
|
verify();
|
|
if (emitFineGrainedDependencyDotFileAfterEveryImport)
|
|
emitDotFileForJob(diags, job);
|
|
return changes;
|
|
}
|
|
|
|
ModuleDepGraph::Changes ModuleDepGraph::loadFromSwiftModuleBuffer(
|
|
const Job *Cmd, llvm::MemoryBuffer &buffer, DiagnosticEngine &diags) {
|
|
FrontendStatsTracer tracer(
|
|
stats, "fine-grained-dependencies-loadFromSwiftModuleBuffer");
|
|
PrettyStackTraceStringAction stackTrace(
|
|
"loading fine-grained dependency graph from swiftmodule",
|
|
buffer.getBufferIdentifier());
|
|
|
|
Optional<SourceFileDepGraph> sourceFileDepGraph =
|
|
SourceFileDepGraph::loadFromSwiftModuleBuffer(buffer);
|
|
if (!sourceFileDepGraph)
|
|
return None;
|
|
jobsBySwiftDeps[buffer.getBufferIdentifier().str()] = Cmd;
|
|
auto changes = integrate(*sourceFileDepGraph, buffer.getBufferIdentifier());
|
|
if (verifyFineGrainedDependencyGraphAfterEveryImport)
|
|
verify();
|
|
if (emitFineGrainedDependencyDotFileAfterEveryImport)
|
|
emitDotFileForJob(diags, Cmd);
|
|
return changes;
|
|
}
|
|
|
|
bool ModuleDepGraph::haveAnyNodesBeenTraversedIn(const Job *cmd) const {
|
|
std::string swiftDeps = getSwiftDeps(cmd).str();
|
|
|
|
// optimization
|
|
const auto fileKey = DependencyKey::createKeyForWholeSourceFile(
|
|
DeclAspect::interface, swiftDeps);
|
|
if (const auto fileNode = nodeMap.find(swiftDeps, fileKey)) {
|
|
if (fileNode && fileNode.getValue()->getHasBeenTraced())
|
|
return true;
|
|
}
|
|
|
|
bool result = false;
|
|
forEachNodeInJob(swiftDeps, [&](const ModuleDepGraphNode *n) {
|
|
if (n->getHasBeenTraced())
|
|
result = true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
std::vector<const Job *> ModuleDepGraph::findJobsToRecompileWhenWholeJobChanges(
|
|
const Job *jobToBeRecompiled) {
|
|
std::vector<ModuleDepGraphNode *> allNodesInJob;
|
|
forEachNodeInJob(getSwiftDeps(jobToBeRecompiled),
|
|
[&](ModuleDepGraphNode *n) { allNodesInJob.push_back(n); });
|
|
return findJobsToRecompileWhenNodesChange(allNodesInJob);
|
|
}
|
|
|
|
std::vector<std::string> ModuleDepGraph::computeSwiftDepsFromNodes(
|
|
ArrayRef<const ModuleDepGraphNode *> nodes) const {
|
|
llvm::StringSet<> swiftDepsOfNodes;
|
|
for (const ModuleDepGraphNode *n : nodes) {
|
|
if (!n->getIsProvides())
|
|
continue;
|
|
const std::string &swiftDeps = n->getSwiftDepsOfProvides();
|
|
swiftDepsOfNodes.insert(swiftDeps);
|
|
}
|
|
std::vector<std::string> swiftDepsVec;
|
|
for (const auto &entry : swiftDepsOfNodes)
|
|
swiftDepsVec.push_back(entry.getKey().str());
|
|
return swiftDepsVec;
|
|
}
|
|
|
|
std::vector<const Job *> ModuleDepGraph::jobsContaining(
|
|
ArrayRef<const ModuleDepGraphNode *> nodes) const {
|
|
std::vector<const Job *> jobs;
|
|
for (StringRef swiftDeps : computeSwiftDepsFromNodes(nodes))
|
|
jobs.push_back(getJob(swiftDeps.str()));
|
|
return jobs;
|
|
}
|
|
|
|
void ModuleDepGraph::registerJob(const Job *job) {
|
|
// No need to create any nodes; that will happen when the swiftdeps file is
|
|
// read. Just record the correspondence.
|
|
jobsBySwiftDeps.insert(std::make_pair(getSwiftDeps(job).str(), job));
|
|
}
|
|
|
|
std::vector<const Job *> ModuleDepGraph::getAllJobs() const {
|
|
std::vector<const Job *> jobs;
|
|
for (auto const &entry : jobsBySwiftDeps)
|
|
jobs.push_back(entry.second);
|
|
return jobs;
|
|
}
|
|
|
|
std::vector<StringRef> ModuleDepGraph::getExternalDependencies() const {
|
|
return std::vector<StringRef>(externalDependencies.begin(),
|
|
externalDependencies.end());
|
|
}
|
|
|
|
std::vector<StringRef>
|
|
ModuleDepGraph::getIncrementalExternalDependencies() const {
|
|
return std::vector<StringRef>(incrementalExternalDependencies.begin(),
|
|
incrementalExternalDependencies.end());
|
|
}
|
|
|
|
// Add every (swiftdeps) use of the external dependency to foundJobs.
|
|
// Can return duplicates, but it doesn't break anything, and they will be
|
|
// canonicalized later.
|
|
std::vector<const Job *> ModuleDepGraph::findExternallyDependentUntracedJobs(
|
|
StringRef externalDependency) {
|
|
FrontendStatsTracer tracer(
|
|
stats, "fine-grained-dependencies-findExternallyDependentUntracedJobs");
|
|
std::vector<const Job *> foundJobs;
|
|
forEachUntracedJobDirectlyDependentOnExternalSwiftDeps(
|
|
externalDependency, [&](const Job *job) {
|
|
foundJobs.push_back(job);
|
|
for (const Job *marked : findJobsToRecompileWhenWholeJobChanges(job)) {
|
|
// findJobsToRecompileWhenWholeJobChanges is reflexive
|
|
// Don't return job twice.
|
|
if (marked != job)
|
|
foundJobs.push_back(marked);
|
|
}
|
|
});
|
|
return foundJobs;
|
|
}
|
|
|
|
std::vector<const Job *>
|
|
ModuleDepGraph::findIncrementalExternallyDependentUntracedJobs(
|
|
StringRef externalDependency) {
|
|
FrontendStatsTracer tracer(stats,
|
|
"fine-grained-dependencies-"
|
|
"findIncrementalExternallyDependentUntracedJobs");
|
|
std::vector<const Job *> foundJobs;
|
|
forEachUntracedJobDirectlyDependentOnExternalIncrementalSwiftDeps(
|
|
externalDependency, [&](const Job *job) {
|
|
foundJobs.push_back(job);
|
|
for (const Job *marked : findJobsToRecompileWhenWholeJobChanges(job)) {
|
|
// findJobsToRecompileWhenWholeJobChanges is reflexive
|
|
// Don't return job twice.
|
|
if (marked != job)
|
|
foundJobs.push_back(marked);
|
|
}
|
|
});
|
|
return foundJobs;
|
|
}
|
|
|
|
void ModuleDepGraph::forEachUntracedJobDirectlyDependentOnExternalSwiftDeps(
|
|
StringRef externalSwiftDeps, function_ref<void(const Job *)> fn) {
|
|
// TODO move nameForDep into key
|
|
// These nodes will depend on the *interface* of the external Decl.
|
|
DependencyKey key(NodeKind::externalDepend, DeclAspect::interface, "",
|
|
externalSwiftDeps.str());
|
|
for (const ModuleDepGraphNode *useNode : usesByDef[key]) {
|
|
if (!useNode->getHasBeenTraced())
|
|
fn(getJob(useNode->getSwiftDepsOfProvides()));
|
|
}
|
|
}
|
|
|
|
void ModuleDepGraph::
|
|
forEachUntracedJobDirectlyDependentOnExternalIncrementalSwiftDeps(
|
|
StringRef externalSwiftDeps, function_ref<void(const Job *)> fn) {
|
|
// TODO move nameForDep into key
|
|
// These nodes will depend on the *interface* of the external Decl.
|
|
DependencyKey key(NodeKind::incrementalExternalDepend, DeclAspect::interface,
|
|
"", externalSwiftDeps.str());
|
|
for (const ModuleDepGraphNode *useNode : usesByDef[key]) {
|
|
if (!useNode->getHasBeenTraced())
|
|
fn(getJob(useNode->getSwiftDepsOfProvides()));
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// MARK: Integrating SourceFileDepGraph into ModuleDepGraph
|
|
//==============================================================================
|
|
|
|
ModuleDepGraph::Changes ModuleDepGraph::integrate(const SourceFileDepGraph &g,
|
|
StringRef swiftDepsOfJob) {
|
|
FrontendStatsTracer tracer(stats, "fine-grained-dependencies-integrate");
|
|
|
|
// When done, disappearedNodes contains the nodes which no longer exist.
|
|
auto disappearedNodes = nodeMap[swiftDepsOfJob.str()];
|
|
// When done, changeDependencyKeys contains a list of keys that changed
|
|
// as a result of this integration.
|
|
// Or if the integration failed, None.
|
|
Optional<std::unordered_set<ModuleDepGraphNode *>> changedNodes =
|
|
std::unordered_set<ModuleDepGraphNode *>();
|
|
|
|
g.forEachNode([&](const SourceFileDepGraphNode *integrand) {
|
|
const auto &key = integrand->getKey();
|
|
StringRef realSwiftDepsPath = swiftDepsOfJob;
|
|
// If we're doing a cross-module incremental build, we'll see these
|
|
// `.external` "swiftdeps" files. See \c writePriorDependencyGraph for
|
|
// the structure of the graph we're traversing here. Essentially, follow
|
|
// the arc laid down there to discover the file path for the swiftmodule
|
|
// where this dependency node originally came from.
|
|
if (swiftDepsOfJob.endswith(file_types::getExtension(file_types::TY_ExternalSwiftDeps)) &&
|
|
integrand->getKey().getKind() != NodeKind::sourceFileProvide) {
|
|
integrand->forEachDefIDependUpon([&](size_t seqNum) {
|
|
auto &external = g.getNode(seqNum)->getKey();
|
|
if (external.getKind() == NodeKind::incrementalExternalDepend) {
|
|
realSwiftDepsPath = external.getName();
|
|
}
|
|
});
|
|
}
|
|
auto preexistingMatch = findPreexistingMatch(realSwiftDepsPath, integrand);
|
|
if (preexistingMatch.hasValue() &&
|
|
preexistingMatch.getValue().first == LocationOfPreexistingNode::here)
|
|
disappearedNodes.erase(key); // Node was and still is. Do not erase it.
|
|
|
|
Optional<NullablePtr<ModuleDepGraphNode>> newNodeOrChangedNode =
|
|
integrateSourceFileDepGraphNode(g, integrand, preexistingMatch,
|
|
realSwiftDepsPath);
|
|
|
|
if (!newNodeOrChangedNode)
|
|
changedNodes = None;
|
|
else if (!changedNodes)
|
|
;
|
|
else if (auto *n = newNodeOrChangedNode.getValue().getPtrOrNull())
|
|
changedNodes.getValue().insert(n);
|
|
});
|
|
if (!changedNodes)
|
|
return None;
|
|
|
|
for (auto &p : disappearedNodes) {
|
|
changedNodes.getValue().insert(p.second);
|
|
eraseNodeFromJob(p.second);
|
|
}
|
|
|
|
// Make sure the changes can be retraced:
|
|
for (auto *n : changedNodes.getValue())
|
|
n->clearHasBeenTraced();
|
|
|
|
return changedNodes.getValue();
|
|
}
|
|
|
|
ModuleDepGraph::PreexistingNodeIfAny ModuleDepGraph::findPreexistingMatch(
|
|
StringRef swiftDepsOfCompilationToBeIntegrated,
|
|
const SourceFileDepGraphNode *integrand) const {
|
|
const auto *matches = nodeMap.find(integrand->getKey()).getPtrOrNull();
|
|
if (!matches)
|
|
return None;
|
|
const auto &expatsIter = matches->find("");
|
|
if (expatsIter != matches->end()) {
|
|
assert(matches->size() == 1 &&
|
|
"If an expat exists, then must not be any matches in other files");
|
|
return std::make_pair(LocationOfPreexistingNode::nowhere,
|
|
expatsIter->second);
|
|
}
|
|
if (integrand->getIsProvides()) {
|
|
const auto &preexistingNodeInPlaceIter =
|
|
matches->find(swiftDepsOfCompilationToBeIntegrated.str());
|
|
if (preexistingNodeInPlaceIter != matches->end())
|
|
return std::make_pair(LocationOfPreexistingNode::here,
|
|
preexistingNodeInPlaceIter->second);
|
|
}
|
|
if (!matches->empty())
|
|
return std::make_pair(LocationOfPreexistingNode::elsewhere,
|
|
matches->begin()->second);
|
|
return None;
|
|
}
|
|
|
|
Optional<NullablePtr<ModuleDepGraphNode>>
|
|
ModuleDepGraph::integrateSourceFileDepGraphNode(
|
|
const SourceFileDepGraph &g, const SourceFileDepGraphNode *integrand,
|
|
const PreexistingNodeIfAny preexistingMatch,
|
|
const StringRef swiftDepsOfJob) {
|
|
if (!integrand->getIsProvides())
|
|
return NullablePtr<ModuleDepGraphNode>(); // depends are captured by
|
|
// recordWhatUseDependsUpon below
|
|
|
|
auto changedAndIntegrationResultNode =
|
|
integrateSourceFileDeclNode(integrand, swiftDepsOfJob, preexistingMatch);
|
|
|
|
ModuleDepGraphNode *const integratedNode =
|
|
changedAndIntegrationResultNode.second;
|
|
const bool hasNewExternalDependency =
|
|
recordWhatUseDependsUpon(g, integrand, integratedNode);
|
|
|
|
NullablePtr<ModuleDepGraphNode> changedNode =
|
|
changedAndIntegrationResultNode.first || hasNewExternalDependency
|
|
? integratedNode
|
|
: nullptr;
|
|
return changedNode;
|
|
}
|
|
|
|
std::pair<bool, ModuleDepGraphNode *>
|
|
ModuleDepGraph::integrateSourceFileDeclNode(
|
|
const SourceFileDepGraphNode *integrand, StringRef swiftDepsOfJob,
|
|
const PreexistingNodeIfAny preexistingMatch) {
|
|
|
|
if (!preexistingMatch.hasValue()) {
|
|
// The driver will be accessing nodes by the swiftDeps of the job,
|
|
// so pass that in.
|
|
auto *newNode =
|
|
integrateByCreatingANewNode(integrand, swiftDepsOfJob.str());
|
|
return std::make_pair(true, newNode); // New node
|
|
}
|
|
const auto where = preexistingMatch.getValue().first;
|
|
auto *match = preexistingMatch.getValue().second;
|
|
switch (where) {
|
|
case LocationOfPreexistingNode::here:
|
|
return std::make_pair(match->integrateFingerprintFrom(integrand), match);
|
|
|
|
case LocationOfPreexistingNode::nowhere:
|
|
// Some other file depended on this, but didn't know where it was.
|
|
moveNodeToDifferentFile(match, swiftDepsOfJob.str());
|
|
match->integrateFingerprintFrom(integrand);
|
|
return std::make_pair(true, match); // New Decl, assume changed
|
|
|
|
case LocationOfPreexistingNode::elsewhere:
|
|
auto *newNode =
|
|
integrateByCreatingANewNode(integrand, swiftDepsOfJob.str());
|
|
return std::make_pair(true, newNode); // New node;
|
|
}
|
|
llvm_unreachable("impossible");
|
|
}
|
|
|
|
ModuleDepGraphNode *ModuleDepGraph::integrateByCreatingANewNode(
|
|
const SourceFileDepGraphNode *integrand,
|
|
const Optional<std::string> swiftDepsForNewNode) {
|
|
assert(integrand->getIsProvides() &&
|
|
"Dependencies are arcs in the module graph");
|
|
const auto &key = integrand->getKey();
|
|
ModuleDepGraphNode *newNode = new ModuleDepGraphNode(
|
|
key, integrand->getFingerprint(), swiftDepsForNewNode);
|
|
addToMap(newNode);
|
|
return newNode;
|
|
}
|
|
|
|
bool ModuleDepGraph::recordWhatUseDependsUpon(
|
|
const SourceFileDepGraph &g,
|
|
const SourceFileDepGraphNode *sourceFileUseNode,
|
|
ModuleDepGraphNode *moduleUseNode) {
|
|
bool useHasNewExternalDependency = false;
|
|
g.forEachDefDependedUponBy(
|
|
sourceFileUseNode, [&](const SourceFileDepGraphNode *def) {
|
|
const bool isNewUse =
|
|
usesByDef[def->getKey()].insert(moduleUseNode).second;
|
|
if (isNewUse) {
|
|
StringRef externalSwiftDeps = def->getKey().getName();
|
|
if (def->getKey().getKind() == NodeKind::externalDepend) {
|
|
externalDependencies.insert(externalSwiftDeps.str());
|
|
useHasNewExternalDependency = true;
|
|
} else if (def->getKey().getKind() ==
|
|
NodeKind::incrementalExternalDepend) {
|
|
incrementalExternalDependencies.insert(externalSwiftDeps.str());
|
|
}
|
|
}
|
|
});
|
|
return useHasNewExternalDependency;
|
|
}
|
|
|
|
void ModuleDepGraph::eraseNodeFromGraphAndFreeIt(ModuleDepGraphNode *n) {
|
|
eraseNodeFromJob(n);
|
|
eraseNodeFromCurrentPathIfTracing(n);
|
|
eraseNodeFromDependencyPathToJobs(n);
|
|
|
|
delete n;
|
|
}
|
|
|
|
void ModuleDepGraph::eraseNodeFromJob(ModuleDepGraphNode *n) {
|
|
eraseNodeFromMap(n);
|
|
eraseUsesOfNode(n);
|
|
}
|
|
|
|
//==============================================================================
|
|
// MARK: ModuleDepGraph access
|
|
//==============================================================================
|
|
void ModuleDepGraph::forEachUseOf(
|
|
const ModuleDepGraphNode *def,
|
|
function_ref<void(ModuleDepGraphNode *)> fn) const {
|
|
auto iter = usesByDef.find(def->getKey());
|
|
if (iter == usesByDef.end())
|
|
return;
|
|
for (ModuleDepGraphNode *useNode : iter->second)
|
|
fn(useNode);
|
|
// Add in implicit interface->implementation dependency
|
|
forCorrespondingImplementationOfProvidedInterface(def, fn);
|
|
}
|
|
|
|
void ModuleDepGraph::forCorrespondingImplementationOfProvidedInterface(
|
|
const ModuleDepGraphNode *interfaceNode,
|
|
function_ref<void(ModuleDepGraphNode *)> fn) const {
|
|
if (!interfaceNode->getKey().isInterface() || !interfaceNode->getIsProvides())
|
|
return;
|
|
const auto swiftDeps = interfaceNode->getSwiftDeps().getValue();
|
|
const auto &interfaceKey = interfaceNode->getKey();
|
|
const DependencyKey implementationKey(
|
|
interfaceKey.getKind(), DeclAspect::implementation,
|
|
interfaceKey.getContext().str(), interfaceKey.getName().str());
|
|
if (const auto implementationNode =
|
|
nodeMap.find(swiftDeps, implementationKey))
|
|
fn(implementationNode.getValue());
|
|
}
|
|
|
|
void ModuleDepGraph::forEachNode(
|
|
function_ref<void(ModuleDepGraphNode *)> fn) const {
|
|
nodeMap.forEachEntry([&](const std::string &, const DependencyKey &,
|
|
ModuleDepGraphNode *n) { fn(n); });
|
|
}
|
|
|
|
void ModuleDepGraph::forEachMatchingNode(
|
|
const DependencyKey &key,
|
|
function_ref<void(ModuleDepGraphNode *)> fn) const {
|
|
nodeMap.forEachValueMatching(
|
|
key, [&](const std::string &, ModuleDepGraphNode *n) { fn(n); });
|
|
}
|
|
|
|
void ModuleDepGraph::forEachArc(
|
|
function_ref<void(const ModuleDepGraphNode *, const ModuleDepGraphNode *)>
|
|
fn) const {
|
|
forEachNode([&](const ModuleDepGraphNode *defNode) {
|
|
forEachUseOf(defNode, [&](const ModuleDepGraphNode *const useNode) {
|
|
fn(defNode, useNode);
|
|
});
|
|
});
|
|
}
|
|
|
|
void ModuleDepGraph::forEachNodeInJob(
|
|
StringRef swiftDeps, function_ref<void(ModuleDepGraphNode *)> fn) const {
|
|
if (const auto *nodesByKeys = nodeMap.find(swiftDeps.str()).getPtrOrNull()) {
|
|
for (const auto &keyAndNode : *nodesByKeys)
|
|
fn(keyAndNode.second);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// MARK: ModuleDepGraph traversal
|
|
//==============================================================================
|
|
|
|
void ModuleDepGraph::findPreviouslyUntracedDependents(
|
|
std::vector<ModuleDepGraphNode *> &foundDependents,
|
|
ModuleDepGraphNode *definition) {
|
|
|
|
if (definition->getHasBeenTraced())
|
|
return;
|
|
definition->setHasBeenTraced();
|
|
|
|
foundDependents.push_back(definition);
|
|
|
|
// If this use also provides something, follow it
|
|
if (!definition->getIsProvides())
|
|
return; // No need to look for uses; provides nothing
|
|
|
|
size_t pathLengthAfterArrival = traceArrival(definition);
|
|
|
|
forEachUseOf(definition, [&](ModuleDepGraphNode *u) {
|
|
// If this use also provides something, follow it
|
|
findPreviouslyUntracedDependents(foundDependents, u);
|
|
});
|
|
|
|
traceDeparture(pathLengthAfterArrival);
|
|
}
|
|
|
|
size_t ModuleDepGraph::traceArrival(const ModuleDepGraphNode *visitedNode) {
|
|
if (!currentPathIfTracing.hasValue())
|
|
return 0;
|
|
auto ¤tPath = currentPathIfTracing.getValue();
|
|
currentPath.push_back(visitedNode);
|
|
const auto visitedSwiftDepsIfAny = visitedNode->getSwiftDeps();
|
|
recordDependencyPathToJob(currentPath, getJob(visitedSwiftDepsIfAny));
|
|
return currentPath.size();
|
|
}
|
|
|
|
void ModuleDepGraph::recordDependencyPathToJob(
|
|
const std::vector<const ModuleDepGraphNode *> &pathToJob,
|
|
const driver::Job *dependentJob) {
|
|
dependencyPathsToJobs.insert(std::make_pair(dependentJob, pathToJob));
|
|
}
|
|
|
|
void ModuleDepGraph::traceDeparture(size_t pathLengthAfterArrival) {
|
|
if (!currentPathIfTracing)
|
|
return;
|
|
auto ¤tPath = currentPathIfTracing.getValue();
|
|
assert(pathLengthAfterArrival == currentPath.size() &&
|
|
"Path must be maintained throughout recursive visits.");
|
|
currentPath.pop_back();
|
|
}
|
|
|
|
// =============================================================================
|
|
// MARK: Emitting Dot file for ModuleDepGraph
|
|
// =============================================================================
|
|
|
|
void ModuleDepGraph::emitDotFileForJob(DiagnosticEngine &diags,
|
|
const Job *job) {
|
|
emitDotFile(diags, getSwiftDeps(job));
|
|
}
|
|
|
|
void ModuleDepGraph::emitDotFile(DiagnosticEngine &diags, StringRef baseName) {
|
|
unsigned seqNo = dotFileSequenceNumber[baseName.str()]++;
|
|
std::string fullName =
|
|
baseName.str() + "-post-integration." + std::to_string(seqNo) + ".dot";
|
|
withOutputFile(diags, fullName, [&](llvm::raw_ostream &out) {
|
|
emitDotFile(out);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void ModuleDepGraph::emitDotFile(llvm::raw_ostream &out) {
|
|
FrontendStatsTracer tracer(stats, "fine-grained-dependencies-emitDotFile");
|
|
DotFileEmitter<ModuleDepGraph>(out, *this, true, false).emit();
|
|
}
|
|
|
|
//==============================================================================
|
|
// MARK: ModuleDepGraph debugging
|
|
//==============================================================================
|
|
|
|
void ModuleDepGraphNode::dump(llvm::raw_ostream &out) const {
|
|
DepGraphNode::dump(out);
|
|
if (getIsProvides())
|
|
out << " swiftDeps: <" << getSwiftDepsOfProvides() << ">\n";
|
|
else
|
|
out << " no swiftDeps\n";
|
|
}
|
|
|
|
void ModuleDepGraphNode::dump() const {
|
|
DepGraphNode::dump();
|
|
if (getIsProvides())
|
|
llvm::errs() << " swiftDeps: <" << getSwiftDepsOfProvides() << ">\n";
|
|
else
|
|
llvm::errs() << " no swiftDeps\n";
|
|
}
|
|
|
|
bool ModuleDepGraph::verify() const {
|
|
FrontendStatsTracer tracer(stats, "fine-grained-dependencies-verify");
|
|
verifyNodeMapEntries();
|
|
verifyCanFindEachJob();
|
|
verifyEachJobInGraphIsTracked();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ModuleDepGraph::verifyNodeMapEntries() const {
|
|
FrontendStatsTracer tracer(stats,
|
|
"fine-grained-dependencies-verifyNodeMapEntries");
|
|
// TODO: disable when not debugging
|
|
std::array<
|
|
std::unordered_map<DependencyKey,
|
|
std::unordered_map<std::string, ModuleDepGraphNode *>>,
|
|
2>
|
|
nodesSeenInNodeMap;
|
|
nodeMap.verify([&](const std::string &swiftDepsString,
|
|
const DependencyKey &key, ModuleDepGraphNode *n,
|
|
unsigned submapIndex) {
|
|
verifyNodeMapEntry(nodesSeenInNodeMap, swiftDepsString, key, n,
|
|
submapIndex);
|
|
});
|
|
}
|
|
|
|
void ModuleDepGraph::verifyNodeMapEntry(
|
|
std::array<std::unordered_map<
|
|
DependencyKey,
|
|
std::unordered_map<std::string, ModuleDepGraphNode *>>,
|
|
2> &nodesSeenInNodeMap,
|
|
const std::string &swiftDepsString, const DependencyKey &key,
|
|
ModuleDepGraphNode *n, const unsigned submapIndex) const {
|
|
verifyNodeIsUniqueWithinSubgraph(nodesSeenInNodeMap, swiftDepsString, key, n,
|
|
submapIndex);
|
|
verifyNodeIsInRightEntryInNodeMap(swiftDepsString, key, n);
|
|
key.verify();
|
|
verifyExternalDependencyUniqueness(key);
|
|
}
|
|
|
|
void ModuleDepGraph::verifyNodeIsUniqueWithinSubgraph(
|
|
std::array<std::unordered_map<
|
|
DependencyKey,
|
|
std::unordered_map<std::string, ModuleDepGraphNode *>>,
|
|
2> &nodesSeenInNodeMap,
|
|
const std::string &swiftDepsString, const DependencyKey &key,
|
|
ModuleDepGraphNode *const n, const unsigned submapIndex) const {
|
|
assert(submapIndex < nodesSeenInNodeMap.size() &&
|
|
"submapIndex is out of bounds.");
|
|
auto iterInserted = nodesSeenInNodeMap[submapIndex][n->getKey()].insert(
|
|
std::make_pair(n->getSwiftDepsForMapKey(), n));
|
|
if (!iterInserted.second) {
|
|
llvm_unreachable("duplicate driver keys");
|
|
}
|
|
}
|
|
|
|
void ModuleDepGraph::verifyNodeIsInRightEntryInNodeMap(
|
|
const std::string &swiftDepsString, const DependencyKey &key,
|
|
const ModuleDepGraphNode *const n) const {
|
|
const DependencyKey &nodeKey = n->getKey();
|
|
const Optional<std::string> swiftDeps =
|
|
swiftDepsString.empty() ? None : Optional<std::string>(swiftDepsString);
|
|
(void)nodeKey;
|
|
(void)swiftDeps;
|
|
assert(n->getSwiftDeps() == swiftDeps ||
|
|
mapCorruption("Node misplaced for swiftDeps"));
|
|
assert(nodeKey == key || mapCorruption("Node misplaced for key"));
|
|
}
|
|
|
|
void ModuleDepGraph::verifyExternalDependencyUniqueness(
|
|
const DependencyKey &key) const {
|
|
assert((key.getKind() != NodeKind::externalDepend ||
|
|
externalDependencies.count(key.getName().str()) == 1) &&
|
|
"Ensure each external dependency is tracked exactly once");
|
|
assert((key.getKind() != NodeKind::incrementalExternalDepend ||
|
|
incrementalExternalDependencies.count(key.getName().str()) == 1) &&
|
|
"Ensure each incremental external dependency is tracked exactly once");
|
|
}
|
|
|
|
void ModuleDepGraph::verifyCanFindEachJob() const {
|
|
FrontendStatsTracer tracer(stats,
|
|
"fine-grained-dependencies-verifyCanFindEachJob");
|
|
for (const auto &p : jobsBySwiftDeps) {
|
|
getJob(p.first);
|
|
}
|
|
}
|
|
|
|
void ModuleDepGraph::verifyEachJobInGraphIsTracked() const {
|
|
FrontendStatsTracer tracer(
|
|
stats, "fine-grained-dependencies-verifyEachJobIsTracked");
|
|
nodeMap.forEachKey1(
|
|
[&](const std::string &swiftDeps, const typename NodeMap::Key2Map &) {
|
|
ensureJobIsTracked(swiftDeps);
|
|
});
|
|
}
|
|
|
|
/// Dump the path(s) that led to \p node.
|
|
/// TODO: break up
|
|
void ModuleDepGraph::printPath(raw_ostream &out,
|
|
const driver::Job *jobToBeBuilt) const {
|
|
assert(currentPathIfTracing.hasValue() &&
|
|
"Cannot print paths of paths weren't tracked.");
|
|
|
|
for (auto paths = dependencyPathsToJobs.find(jobToBeBuilt);
|
|
paths != dependencyPathsToJobs.end() && paths->first == jobToBeBuilt;
|
|
++paths) {
|
|
const auto &path = paths->second;
|
|
bool first = true;
|
|
out << "\t";
|
|
for (const ModuleDepGraphNode *n : path) {
|
|
if (first)
|
|
first = false;
|
|
else
|
|
out << " -> ";
|
|
|
|
const StringRef providerName = getProvidingFilename(n->getSwiftDeps());
|
|
printOneNodeOfPath(out, n->getKey(), providerName);
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
StringRef ModuleDepGraph::getProvidingFilename(
|
|
const Optional<std::string> &swiftDeps) const {
|
|
if (!swiftDeps)
|
|
return "<unknown";
|
|
auto ext = llvm::sys::path::extension(*swiftDeps);
|
|
if (file_types::lookupTypeForExtension(ext) ==
|
|
file_types::TY_SwiftModuleFile) {
|
|
return *swiftDeps;
|
|
}
|
|
const StringRef inputName =
|
|
llvm::sys::path::filename(getJob(swiftDeps)->getFirstSwiftPrimaryInput());
|
|
// FineGrainedDependencyGraphTests work with simulated jobs with empty
|
|
// input names.
|
|
return !inputName.empty() ? inputName : StringRef(swiftDeps.getValue());
|
|
}
|
|
|
|
void ModuleDepGraph::printOneNodeOfPath(raw_ostream &out,
|
|
const DependencyKey &key,
|
|
const StringRef filename) {
|
|
switch (key.getKind()) {
|
|
case NodeKind::topLevel:
|
|
out << key.aspectName() << " of top-level name '" << key.humanReadableName()
|
|
<< "' in " << filename;
|
|
break;
|
|
case NodeKind::nominal:
|
|
out << key.aspectName() << " of type '" << key.humanReadableName()
|
|
<< "' in " << filename;
|
|
break;
|
|
case NodeKind::potentialMember:
|
|
out << key.aspectName() << " of non-private members '"
|
|
<< key.humanReadableName() << "' in " << filename;
|
|
break;
|
|
case NodeKind::member:
|
|
out << key.aspectName() << " of member '" << key.humanReadableName()
|
|
<< "' in " << filename;
|
|
break;
|
|
case NodeKind::dynamicLookup:
|
|
out << key.aspectName() << " of AnyObject member '"
|
|
<< key.humanReadableName() << "' in " << filename;
|
|
break;
|
|
case NodeKind::externalDepend:
|
|
out << filename << " depends on " << key.aspectName() << " of module '"
|
|
<< key.humanReadableName() << "'";
|
|
break;
|
|
case NodeKind::incrementalExternalDepend:
|
|
out << filename << " depends on " << key.aspectName()
|
|
<< " of incremental module '" << key.humanReadableName() << "'";
|
|
break;
|
|
case NodeKind::sourceFileProvide:
|
|
out << key.aspectName() << " of source file " << key.humanReadableName();
|
|
break;
|
|
default:
|
|
llvm_unreachable("unknown NodeKind");
|
|
}
|
|
}
|