//===--- 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 #include #include #include // 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 swiftDeps; public: ModuleDepGraphNode(const DependencyKey &key, Optional fingerprint, Optional 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(*this) == static_cast(other) && getSwiftDeps() == other.getSwiftDeps(); } const Optional &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 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; 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> usesByDef; // Supports requests from the driver to getExternalDependencies. std::unordered_set 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 cascadingJobs; /// Keyed by swiftdeps filename, so we can get back to Jobs. std::unordered_map 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 dotFileSequenceNumber; const bool verifyExperimentalDependencyGraphAfterEveryImport; const bool emitExperimentalDependencyDotFileAfterEveryImport; /// If tracing dependencies, holds the current node traversal path Optional> currentPathIfTracing; /// If tracing dependencies, record the node sequence std::unordered_multimap> 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 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 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()) : 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 forEachNode(function_ref) const; void forEachArc(function_ref) const; /// Call \p fn for each node whose key matches \p key. void forEachMatchingNode(const DependencyKey &key, function_ref) 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 &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 getExternalDependencies() const; void markExternal(SmallVectorImpl &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>, 2> &nodesSeenInNodeMap, const std::string &swiftDepsString, const DependencyKey &, ModuleDepGraphNode *, unsigned submapIndex) const; /// See ModuleDepGraph::verifyNodeMapEntry for argument descriptions void verifyNodeIsUniqueWithinSubgraph( std::array>, 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> 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 integrateSourceFileDeclNode(const SourceFileDepGraphNode *integrand, StringRef swiftDepsOfSourceFileGraph, const PreexistingNodeIfAny preexistingMatch); /// Create a brand-new ModuleDepGraphNode to integrate \p integrand. ModuleDepGraphNode * integrateByCreatingANewNode(const SourceFileDepGraphNode *integrand, Optional 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 &foundDependents, const ModuleDepGraphNode *definition); void computeUniqueJobsFromNodes( SmallVectorImpl &uniqueJobs, const std::unordered_set &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 &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 */