//===--- ModulesDriver.cpp - Driver managed module builds -----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file /// This file defines functionality to support driver managed builds for /// compilations which use Clang modules or standard C++20 named modules. /// //===----------------------------------------------------------------------===// #include "clang/Driver/ModulesDriver.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LLVM.h" #include "clang/DependencyScanning/DependencyScanningUtils.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" #include "clang/Driver/Job.h" #include "clang/Driver/Tool.h" #include "clang/Driver/ToolChain.h" #include "clang/Frontend/StandaloneDiagnostic.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/DirectedGraph.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVectorExtras.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Casting.h" #include "llvm/Support/GraphWriter.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Support/VirtualFileSystem.h" #include namespace deps = clang::dependencies; using namespace llvm::opt; using namespace clang; using namespace driver; using namespace modules; namespace clang::driver::modules { static bool fromJSON(const llvm::json::Value &Params, StdModuleManifest::Module::LocalArguments &LocalArgs, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); return O.mapOptional("system-include-directories", LocalArgs.SystemIncludeDirs); } static bool fromJSON(const llvm::json::Value &Params, StdModuleManifest::Module &ModuleEntry, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); return O.map("is-std-library", ModuleEntry.IsStdlib) && O.map("logical-name", ModuleEntry.LogicalName) && O.map("source-path", ModuleEntry.SourcePath) && O.mapOptional("local-arguments", ModuleEntry.LocalArgs); } static bool fromJSON(const llvm::json::Value &Params, StdModuleManifest &Manifest, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); return O.map("modules", Manifest.Modules); } } // namespace clang::driver::modules /// Parses the Standard library module manifest from \p Buffer. static Expected parseManifest(StringRef Buffer) { auto ParsedOrErr = llvm::json::parse(Buffer); if (!ParsedOrErr) return ParsedOrErr.takeError(); StdModuleManifest Manifest; llvm::json::Path::Root Root; if (!fromJSON(*ParsedOrErr, Manifest, Root)) return Root.getError(); return Manifest; } /// Converts each file path in manifest from relative to absolute. /// /// Each file path in the manifest is expected to be relative the manifest's /// location \p ManifestPath itself. static void makeManifestPathsAbsolute( MutableArrayRef ManifestEntries, StringRef ManifestPath) { StringRef ManifestDir = llvm::sys::path::parent_path(ManifestPath); SmallString<256> TempPath; auto PrependManifestDir = [&](StringRef Path) { TempPath = ManifestDir; llvm::sys::path::append(TempPath, Path); return std::string(TempPath); }; for (auto &Entry : ManifestEntries) { Entry.SourcePath = PrependManifestDir(Entry.SourcePath); if (!Entry.LocalArgs) continue; for (auto &IncludeDir : Entry.LocalArgs->SystemIncludeDirs) IncludeDir = PrependManifestDir(IncludeDir); } } Expected driver::modules::readStdModuleManifest(StringRef ManifestPath, llvm::vfs::FileSystem &VFS) { auto MemBufOrErr = VFS.getBufferForFile(ManifestPath); if (!MemBufOrErr) return llvm::createFileError(ManifestPath, MemBufOrErr.getError()); auto ManifestOrErr = parseManifest((*MemBufOrErr)->getBuffer()); if (!ManifestOrErr) return ManifestOrErr.takeError(); auto Manifest = std::move(*ManifestOrErr); makeManifestPathsAbsolute(Manifest.Modules, ManifestPath); return Manifest; } void driver::modules::buildStdModuleManifestInputs( ArrayRef ManifestEntries, Compilation &C, InputList &Inputs) { DerivedArgList &Args = C.getArgs(); const OptTable &Opts = C.getDriver().getOpts(); for (const auto &Entry : ManifestEntries) { auto *InputArg = makeInputArg(Args, Opts, Args.MakeArgString(Entry.SourcePath)); Inputs.emplace_back(types::TY_CXXModule, InputArg); } } using ManifestEntryLookup = llvm::DenseMap; /// Builds a mapping from a module's source path to its entry in the manifest. static ManifestEntryLookup buildManifestLookupMap(ArrayRef ManifestEntries) { ManifestEntryLookup ManifestEntryBySource; for (auto &Entry : ManifestEntries) { [[maybe_unused]] const bool Inserted = ManifestEntryBySource.try_emplace(Entry.SourcePath, &Entry).second; assert(Inserted && "Manifest defines multiple modules with the same source path."); } return ManifestEntryBySource; } /// Returns the manifest entry corresponding to \p Job, or \c nullptr if none /// exists. static const StdModuleManifest::Module * getManifestEntryForCommand(const Command &Job, const ManifestEntryLookup &ManifestEntryBySource) { for (const auto &II : Job.getInputInfos()) { if (const auto It = ManifestEntryBySource.find(II.getFilename()); It != ManifestEntryBySource.end()) return It->second; } return nullptr; } /// Adds all \p SystemIncludeDirs to the \p CC1Args of \p Job. static void addSystemIncludeDirsFromManifest(Compilation &C, Command &Job, ArgStringList &CC1Args, ArrayRef SystemIncludeDirs) { const ToolChain &TC = Job.getCreator().getToolChain(); const DerivedArgList &TCArgs = C.getArgsForToolChain(&TC, Job.getSource().getOffloadingArch(), Job.getSource().getOffloadingDeviceKind()); for (const auto &IncludeDir : SystemIncludeDirs) TC.addSystemInclude(TCArgs, CC1Args, IncludeDir); } static bool isCC1Job(const Command &Job) { return StringRef(Job.getCreator().getName()) == "clang"; } /// Apply command-line modifications specific for inputs originating from the /// Standard library module manifest. static void applyArgsForStdModuleManifestInputs( Compilation &C, const ManifestEntryLookup &ManifestEntryBySource, MutableArrayRef> Jobs) { for (auto &Job : Jobs) { if (!isCC1Job(*Job)) continue; const auto *Entry = getManifestEntryForCommand(*Job, ManifestEntryBySource); if (!Entry) continue; auto CC1Args = Job->getArguments(); if (Entry->IsStdlib) CC1Args.push_back("-Wno-reserved-module-identifier"); if (Entry->LocalArgs) addSystemIncludeDirsFromManifest(C, *Job, CC1Args, Entry->LocalArgs->SystemIncludeDirs); Job->replaceArguments(CC1Args); } } /// Computes the -fmodule-cache-path for this compilation. static std::optional getModuleCachePath(llvm::opt::DerivedArgList &Args) { if (const Arg *A = Args.getLastArg(options::OPT_fmodules_cache_path)) return A->getValue(); if (SmallString<128> Path; Driver::getDefaultModuleCachePath(Path)) return std::string(Path); return std::nullopt; } /// Returns true if a dependency scan can be performed using \p Job. static bool isDependencyScannableJob(const Command &Job) { if (!isCC1Job(Job)) return false; const auto &InputInfos = Job.getInputInfos(); return !InputInfos.empty() && types::isSrcFile(InputInfos.front().getType()); } namespace { /// Pool of reusable dependency scanning workers and their contexts with /// RAII-based acquire/release. class ScanningWorkerPool { public: ScanningWorkerPool(size_t NumWorkers, deps::DependencyScanningService &ScanningService) { for (size_t I = 0; I < NumWorkers; ++I) Slots.emplace_back(ScanningService); AvailableSlots.resize(NumWorkers); std::iota(AvailableSlots.begin(), AvailableSlots.end(), 0); } /// Acquires a unique pointer to a dependency scanning worker and its /// context. /// /// The worker bundle automatically released back to the pool when the /// pointer is destroyed. The pool has to outlive the leased worker bundle. [[nodiscard]] auto scopedAcquire() { std::unique_lock UL(Lock); CV.wait(UL, [&] { return !AvailableSlots.empty(); }); const size_t Index = AvailableSlots.pop_back_val(); auto ReleaseHandle = [this, Index](WorkerBundle *) { release(Index); }; return std::unique_ptr( &Slots[Index], ReleaseHandle); } private: /// Releases the worker bundle at \c Index back into the pool. void release(size_t Index) { { std::scoped_lock SL(Lock); AvailableSlots.push_back(Index); } CV.notify_one(); } /// A scanning worker with its associated context. struct WorkerBundle { WorkerBundle(deps::DependencyScanningService &ScanningService) : Worker(std::make_unique( ScanningService)) {} std::unique_ptr Worker; llvm::DenseSet SeenModules; }; std::mutex Lock; std::condition_variable CV; SmallVector AvailableSlots; SmallVector Slots; }; } // anonymous namespace // Creates a ThreadPool and a corresponding ScanningWorkerPool optimized for // the configuration of dependency scan inputs. static std::pair, std::unique_ptr> createOptimalThreadAndWorkerPool( size_t NumScanInputs, bool HasStdlibModuleInputs, deps::DependencyScanningService &ScanningService) { // TODO: Benchmark: Determine the optimal number of worker threads for a // given number of inputs. How many inputs are required for multi-threading // to be beneficial? How many inputs should each thread scan at least? #if LLVM_ENABLE_THREADS std::unique_ptr ThreadPool; size_t WorkerCount; if (NumScanInputs == 1 || (HasStdlibModuleInputs && NumScanInputs <= 2)) { auto S = llvm::optimal_concurrency(1); ThreadPool = std::make_unique(std::move(S)); WorkerCount = 1; } else { auto ThreadPoolStrategy = llvm::optimal_concurrency( NumScanInputs - static_cast(HasStdlibModuleInputs)); ThreadPool = std::make_unique( std::move(ThreadPoolStrategy)); const size_t MaxConcurrency = ThreadPool->getMaxConcurrency(); const size_t MaxConcurrentlyScannedInputs = NumScanInputs - (HasStdlibModuleInputs && NumScanInputs < MaxConcurrency ? 1 : 0); WorkerCount = std::min(MaxConcurrency, MaxConcurrentlyScannedInputs); } #else auto ThreadPool = std::make_unique(); size_t WorkerCount = 1; #endif return {std::move(ThreadPool), std::make_unique(WorkerCount, ScanningService)}; } static StringRef getTriple(const Command &Job) { return Job.getCreator().getToolChain().getTriple().getTriple(); } using ModuleNameAndTriple = std::pair; namespace { /// Helper to schedule on-demand dependency scans for modules originating from /// the Standard library module manifest. struct StdlibModuleScanScheduler { StdlibModuleScanScheduler(const llvm::DenseMap &StdlibModuleScanIndexByID) : StdlibModuleScanIndexByID(StdlibModuleScanIndexByID) { ScheduledScanInputs.reserve(StdlibModuleScanIndexByID.size()); } /// Returns the indices of scan inputs corresponding to newly imported /// Standard library modules. /// /// Thread-safe. SmallVector getNewScanInputs(ArrayRef NamedModuleDeps, StringRef Triple) { SmallVector NewScanInputs; std::scoped_lock Guard(Lock); for (const auto &ModuleName : NamedModuleDeps) { const auto It = StdlibModuleScanIndexByID.find({ModuleName, Triple}); if (It == StdlibModuleScanIndexByID.end()) continue; const size_t ScanIndex = It->second; const bool AlreadyScheduled = !ScheduledScanInputs.insert(ScanIndex).second; if (AlreadyScheduled) continue; NewScanInputs.push_back(ScanIndex); } return NewScanInputs; } private: const llvm::DenseMap &StdlibModuleScanIndexByID; llvm::SmallDenseSet ScheduledScanInputs; std::mutex Lock; }; /// Collects diagnostics in a form that can be retained until after their /// associated SourceManager is destroyed. class StandaloneDiagCollector : public DiagnosticConsumer { public: void BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP = nullptr) override { this->LangOpts = &LangOpts; } void HandleDiagnostic(DiagnosticsEngine::Level Level, const Diagnostic &Info) override { StoredDiagnostic StoredDiag(Level, Info); StandaloneDiags.emplace_back(*LangOpts, StoredDiag); DiagnosticConsumer::HandleDiagnostic(Level, Info); } SmallVector takeDiagnostics() { return std::move(StandaloneDiags); } private: const LangOptions *LangOpts = nullptr; SmallVector StandaloneDiags; }; /// RAII utility to report collected StandaloneDiagnostic through a /// DiagnosticsEngine. /// /// The driver's DiagnosticsEngine usually does not have a SourceManager at /// this point of building the compilation, in which case the /// StandaloneDiagReporter supplies its own. class StandaloneDiagReporter { public: explicit StandaloneDiagReporter(DiagnosticsEngine &Diags) : Diags(Diags) { if (!Diags.hasSourceManager()) { FileSystemOptions Opts; Opts.WorkingDir = "."; OwnedFileMgr = llvm::makeIntrusiveRefCnt(std::move(Opts)); OwnedSrcMgr = llvm::makeIntrusiveRefCnt(Diags, *OwnedFileMgr); } } /// Emits all diagnostics in \c StandaloneDiags using the associated /// DiagnosticsEngine. void Report(ArrayRef StandaloneDiags) const { llvm::StringMap SrcLocCache; Diags.getClient()->BeginSourceFile(LangOptions(), nullptr); for (const auto &StandaloneDiag : StandaloneDiags) { const auto StoredDiag = translateStandaloneDiag( getFileManager(), getSourceManager(), StandaloneDiag, SrcLocCache); Diags.Report(StoredDiag); } Diags.getClient()->EndSourceFile(); } private: DiagnosticsEngine &Diags; IntrusiveRefCntPtr OwnedFileMgr; IntrusiveRefCntPtr OwnedSrcMgr; FileManager &getFileManager() const { if (OwnedFileMgr) return *OwnedFileMgr; return Diags.getSourceManager().getFileManager(); } SourceManager &getSourceManager() const { if (OwnedSrcMgr) return *OwnedSrcMgr; return Diags.getSourceManager(); } }; } // anonymous namespace /// Report the diagnostics collected during each dependency scan. static void reportAllScanDiagnostics( SmallVectorImpl> &&AllScanDiags, DiagnosticsEngine &Diags) { StandaloneDiagReporter Reporter(Diags); for (auto &SingleScanDiags : AllScanDiags) Reporter.Report(SingleScanDiags); } /// Construct a path for the explicitly built PCM. static std::string constructPCMPath(const deps::ModuleID &ID, StringRef OutputDir) { assert(!ID.ModuleName.empty() && !ID.ContextHash.empty() && "Invalid ModuleID!"); SmallString<256> ExplicitPCMPath(OutputDir); llvm::sys::path::append(ExplicitPCMPath, ID.ContextHash, ID.ModuleName + "-" + ID.ContextHash + ".pcm"); return std::string(ExplicitPCMPath); } namespace { /// A simple dependency action controller that only provides module lookup for /// Clang modules. class ModuleLookupController : public deps::DependencyActionController { public: ModuleLookupController(StringRef OutputDir) : OutputDir(OutputDir) {} std::string lookupModuleOutput(const deps::ModuleDeps &MD, deps::ModuleOutputKind Kind) override { if (Kind == deps::ModuleOutputKind::ModuleFile) return constructPCMPath(MD.ID, OutputDir); // Driver command lines that trigger lookups for unsupported // ModuleOutputKinds are not supported by the modules driver. Those // command lines should probably be adjusted or rejected in // Driver::handleArguments or Driver::HandleImmediateArgs. llvm::reportFatalInternalError( "call to lookupModuleOutput with unexpected ModuleOutputKind"); } std::unique_ptr clone() const override { return std::make_unique(OutputDir); } private: StringRef OutputDir; }; /// The full dependencies for a specific command-line input. struct InputDependencies { /// The name of the C++20 module provided by this translation unit. std::string ModuleName; /// A list of modules this translation unit directly depends on, not including /// transitive dependencies. /// /// This may include modules with a different context hash when it can be /// determined that the differences are benign for this compilation. std::vector ClangModuleDeps; /// A list of the C++20 named modules this translation unit depends on. /// /// These correspond only to modules built with compatible compiler /// invocations. std::vector NamedModuleDeps; /// A collection of absolute paths to files that this translation unit /// directly depends on, not including transitive dependencies. std::vector FileDeps; /// The compiler invocation with modifications to properly import all Clang /// module dependencies. Does not include argv[0]. std::vector BuildArgs; }; } // anonymous namespace static InputDependencies makeInputDeps(deps::TranslationUnitDeps &&TUDeps) { InputDependencies InputDeps; InputDeps.ModuleName = std::move(TUDeps.ID.ModuleName); InputDeps.NamedModuleDeps = std::move(TUDeps.NamedModuleDeps); InputDeps.ClangModuleDeps = std::move(TUDeps.ClangModuleDeps); InputDeps.FileDeps = std::move(TUDeps.FileDeps); assert(TUDeps.Commands.size() == 1 && "Expected exactly one command"); InputDeps.BuildArgs = std::move(TUDeps.Commands.front().Arguments); return InputDeps; } /// Constructs the full command line, including the executable, for \p Job. static SmallVector buildCommandLine(const Command &Job) { const auto &JobArgs = Job.getArguments(); SmallVector CommandLine; CommandLine.reserve(JobArgs.size() + 1); CommandLine.emplace_back(Job.getExecutable()); for (const char *Arg : JobArgs) CommandLine.emplace_back(Arg); return CommandLine; } /// Performs a dependency scan for a single job. /// /// \returns a pair containing TranslationUnitDeps on success, or std::nullopt /// on failure, along with any diagnostics produced. static std::pair, SmallVector> scanDependenciesForJob(const Command &Job, ScanningWorkerPool &WorkerPool, StringRef WorkingDirectory, ModuleLookupController &LookupController) { StandaloneDiagCollector DiagConsumer; std::optional MaybeTUDeps; { const auto CC1CommandLine = buildCommandLine(Job); auto WorkerBundleHandle = WorkerPool.scopedAcquire(); deps::FullDependencyConsumer DepConsumer(WorkerBundleHandle->SeenModules); if (WorkerBundleHandle->Worker->computeDependencies( WorkingDirectory, CC1CommandLine, DepConsumer, LookupController, DiagConsumer)) MaybeTUDeps = DepConsumer.takeTranslationUnitDeps(); } return {std::move(MaybeTUDeps), DiagConsumer.takeDiagnostics()}; } namespace { struct DependencyScanResult { /// Indices of jobs that were successfully scanned. SmallVector ScannedJobIndices; /// Input dependencies for scanned jobs. Parallel to \c ScannedJobIndices. SmallVector InputDepsForScannedJobs; /// Module dependency graphs for scanned jobs. Parallel to \c /// ScannedJobIndices. SmallVector ModuleDepGraphsForScannedJobs; /// Indices of Standard library module jobs not discovered as dependencies. SmallVector UnusedStdlibModuleJobIndices; /// Indices of jobs that could not be scanned (e.g. image jobs, ...). SmallVector NonScannableJobIndices; }; } // anonymous namespace /// Scans the compilations job list \p Jobs for module dependencies. /// /// Standard library module jobs are scanned on demand if imported by any /// user-provided input. /// /// \returns DependencyScanResult on success, or std::nullopt on failure, with /// diagnostics reported via \p Diags in both cases. static std::optional scanDependencies( ArrayRef> Jobs, llvm::DenseMap ManifestLookup, StringRef ModuleCachePath, StringRef WorkingDirectory, DiagnosticsEngine &Diags) { llvm::PrettyStackTraceString CrashInfo("Performing module dependency scan."); // Classify the jobs based on scan eligibility. SmallVector ScannableJobIndices; SmallVector NonScannableJobIndices; for (const auto &&[Index, Job] : llvm::enumerate(Jobs)) { if (isDependencyScannableJob(*Job)) ScannableJobIndices.push_back(Index); else NonScannableJobIndices.push_back(Index); } // Classify scannable jobs by origin. User-provided inputs will be scanned // immediately, while Standard library modules are indexed for on-demand // scanning when discovered as dependencies. SmallVector UserInputScanIndices; llvm::DenseMap StdlibModuleScanIndexByID; for (const auto &&[ScanIndex, JobIndex] : llvm::enumerate(ScannableJobIndices)) { const Command &ScanJob = *Jobs[JobIndex]; if (const auto *Entry = getManifestEntryForCommand(ScanJob, ManifestLookup)) { ModuleNameAndTriple ID{Entry->LogicalName, getTriple(ScanJob)}; [[maybe_unused]] const bool Inserted = StdlibModuleScanIndexByID.try_emplace(ID, ScanIndex).second; assert(Inserted && "Multiple jobs build the same module for the same triple."); } else { UserInputScanIndices.push_back(ScanIndex); } } // Initialize the scan context. const size_t NumScanInputs = ScannableJobIndices.size(); const bool HasStdlibModuleInputs = !StdlibModuleScanIndexByID.empty(); deps::DependencyScanningServiceOptions Opts; deps::DependencyScanningService ScanningService(std::move(Opts)); std::unique_ptr ThreadPool; std::unique_ptr WorkerPool; std::tie(ThreadPool, WorkerPool) = createOptimalThreadAndWorkerPool( NumScanInputs, HasStdlibModuleInputs, ScanningService); StdlibModuleScanScheduler StdlibModuleRegistry(StdlibModuleScanIndexByID); ModuleLookupController LookupController(ModuleCachePath); // Scan results are indexed by ScanIndex into ScannableJobIndices, not by // JobIndex into Jobs. This allows one result slot per scannable job. SmallVector, 0> AllScanResults( NumScanInputs); SmallVector, 0> AllScanDiags( NumScanInputs); std::atomic HasError{false}; // Scans the job at the given scan index and schedules scans for any newly // discovered Standard library module dependencies. std::function ScanOneAndScheduleNew; ScanOneAndScheduleNew = [&](size_t ScanIndex) { const size_t JobIndex = ScannableJobIndices[ScanIndex]; const Command &Job = *Jobs[JobIndex]; auto [MaybeTUDeps, ScanDiags] = scanDependenciesForJob( Job, *WorkerPool, WorkingDirectory, LookupController); // Store diagnostics even for successful scans to also capture any warnings // or notes. assert(AllScanDiags[ScanIndex].empty() && "Each slot should be written to at most once."); AllScanDiags[ScanIndex] = std::move(ScanDiags); if (!MaybeTUDeps) { HasError.store(true, std::memory_order_relaxed); return; } // Schedule scans for newly discovered Standard library module dependencies. const auto NewScanInputs = StdlibModuleRegistry.getNewScanInputs( MaybeTUDeps->NamedModuleDeps, getTriple(Job)); for (const size_t NewScanIndex : NewScanInputs) ThreadPool->async( [&, NewScanIndex]() { ScanOneAndScheduleNew(NewScanIndex); }); assert(!AllScanResults[ScanIndex].has_value() && "Each slot should be written to at most once."); AllScanResults[ScanIndex] = std::move(MaybeTUDeps); }; // Initiate the scan with all jobs for user-provided inputs. for (const size_t ScanIndex : UserInputScanIndices) ThreadPool->async([&ScanOneAndScheduleNew, ScanIndex]() { ScanOneAndScheduleNew(ScanIndex); }); ThreadPool->wait(); reportAllScanDiagnostics(std::move(AllScanDiags), Diags); if (HasError.load(std::memory_order_relaxed)) return std::nullopt; // Collect results, mapping scan indices back to job indices. DependencyScanResult Result; for (auto &&[JobIndex, MaybeTUDeps] : llvm::zip_equal(ScannableJobIndices, AllScanResults)) { if (MaybeTUDeps) { Result.ScannedJobIndices.push_back(JobIndex); Result.ModuleDepGraphsForScannedJobs.push_back( std::move(MaybeTUDeps->ModuleGraph)); Result.InputDepsForScannedJobs.push_back( makeInputDeps(std::move(*MaybeTUDeps))); } else Result.UnusedStdlibModuleJobIndices.push_back(JobIndex); } Result.NonScannableJobIndices = std::move(NonScannableJobIndices); #ifndef NDEBUG llvm::SmallDenseSet SeenJobIndices; SeenJobIndices.insert_range(Result.ScannedJobIndices); SeenJobIndices.insert_range(Result.UnusedStdlibModuleJobIndices); SeenJobIndices.insert_range(Result.NonScannableJobIndices); assert(llvm::all_of(llvm::index_range(0, Jobs.size()), [&](size_t JobIndex) { return SeenJobIndices.contains(JobIndex); }) && "Scan result must partition all jobs"); #endif return Result; } namespace { class CGNode; class CGEdge; using CGNodeBase = llvm::DGNode; using CGEdgeBase = llvm::DGEdge; using CGBase = llvm::DirectedGraph; /// Compilation Graph Node class CGNode : public CGNodeBase { public: enum class NodeKind { ClangModuleCC1Job, NamedModuleCC1Job, NonModuleCC1Job, MiscJob, ImageJob, Root, }; CGNode(const NodeKind K) : Kind(K) {} CGNode(const CGNode &) = delete; CGNode(CGNode &&) = delete; virtual ~CGNode() = 0; NodeKind getKind() const { return Kind; } private: NodeKind Kind; }; CGNode::~CGNode() = default; /// Subclass of CGNode representing the root node of the graph. /// /// The root node is a special node that connects to all other nodes with /// no incoming edges, so that there is always a path from it to any node /// in the graph. /// /// There should only be one such node in a given graph. class RootNode : public CGNode { public: RootNode() : CGNode(NodeKind::Root) {} ~RootNode() override = default; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::Root; } }; /// Base class for any CGNode type that represents a job. class JobNode : public CGNode { public: JobNode(std::unique_ptr &&Job, NodeKind Kind) : CGNode(Kind), Job(std::move(Job)) { assert(this->Job && "Expected valid job!"); } virtual ~JobNode() override = 0; std::unique_ptr Job; static bool classof(const CGNode *N) { return N->getKind() != NodeKind::Root; } }; JobNode::~JobNode() = default; /// Subclass of CGNode representing a -cc1 job which produces a Clang module. class ClangModuleJobNode : public JobNode { public: ClangModuleJobNode(std::unique_ptr &&Job, deps::ModuleDeps &&MD) : JobNode(std::move(Job), NodeKind::ClangModuleCC1Job), MD(std::move(MD)) {} ~ClangModuleJobNode() override = default; deps::ModuleDeps MD; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::ClangModuleCC1Job; } }; /// Base class for any CGNode type that represents any scanned -cc1 job. class ScannedJobNode : public JobNode { public: ScannedJobNode(std::unique_ptr &&Job, InputDependencies &&InputDeps, NodeKind Kind) : JobNode(std::move(Job), Kind), InputDeps(std::move(InputDeps)) {} ~ScannedJobNode() override = default; InputDependencies InputDeps; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::NamedModuleCC1Job || N->getKind() == NodeKind::NonModuleCC1Job; } }; /// Subclass of CGNode representing a -cc1 job which produces a C++20 named /// module. class NamedModuleJobNode : public ScannedJobNode { public: NamedModuleJobNode(std::unique_ptr &&Job, InputDependencies &&InputDeps) : ScannedJobNode(std::move(Job), std::move(InputDeps), NodeKind::NamedModuleCC1Job) {} ~NamedModuleJobNode() override = default; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::NamedModuleCC1Job; } }; /// Subclass of CGNode representing a -cc1 job which does not produce any /// module, but might still have module imports. class NonModuleTUJobNode : public ScannedJobNode { public: NonModuleTUJobNode(std::unique_ptr &&Job, InputDependencies &&InputDeps) : ScannedJobNode(std::move(Job), std::move(InputDeps), NodeKind::NonModuleCC1Job) {} ~NonModuleTUJobNode() override = default; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::NonModuleCC1Job; } }; /// Subclass of CGNode representing a job which produces an image file, such as /// a linker or interface stub merge job. class ImageJobNode : public JobNode { public: ImageJobNode(std::unique_ptr &&Job) : JobNode(std::move(Job), NodeKind::ImageJob) {} ~ImageJobNode() override = default; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::ImageJob; } }; /// Subclass of CGNode representing any job not covered by the other node types. /// /// Jobs represented by this node type are not modified by the modules driver. class MiscJobNode : public JobNode { public: MiscJobNode(std::unique_ptr &&Job) : JobNode(std::move(Job), NodeKind::MiscJob) {} ~MiscJobNode() override = default; static bool classof(const CGNode *N) { return N->getKind() == NodeKind::MiscJob; } }; /// Compilation Graph Edge /// /// Edges connect the producer of an output to its consumer, except for edges /// stemming from the root node. class CGEdge : public CGEdgeBase { public: enum class EdgeKind { Regular, ModuleDependency, Rooted, }; CGEdge(CGNode &N, EdgeKind K) : CGEdgeBase(N), Kind(K) {} EdgeKind getKind() const { return Kind; } private: EdgeKind Kind; }; /// Compilation Graph /// /// The graph owns all of its components. /// All nodes and edges created by the graph have the same livetime as the /// graph, even if removed from the graph's node list. class CompilationGraph : public CGBase { public: CompilationGraph() = default; CompilationGraph(const CompilationGraph &) = delete; CompilationGraph(CompilationGraph &&G) = default; CGNode &getRoot() const { assert(Root && "Root node has not yet been created!"); return *Root; } RootNode &createRoot() { assert(!Root && "Root node has already been created!"); auto &RootRef = createNodeImpl(); Root = &RootRef; return RootRef; } template T &createJobNode(Args &&...Arg) { static_assert(std::is_base_of::value, "T must be derived from JobNode"); return createNodeImpl(std::forward(Arg)...); } CGEdge &createEdge(CGEdge::EdgeKind Kind, CGNode &Src, CGNode &Dst) { auto Edge = std::make_unique(Dst, Kind); CGEdge &EdgeRef = *Edge; AllEdges.push_back(std::move(Edge)); connect(Src, Dst, EdgeRef); return EdgeRef; } private: using CGBase::addNode; using CGBase::connect; template T &createNodeImpl(Args &&...Arg) { auto Node = std::make_unique(std::forward(Arg)...); T &NodeRef = *Node; AllNodes.push_back(std::move(Node)); addNode(NodeRef); return NodeRef; } CGNode *Root = nullptr; SmallVector> AllNodes; SmallVector> AllEdges; }; } // anonymous namespace static StringRef getFirstInputFilename(const Command &Job) { return Job.getInputInfos().front().getFilename(); } namespace llvm { /// Non-const versions of the GraphTraits specializations for CompilationGraph. template <> struct GraphTraits { using NodeRef = CGNode *; static NodeRef CGGetTargetNode(CGEdge *E) { return &E->getTargetNode(); } using ChildIteratorType = mapped_iterator; using ChildEdgeIteratorType = CGNode::iterator; static NodeRef getEntryNode(NodeRef N) { return N; } static ChildIteratorType child_begin(NodeRef N) { return ChildIteratorType(N->begin(), &CGGetTargetNode); } static ChildIteratorType child_end(NodeRef N) { return ChildIteratorType(N->end(), &CGGetTargetNode); } static ChildEdgeIteratorType child_edge_begin(NodeRef N) { return N->begin(); } static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } }; template <> struct GraphTraits : GraphTraits { using GraphRef = CompilationGraph *; using NodeRef = CGNode *; using nodes_iterator = CompilationGraph::iterator; static NodeRef getEntryNode(GraphRef G) { return &G->getRoot(); } static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } static nodes_iterator nodes_end(GraphRef G) { return G->end(); } }; /// Const versions of the GraphTraits specializations for CompilationGraph. template <> struct GraphTraits { using NodeRef = const CGNode *; static NodeRef CGGetTargetNode(const CGEdge *E) { return &E->getTargetNode(); } using ChildIteratorType = mapped_iterator; using ChildEdgeIteratorType = CGNode::const_iterator; static NodeRef getEntryNode(NodeRef N) { return N; } static ChildIteratorType child_begin(NodeRef N) { return ChildIteratorType(N->begin(), &CGGetTargetNode); } static ChildIteratorType child_end(NodeRef N) { return ChildIteratorType(N->end(), &CGGetTargetNode); } static ChildEdgeIteratorType child_edge_begin(NodeRef N) { return N->begin(); } static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } }; template <> struct GraphTraits : GraphTraits { using GraphRef = const CompilationGraph *; using NodeRef = const CGNode *; using nodes_iterator = CompilationGraph::const_iterator; static NodeRef getEntryNode(GraphRef G) { return &G->getRoot(); } static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } static nodes_iterator nodes_end(GraphRef G) { return G->end(); } }; template <> struct DOTGraphTraits : DefaultDOTGraphTraits { explicit DOTGraphTraits(bool IsSimple = false) : DefaultDOTGraphTraits(IsSimple) {} using GraphRef = const CompilationGraph *; using NodeRef = const CGNode *; static std::string getGraphName(GraphRef) { return "Module Dependency Graph"; } static std::string getGraphProperties(GraphRef) { return "\tnode [shape=Mrecord, colorscheme=set23, style=filled];\n"; } static bool renderGraphFromBottomUp() { return true; } static bool isNodeHidden(NodeRef N, GraphRef) { // Only show nodes with module dependency relations. return !isa(N); } static std::string getNodeIdentifier(NodeRef N, GraphRef) { return llvm::TypeSwitch(N) .Case([](const ClangModuleJobNode *ClangModuleNode) { const auto &ID = ClangModuleNode->MD.ID; return llvm::formatv("{0}-{1}", ID.ModuleName, ID.ContextHash).str(); }) .Case([](const NamedModuleJobNode *NamedModuleNode) { return llvm::formatv("{0}-{1}", NamedModuleNode->InputDeps.ModuleName, getTriple(*NamedModuleNode->Job)) .str(); }) .Case([](const NonModuleTUJobNode *NonModuleTUNode) { const auto &Job = *NonModuleTUNode->Job; return llvm::formatv("{0}-{1}", getFirstInputFilename(Job), getTriple(Job)) .str(); }) .DefaultUnreachable("Unexpected node kind! Is this node hidden?"); } static std::string getNodeLabel(NodeRef N, GraphRef) { return llvm::TypeSwitch(N) .Case([](const ClangModuleJobNode *ClangModuleNode) { const auto &ID = ClangModuleNode->MD.ID; return llvm::formatv("Module type: Clang module \\| Module name: {0} " "\\| Hash: {1}", ID.ModuleName, ID.ContextHash) .str(); }) .Case([](const NamedModuleJobNode *NamedModuleNode) { const auto &Job = *NamedModuleNode->Job; return llvm::formatv( "Filename: {0} \\| Module type: Named module \\| " "Module name: {1} \\| Triple: {2}", getFirstInputFilename(Job), NamedModuleNode->InputDeps.ModuleName, getTriple(Job)) .str(); }) .Case([](const NonModuleTUJobNode *NonModuleTUNode) { const auto &Job = *NonModuleTUNode->Job; return llvm::formatv("Filename: {0} \\| Triple: {1}", getFirstInputFilename(Job), getTriple(Job)) .str(); }) .DefaultUnreachable("Unexpected node kind! Is this node hidden?"); } static std::string getNodeAttributes(NodeRef N, GraphRef) { switch (N->getKind()) { case CGNode::NodeKind::ClangModuleCC1Job: return "fillcolor=1"; case CGNode::NodeKind::NamedModuleCC1Job: return "fillcolor=2"; case CGNode::NodeKind::NonModuleCC1Job: return "fillcolor=3"; default: llvm_unreachable("Unexpected node kind! Is this node hidden?"); } } }; /// GraphWriter specialization for CompilationGraph that emits a more /// human-readable DOT graph. template <> class GraphWriter : public GraphWriterBase> { public: using GraphType = const CompilationGraph *; using Base = GraphWriterBase>; GraphWriter(llvm::raw_ostream &O, const GraphType &G, bool IsSimple) : Base(O, G, IsSimple), EscapedIDByNodeRef(G->size()) {} void writeNodes() { auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; auto VisibleNodes = llvm::filter_to_vector(nodes(G), IsNodeVisible); writeNodeDefinitions(VisibleNodes); O << "\n"; writeNodeRelations(VisibleNodes); } private: using Base::DOTTraits; using Base::GTraits; using Base::NodeRef; void writeNodeDefinitions(ArrayRef VisibleNodes) { for (NodeRef Node : VisibleNodes) { std::string EscapedNodeID = DOT::EscapeString(DTraits.getNodeIdentifier(Node, G)); const std::string NodeLabel = DTraits.getNodeLabel(Node, G); const std::string NodeAttrs = DTraits.getNodeAttributes(Node, G); O << '\t' << '"' << EscapedNodeID << "\" [" << NodeAttrs << ", label=\"{ " << DOT::EscapeString(NodeLabel) << " }\"];\n"; EscapedIDByNodeRef.try_emplace(Node, std::move(EscapedNodeID)); } } void writeNodeRelations(ArrayRef VisibleNodes) { auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; for (NodeRef Node : VisibleNodes) { auto DstNodes = llvm::make_range(GTraits::child_begin(Node), GTraits::child_end(Node)); auto VisibleDstNodes = llvm::make_filter_range(DstNodes, IsNodeVisible); StringRef EscapedSrcNodeID = EscapedIDByNodeRef.at(Node); for (NodeRef DstNode : VisibleDstNodes) { StringRef EscapedTgtNodeID = EscapedIDByNodeRef.at(DstNode); O << '\t' << '"' << EscapedSrcNodeID << "\" -> \"" << EscapedTgtNodeID << "\";\n"; } } } DenseMap EscapedIDByNodeRef; }; } // namespace llvm static SmallVector> takeJobsAtIndices(SmallVectorImpl> &Jobs, ArrayRef Indices) { SmallVector> Out; for (const auto JobIndex : Indices) { assert(Jobs[JobIndex] && "Expected valid job!"); Out.push_back(std::move(Jobs[JobIndex])); } return Out; } /// Creates nodes for all jobs that could not be scanned (e.g. image jobs, ...). static void createNodesForNonScannableJobs( CompilationGraph &Graph, SmallVectorImpl> &&NonScannableJobs) { for (auto &Job : NonScannableJobs) { if (Job->getCreator().isLinkJob()) Graph.createJobNode(std::move(Job)); else Graph.createJobNode(std::move(Job)); } } /// Creates nodes for the Standard library module jobs not discovered as /// dependencies. /// /// These and any dependent (non-image) job nodes should be pruned from the /// graph later. static SmallVector createNodesForUnusedStdlibModuleJobs( CompilationGraph &Graph, SmallVectorImpl> &&UnusedStdlibModuleJobs) { SmallVector StdlibModuleNodesToPrune; for (auto &Job : UnusedStdlibModuleJobs) { auto &NewNode = Graph.createJobNode(std::move(Job)); StdlibModuleNodesToPrune.push_back(&NewNode); } return StdlibModuleNodesToPrune; } /// Creates a job for the Clang module described by \p MD. static std::unique_ptr createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob, const deps::ModuleDeps &MD) { DerivedArgList &Args = C.getArgs(); const OptTable &Opts = C.getDriver().getOpts(); Arg *InputArg = makeInputArg(Args, Opts, ""); Action *IA = C.MakeAction(*InputArg, types::ID::TY_ModuleFile); Action *PA = C.MakeAction(IA, types::ID::TY_ModuleFile); PA->propagateOffloadInfo(&ImportingJob.getSource()); const auto &TC = ImportingJob.getCreator().getToolChain(); const auto &TCArgs = C.getArgsForToolChain(&TC, PA->getOffloadingArch(), PA->getOffloadingDeviceKind()); const auto &BuildArgs = MD.getBuildArguments(); ArgStringList JobArgs; JobArgs.reserve(BuildArgs.size()); for (const auto &Arg : BuildArgs) JobArgs.push_back(TCArgs.MakeArgString(Arg)); return std::make_unique( *PA, ImportingJob.getCreator(), ResponseFileSupport::AtFileUTF8(), C.getDriver().getClangProgramPath(), JobArgs, /*Inputs=*/ArrayRef{}, /*Outputs=*/ArrayRef{}); } /// Creates a ClangModuleJobNode and its job for each unique Clang module /// in \p ModuleDepGraphsForScannedJobs. /// /// Only the jobs at indices \p ScannedJobIndices in \p Jobs are expected to be /// non-null. static void createClangModuleJobsAndNodes( CompilationGraph &Graph, Compilation &C, ArrayRef> Jobs, ArrayRef ScannedJobIndices, SmallVectorImpl &&ModuleDepGraphsForScannedJobs) { llvm::DenseSet AlreadySeen; for (auto &&[ScanIndex, ModuleDepsGraph] : llvm::enumerate(ModuleDepGraphsForScannedJobs)) { const auto &ImportingJob = *Jobs[ScannedJobIndices[ScanIndex]]; for (auto &MD : ModuleDepsGraph) { const auto Inserted = AlreadySeen.insert(MD.ID).second; if (!Inserted) continue; auto ClangModuleJob = createClangModulePrecompileJob(C, ImportingJob, MD); Graph.createJobNode(std::move(ClangModuleJob), std::move(MD)); } } } /// Creates nodes for all jobs which were scanned for dependencies. /// /// The updated command lines produced by the dependency scan are installed at a /// later point. static void createNodesForScannedJobs( CompilationGraph &Graph, SmallVectorImpl> &&ScannedJobs, SmallVectorImpl &&InputDepsForScannedJobs) { for (auto &&[Job, InputDeps] : llvm::zip_equal(ScannedJobs, InputDepsForScannedJobs)) { if (InputDeps.ModuleName.empty()) Graph.createJobNode(std::move(Job), std::move(InputDeps)); else Graph.createJobNode(std::move(Job), std::move(InputDeps)); } } template static void connectEdgesViaLookup(CompilationGraph &Graph, CGNode &TgtNode, const LookupT &SrcNodeLookup, const KeyRangeT &SrcNodeLookupKeys, CGEdge::EdgeKind Kind) { for (const auto &Key : SrcNodeLookupKeys) { const auto It = SrcNodeLookup.find(Key); if (It == SrcNodeLookup.end()) continue; auto &SrcNode = *It->second; Graph.createEdge(Kind, SrcNode, TgtNode); } } /// Create edges for regular (non-module) dependencies in \p Graph. static void createRegularEdges(CompilationGraph &Graph) { llvm::DenseMap NodeByOutputFiles; for (auto *Node : Graph) { for (const auto &Output : cast(Node)->Job->getOutputFilenames()) { [[maybe_unused]] const bool Inserted = NodeByOutputFiles.try_emplace(Output, Node).second; assert(Inserted && "Driver should not produce multiple jobs with identical outputs!"); } } for (auto *Node : Graph) { const auto &InputInfos = cast(Node)->Job->getInputInfos(); auto InputFilenames = llvm::map_range( InputInfos, [](const auto &II) { return II.getFilename(); }); connectEdgesViaLookup(Graph, *Node, NodeByOutputFiles, InputFilenames, CGEdge::EdgeKind::Regular); } } /// Create edges for module dependencies in \p Graph. /// /// \returns false if there are multiple definitions for a named module, with /// diagnostics reported to \p Diags; otherwise returns true. static bool createModuleDependencyEdges(CompilationGraph &Graph, DiagnosticsEngine &Diags) { llvm::DenseMap ClangModuleNodeByID; llvm::DenseMap NamedModuleNodeByID; // Map each module to the job that produces it. bool HasDuplicateModuleError = false; for (auto *Node : Graph) { llvm::TypeSwitch(Node) .Case([&](ClangModuleJobNode *ClangModuleNode) { [[maybe_unused]] const bool Inserted = ClangModuleNodeByID.try_emplace(ClangModuleNode->MD.ID, Node) .second; assert(Inserted && "Multiple Clang module nodes with the same module ID!"); }) .Case([&](NamedModuleJobNode *NamedModuleNode) { StringRef ModuleName = NamedModuleNode->InputDeps.ModuleName; ModuleNameAndTriple ID{ModuleName, getTriple(*NamedModuleNode->Job)}; const auto [It, Inserted] = NamedModuleNodeByID.try_emplace(ID, Node); if (!Inserted) { // For scan input jobs, their first input is always a filename and // the scanned source. // We don't use InputDeps.FileDeps here because diagnostics should // refer to the filename as specified on the command line, not the // canonical absolute path. StringRef PrevFile = getFirstInputFilename(*cast(It->second)->Job); StringRef CurFile = getFirstInputFilename(*NamedModuleNode->Job); Diags.Report(diag::err_modules_driver_named_module_redefinition) << ModuleName << PrevFile << CurFile; HasDuplicateModuleError = true; } }); } if (HasDuplicateModuleError) return false; // Create edges from the module nodes to their importers. for (auto *Node : Graph) { llvm::TypeSwitch(Node) .Case([&](ClangModuleJobNode *ClangModuleNode) { connectEdgesViaLookup(Graph, *ClangModuleNode, ClangModuleNodeByID, ClangModuleNode->MD.ClangModuleDeps, CGEdge::EdgeKind::ModuleDependency); }) .Case([&](ScannedJobNode *NodeWithInputDeps) { connectEdgesViaLookup(Graph, *NodeWithInputDeps, ClangModuleNodeByID, NodeWithInputDeps->InputDeps.ClangModuleDeps, CGEdge::EdgeKind::ModuleDependency); StringRef Triple = getTriple(*NodeWithInputDeps->Job); const auto NamedModuleDepIDs = llvm::map_range(NodeWithInputDeps->InputDeps.NamedModuleDeps, [&](StringRef ModuleName) { return ModuleNameAndTriple{ModuleName, Triple}; }); connectEdgesViaLookup(Graph, *NodeWithInputDeps, NamedModuleNodeByID, NamedModuleDepIDs, CGEdge::EdgeKind::ModuleDependency); }); } return true; } /// Prunes the compilation graph of any jobs which build Standard library /// modules not required in this compilation. static void pruneUnusedStdlibModuleJobs(CompilationGraph &Graph, ArrayRef UnusedStdlibModuleJobNodes) { // Collect all reachable non-image job nodes. llvm::SmallPtrSet PrunableJobNodes; for (auto *PrunableJobNodeRoot : UnusedStdlibModuleJobNodes) { auto ReachableJobNodes = llvm::map_range(llvm::depth_first(cast(PrunableJobNodeRoot)), llvm::CastTo); auto ReachableNonImageNodes = llvm::make_filter_range( ReachableJobNodes, [](auto *N) { return !llvm::isa(N); }); PrunableJobNodes.insert_range(ReachableNonImageNodes); } // Map image job nodes to the prunable job nodes that feed into them. llvm::DenseMap> PrunableJobNodesByImageNode; for (auto *PrunableJobNode : PrunableJobNodes) { auto ReachableJobNodes = llvm::depth_first(cast(PrunableJobNode)); auto ReachableImageJobNodes = llvm::map_range( llvm::make_filter_range(ReachableJobNodes, llvm::IsaPred), llvm::CastTo); for (auto *ImageNode : ReachableImageJobNodes) PrunableJobNodesByImageNode[ImageNode].insert(PrunableJobNode); } // Remove from each affected image job node any arguments corresponding to // outputs of the connected prunable job nodes. for (auto &[ImageNode, PrunableJobNodeInputs] : PrunableJobNodesByImageNode) { SmallVector OutputsToRemove; for (auto *JN : PrunableJobNodeInputs) llvm::append_range(OutputsToRemove, JN->Job->getOutputFilenames()); auto NewArgs = ImageNode->Job->getArguments(); llvm::erase_if(NewArgs, [&](StringRef Arg) { return llvm::is_contained(OutputsToRemove, Arg); }); ImageNode->Job->replaceArguments(NewArgs); } // Erase all prunable job nodes from the graph. for (auto *JN : PrunableJobNodes) { // Nodes are owned by the graph, but we can release the associated job. JN->Job.reset(); Graph.removeNode(*JN); } } /// Creates the root node and connects it to all nodes with no incoming edges /// ensuring that every node in the graph is reachable from the root. static void createAndConnectRoot(CompilationGraph &Graph) { llvm::SmallPtrSet HasIncomingEdge; for (auto *Node : Graph) for (auto *Edge : Node->getEdges()) HasIncomingEdge.insert(&Edge->getTargetNode()); auto AllNonRootNodes = llvm::iterator_range(Graph); auto &Root = Graph.createRoot(); for (auto *Node : AllNonRootNodes) { if (HasIncomingEdge.contains(Node)) continue; Graph.createEdge(CGEdge::EdgeKind::Rooted, Root, *Node); } } void driver::modules::runModulesDriver( Compilation &C, ArrayRef ManifestEntries) { llvm::PrettyStackTraceString CrashInfo("Running modules driver."); auto Jobs = C.getJobs().takeJobs(); const auto ManifestEntryBySource = buildManifestLookupMap(ManifestEntries); // Apply manifest-entry specific command-line modifications before the scan as // they might affect it. applyArgsForStdModuleManifestInputs(C, ManifestEntryBySource, Jobs); DiagnosticsEngine &Diags = C.getDriver().getDiags(); // Run the dependency scan. const auto MaybeModuleCachePath = getModuleCachePath(C.getArgs()); if (!MaybeModuleCachePath) { Diags.Report(diag::err_default_modules_cache_not_available); return; } auto MaybeCWD = C.getDriver().getVFS().getCurrentWorkingDirectory(); const auto CWD = MaybeCWD ? std::move(*MaybeCWD) : "."; auto MaybeScanResults = scanDependencies(Jobs, ManifestEntryBySource, *MaybeModuleCachePath, CWD, Diags); if (!MaybeScanResults) { Diags.Report(diag::err_dependency_scan_failed); return; } auto &ScanResult = *MaybeScanResults; // Build the compilation graph. CompilationGraph Graph; createNodesForNonScannableJobs( Graph, takeJobsAtIndices(Jobs, ScanResult.NonScannableJobIndices)); auto UnusedStdlibModuleJobNodes = createNodesForUnusedStdlibModuleJobs( Graph, takeJobsAtIndices(Jobs, ScanResult.UnusedStdlibModuleJobIndices)); createClangModuleJobsAndNodes( Graph, C, Jobs, ScanResult.ScannedJobIndices, std::move(ScanResult.ModuleDepGraphsForScannedJobs)); createNodesForScannedJobs( Graph, takeJobsAtIndices(Jobs, ScanResult.ScannedJobIndices), std::move(ScanResult.InputDepsForScannedJobs)); createRegularEdges(Graph); pruneUnusedStdlibModuleJobs(Graph, UnusedStdlibModuleJobNodes); if (!createModuleDependencyEdges(Graph, Diags)) return; createAndConnectRoot(Graph); Diags.Report(diag::remark_printing_module_graph); if (!Diags.isLastDiagnosticIgnored()) llvm::WriteGraph(llvm::errs(), &Graph); // TODO: Install all updated command-lines produced by the dependency scan. // TODO: Fix-up command-lines for named module imports. // TODO: Sort the graph topologically before adding jobs back to the // Compilation being built. for (auto *N : Graph) if (auto *JN = dyn_cast(N)) C.addCommand(std::move(JN->Job)); }