//===---- FineGrainedDependencyModuleDepGraph.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 SWIFT_DRIVER_FINE_GRAINED_DEPENDENCY_DRIVER_GRAPH_H
#define SWIFT_DRIVER_FINE_GRAINED_DEPENDENCY_DRIVER_GRAPH_H

#include "swift/AST/FineGrainedDependencies.h"
#include "swift/Basic/Debug.h"
#include "swift/Basic/Fingerprint.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/OptionSet.h"
#include "swift/Driver/Job.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.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 fine-grained dependency system used by the
// driver.

namespace swift {

class UnifiedStatsReporter;

namespace fine_grained_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 iff this is a provides node.
  /// If more than one source file has the same DependencyKey, then there
  /// will be one node for each in the driver, distinguished by this field.
  Optional<std::string> swiftDeps;

  /// When finding transitive dependents, this node has been traversed.
  bool hasBeenTracedAsADependent = false;

public:
  ModuleDepGraphNode(const DependencyKey &key,
                     Optional<Fingerprint> fingerprint,
                     Optional<std::string> swiftDeps)
      : DepGraphNode(key, fingerprint), swiftDeps(swiftDeps) {}

  bool getHasBeenTraced() const { return hasBeenTracedAsADependent; }
  void setHasBeenTraced() { hasBeenTracedAsADependent = true; }
  void clearHasBeenTraced() { hasBeenTracedAsADependent = false; }

  /// 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; }

  std::string getSwiftDepsOrEmpty() const {
    return getSwiftDeps().getValueOr(std::string());
  }

  std::string getSwiftDepsForMapKey() const {
    // Use the empty string for nodes whose source file is unknown,
    // i.e. depends. (Known depends are represented by arcs, not nodes.)
    return getSwiftDepsOrEmpty();
  }

  const std::string &getSwiftDepsOfProvides() const {
    return getSwiftDeps().getValue();
  }

  /// 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(); }

  /// Return true if this node describes a definition for which the job is known
  bool isDefinedInAKnownFile() const { return getIsProvides(); }

  bool doesNodeProvideAnInterface() const {
    return getKey().isInterface() && getIsProvides();
  }

  bool assertImplementationMustBeInAFile() const {
    assert((isDefinedInAKnownFile() || !getKey().isImplementation()) &&
           "Implementations must be in some file.");
    return true;
  }

  std::string humanReadableName() const {
    StringRef where = !getIsProvides()
                          ? ""
                          : llvm::sys::path::filename(getSwiftDepsOfProvides());
    return DepGraphNode::humanReadableName(where);
  }

  void dump(raw_ostream &) const;

  SWIFT_DEBUG_DUMP;
};

//==============================================================================
// MARK: ModuleDepGraph
//==============================================================================

/// See \ref Node in FineGrainedDependencies.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;

  /// 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;

public:
  bool verifyFineGrainedDependencyGraphAfterEveryImport;
  bool emitFineGrainedDependencyDotFileAfterEveryImport;

private:
  /// If tracing dependencies, holds a vector used to hold the current path
  /// def - use/def - use/def - ...
  Optional<std::vector<const ModuleDepGraphNode *>> currentPathIfTracing;

  /// If tracing dependencies, holds the sequence of defs used to get to the job
  /// that is the key
  std::unordered_multimap<const driver::Job *,
                          std::vector<const ModuleDepGraphNode *>>
      dependencyPathsToJobs;

  /// For helping with performance tuning, may be null:
  UnifiedStatsReporter *stats;

  //==============================================================================
  // MARK: ModuleDepGraph - mutating dependencies
  //==============================================================================

  /// Encapsulate the invariant between where the node resides in
  /// nodesBySwiftDepsFile and the swiftDeps node instance variable here.
  void addToMap(ModuleDepGraphNode *n) {
    nodeMap.insert(n->getSwiftDepsForMapKey(), 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->getSwiftDepsForMapKey(), nodeToErase->getKey());
    (void)nodeActuallyErased;
    assert(
        nodeToErase == nodeActuallyErased ||
        mapCorruption("Node found from key must be same as node holding key."));
    return nodeToErase;
  }

  void eraseUsesOfNode(ModuleDepGraphNode *nodeToErase) {
    for (auto &defAndUses : usesByDef)
      defAndUses.second.erase(nodeToErase);
  }

  void eraseNodeFromCurrentPathIfTracing(ModuleDepGraphNode *nodeToErase) {
    if (currentPathIfTracing)
      eraseNodeFromVector(currentPathIfTracing.getValue(), nodeToErase);
  }

  void eraseNodeFromDependencyPathToJobs(ModuleDepGraphNode *nodeToErase) {
    for (auto &jobAndPath : dependencyPathsToJobs)
      eraseNodeFromVector(jobAndPath.second, nodeToErase);
  }

  static void eraseNodeFromVector(std::vector<const ModuleDepGraphNode *> &v,
                                  const ModuleDepGraphNode *n) {
    const auto where = std::find(v.begin(), v.end(), n);
    if (where != v.end())
      v.erase(where);
  }

  void eraseNodeFromGraphAndFreeIt(ModuleDepGraphNode *);

  /// If the programmer removes a Decl from a source file, the corresponding
  /// ModuleDepGraphNode needs to be removed.
  void eraseNodeFromJob(ModuleDepGraphNode *);

  //============================================================================
  // MARK: ModuleDepGraph - utilities for Job and swiftdeps
  //============================================================================
private:
  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.");
    return iter->second;
  }

  //============================================================================
  // MARK: ModuleDepGraph - creation
  //============================================================================

public:
  /// For templates such as DotFileEmitter.
  using NodeType = ModuleDepGraphNode;

  /// \p stats may be null
  ModuleDepGraph(const bool verifyFineGrainedDependencyGraphAfterEveryImport,
                 const bool emitFineGrainedDependencyDotFileAfterEveryImport,
                 const bool shouldTraceDependencies,
                 UnifiedStatsReporter *stats)
      : verifyFineGrainedDependencyGraphAfterEveryImport(
            verifyFineGrainedDependencyGraphAfterEveryImport),
        emitFineGrainedDependencyDotFileAfterEveryImport(
            emitFineGrainedDependencyDotFileAfterEveryImport),
        currentPathIfTracing(
            shouldTraceDependencies
                ? llvm::Optional<std::vector<const ModuleDepGraphNode *>>(
                      std::vector<const ModuleDepGraphNode *>())
                : None),
        stats(stats) {
    assert(verify() && "ModuleDepGraph should be fine when created");
  }

  /// For unit tests.
  ModuleDepGraph(const bool EmitDotFilesForDebugging = false)
      : ModuleDepGraph(
            true, /*emitFineGrainedDependencyDotFileAfterEveryImport=*/
            EmitDotFilesForDebugging, false, nullptr) {}

  //============================================================================
  // MARK: ModuleDepGraph - updating from a switdeps file
  //============================================================================
public:
  using Changes = Optional<std::unordered_set<ModuleDepGraphNode *>>;

  /// Unlike the standard \c CoarseGrainedDependencyGraph, returns \c
  /// CoarseGrainedDependencyGraphImpl::LoadResult::AffectsDownstream when
  /// loading a new file, i.e. when determining the initial set. Caller
  /// compensates.
  Changes loadFromPath(const driver::Job *, StringRef, DiagnosticEngine &);

  Changes loadFromSourceFileDepGraph(const driver::Job *cmd,
                                     const SourceFileDepGraph &,
                                     DiagnosticEngine &);

  Changes loadFromSwiftModuleBuffer(const driver::Job *, llvm::MemoryBuffer &,
                                    DiagnosticEngine &);

private:
  /// 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.
  Changes loadFromBuffer(const driver::Job *, llvm::MemoryBuffer &,
                         DiagnosticEngine &);

  /// Integrate a SourceFileDepGraph into the receiver.
  /// Integration happens when the driver needs to read SourceFileDepGraph.
  Changes
  integrate(const SourceFileDepGraph &, StringRef swiftDepsOfJob);

  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) const;

  /// Integrate the \p integrand into the receiver.
  /// If an illegal value was found, return \c None, otherwise
  /// return the changed node if any..
  Optional<NullablePtr<ModuleDepGraphNode>>
  integrateSourceFileDepGraphNode(const SourceFileDepGraph &g,
                                  const SourceFileDepGraphNode *integrand,
                                  const PreexistingNodeIfAny preexistingMatch,
                                  StringRef swiftDepsOfJob);

  /// 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 swiftDepsOfJob,
                              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.
  /// Return true if moduleUseNode picks up a new external-dependency
  ///
  /// \param g The source file graph being integrated into the module graph
  /// \param sourceFileUseNode  The source file node just integrated, which may
  /// also be a use (i.e. a "depends", a declaration used by something else)
  /// \param moduleUseNode The module file node corresponding to the \c
  /// sourceFileUseNode
  bool recordWhatUseDependsUpon(const SourceFileDepGraph &g,
                                const SourceFileDepGraphNode *sourceFileUseNode,
                                ModuleDepGraphNode *moduleUseNode);

  //============================================================================
  // MARK: ModuleDepGraph - dot file support
  //============================================================================
public:
  /// For the dot file.
  std::string getGraphID() const { return "driver"; }

  /// Don't want to do this after every integration--too slow--
  /// So export this hook to the driver.
  bool emitDotFileAndVerify(DiagnosticEngine &) const;

  /// Use the known swiftDeps to find a directory for
  /// the job-independent dot file.
  std::string computePathForDotFile() const;

  /// For debugging and visualization, 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 &);

  //============================================================================
  // MARK: ModuleDepGraph - traversal
  //============================================================================
public:
  void forCorrespondingImplementationOfProvidedInterface(
      const ModuleDepGraphNode *,
      function_ref<void(ModuleDepGraphNode *)>) const;

  void forEachUseOf(const ModuleDepGraphNode *def,
                    function_ref<void(ModuleDepGraphNode *use)>) const;

  void forEachNode(function_ref<void(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(ModuleDepGraphNode *)>) const;

  void forEachNodeInJob(StringRef swiftDeps,
                        function_ref<void(ModuleDepGraphNode *)>) const;

  /// Given a definition node, transitively find all previous untraced
  /// dependents and add them to the array. Also returns definition if that is
  /// untraced.
  void findPreviouslyUntracedDependents(
      std::vector<ModuleDepGraphNode *> &foundDependents,
      ModuleDepGraphNode *definition);

  /// Givien a set of nodes, return the set of swiftDeps for the jobs those
  /// nodes are in.
  std::vector<std::string>
  computeSwiftDepsFromNodes(ArrayRef<const ModuleDepGraphNode *> nodes) const;

  /// 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);

  /// Dump the path that led to \p node.
  void printPath(raw_ostream &out, const driver::Job *node) const;

  /// Get a printable filename, given a node's swiftDeps.
  StringRef getProvidingFilename(const Optional<std::string> &swiftDeps) const;

  /// Print one node on the dependency path.
  static void printOneNodeOfPath(raw_ostream &out, const DependencyKey &key,
                                 const StringRef filename);

  bool isCurrentPathForTracingEmpty() const {
    return !currentPathIfTracing.hasValue() || currentPathIfTracing->empty();
  }

  //============================================================================
  // MARK: ModuleDepGraph - job-level queries and operations
  //============================================================================
public:
  // This section contains the interface to the status quo code in the driver.

  bool haveAnyNodesBeenTraversedIn(const driver::Job *) const;

  /// Find all jobs (possibly including the argument) requiring recompilation
  /// assuming that every entity in \p jobToBeRecompiled has changed.
  std::vector<const driver::Job *>
  findJobsToRecompileWhenWholeJobChanges(const driver::Job *jobToBeRecompiled);

  template <typename Nodes>
  std::vector<const driver::Job *>
  findJobsToRecompileWhenNodesChange(const Nodes &nodes) {
    std::vector<ModuleDepGraphNode *> foundDependents;
    for (ModuleDepGraphNode *n : nodes)
      findPreviouslyUntracedDependents(foundDependents, n);
    return jobsContaining(foundDependents);
  }

private:
  std::vector<const driver::Job *>
  jobsContaining(const ArrayRef<const ModuleDepGraphNode *> uses) const;

public:
  /// Record a new (to this graph) Job.
  void registerJob(const driver::Job *);

  std::vector<const driver::Job *> getAllJobs() const;

  /// Find jobs that were previously not known to need compilation but that
  /// depend on \c externalDependency.
  std::vector<const driver::Job *>
  findExternallyDependentUntracedJobs(StringRef externalDependency);

  //============================================================================
  // MARK: ModuleDepGraph - External dependencies
  //============================================================================

public:
  std::vector<StringRef> getExternalDependencies() const;

  void forEachUntracedJobDirectlyDependentOnExternalSwiftDeps(
      StringRef externalDependency, function_ref<void(const driver::Job *)> fn);
  void forEachUntracedJobDirectlyDependentOnExternalIncrementalSwiftDeps(
      StringRef externalDependency, function_ref<void(const driver::Job *)> fn);

  //============================================================================
  // MARK: ModuleDepGraph - verification
  //============================================================================

private:
  /// Return true or abort
  bool verify() const;

  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); }


  bool ensureJobIsTracked(const std::string &swiftDeps) const {
    assert(swiftDeps.empty() || getJob(swiftDeps));
    return true;
  }

  static std::vector<const driver::Job *>
  printJobsForDebugging(const std::vector<const driver::Job *> &jobs);
};
} // namespace fine_grained_dependencies
} // namespace swift

#endif // SWIFT_DRIVER_FINE_GRAINED_DEPENDENCY_DRIVER_GRAPH_H
