blob: 9c1f610e6597ef281a760390a1e1c5bde32571de [file] [log] [blame]
//===--- ExperimentalDependencyModuleDepGraph.h ------------------*- C++-*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef ExperimentalDependencyGraph_h
#define ExperimentalDependencyGraph_h
#include "swift/AST/ExperimentalDependencies.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/OptionSet.h"
#include "swift/Driver/DependencyGraph.h"
#include "swift/Driver/Job.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PointerLikeTypeTraits.h"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// Declarations for the portion experimental dependency system used by the
// driver.
namespace swift {
namespace experimental_dependencies {
//==============================================================================
// MARK: ModuleDepGraphNode
//==============================================================================
/// A node in the DriverDependencyGraph
/// Keep separate type from Node for type-checking.
class ModuleDepGraphNode : public DepGraphNode {
/// The swiftDeps file that holds this entity.
/// If more than one source file has the same DependencyKey, then there
/// will be one node for each in the driver.
Optional<std::string> swiftDeps;
public:
ModuleDepGraphNode(const DependencyKey &key,
Optional<std::string> fingerprint,
Optional<std::string> swiftDeps)
: DepGraphNode(key, fingerprint), swiftDeps(swiftDeps) {}
/// Integrate \p integrand's fingerprint into \p dn.
/// \returns true if there was a change requiring recompilation.
bool integrateFingerprintFrom(const SourceFileDepGraphNode *integrand) {
if (getFingerprint() == integrand->getFingerprint())
return false;
setFingerprint(integrand->getFingerprint());
return true;
}
bool operator==(const ModuleDepGraphNode &other) const {
return static_cast<DepGraphNode>(*this) ==
static_cast<DepGraphNode>(other) &&
getSwiftDeps() == other.getSwiftDeps();
}
const Optional<std::string> &getSwiftDeps() const { return swiftDeps; }
bool assertImplementationMustBeInAFile() const {
assert((getSwiftDeps().hasValue() || !getKey().isImplementation()) &&
"Implementations must be in some file.");
return true;
}
std::string humanReadableName() const {
StringRef where =
!getSwiftDeps().hasValue()
? ""
: llvm::sys::path::filename(getSwiftDeps().getValue());
return DepGraphNode::humanReadableName(where);
}
void dump() const;
bool assertProvidedEntityMustBeInAFile() const {
assert((getSwiftDeps().hasValue() || !getKey().isImplementation()) &&
"Implementations must be in some file.");
return true;
}
/// Nodes can move from file to file when the driver reads the result of a
/// compilation.
void setSwiftDeps(Optional<std::string> s) { swiftDeps = s; }
bool getIsProvides() const { return getSwiftDeps().hasValue(); }
};
/// A placeholder allowing the experimental system to fit into the driver
/// without changing as much code.
class DependencyGraphImpl {
public:
/// Use the status quo LoadResult for now.
using LoadResult = typename swift::DependencyGraphImpl::LoadResult;
};
//==============================================================================
// MARK: ModuleDepGraph
//==============================================================================
/// See \ref Node in ExperimentalDependencies.h
class ModuleDepGraph {
/// Find nodes, first by the swiftDeps file, then by key.
/// Supports searching specific files for a node matching a key.
/// Such a search is useful when integrating nodes from a given source file to
/// see which nodes were there before integration and so might have
/// disappeared.
///
/// Some nodes are in no file, for instance a dependency on a Decl in a source
/// file whose swiftdeps has not been read yet. For these, the filename is the
/// empty string.
///
/// Don't add to this collection directly; use \ref addToMap
/// instead because it enforces the correspondence with the swiftFileDeps
/// field of the node.
/// TODO: Fix above comment
///
/// Sadly, cannot use an optional string for a key.
using NodeMap =
BiIndexedTwoStageMap<std::string, DependencyKey, ModuleDepGraphNode *>;
NodeMap nodeMap;
/// Since dependency keys use baseNames, they are coarser than individual
/// decls. So two decls might map to the same key. Given a use, which is
/// denoted by a node, the code needs to find the files to recompile. So, the
/// key indexes into the nodeMap, and that yields a submap of nodes keyed by
/// file. The set of keys in the submap are the files that must be recompiled
/// for the use.
/// (In a given file, only one node exists with a given key, but in the future
/// that would need to change if/when we can recompile a smaller unit than a
/// source file.)
/// Tracks def-use relationships by DependencyKey.
std::unordered_map<DependencyKey, std::unordered_set<ModuleDepGraphNode *>>
usesByDef;
// Supports requests from the driver to getExternalDependencies.
std::unordered_set<std::string> externalDependencies;
/// The new version of "Marked."
/// Record cascading jobs by swiftDepsFilename because that's what
/// nodes store directly.
///
/// The status quo system uses "cascade" for the following:
/// Def1 -> def2 -> def3, where arrows are uses, so 3 depends on 2 which
/// depends on 1. The first use is said to "cascade" if when def1 changes,
/// def3 is dirtied.
/// TODO: Move cascadingJobs out of the graph, ultimately.
/// If marked, any Job that depends on me must be rebuilt after compiling me
/// if I have changed.
std::unordered_set<std::string> cascadingJobs;
/// Keyed by swiftdeps filename, so we can get back to Jobs.
std::unordered_map<std::string, const driver::Job *> jobsBySwiftDeps;
/// For debugging, a dot file can be emitted. This file can be read into
/// various graph-drawing programs.
/// The driver emits this file into the same directory as the swiftdeps
/// files it reads, so when reading a file compute the base path here.
/// Initialize to empty in case no swiftdeps file has been read.
SmallString<128> driverDotFileBasePath = StringRef("");
/// For debugging, the driver can write out a dot file, for instance when a
/// Frontend swiftdeps is read and integrated. In order to keep subsequent
/// files for the same name distinct, keep a sequence number for each name.
std::unordered_map<std::string, unsigned> dotFileSequenceNumber;
const bool verifyExperimentalDependencyGraphAfterEveryImport;
const bool emitExperimentalDependencyDotFileAfterEveryImport;
/// If tracing dependencies, holds the current node traversal path
Optional<std::vector<const ModuleDepGraphNode *>> currentPathIfTracing;
/// If tracing dependencies, record the node sequence
std::unordered_multimap<const driver::Job *,
std::vector<const ModuleDepGraphNode *>>
dependencyPathsToJobs;
/// For helping with performance tuning, may be null:
UnifiedStatsReporter *const stats;
/// Encapsulate the invariant between where the node resides in
/// nodesBySwiftDepsFile and the swiftDeps node instance variable here.
void addToMap(ModuleDepGraphNode *n) {
nodeMap.insert(n->getSwiftDeps().getValueOr(std::string()), n->getKey(), n);
}
/// When integrating a SourceFileDepGraph, there might be a node representing
/// a Decl that had previously been read as an expat, that is a node
/// representing a Decl in no known file (to that point). (Recall the the
/// Frontend processes name lookups as dependencies, but does not record in
/// which file the name was found.) In such a case, it is necessary to move
/// the node to the proper collection.
void moveNodeToDifferentFile(ModuleDepGraphNode *n,
Optional<std::string> newFile) {
eraseNodeFromMap(n);
n->setSwiftDeps(newFile);
addToMap(n);
}
/// Remove node from nodeMap, check invariants.
ModuleDepGraphNode *eraseNodeFromMap(ModuleDepGraphNode *nodeToErase) {
ModuleDepGraphNode *nodeActuallyErased = nodeMap.findAndErase(
nodeToErase->getSwiftDeps().getValueOr(std::string()),
nodeToErase->getKey());
assert(
nodeToErase == nodeActuallyErased ||
mapCorruption("Node found from key must be same as node holding key."));
return nodeToErase;
}
static StringRef getSwiftDeps(const driver::Job *cmd) {
return cmd->getOutput().getAdditionalOutputForType(
file_types::TY_SwiftDeps);
}
const driver::Job *getJob(Optional<std::string> swiftDeps) const {
assert(swiftDeps.hasValue() && "Don't call me for expats.");
auto iter = jobsBySwiftDeps.find(swiftDeps.getValue());
assert(iter != jobsBySwiftDeps.end() && "All jobs should be tracked.");
assert(getSwiftDeps(iter->second) == swiftDeps.getValue() &&
"jobsBySwiftDeps should be inverse of getSwiftDeps.");
return iter->second;
}
public:
/// For templates such as DotFileEmitter.
using NodeType = ModuleDepGraphNode;
/// \p stats may be null
ModuleDepGraph(const bool verifyExperimentalDependencyGraphAfterEveryImport,
const bool emitExperimentalDependencyDotFileAfterEveryImport,
const bool shouldTraceDependencies,
UnifiedStatsReporter *stats)
: verifyExperimentalDependencyGraphAfterEveryImport(
verifyExperimentalDependencyGraphAfterEveryImport),
emitExperimentalDependencyDotFileAfterEveryImport(
emitExperimentalDependencyDotFileAfterEveryImport),
currentPathIfTracing(
shouldTraceDependencies
? llvm::Optional<std::vector<const ModuleDepGraphNode *>>(
std::vector<const ModuleDepGraphNode *>())
: None),
stats(stats) {
assert(verify() && "ModuleDepGraph should be fine when created");
}
DependencyGraphImpl::LoadResult loadFromPath(const driver::Job *, StringRef,
DiagnosticEngine &);
/// For the dot file.
std::string getGraphID() const { return "driver"; }
void forEachUseOf(const ModuleDepGraphNode *def,
function_ref<void(const ModuleDepGraphNode *use)>);
void forEachNode(function_ref<void(const ModuleDepGraphNode *)>) const;
void forEachArc(function_ref<void(const ModuleDepGraphNode *def,
const ModuleDepGraphNode *use)>) const;
/// Call \p fn for each node whose key matches \p key.
void
forEachMatchingNode(const DependencyKey &key,
function_ref<void(const ModuleDepGraphNode *)>) const;
public:
// This section contains the interface to the status quo code in the driver.
/// Interface to status quo code in the driver.
bool isMarked(const driver::Job *) const;
/// Given a "cascading" job, that is a job whose dependents must be recompiled
/// when this job is recompiled, Compute two sets of jobs:
/// 1. Return value (via visited) is the set of jobs needing recompilation
/// after this one, and
/// 2. Jobs not previously known to need dependencies reexamined after they
/// are recompiled. Such jobs are added to the \ref cascadingJobs set, and
/// accessed via \ref isMarked.
void markTransitive(
SmallVectorImpl<const driver::Job *> &consequentJobsToRecompile,
const driver::Job *jobToBeRecompiled, const void *ignored = nullptr);
/// "Mark" this node only.
bool markIntransitive(const driver::Job *);
/// Record a new (to this graph) Job.
void addIndependentNode(const driver::Job *);
std::vector<std::string> getExternalDependencies() const;
void markExternal(SmallVectorImpl<const driver::Job *> &uses,
StringRef externalDependency);
/// Return true or abort
bool verify() const;
/// Don't want to do this after every integration--too slow--
/// So export this hook to the driver.
bool emitDotFileAndVerify(DiagnosticEngine &);
private:
void verifyNodeMapEntries() const;
/// Called for each \ref nodeMap entry during verification.
/// \p nodesSeenInNodeMap ensures that nodes are unique in each submap
/// \p swiftDepsString is the swiftdeps file name in the map
/// \p key is the DependencyKey in the map
/// \p n is the node for that map entry
void verifyNodeMapEntry(
std::array<std::unordered_map<
DependencyKey,
std::unordered_map<std::string, ModuleDepGraphNode *>>,
2> &nodesSeenInNodeMap,
const std::string &swiftDepsString, const DependencyKey &,
ModuleDepGraphNode *, unsigned submapIndex) const;
/// See ModuleDepGraph::verifyNodeMapEntry for argument descriptions
void verifyNodeIsUniqueWithinSubgraph(
std::array<std::unordered_map<
DependencyKey,
std::unordered_map<std::string, ModuleDepGraphNode *>>,
2> &nodesSeenInNodeMap,
const std::string &swiftDepsString, const DependencyKey &,
ModuleDepGraphNode *, unsigned submapIndex) const;
/// See ModuleDepGraph::verifyNodeMapEntry for argument descriptions
void verifyNodeIsInRightEntryInNodeMap(const std::string &swiftDepsString,
const DependencyKey &,
const ModuleDepGraphNode *) const;
void verifyExternalDependencyUniqueness(const DependencyKey &) const;
void verifyCanFindEachJob() const;
void verifyEachJobInGraphIsTracked() const;
static bool mapCorruption(const char *msg) { llvm_unreachable(msg); }
/// Use the known swiftDeps to find a directory for
/// the job-independent dot file.
std::string computePathForDotFile() const;
/// Read a SourceFileDepGraph belonging to \p job from \p buffer
/// and integrate it into the ModuleDepGraph.
/// Used both the first time, and to reload the SourceFileDepGraph.
/// If any changes were observed, indicate same in the return vale.
DependencyGraphImpl::LoadResult loadFromBuffer(const driver::Job *,
llvm::MemoryBuffer &);
/// Integrate a SourceFileDepGraph into the receiver.
/// Integration happens when the driver needs to read SourceFileDepGraph.
DependencyGraphImpl::LoadResult integrate(const SourceFileDepGraph &);
enum class LocationOfPreexistingNode { nowhere, here, elsewhere };
typedef Optional<std::pair<LocationOfPreexistingNode, ModuleDepGraphNode *>>
PreexistingNodeIfAny;
/// Find the preexisting node here that best matches the integrand.
PreexistingNodeIfAny
findPreexistingMatch(StringRef swiftDepsOfCompilationToBeIntegrated,
const SourceFileDepGraphNode *integrand);
/// Integrate the \p integrand into the receiver.
/// Return a bool indicating if this node represents a change that must be
/// propagated.
bool
integrateSourceFileDepGraphNode(const SourceFileDepGraph &g,
const SourceFileDepGraphNode *integrand,
const PreexistingNodeIfAny preexistingMatch);
/// Integrate the \p integrand, a node that represents a Decl in the swiftDeps
/// file being integrated. \p preexistingNodeInPlace holds the node
/// representing the same Decl that already exists, if there is one. \p
/// prexisintExpat holds a node with the same key that already exists, but was
/// not known to reside in any swiftDeps file. Return a bool indicating if
/// this node represents a change that must be propagated, and the integrated
/// ModuleDepGraphNode.
std::pair<bool, ModuleDepGraphNode *>
integrateSourceFileDeclNode(const SourceFileDepGraphNode *integrand,
StringRef swiftDepsOfSourceFileGraph,
const PreexistingNodeIfAny preexistingMatch);
/// Create a brand-new ModuleDepGraphNode to integrate \p integrand.
ModuleDepGraphNode *
integrateByCreatingANewNode(const SourceFileDepGraphNode *integrand,
Optional<std::string> swiftDepsForNewNode);
/// After importing a provides node from the frontend, record its
/// dependencies.
void recordWhatUseDependsUpon(const SourceFileDepGraph &g,
const SourceFileDepGraphNode *sourceFileUseNode,
ModuleDepGraphNode *moduleUseNode);
/// If the programmer removes a Decl from a source file, the corresponding
/// ModuleDepGraphNode needs to be removed.
void removeNode(ModuleDepGraphNode *);
/// Given a definition node, and a list of already found dependents,
/// recursively add transitive closure of dependents of the definition
/// into the already found dependents.
/// Also record any dependents that "cascade", i.e. whose dependencies must be
/// recomputed after recompilation so that its dependents can be recompiled.
void findDependentNodesAndRecordCascadingOnes(
std::unordered_set<const ModuleDepGraphNode *> &foundDependents,
const ModuleDepGraphNode *definition);
void computeUniqueJobsFromNodes(
SmallVectorImpl<const driver::Job *> &uniqueJobs,
const std::unordered_set<const ModuleDepGraphNode *> &nodes);
/// Record a visit to this node for later dependency printing
size_t traceArrival(const ModuleDepGraphNode *visitedNode);
/// Record end of visit to this node.
void traceDeparture(size_t pathLengthAfterArrival);
/// For printing why a Job was compiled, record how it was found.
void recordDependencyPathToJob(
const std::vector<const ModuleDepGraphNode *> &pathToJob,
const driver::Job *dependentJob);
/// Return true if job did not cascade before
bool rememberThatJobCascades(StringRef swiftDeps) {
return cascadingJobs.insert(swiftDeps).second;
}
/// For debugging, write out the graph to a dot file.
/// \p diags may be null if no diagnostics are needed.
void emitDotFileForJob(DiagnosticEngine &, const driver::Job *);
void emitDotFile(DiagnosticEngine &, StringRef baseName);
void emitDotFile() { emitDotFile(llvm::errs()); }
void emitDotFile(llvm::raw_ostream &);
bool ensureJobIsTracked(const std::string &swiftDeps) const {
assert(swiftDeps.empty() || getJob(swiftDeps));
return true;
}
public:
/// Dump the path that led to \p node.
void printPath(raw_ostream &out, const driver::Job *node) const;
private:
bool isCurrentPathForTracingEmpty() const {
return !currentPathIfTracing.hasValue() || currentPathIfTracing->empty();
}
};
} // namespace experimental_dependencies
} // namespace swift
#endif /* ExperimentalDependencyGraph_h */