| //===--- 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/Driver/Types.h" |
| #include "clang/Frontend/StandaloneDiagnostic.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/DepthFirstIterator.h" |
| #include "llvm/ADT/DirectedGraph.h" |
| #include "llvm/ADT/PostOrderIterator.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallVectorExtras.h" |
| #include "llvm/ADT/TypeSwitch.h" |
| #include "llvm/ADT/iterator_range.h" |
| #include "llvm/Option/ArgList.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 <utility> |
| |
| namespace deps = clang::dependencies; |
| |
| using namespace llvm::opt; |
| using namespace clang; |
| using namespace driver; |
| using namespace modules; |
| |
| void driver::modules::diagnoseModulesDriverArgs(llvm::opt::DerivedArgList &DAL, |
| DiagnosticsEngine &Diags) { |
| if (!DAL.hasFlag(options::OPT_fmodules_reduced_bmi, |
| options::OPT_fno_modules_reduced_bmi, true)) { |
| Diags.Report(diag::err_drv_modules_driver_requires_reduced_bmi); |
| } |
| } |
| |
| 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<StdModuleManifest> 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<StdModuleManifest::Module> 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<StdModuleManifest> |
| 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<StdModuleManifest::Module> 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_CXXStdModule, InputArg); |
| } |
| } |
| |
| using ManifestEntryLookup = |
| llvm::DenseMap<StringRef, const StdModuleManifest::Module *>; |
| |
| /// Builds a mapping from a module's source path to its entry in the manifest. |
| static ManifestEntryLookup |
| buildManifestLookupMap(ArrayRef<StdModuleManifest::Module> 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<std::string> 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<std::unique_ptr<Command>> 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<std::string> |
| 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<std::mutex> 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<WorkerBundle, decltype(ReleaseHandle)>( |
| &Slots[Index], ReleaseHandle); |
| } |
| |
| private: |
| /// Releases the worker bundle at \c Index back into the pool. |
| void release(size_t Index) { |
| { |
| std::scoped_lock<std::mutex> 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<deps::DependencyScanningWorker>( |
| ScanningService)) {} |
| |
| std::unique_ptr<deps::DependencyScanningWorker> Worker; |
| llvm::DenseSet<deps::ModuleID> SeenModules; |
| }; |
| |
| std::mutex Lock; |
| std::condition_variable CV; |
| SmallVector<size_t> AvailableSlots; |
| SmallVector<WorkerBundle, 0> Slots; |
| }; |
| } // anonymous namespace |
| |
| // Creates a ThreadPool and a corresponding ScanningWorkerPool optimized for |
| // the configuration of dependency scan inputs. |
| static std::pair<std::unique_ptr<llvm::ThreadPoolInterface>, |
| std::unique_ptr<ScanningWorkerPool>> |
| 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<llvm::ThreadPoolInterface> ThreadPool; |
| size_t WorkerCount; |
| |
| if (NumScanInputs == 1 || (HasStdlibModuleInputs && NumScanInputs <= 2)) { |
| auto S = llvm::optimal_concurrency(1); |
| ThreadPool = std::make_unique<llvm::SingleThreadExecutor>(std::move(S)); |
| WorkerCount = 1; |
| } else { |
| auto ThreadPoolStrategy = llvm::optimal_concurrency( |
| NumScanInputs - static_cast<size_t>(HasStdlibModuleInputs)); |
| ThreadPool = std::make_unique<llvm::DefaultThreadPool>( |
| 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<llvm::SingleThreadExecutor>(); |
| size_t WorkerCount = 1; |
| #endif |
| |
| return {std::move(ThreadPool), |
| std::make_unique<ScanningWorkerPool>(WorkerCount, ScanningService)}; |
| } |
| |
| static StringRef getTriple(const Command &Job) { |
| return Job.getCreator().getToolChain().getTriple().getTriple(); |
| } |
| |
| using ModuleNameAndTriple = std::pair<StringRef, StringRef>; |
| |
| namespace { |
| /// Helper to schedule on-demand dependency scans for modules originating from |
| /// the Standard library module manifest. |
| struct StdlibModuleScanScheduler { |
| StdlibModuleScanScheduler(const llvm::DenseMap<ModuleNameAndTriple, size_t> |
| &StdlibModuleScanIndexByID) |
| : StdlibModuleScanIndexByID(StdlibModuleScanIndexByID) { |
| ScheduledScanInputs.reserve(StdlibModuleScanIndexByID.size()); |
| } |
| |
| /// Returns the indices of scan inputs corresponding to newly imported |
| /// Standard library modules. |
| /// |
| /// Thread-safe. |
| SmallVector<size_t, 2> getNewScanInputs(ArrayRef<std::string> NamedModuleDeps, |
| StringRef Triple) { |
| SmallVector<size_t, 2> NewScanInputs; |
| std::scoped_lock<std::mutex> 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<ModuleNameAndTriple, size_t> &StdlibModuleScanIndexByID; |
| llvm::SmallDenseSet<size_t> 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<StandaloneDiagnostic, 0> takeDiagnostics() { |
| return std::move(StandaloneDiags); |
| } |
| |
| private: |
| const LangOptions *LangOpts = nullptr; |
| SmallVector<StandaloneDiagnostic, 0> 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<FileManager>(std::move(Opts)); |
| OwnedSrcMgr = |
| llvm::makeIntrusiveRefCnt<SourceManager>(Diags, *OwnedFileMgr); |
| } |
| } |
| |
| /// Emits all diagnostics in \c StandaloneDiags using the associated |
| /// DiagnosticsEngine. |
| void Report(ArrayRef<StandaloneDiagnostic> StandaloneDiags) const { |
| llvm::StringMap<SourceLocation> 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<FileManager> OwnedFileMgr; |
| IntrusiveRefCntPtr<SourceManager> 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<SmallVector<StandaloneDiagnostic, 0>> &&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<DependencyActionController> clone() const override { |
| return std::make_unique<ModuleLookupController>(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<deps::ModuleID> 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<std::string> NamedModuleDeps; |
| |
| /// A collection of absolute paths to files that this translation unit |
| /// directly depends on, not including transitive dependencies. |
| std::vector<std::string> FileDeps; |
| |
| /// The compiler invocation with modifications to properly import all Clang |
| /// module dependencies. Does not include argv[0]. |
| std::vector<std::string> 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<std::string, 0> buildCommandLine(const Command &Job) { |
| const auto &JobArgs = Job.getArguments(); |
| SmallVector<std::string, 0> 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<std::optional<deps::TranslationUnitDeps>, |
| SmallVector<StandaloneDiagnostic, 0>> |
| scanDependenciesForJob(const Command &Job, ScanningWorkerPool &WorkerPool, |
| StringRef WorkingDirectory, |
| ModuleLookupController &LookupController) { |
| StandaloneDiagCollector DiagConsumer; |
| std::optional<deps::TranslationUnitDeps> 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<size_t> ScannedJobIndices; |
| |
| /// Input dependencies for scanned jobs. Parallel to \c ScannedJobIndices. |
| SmallVector<InputDependencies, 0> InputDepsForScannedJobs; |
| |
| /// Module dependency graphs for scanned jobs. Parallel to \c |
| /// ScannedJobIndices. |
| SmallVector<deps::ModuleDepsGraph, 0> ModuleDepGraphsForScannedJobs; |
| |
| /// Indices of Standard library module jobs not discovered as dependencies. |
| SmallVector<size_t> UnusedStdlibModuleJobIndices; |
| |
| /// Indices of jobs that could not be scanned (e.g. image jobs, ...). |
| SmallVector<size_t> 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<DependencyScanResult> scanDependencies( |
| ArrayRef<std::unique_ptr<Command>> Jobs, |
| llvm::DenseMap<StringRef, const StdModuleManifest::Module *> ManifestLookup, |
| StringRef ModuleCachePath, StringRef WorkingDirectory, |
| DiagnosticsEngine &Diags) { |
| llvm::PrettyStackTraceString CrashInfo("Performing module dependency scan."); |
| |
| // Classify the jobs based on scan eligibility. |
| SmallVector<size_t> ScannableJobIndices; |
| SmallVector<size_t> 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<size_t> UserInputScanIndices; |
| llvm::DenseMap<ModuleNameAndTriple, size_t> 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<llvm::ThreadPoolInterface> ThreadPool; |
| std::unique_ptr<ScanningWorkerPool> 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<std::optional<deps::TranslationUnitDeps>, 0> AllScanResults( |
| NumScanInputs); |
| SmallVector<SmallVector<StandaloneDiagnostic, 0>, 0> AllScanDiags( |
| NumScanInputs); |
| std::atomic<bool> HasError{false}; |
| |
| // Scans the job at the given scan index and schedules scans for any newly |
| // discovered Standard library module dependencies. |
| std::function<void(size_t)> 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<size_t> 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<CGNode, CGEdge>; |
| using CGEdgeBase = llvm::DGEdge<CGNode, CGEdge>; |
| using CGBase = llvm::DirectedGraph<CGNode, CGEdge>; |
| |
| /// 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; |
| CGNode &operator=(const CGNode &) = delete; |
| CGNode &operator=(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<Command> &&Job, NodeKind Kind) |
| : CGNode(Kind), Job(std::move(Job)) { |
| assert(this->Job && "Expected valid job!"); |
| } |
| virtual ~JobNode() override = 0; |
| |
| std::unique_ptr<Command> 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<Command> &&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<Command> &&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<Command> &&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<Command> &&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<Command> &&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<Command> &&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) {} |
| CGEdge(const CGEdge &) = delete; |
| CGEdge &operator=(const CGEdge &) = delete; |
| CGEdge(CGEdge &&) = delete; |
| CGEdge &operator=(CGEdge &&) = delete; |
| |
| 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 &operator=(const CompilationGraph &) = delete; |
| CompilationGraph(CompilationGraph &&G) = default; |
| CompilationGraph &operator=(CompilationGraph &&) = default; |
| ~CompilationGraph() = 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<RootNode>(); |
| Root = &RootRef; |
| return RootRef; |
| } |
| |
| template <typename T, typename... Args> T &createJobNode(Args &&...Arg) { |
| static_assert(std::is_base_of<JobNode, T>::value, |
| "T must be derived from JobNode"); |
| return createNodeImpl<T>(std::forward<Args>(Arg)...); |
| } |
| |
| CGEdge &createEdge(CGEdge::EdgeKind Kind, CGNode &Src, CGNode &Dst) { |
| auto Edge = std::make_unique<CGEdge>(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 <typename T, typename... Args> T &createNodeImpl(Args &&...Arg) { |
| auto Node = std::make_unique<T>(std::forward<Args>(Arg)...); |
| T &NodeRef = *Node; |
| AllNodes.push_back(std::move(Node)); |
| addNode(NodeRef); |
| return NodeRef; |
| } |
| |
| CGNode *Root = nullptr; |
| SmallVector<std::unique_ptr<CGNode>> AllNodes; |
| SmallVector<std::unique_ptr<CGEdge>> 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<CGNode *> { |
| using NodeRef = CGNode *; |
| |
| static NodeRef CGGetTargetNode(CGEdge *E) { return &E->getTargetNode(); } |
| |
| using ChildIteratorType = |
| mapped_iterator<CGNode::iterator, decltype(&CGGetTargetNode)>; |
| 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<CompilationGraph *> : GraphTraits<CGNode *> { |
| 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<const CGNode *> { |
| using NodeRef = const CGNode *; |
| |
| static NodeRef CGGetTargetNode(const CGEdge *E) { |
| return &E->getTargetNode(); |
| } |
| |
| using ChildIteratorType = |
| mapped_iterator<CGNode::const_iterator, decltype(&CGGetTargetNode)>; |
| 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<const CompilationGraph *> : GraphTraits<const CGNode *> { |
| 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<const CompilationGraph *> : 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<ClangModuleJobNode, ScannedJobNode>(N); |
| } |
| |
| static std::string getNodeIdentifier(NodeRef N, GraphRef) { |
| return llvm::TypeSwitch<NodeRef, std::string>(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<NodeRef, std::string>(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<const CompilationGraph *> |
| : public GraphWriterBase<const CompilationGraph *, |
| GraphWriter<const CompilationGraph *>> { |
| public: |
| using GraphType = const CompilationGraph *; |
| using Base = GraphWriterBase<GraphType, GraphWriter<GraphType>>; |
| |
| 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<NodeRef> 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<NodeRef> 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<NodeRef, std::string> EscapedIDByNodeRef; |
| }; |
| } // namespace llvm |
| |
| /// Validates that each module-defining source is of type \c TY_CXXModule. |
| /// |
| /// \returns false on error, with diagnostics emitted via \p Diags. |
| static bool validateScannedJobInputKinds( |
| ArrayRef<std::unique_ptr<Command>> ScannedJobs, |
| ArrayRef<InputDependencies> InputDepsForScannedJobs, |
| DiagnosticsEngine &Diags) { |
| for (const auto &&[Job, InputDeps] : llvm::zip_equal( |
| llvm::make_pointee_range(ScannedJobs), InputDepsForScannedJobs)) { |
| const auto &MainInput = Job.getInputInfos().front(); |
| const bool DefinesNamedModule = !InputDeps.ModuleName.empty(); |
| |
| if (DefinesNamedModule && MainInput.getType() != types::TY_CXXModule && |
| MainInput.getType() != types::TY_CXXStdModule) { |
| Diags.Report(diag::err_module_defined_outside_of_module_source) |
| << InputDeps.ModuleName << MainInput.getFilename(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static SmallVector<std::unique_ptr<Command>> |
| takeJobsAtIndices(SmallVectorImpl<std::unique_ptr<Command>> &Jobs, |
| ArrayRef<size_t> Indices) { |
| SmallVector<std::unique_ptr<Command>> 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<std::unique_ptr<Command>> &&NonScannableJobs) { |
| for (auto &Job : NonScannableJobs) { |
| if (Job->getCreator().isLinkJob()) |
| Graph.createJobNode<ImageJobNode>(std::move(Job)); |
| else |
| Graph.createJobNode<MiscJobNode>(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<JobNode *> createNodesForUnusedStdlibModuleJobs( |
| CompilationGraph &Graph, |
| SmallVectorImpl<std::unique_ptr<Command>> &&UnusedStdlibModuleJobs) { |
| SmallVector<JobNode *> StdlibModuleNodesToPrune; |
| for (auto &Job : UnusedStdlibModuleJobs) { |
| auto &NewNode = Graph.createJobNode<MiscJobNode>(std::move(Job)); |
| StdlibModuleNodesToPrune.push_back(&NewNode); |
| } |
| return StdlibModuleNodesToPrune; |
| } |
| |
| // Returns the derived argument list for the tool chain responsible |
| // for creating \p Job. |
| static const DerivedArgList &getToolChainArgs(Compilation &C, |
| const Command &Job) { |
| const auto &TC = Job.getCreator().getToolChain(); |
| const auto &SourceAction = Job.getSource(); |
| return C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(), |
| SourceAction.getOffloadingDeviceKind()); |
| } |
| |
| /// Creates a job for the Clang module described by \p MD. |
| static std::unique_ptr<Command> |
| 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, "<discovered clang module>"); |
| Action *IA = C.MakeAction<InputAction>(*InputArg, types::ID::TY_ModuleFile); |
| Action *PA = C.MakeAction<PrecompileJobAction>(IA, types::ID::TY_ModuleFile); |
| PA->propagateOffloadInfo(&ImportingJob.getSource()); |
| |
| const auto &TCArgs = getToolChainArgs(C, ImportingJob); |
| |
| const auto &BuildArgs = MD.getBuildArguments(); |
| ArgStringList JobArgs; |
| JobArgs.reserve(BuildArgs.size()); |
| for (const auto &Arg : BuildArgs) |
| JobArgs.push_back(TCArgs.MakeArgString(Arg)); |
| |
| const auto &D = C.getDriver(); |
| return std::make_unique<Command>( |
| *PA, ImportingJob.getCreator(), ResponseFileSupport::AtFileUTF8(), |
| D.getDriverProgramPath(), JobArgs, |
| /*Inputs=*/ArrayRef<InputInfo>{}, |
| /*Outputs=*/ArrayRef<InputInfo>{}, D.getPrependArg()); |
| } |
| |
| /// Creates a \c ClangModuleJobNode with associated job for each unique Clang |
| /// module in \p ModuleDepGraphsForScannedJobs. |
| /// |
| /// \param ImportingJobs Jobs whose module dependencies were scanned. |
| /// \param ModuleDepGraphsForScannedJobs Full Clang module dependency graphs |
| /// corresponding to \p ImportingJobs, in order. |
| static void createClangModuleJobsAndNodes( |
| CompilationGraph &Graph, Compilation &C, |
| ArrayRef<std::unique_ptr<Command>> ImportingJobs, |
| SmallVectorImpl<deps::ModuleDepsGraph> &&ModuleDepGraphsForScannedJobs) { |
| llvm::DenseSet<deps::ModuleID> AlreadySeen; |
| for (auto &&[ImportingJob, ModuleDepsGraph] : |
| llvm::zip_equal(llvm::make_pointee_range(ImportingJobs), |
| ModuleDepGraphsForScannedJobs)) { |
| for (auto &MD : ModuleDepsGraph) { |
| const auto Inserted = AlreadySeen.insert(MD.ID).second; |
| if (!Inserted) |
| continue; |
| |
| auto ClangModuleJob = createClangModulePrecompileJob(C, ImportingJob, MD); |
| Graph.createJobNode<ClangModuleJobNode>(std::move(ClangModuleJob), |
| std::move(MD)); |
| } |
| } |
| } |
| |
| /// Installs the command lines produced by the dependency scan into |
| /// \p ScannedJobs. |
| static void |
| installScanCommandLines(Compilation &C, |
| MutableArrayRef<std::unique_ptr<Command>> ScannedJobs, |
| ArrayRef<InputDependencies> InputDepsForScannedJobs) { |
| for (auto &&[Job, InputDeps] : llvm::zip_equal( |
| llvm::make_pointee_range(ScannedJobs), InputDepsForScannedJobs)) { |
| const auto &BuildArgs = InputDeps.BuildArgs; |
| ArgStringList JobArgs; |
| JobArgs.reserve(BuildArgs.size()); |
| |
| auto &TCArgs = getToolChainArgs(C, Job); |
| for (const auto &Arg : BuildArgs) |
| JobArgs.push_back(TCArgs.MakeArgString(Arg)); |
| |
| Job.replaceArguments(std::move(JobArgs)); |
| } |
| } |
| |
| /// 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<std::unique_ptr<Command>> &&ScannedJobs, |
| SmallVectorImpl<InputDependencies> &&InputDepsForScannedJobs) { |
| for (auto &&[Job, InputDeps] : |
| llvm::zip_equal(ScannedJobs, InputDepsForScannedJobs)) { |
| if (InputDeps.ModuleName.empty()) |
| Graph.createJobNode<NonModuleTUJobNode>(std::move(Job), |
| std::move(InputDeps)); |
| else |
| Graph.createJobNode<NamedModuleJobNode>(std::move(Job), |
| std::move(InputDeps)); |
| } |
| } |
| |
| template <typename LookupT, typename KeyRangeT> |
| 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<StringRef, CGNode *> NodeByOutputFiles; |
| for (auto *Node : Graph) { |
| for (const auto &Output : cast<JobNode>(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<JobNode>(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<deps::ModuleID, CGNode *> ClangModuleNodeByID; |
| llvm::DenseMap<ModuleNameAndTriple, CGNode *> NamedModuleNodeByID; |
| |
| // Map each module to the job that produces it. |
| bool HasDuplicateModuleError = false; |
| for (auto *Node : Graph) { |
| llvm::TypeSwitch<CGNode *>(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<JobNode>(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<CGNode *>(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<JobNode *> UnusedStdlibModuleJobNodes) { |
| // Collect all reachable non-image job nodes. |
| llvm::SmallPtrSet<JobNode *, 16> PrunableJobNodes; |
| for (auto *PrunableJobNodeRoot : UnusedStdlibModuleJobNodes) { |
| auto ReachableJobNodes = |
| llvm::map_range(llvm::depth_first(cast<CGNode>(PrunableJobNodeRoot)), |
| llvm::CastTo<JobNode>); |
| auto ReachableNonImageNodes = llvm::make_filter_range( |
| ReachableJobNodes, [](auto *N) { return !llvm::isa<ImageJobNode>(N); }); |
| PrunableJobNodes.insert_range(ReachableNonImageNodes); |
| } |
| |
| // Map image job nodes to the prunable job nodes that feed into them. |
| llvm::DenseMap<ImageJobNode *, llvm::SmallPtrSet<JobNode *, 4>> |
| PrunableJobNodesByImageNode; |
| for (auto *PrunableJobNode : PrunableJobNodes) { |
| auto ReachableJobNodes = llvm::depth_first(cast<CGNode>(PrunableJobNode)); |
| auto ReachableImageJobNodes = llvm::map_range( |
| llvm::make_filter_range(ReachableJobNodes, llvm::IsaPred<ImageJobNode>), |
| llvm::CastTo<ImageJobNode>); |
| |
| 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<StringRef, 4> 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<CGNode *, 16> 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); |
| } |
| } |
| |
| /// Creates a temporary output path for \p ModuleName. |
| static std::string createModuleOutputPath(const Compilation &C, |
| StringRef ModuleName) { |
| // Sanitize the ':' included in parition names. It is illegal for filenames on |
| // Windows. |
| SmallString<32> SanitizedModuleName(ModuleName); |
| llvm::replace(SanitizedModuleName, ':', '-'); |
| auto ModuleOutputPath = C.getDriver().GetTemporaryPath( |
| SanitizedModuleName, types::getTypeTempSuffix(types::TY_ModuleFile)); |
| return ModuleOutputPath; |
| } |
| |
| /// Adds the '-fmodule-output=' argument for the module produced by \p Node. |
| static void configureNamedModuleOutputArg(Compilation &C, |
| NamedModuleJobNode &Node, |
| StringRef ModuleOutputPath) { |
| auto &Job = *Node.Job; |
| const auto &TCArgs = getToolChainArgs(C, Job); |
| auto JobArgs = Job.getArguments(); |
| JobArgs.push_back( |
| TCArgs.MakeArgString("-fmodule-output=" + ModuleOutputPath)); |
| Job.replaceArguments(std::move(JobArgs)); |
| } |
| |
| /// Propagates the '-fmodule-file=' mapping for the named module described by |
| /// \p Node to each dependent job. |
| static void propagateModuleFileMappingArg(Compilation &C, |
| NamedModuleJobNode &Node, |
| StringRef ModuleOutputPath) { |
| const StringRef ModuleName = Node.InputDeps.ModuleName; |
| |
| auto DependentNodes = llvm::drop_begin(llvm::depth_first<CGNode *>(&Node)); |
| auto DependentScannedNodes = llvm::map_range( |
| llvm::make_filter_range(DependentNodes, llvm::IsaPred<ScannedJobNode>), |
| llvm::CastTo<ScannedJobNode>); |
| |
| for (ScannedJobNode *DependentNode : DependentScannedNodes) { |
| auto &DependentJob = *DependentNode->Job; |
| const auto &TCArgs = getToolChainArgs(C, DependentJob); |
| auto JobArgs = DependentJob.getArguments(); |
| JobArgs.push_back(TCArgs.MakeArgString("-fmodule-file=" + ModuleName + "=" + |
| ModuleOutputPath)); |
| DependentJob.replaceArguments(std::move(JobArgs)); |
| } |
| } |
| |
| /// Finalizes command lines for C++20 named module dependencies. |
| /// |
| /// The command lines produced by dependency scanning are only adjusted to |
| /// handle discovered Clang modules. For C++20 named modules, we update the |
| /// command-lines here. |
| static void fixupNamedModuleCommandLines(Compilation &C, |
| CompilationGraph &Graph) { |
| const auto NamedModuleNodes = llvm::map_range( |
| llvm::make_filter_range(Graph, llvm::IsaPred<NamedModuleJobNode>), |
| llvm::CastTo<NamedModuleJobNode>); |
| |
| for (NamedModuleJobNode *Node : NamedModuleNodes) { |
| const auto &Job = *Node->Job; |
| |
| // For Standard library modules, the driver already creates the module |
| // output as a temp file, so we can use that path directly. |
| const bool IsStdModule = |
| Job.getInputInfos().front().getType() == types::TY_CXXStdModule; |
| if (IsStdModule) { |
| StringRef ModuleOutputPath = Job.getOutputFilenames().front(); |
| propagateModuleFileMappingArg(C, *Node, ModuleOutputPath); |
| continue; |
| } |
| |
| const StringRef ModuleName = Node->InputDeps.ModuleName; |
| const auto ModuleOutputPath = createModuleOutputPath(C, ModuleName); |
| C.addTempFile(C.getArgs().MakeArgString(ModuleOutputPath)); |
| |
| configureNamedModuleOutputArg(C, *Node, ModuleOutputPath); |
| propagateModuleFileMappingArg(C, *Node, ModuleOutputPath); |
| } |
| } |
| |
| /// Moves jobs from \p Graph into \p C in the graph's topological order. |
| static void feedJobsBackIntoCompilation(Compilation &C, |
| CompilationGraph &&Graph) { |
| llvm::ReversePostOrderTraversal<CompilationGraph *> TopologicallySortedNodes( |
| &Graph); |
| assert(isa<RootNode>(*TopologicallySortedNodes.begin()) && |
| "First node in topological order must be the root!"); |
| auto TopologicallySortedJobNodes = llvm::map_range( |
| llvm::drop_begin(TopologicallySortedNodes), llvm::CastTo<JobNode>); |
| for (auto *JN : TopologicallySortedJobNodes) |
| C.addCommand(std::move(JN->Job)); |
| } |
| |
| void driver::modules::runModulesDriver( |
| Compilation &C, ArrayRef<StdModuleManifest::Module> 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)); |
| |
| auto ScannedJobs = takeJobsAtIndices(Jobs, ScanResult.ScannedJobIndices); |
| if (!validateScannedJobInputKinds(ScannedJobs, |
| ScanResult.InputDepsForScannedJobs, Diags)) |
| return; |
| installScanCommandLines(C, ScannedJobs, ScanResult.InputDepsForScannedJobs); |
| |
| createClangModuleJobsAndNodes( |
| Graph, C, /*ImportingJobs*/ ScannedJobs, |
| std::move(ScanResult.ModuleDepGraphsForScannedJobs)); |
| createNodesForScannedJobs(Graph, std::move(ScannedJobs), |
| 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<const CompilationGraph *>(llvm::errs(), &Graph); |
| |
| fixupNamedModuleCommandLines(C, Graph); |
| feedJobsBackIntoCompilation(C, std::move(Graph)); |
| } |