| 1 | //===--- ModulesDriver.cpp - Driver managed module builds -----------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// |
| 9 | /// \file |
| 10 | /// This file defines functionality to support driver managed builds for |
| 11 | /// compilations which use Clang modules or standard C++20 named modules. |
| 12 | /// |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | |
| 15 | #include "clang/Driver/ModulesDriver.h" |
| 16 | #include "clang/Basic/Diagnostic.h" |
| 17 | #include "clang/Basic/LLVM.h" |
| 18 | #include "clang/DependencyScanning/DependencyScanningUtils.h" |
| 19 | #include "clang/Driver/Compilation.h" |
| 20 | #include "clang/Driver/Driver.h" |
| 21 | #include "clang/Driver/Job.h" |
| 22 | #include "clang/Driver/Tool.h" |
| 23 | #include "clang/Driver/ToolChain.h" |
| 24 | #include "clang/Driver/Types.h" |
| 25 | #include "clang/Frontend/StandaloneDiagnostic.h" |
| 26 | #include "llvm/ADT/DenseSet.h" |
| 27 | #include "llvm/ADT/DepthFirstIterator.h" |
| 28 | #include "llvm/ADT/DirectedGraph.h" |
| 29 | #include "llvm/ADT/PostOrderIterator.h" |
| 30 | #include "llvm/ADT/STLExtras.h" |
| 31 | #include "llvm/ADT/SmallVectorExtras.h" |
| 32 | #include "llvm/ADT/TypeSwitch.h" |
| 33 | #include "llvm/ADT/iterator_range.h" |
| 34 | #include "llvm/Option/ArgList.h" |
| 35 | #include "llvm/Support/Casting.h" |
| 36 | #include "llvm/Support/GraphWriter.h" |
| 37 | #include "llvm/Support/JSON.h" |
| 38 | #include "llvm/Support/Path.h" |
| 39 | #include "llvm/Support/PrettyStackTrace.h" |
| 40 | #include "llvm/Support/ThreadPool.h" |
| 41 | #include "llvm/Support/VirtualFileSystem.h" |
| 42 | #include <utility> |
| 43 | |
| 44 | namespace deps = clang::dependencies; |
| 45 | |
| 46 | using namespace llvm::opt; |
| 47 | using namespace clang; |
| 48 | using namespace driver; |
| 49 | using namespace modules; |
| 50 | |
| 51 | void driver::modules::diagnoseModulesDriverArgs(llvm::opt::DerivedArgList &DAL, |
| 52 | DiagnosticsEngine &Diags) { |
| 53 | if (!DAL.hasFlag(Pos: options::OPT_fmodules_reduced_bmi, |
| 54 | Neg: options::OPT_fno_modules_reduced_bmi, Default: true)) { |
| 55 | Diags.Report(DiagID: diag::err_drv_modules_driver_requires_reduced_bmi); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | namespace clang::driver::modules { |
| 60 | static bool fromJSON(const llvm::json::Value &Params, |
| 61 | StdModuleManifest::Module::LocalArguments &LocalArgs, |
| 62 | llvm::json::Path P) { |
| 63 | llvm::json::ObjectMapper O(Params, P); |
| 64 | return O.mapOptional(Prop: "system-include-directories" , |
| 65 | Out&: LocalArgs.SystemIncludeDirs); |
| 66 | } |
| 67 | |
| 68 | static bool fromJSON(const llvm::json::Value &Params, |
| 69 | StdModuleManifest::Module &ModuleEntry, |
| 70 | llvm::json::Path P) { |
| 71 | llvm::json::ObjectMapper O(Params, P); |
| 72 | return O.map(Prop: "is-std-library" , Out&: ModuleEntry.IsStdlib) && |
| 73 | O.map(Prop: "logical-name" , Out&: ModuleEntry.LogicalName) && |
| 74 | O.map(Prop: "source-path" , Out&: ModuleEntry.SourcePath) && |
| 75 | O.mapOptional(Prop: "local-arguments" , Out&: ModuleEntry.LocalArgs); |
| 76 | } |
| 77 | |
| 78 | static bool fromJSON(const llvm::json::Value &Params, |
| 79 | StdModuleManifest &Manifest, llvm::json::Path P) { |
| 80 | llvm::json::ObjectMapper O(Params, P); |
| 81 | return O.map(Prop: "modules" , Out&: Manifest.Modules); |
| 82 | } |
| 83 | } // namespace clang::driver::modules |
| 84 | |
| 85 | /// Parses the Standard library module manifest from \p Buffer. |
| 86 | static Expected<StdModuleManifest> parseManifest(StringRef Buffer) { |
| 87 | auto ParsedOrErr = llvm::json::parse(JSON: Buffer); |
| 88 | if (!ParsedOrErr) |
| 89 | return ParsedOrErr.takeError(); |
| 90 | |
| 91 | StdModuleManifest Manifest; |
| 92 | llvm::json::Path::Root Root; |
| 93 | if (!fromJSON(Params: *ParsedOrErr, Manifest, P: Root)) |
| 94 | return Root.getError(); |
| 95 | |
| 96 | return Manifest; |
| 97 | } |
| 98 | |
| 99 | /// Converts each file path in manifest from relative to absolute. |
| 100 | /// |
| 101 | /// Each file path in the manifest is expected to be relative the manifest's |
| 102 | /// location \p ManifestPath itself. |
| 103 | static void makeManifestPathsAbsolute( |
| 104 | MutableArrayRef<StdModuleManifest::Module> ManifestEntries, |
| 105 | StringRef ManifestPath) { |
| 106 | StringRef ManifestDir = llvm::sys::path::parent_path(path: ManifestPath); |
| 107 | SmallString<256> TempPath; |
| 108 | |
| 109 | auto PrependManifestDir = [&](StringRef Path) { |
| 110 | TempPath = ManifestDir; |
| 111 | llvm::sys::path::append(path&: TempPath, a: Path); |
| 112 | return std::string(TempPath); |
| 113 | }; |
| 114 | |
| 115 | for (auto &Entry : ManifestEntries) { |
| 116 | Entry.SourcePath = PrependManifestDir(Entry.SourcePath); |
| 117 | if (!Entry.LocalArgs) |
| 118 | continue; |
| 119 | |
| 120 | for (auto &IncludeDir : Entry.LocalArgs->SystemIncludeDirs) |
| 121 | IncludeDir = PrependManifestDir(IncludeDir); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | Expected<StdModuleManifest> |
| 126 | driver::modules::readStdModuleManifest(StringRef ManifestPath, |
| 127 | llvm::vfs::FileSystem &VFS) { |
| 128 | auto MemBufOrErr = VFS.getBufferForFile(Name: ManifestPath); |
| 129 | if (!MemBufOrErr) |
| 130 | return llvm::createFileError(F: ManifestPath, EC: MemBufOrErr.getError()); |
| 131 | |
| 132 | auto ManifestOrErr = parseManifest(Buffer: (*MemBufOrErr)->getBuffer()); |
| 133 | if (!ManifestOrErr) |
| 134 | return ManifestOrErr.takeError(); |
| 135 | auto Manifest = std::move(*ManifestOrErr); |
| 136 | |
| 137 | makeManifestPathsAbsolute(ManifestEntries: Manifest.Modules, ManifestPath); |
| 138 | return Manifest; |
| 139 | } |
| 140 | |
| 141 | void driver::modules::buildStdModuleManifestInputs( |
| 142 | ArrayRef<StdModuleManifest::Module> ManifestEntries, Compilation &C, |
| 143 | InputList &Inputs) { |
| 144 | DerivedArgList &Args = C.getArgs(); |
| 145 | const OptTable &Opts = C.getDriver().getOpts(); |
| 146 | for (const auto &Entry : ManifestEntries) { |
| 147 | auto *InputArg = |
| 148 | makeInputArg(Args, Opts, Value: Args.MakeArgString(Str: Entry.SourcePath)); |
| 149 | Inputs.emplace_back(Args: types::TY_CXXStdModule, Args&: InputArg); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | using ManifestEntryLookup = |
| 154 | llvm::DenseMap<StringRef, const StdModuleManifest::Module *>; |
| 155 | |
| 156 | /// Builds a mapping from a module's source path to its entry in the manifest. |
| 157 | static ManifestEntryLookup |
| 158 | buildManifestLookupMap(ArrayRef<StdModuleManifest::Module> ManifestEntries) { |
| 159 | ManifestEntryLookup ManifestEntryBySource; |
| 160 | for (auto &Entry : ManifestEntries) { |
| 161 | [[maybe_unused]] const bool Inserted = |
| 162 | ManifestEntryBySource.try_emplace(Key: Entry.SourcePath, Args: &Entry).second; |
| 163 | assert(Inserted && |
| 164 | "Manifest defines multiple modules with the same source path." ); |
| 165 | } |
| 166 | return ManifestEntryBySource; |
| 167 | } |
| 168 | |
| 169 | /// Returns the manifest entry corresponding to \p Job, or \c nullptr if none |
| 170 | /// exists. |
| 171 | static const StdModuleManifest::Module * |
| 172 | getManifestEntryForCommand(const Command &Job, |
| 173 | const ManifestEntryLookup &ManifestEntryBySource) { |
| 174 | for (const auto &II : Job.getInputInfos()) { |
| 175 | if (const auto It = ManifestEntryBySource.find(Val: II.getFilename()); |
| 176 | It != ManifestEntryBySource.end()) |
| 177 | return It->second; |
| 178 | } |
| 179 | return nullptr; |
| 180 | } |
| 181 | |
| 182 | /// Adds all \p SystemIncludeDirs to the \p CC1Args of \p Job. |
| 183 | static void |
| 184 | addSystemIncludeDirsFromManifest(Compilation &C, Command &Job, |
| 185 | ArgStringList &CC1Args, |
| 186 | ArrayRef<std::string> SystemIncludeDirs) { |
| 187 | const ToolChain &TC = Job.getCreator().getToolChain(); |
| 188 | const DerivedArgList &TCArgs = |
| 189 | C.getArgsForToolChain(TC: &TC, BA: Job.getSource().getOffloadingArch(), |
| 190 | DeviceOffloadKind: Job.getSource().getOffloadingDeviceKind()); |
| 191 | |
| 192 | for (const auto &IncludeDir : SystemIncludeDirs) |
| 193 | TC.addSystemInclude(DriverArgs: TCArgs, CC1Args, Path: IncludeDir); |
| 194 | } |
| 195 | |
| 196 | static bool isCC1Job(const Command &Job) { |
| 197 | return StringRef(Job.getCreator().getName()) == "clang" ; |
| 198 | } |
| 199 | |
| 200 | /// Apply command-line modifications specific for inputs originating from the |
| 201 | /// Standard library module manifest. |
| 202 | static void applyArgsForStdModuleManifestInputs( |
| 203 | Compilation &C, const ManifestEntryLookup &ManifestEntryBySource, |
| 204 | MutableArrayRef<std::unique_ptr<Command>> Jobs) { |
| 205 | for (auto &Job : Jobs) { |
| 206 | if (!isCC1Job(Job: *Job)) |
| 207 | continue; |
| 208 | |
| 209 | const auto *Entry = getManifestEntryForCommand(Job: *Job, ManifestEntryBySource); |
| 210 | if (!Entry) |
| 211 | continue; |
| 212 | |
| 213 | auto CC1Args = Job->getArguments(); |
| 214 | if (Entry->IsStdlib) |
| 215 | CC1Args.push_back(Elt: "-Wno-reserved-module-identifier" ); |
| 216 | if (Entry->LocalArgs) |
| 217 | addSystemIncludeDirsFromManifest(C, Job&: *Job, CC1Args, |
| 218 | SystemIncludeDirs: Entry->LocalArgs->SystemIncludeDirs); |
| 219 | Job->replaceArguments(List: CC1Args); |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /// Computes the -fmodule-cache-path for this compilation. |
| 224 | static std::optional<std::string> |
| 225 | getModuleCachePath(llvm::opt::DerivedArgList &Args) { |
| 226 | if (const Arg *A = Args.getLastArg(Ids: options::OPT_fmodules_cache_path)) |
| 227 | return A->getValue(); |
| 228 | |
| 229 | if (SmallString<128> Path; Driver::getDefaultModuleCachePath(Result&: Path)) |
| 230 | return std::string(Path); |
| 231 | |
| 232 | return std::nullopt; |
| 233 | } |
| 234 | |
| 235 | /// Returns true if a dependency scan can be performed using \p Job. |
| 236 | static bool isDependencyScannableJob(const Command &Job) { |
| 237 | if (!isCC1Job(Job)) |
| 238 | return false; |
| 239 | const auto &InputInfos = Job.getInputInfos(); |
| 240 | return !InputInfos.empty() && types::isSrcFile(Id: InputInfos.front().getType()); |
| 241 | } |
| 242 | |
| 243 | namespace { |
| 244 | /// Pool of reusable dependency scanning workers and their contexts with |
| 245 | /// RAII-based acquire/release. |
| 246 | class ScanningWorkerPool { |
| 247 | public: |
| 248 | ScanningWorkerPool(size_t NumWorkers, |
| 249 | deps::DependencyScanningService &ScanningService) { |
| 250 | for (size_t I = 0; I < NumWorkers; ++I) |
| 251 | Slots.emplace_back(Args&: ScanningService); |
| 252 | |
| 253 | AvailableSlots.resize(N: NumWorkers); |
| 254 | std::iota(first: AvailableSlots.begin(), last: AvailableSlots.end(), value: 0); |
| 255 | } |
| 256 | |
| 257 | /// Acquires a unique pointer to a dependency scanning worker and its |
| 258 | /// context. |
| 259 | /// |
| 260 | /// The worker bundle automatically released back to the pool when the |
| 261 | /// pointer is destroyed. The pool has to outlive the leased worker bundle. |
| 262 | [[nodiscard]] auto scopedAcquire() { |
| 263 | std::unique_lock<std::mutex> UL(Lock); |
| 264 | CV.wait(lock&: UL, p: [&] { return !AvailableSlots.empty(); }); |
| 265 | const size_t Index = AvailableSlots.pop_back_val(); |
| 266 | auto ReleaseHandle = [this, Index](WorkerBundle *) { release(Index); }; |
| 267 | return std::unique_ptr<WorkerBundle, decltype(ReleaseHandle)>( |
| 268 | &Slots[Index], ReleaseHandle); |
| 269 | } |
| 270 | |
| 271 | private: |
| 272 | /// Releases the worker bundle at \c Index back into the pool. |
| 273 | void release(size_t Index) { |
| 274 | { |
| 275 | std::scoped_lock<std::mutex> SL(Lock); |
| 276 | AvailableSlots.push_back(Elt: Index); |
| 277 | } |
| 278 | CV.notify_one(); |
| 279 | } |
| 280 | |
| 281 | /// A scanning worker with its associated context. |
| 282 | struct WorkerBundle { |
| 283 | WorkerBundle(deps::DependencyScanningService &ScanningService) |
| 284 | : Worker(std::make_unique<deps::DependencyScanningWorker>( |
| 285 | args&: ScanningService)) {} |
| 286 | |
| 287 | std::unique_ptr<deps::DependencyScanningWorker> Worker; |
| 288 | llvm::DenseSet<deps::ModuleID> SeenModules; |
| 289 | }; |
| 290 | |
| 291 | std::mutex Lock; |
| 292 | std::condition_variable CV; |
| 293 | SmallVector<size_t> AvailableSlots; |
| 294 | SmallVector<WorkerBundle, 0> Slots; |
| 295 | }; |
| 296 | } // anonymous namespace |
| 297 | |
| 298 | // Creates a ThreadPool and a corresponding ScanningWorkerPool optimized for |
| 299 | // the configuration of dependency scan inputs. |
| 300 | static std::pair<std::unique_ptr<llvm::ThreadPoolInterface>, |
| 301 | std::unique_ptr<ScanningWorkerPool>> |
| 302 | createOptimalThreadAndWorkerPool( |
| 303 | size_t NumScanInputs, bool HasStdlibModuleInputs, |
| 304 | deps::DependencyScanningService &ScanningService) { |
| 305 | // TODO: Benchmark: Determine the optimal number of worker threads for a |
| 306 | // given number of inputs. How many inputs are required for multi-threading |
| 307 | // to be beneficial? How many inputs should each thread scan at least? |
| 308 | #if LLVM_ENABLE_THREADS |
| 309 | std::unique_ptr<llvm::ThreadPoolInterface> ThreadPool; |
| 310 | size_t WorkerCount; |
| 311 | |
| 312 | if (NumScanInputs == 1 || (HasStdlibModuleInputs && NumScanInputs <= 2)) { |
| 313 | auto S = llvm::optimal_concurrency(TaskCount: 1); |
| 314 | ThreadPool = std::make_unique<llvm::SingleThreadExecutor>(args: std::move(S)); |
| 315 | WorkerCount = 1; |
| 316 | } else { |
| 317 | auto ThreadPoolStrategy = llvm::optimal_concurrency( |
| 318 | TaskCount: NumScanInputs - static_cast<size_t>(HasStdlibModuleInputs)); |
| 319 | ThreadPool = std::make_unique<llvm::DefaultThreadPool>( |
| 320 | args: std::move(ThreadPoolStrategy)); |
| 321 | const size_t MaxConcurrency = ThreadPool->getMaxConcurrency(); |
| 322 | const size_t MaxConcurrentlyScannedInputs = |
| 323 | NumScanInputs - |
| 324 | (HasStdlibModuleInputs && NumScanInputs < MaxConcurrency ? 1 : 0); |
| 325 | WorkerCount = std::min(a: MaxConcurrency, b: MaxConcurrentlyScannedInputs); |
| 326 | } |
| 327 | #else |
| 328 | auto ThreadPool = std::make_unique<llvm::SingleThreadExecutor>(); |
| 329 | size_t WorkerCount = 1; |
| 330 | #endif |
| 331 | |
| 332 | return {std::move(ThreadPool), |
| 333 | std::make_unique<ScanningWorkerPool>(args&: WorkerCount, args&: ScanningService)}; |
| 334 | } |
| 335 | |
| 336 | static StringRef getTriple(const Command &Job) { |
| 337 | return Job.getCreator().getToolChain().getTriple().getTriple(); |
| 338 | } |
| 339 | |
| 340 | using ModuleNameAndTriple = std::pair<StringRef, StringRef>; |
| 341 | |
| 342 | namespace { |
| 343 | /// Helper to schedule on-demand dependency scans for modules originating from |
| 344 | /// the Standard library module manifest. |
| 345 | struct StdlibModuleScanScheduler { |
| 346 | StdlibModuleScanScheduler(const llvm::DenseMap<ModuleNameAndTriple, size_t> |
| 347 | &StdlibModuleScanIndexByID) |
| 348 | : StdlibModuleScanIndexByID(StdlibModuleScanIndexByID) { |
| 349 | ScheduledScanInputs.reserve(Size: StdlibModuleScanIndexByID.size()); |
| 350 | } |
| 351 | |
| 352 | /// Returns the indices of scan inputs corresponding to newly imported |
| 353 | /// Standard library modules. |
| 354 | /// |
| 355 | /// Thread-safe. |
| 356 | SmallVector<size_t, 2> getNewScanInputs(ArrayRef<std::string> NamedModuleDeps, |
| 357 | StringRef Triple) { |
| 358 | SmallVector<size_t, 2> NewScanInputs; |
| 359 | std::scoped_lock<std::mutex> Guard(Lock); |
| 360 | for (const auto &ModuleName : NamedModuleDeps) { |
| 361 | const auto It = StdlibModuleScanIndexByID.find(Val: {ModuleName, Triple}); |
| 362 | if (It == StdlibModuleScanIndexByID.end()) |
| 363 | continue; |
| 364 | const size_t ScanIndex = It->second; |
| 365 | const bool AlreadyScheduled = |
| 366 | !ScheduledScanInputs.insert(V: ScanIndex).second; |
| 367 | if (AlreadyScheduled) |
| 368 | continue; |
| 369 | NewScanInputs.push_back(Elt: ScanIndex); |
| 370 | } |
| 371 | return NewScanInputs; |
| 372 | } |
| 373 | |
| 374 | private: |
| 375 | const llvm::DenseMap<ModuleNameAndTriple, size_t> &StdlibModuleScanIndexByID; |
| 376 | llvm::SmallDenseSet<size_t> ScheduledScanInputs; |
| 377 | std::mutex Lock; |
| 378 | }; |
| 379 | |
| 380 | /// Collects diagnostics in a form that can be retained until after their |
| 381 | /// associated SourceManager is destroyed. |
| 382 | class StandaloneDiagCollector : public DiagnosticConsumer { |
| 383 | public: |
| 384 | void BeginSourceFile(const LangOptions &LangOpts, |
| 385 | const Preprocessor *PP = nullptr) override { |
| 386 | this->LangOpts = &LangOpts; |
| 387 | } |
| 388 | |
| 389 | void HandleDiagnostic(DiagnosticsEngine::Level Level, |
| 390 | const Diagnostic &Info) override { |
| 391 | StoredDiagnostic StoredDiag(Level, Info); |
| 392 | StandaloneDiags.emplace_back(Args: *LangOpts, Args&: StoredDiag); |
| 393 | DiagnosticConsumer::HandleDiagnostic(DiagLevel: Level, Info); |
| 394 | } |
| 395 | |
| 396 | SmallVector<StandaloneDiagnostic, 0> takeDiagnostics() { |
| 397 | return std::move(StandaloneDiags); |
| 398 | } |
| 399 | |
| 400 | private: |
| 401 | const LangOptions *LangOpts = nullptr; |
| 402 | SmallVector<StandaloneDiagnostic, 0> StandaloneDiags; |
| 403 | }; |
| 404 | |
| 405 | /// RAII utility to report collected StandaloneDiagnostic through a |
| 406 | /// DiagnosticsEngine. |
| 407 | /// |
| 408 | /// The driver's DiagnosticsEngine usually does not have a SourceManager at |
| 409 | /// this point of building the compilation, in which case the |
| 410 | /// StandaloneDiagReporter supplies its own. |
| 411 | class StandaloneDiagReporter { |
| 412 | public: |
| 413 | explicit StandaloneDiagReporter(DiagnosticsEngine &Diags) : Diags(Diags) { |
| 414 | if (!Diags.hasSourceManager()) { |
| 415 | FileSystemOptions Opts; |
| 416 | Opts.WorkingDir = "." ; |
| 417 | OwnedFileMgr = llvm::makeIntrusiveRefCnt<FileManager>(A: std::move(Opts)); |
| 418 | OwnedSrcMgr = |
| 419 | llvm::makeIntrusiveRefCnt<SourceManager>(A&: Diags, A&: *OwnedFileMgr); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | /// Emits all diagnostics in \c StandaloneDiags using the associated |
| 424 | /// DiagnosticsEngine. |
| 425 | void Report(ArrayRef<StandaloneDiagnostic> StandaloneDiags) const { |
| 426 | llvm::StringMap<SourceLocation> SrcLocCache; |
| 427 | Diags.getClient()->BeginSourceFile(LangOpts: LangOptions(), PP: nullptr); |
| 428 | for (const auto &StandaloneDiag : StandaloneDiags) { |
| 429 | const auto StoredDiag = translateStandaloneDiag( |
| 430 | FileMgr&: getFileManager(), SrcMgr&: getSourceManager(), StandaloneDiag, SrcLocCache); |
| 431 | Diags.Report(storedDiag: StoredDiag); |
| 432 | } |
| 433 | Diags.getClient()->EndSourceFile(); |
| 434 | } |
| 435 | |
| 436 | private: |
| 437 | DiagnosticsEngine &Diags; |
| 438 | IntrusiveRefCntPtr<FileManager> OwnedFileMgr; |
| 439 | IntrusiveRefCntPtr<SourceManager> OwnedSrcMgr; |
| 440 | |
| 441 | FileManager &getFileManager() const { |
| 442 | if (OwnedFileMgr) |
| 443 | return *OwnedFileMgr; |
| 444 | return Diags.getSourceManager().getFileManager(); |
| 445 | } |
| 446 | |
| 447 | SourceManager &getSourceManager() const { |
| 448 | if (OwnedSrcMgr) |
| 449 | return *OwnedSrcMgr; |
| 450 | return Diags.getSourceManager(); |
| 451 | } |
| 452 | }; |
| 453 | } // anonymous namespace |
| 454 | |
| 455 | /// Report the diagnostics collected during each dependency scan. |
| 456 | static void reportAllScanDiagnostics( |
| 457 | SmallVectorImpl<SmallVector<StandaloneDiagnostic, 0>> &&AllScanDiags, |
| 458 | DiagnosticsEngine &Diags) { |
| 459 | StandaloneDiagReporter Reporter(Diags); |
| 460 | for (auto &SingleScanDiags : AllScanDiags) |
| 461 | Reporter.Report(StandaloneDiags: SingleScanDiags); |
| 462 | } |
| 463 | |
| 464 | /// Construct a path for the explicitly built PCM. |
| 465 | static std::string constructPCMPath(const deps::ModuleID &ID, |
| 466 | StringRef OutputDir) { |
| 467 | assert(!ID.ModuleName.empty() && !ID.ContextHash.empty() && |
| 468 | "Invalid ModuleID!" ); |
| 469 | SmallString<256> ExplicitPCMPath(OutputDir); |
| 470 | llvm::sys::path::append(path&: ExplicitPCMPath, a: ID.ContextHash, |
| 471 | b: ID.ModuleName + "-" + ID.ContextHash + ".pcm" ); |
| 472 | return std::string(ExplicitPCMPath); |
| 473 | } |
| 474 | |
| 475 | namespace { |
| 476 | /// A simple dependency action controller that only provides module lookup for |
| 477 | /// Clang modules. |
| 478 | class ModuleLookupController : public deps::DependencyActionController { |
| 479 | public: |
| 480 | ModuleLookupController(StringRef OutputDir) : OutputDir(OutputDir) {} |
| 481 | |
| 482 | std::string lookupModuleOutput(const deps::ModuleDeps &MD, |
| 483 | deps::ModuleOutputKind Kind) override { |
| 484 | if (Kind == deps::ModuleOutputKind::ModuleFile) |
| 485 | return constructPCMPath(ID: MD.ID, OutputDir); |
| 486 | |
| 487 | // Driver command lines that trigger lookups for unsupported |
| 488 | // ModuleOutputKinds are not supported by the modules driver. Those |
| 489 | // command lines should probably be adjusted or rejected in |
| 490 | // Driver::handleArguments or Driver::HandleImmediateArgs. |
| 491 | llvm::reportFatalInternalError( |
| 492 | reason: "call to lookupModuleOutput with unexpected ModuleOutputKind" ); |
| 493 | } |
| 494 | |
| 495 | std::unique_ptr<DependencyActionController> clone() const override { |
| 496 | return std::make_unique<ModuleLookupController>(args: OutputDir); |
| 497 | } |
| 498 | |
| 499 | private: |
| 500 | StringRef OutputDir; |
| 501 | }; |
| 502 | |
| 503 | /// The full dependencies for a specific command-line input. |
| 504 | struct InputDependencies { |
| 505 | /// The name of the C++20 module provided by this translation unit. |
| 506 | std::string ModuleName; |
| 507 | |
| 508 | /// A list of modules this translation unit directly depends on, not including |
| 509 | /// transitive dependencies. |
| 510 | /// |
| 511 | /// This may include modules with a different context hash when it can be |
| 512 | /// determined that the differences are benign for this compilation. |
| 513 | std::vector<deps::ModuleID> ClangModuleDeps; |
| 514 | |
| 515 | /// A list of the C++20 named modules this translation unit depends on. |
| 516 | /// |
| 517 | /// These correspond only to modules built with compatible compiler |
| 518 | /// invocations. |
| 519 | std::vector<std::string> NamedModuleDeps; |
| 520 | |
| 521 | /// A collection of absolute paths to files that this translation unit |
| 522 | /// directly depends on, not including transitive dependencies. |
| 523 | std::vector<std::string> FileDeps; |
| 524 | |
| 525 | /// The compiler invocation with modifications to properly import all Clang |
| 526 | /// module dependencies. Does not include argv[0]. |
| 527 | std::vector<std::string> BuildArgs; |
| 528 | }; |
| 529 | } // anonymous namespace |
| 530 | |
| 531 | static InputDependencies makeInputDeps(deps::TranslationUnitDeps &&TUDeps) { |
| 532 | InputDependencies InputDeps; |
| 533 | InputDeps.ModuleName = std::move(TUDeps.ID.ModuleName); |
| 534 | InputDeps.NamedModuleDeps = std::move(TUDeps.NamedModuleDeps); |
| 535 | InputDeps.ClangModuleDeps = std::move(TUDeps.ClangModuleDeps); |
| 536 | InputDeps.FileDeps = std::move(TUDeps.FileDeps); |
| 537 | assert(TUDeps.Commands.size() == 1 && "Expected exactly one command" ); |
| 538 | InputDeps.BuildArgs = std::move(TUDeps.Commands.front().Arguments); |
| 539 | return InputDeps; |
| 540 | } |
| 541 | |
| 542 | /// Constructs the full command line, including the executable, for \p Job. |
| 543 | static SmallVector<std::string, 0> buildCommandLine(const Command &Job) { |
| 544 | const auto &JobArgs = Job.getArguments(); |
| 545 | SmallVector<std::string, 0> CommandLine; |
| 546 | CommandLine.reserve(N: JobArgs.size() + 1); |
| 547 | CommandLine.emplace_back(Args: Job.getExecutable()); |
| 548 | for (const char *Arg : JobArgs) |
| 549 | CommandLine.emplace_back(Args&: Arg); |
| 550 | return CommandLine; |
| 551 | } |
| 552 | |
| 553 | /// Performs a dependency scan for a single job. |
| 554 | /// |
| 555 | /// \returns a pair containing TranslationUnitDeps on success, or std::nullopt |
| 556 | /// on failure, along with any diagnostics produced. |
| 557 | static std::pair<std::optional<deps::TranslationUnitDeps>, |
| 558 | SmallVector<StandaloneDiagnostic, 0>> |
| 559 | scanDependenciesForJob(const Command &Job, ScanningWorkerPool &WorkerPool, |
| 560 | StringRef WorkingDirectory, |
| 561 | ModuleLookupController &LookupController) { |
| 562 | StandaloneDiagCollector DiagConsumer; |
| 563 | std::optional<deps::TranslationUnitDeps> MaybeTUDeps; |
| 564 | |
| 565 | { |
| 566 | const auto CC1CommandLine = buildCommandLine(Job); |
| 567 | auto WorkerBundleHandle = WorkerPool.scopedAcquire(); |
| 568 | deps::FullDependencyConsumer DepConsumer(WorkerBundleHandle->SeenModules); |
| 569 | |
| 570 | if (WorkerBundleHandle->Worker->computeDependencies( |
| 571 | WorkingDirectory, CommandLines: {CC1CommandLine}, DepConsumer, Controller&: LookupController, |
| 572 | DiagConsumer)) |
| 573 | MaybeTUDeps = DepConsumer.takeTranslationUnitDeps(); |
| 574 | } |
| 575 | |
| 576 | return {std::move(MaybeTUDeps), DiagConsumer.takeDiagnostics()}; |
| 577 | } |
| 578 | |
| 579 | namespace { |
| 580 | struct DependencyScanResult { |
| 581 | /// Indices of jobs that were successfully scanned. |
| 582 | SmallVector<size_t> ScannedJobIndices; |
| 583 | |
| 584 | /// Input dependencies for scanned jobs. Parallel to \c ScannedJobIndices. |
| 585 | SmallVector<InputDependencies, 0> InputDepsForScannedJobs; |
| 586 | |
| 587 | /// Module dependency graphs for scanned jobs. Parallel to \c |
| 588 | /// ScannedJobIndices. |
| 589 | SmallVector<deps::ModuleDepsGraph, 0> ModuleDepGraphsForScannedJobs; |
| 590 | |
| 591 | /// Indices of Standard library module jobs not discovered as dependencies. |
| 592 | SmallVector<size_t> UnusedStdlibModuleJobIndices; |
| 593 | |
| 594 | /// Indices of jobs that could not be scanned (e.g. image jobs, ...). |
| 595 | SmallVector<size_t> NonScannableJobIndices; |
| 596 | }; |
| 597 | } // anonymous namespace |
| 598 | |
| 599 | /// Scans the compilations job list \p Jobs for module dependencies. |
| 600 | /// |
| 601 | /// Standard library module jobs are scanned on demand if imported by any |
| 602 | /// user-provided input. |
| 603 | /// |
| 604 | /// \returns DependencyScanResult on success, or std::nullopt on failure, with |
| 605 | /// diagnostics reported via \p Diags in both cases. |
| 606 | static std::optional<DependencyScanResult> scanDependencies( |
| 607 | ArrayRef<std::unique_ptr<Command>> Jobs, |
| 608 | llvm::DenseMap<StringRef, const StdModuleManifest::Module *> ManifestLookup, |
| 609 | StringRef ModuleCachePath, StringRef WorkingDirectory, |
| 610 | DiagnosticsEngine &Diags) { |
| 611 | llvm::PrettyStackTraceString CrashInfo("Performing module dependency scan." ); |
| 612 | |
| 613 | // Classify the jobs based on scan eligibility. |
| 614 | SmallVector<size_t> ScannableJobIndices; |
| 615 | SmallVector<size_t> NonScannableJobIndices; |
| 616 | for (const auto &&[Index, Job] : llvm::enumerate(First&: Jobs)) { |
| 617 | if (isDependencyScannableJob(Job: *Job)) |
| 618 | ScannableJobIndices.push_back(Elt: Index); |
| 619 | else |
| 620 | NonScannableJobIndices.push_back(Elt: Index); |
| 621 | } |
| 622 | |
| 623 | // Classify scannable jobs by origin. User-provided inputs will be scanned |
| 624 | // immediately, while Standard library modules are indexed for on-demand |
| 625 | // scanning when discovered as dependencies. |
| 626 | SmallVector<size_t> UserInputScanIndices; |
| 627 | llvm::DenseMap<ModuleNameAndTriple, size_t> StdlibModuleScanIndexByID; |
| 628 | for (const auto &&[ScanIndex, JobIndex] : |
| 629 | llvm::enumerate(First&: ScannableJobIndices)) { |
| 630 | const Command &ScanJob = *Jobs[JobIndex]; |
| 631 | if (const auto *Entry = |
| 632 | getManifestEntryForCommand(Job: ScanJob, ManifestEntryBySource: ManifestLookup)) { |
| 633 | ModuleNameAndTriple ID{Entry->LogicalName, getTriple(Job: ScanJob)}; |
| 634 | [[maybe_unused]] const bool Inserted = |
| 635 | StdlibModuleScanIndexByID.try_emplace(Key: ID, Args&: ScanIndex).second; |
| 636 | assert(Inserted && |
| 637 | "Multiple jobs build the same module for the same triple." ); |
| 638 | } else { |
| 639 | UserInputScanIndices.push_back(Elt: ScanIndex); |
| 640 | } |
| 641 | } |
| 642 | |
| 643 | // Initialize the scan context. |
| 644 | const size_t NumScanInputs = ScannableJobIndices.size(); |
| 645 | const bool HasStdlibModuleInputs = !StdlibModuleScanIndexByID.empty(); |
| 646 | |
| 647 | deps::DependencyScanningServiceOptions Opts; |
| 648 | deps::DependencyScanningService ScanningService(std::move(Opts)); |
| 649 | |
| 650 | std::unique_ptr<llvm::ThreadPoolInterface> ThreadPool; |
| 651 | std::unique_ptr<ScanningWorkerPool> WorkerPool; |
| 652 | std::tie(args&: ThreadPool, args&: WorkerPool) = createOptimalThreadAndWorkerPool( |
| 653 | NumScanInputs, HasStdlibModuleInputs, ScanningService); |
| 654 | |
| 655 | StdlibModuleScanScheduler StdlibModuleRegistry(StdlibModuleScanIndexByID); |
| 656 | ModuleLookupController LookupController(ModuleCachePath); |
| 657 | |
| 658 | // Scan results are indexed by ScanIndex into ScannableJobIndices, not by |
| 659 | // JobIndex into Jobs. This allows one result slot per scannable job. |
| 660 | SmallVector<std::optional<deps::TranslationUnitDeps>, 0> AllScanResults( |
| 661 | NumScanInputs); |
| 662 | SmallVector<SmallVector<StandaloneDiagnostic, 0>, 0> AllScanDiags( |
| 663 | NumScanInputs); |
| 664 | std::atomic<bool> HasError{false}; |
| 665 | |
| 666 | // Scans the job at the given scan index and schedules scans for any newly |
| 667 | // discovered Standard library module dependencies. |
| 668 | std::function<void(size_t)> ScanOneAndScheduleNew; |
| 669 | ScanOneAndScheduleNew = [&](size_t ScanIndex) { |
| 670 | const size_t JobIndex = ScannableJobIndices[ScanIndex]; |
| 671 | const Command &Job = *Jobs[JobIndex]; |
| 672 | auto [MaybeTUDeps, ScanDiags] = scanDependenciesForJob( |
| 673 | Job, WorkerPool&: *WorkerPool, WorkingDirectory, LookupController); |
| 674 | |
| 675 | // Store diagnostics even for successful scans to also capture any warnings |
| 676 | // or notes. |
| 677 | assert(AllScanDiags[ScanIndex].empty() && |
| 678 | "Each slot should be written to at most once." ); |
| 679 | AllScanDiags[ScanIndex] = std::move(ScanDiags); |
| 680 | |
| 681 | if (!MaybeTUDeps) { |
| 682 | HasError.store(i: true, m: std::memory_order_relaxed); |
| 683 | return; |
| 684 | } |
| 685 | |
| 686 | // Schedule scans for newly discovered Standard library module dependencies. |
| 687 | const auto NewScanInputs = StdlibModuleRegistry.getNewScanInputs( |
| 688 | NamedModuleDeps: MaybeTUDeps->NamedModuleDeps, Triple: getTriple(Job)); |
| 689 | for (const size_t NewScanIndex : NewScanInputs) |
| 690 | ThreadPool->async( |
| 691 | F: [&, NewScanIndex]() { ScanOneAndScheduleNew(NewScanIndex); }); |
| 692 | |
| 693 | assert(!AllScanResults[ScanIndex].has_value() && |
| 694 | "Each slot should be written to at most once." ); |
| 695 | AllScanResults[ScanIndex] = std::move(MaybeTUDeps); |
| 696 | }; |
| 697 | |
| 698 | // Initiate the scan with all jobs for user-provided inputs. |
| 699 | for (const size_t ScanIndex : UserInputScanIndices) |
| 700 | ThreadPool->async(F: [&ScanOneAndScheduleNew, ScanIndex]() { |
| 701 | ScanOneAndScheduleNew(ScanIndex); |
| 702 | }); |
| 703 | ThreadPool->wait(); |
| 704 | |
| 705 | reportAllScanDiagnostics(AllScanDiags: std::move(AllScanDiags), Diags); |
| 706 | if (HasError.load(m: std::memory_order_relaxed)) |
| 707 | return std::nullopt; |
| 708 | |
| 709 | // Collect results, mapping scan indices back to job indices. |
| 710 | DependencyScanResult Result; |
| 711 | for (auto &&[JobIndex, MaybeTUDeps] : |
| 712 | llvm::zip_equal(t&: ScannableJobIndices, u&: AllScanResults)) { |
| 713 | if (MaybeTUDeps) { |
| 714 | Result.ScannedJobIndices.push_back(Elt: JobIndex); |
| 715 | Result.ModuleDepGraphsForScannedJobs.push_back( |
| 716 | Elt: std::move(MaybeTUDeps->ModuleGraph)); |
| 717 | Result.InputDepsForScannedJobs.push_back( |
| 718 | Elt: makeInputDeps(TUDeps: std::move(*MaybeTUDeps))); |
| 719 | } else |
| 720 | Result.UnusedStdlibModuleJobIndices.push_back(Elt: JobIndex); |
| 721 | } |
| 722 | Result.NonScannableJobIndices = std::move(NonScannableJobIndices); |
| 723 | |
| 724 | #ifndef NDEBUG |
| 725 | llvm::SmallDenseSet<size_t> SeenJobIndices; |
| 726 | SeenJobIndices.insert_range(Result.ScannedJobIndices); |
| 727 | SeenJobIndices.insert_range(Result.UnusedStdlibModuleJobIndices); |
| 728 | SeenJobIndices.insert_range(Result.NonScannableJobIndices); |
| 729 | assert(llvm::all_of(llvm::index_range(0, Jobs.size()), |
| 730 | [&](size_t JobIndex) { |
| 731 | return SeenJobIndices.contains(JobIndex); |
| 732 | }) && |
| 733 | "Scan result must partition all jobs" ); |
| 734 | #endif |
| 735 | |
| 736 | return Result; |
| 737 | } |
| 738 | |
| 739 | namespace { |
| 740 | class CGNode; |
| 741 | class CGEdge; |
| 742 | using CGNodeBase = llvm::DGNode<CGNode, CGEdge>; |
| 743 | using CGEdgeBase = llvm::DGEdge<CGNode, CGEdge>; |
| 744 | using CGBase = llvm::DirectedGraph<CGNode, CGEdge>; |
| 745 | |
| 746 | /// Compilation Graph Node |
| 747 | class CGNode : public CGNodeBase { |
| 748 | public: |
| 749 | enum class NodeKind { |
| 750 | ClangModuleCC1Job, |
| 751 | NamedModuleCC1Job, |
| 752 | NonModuleCC1Job, |
| 753 | MiscJob, |
| 754 | ImageJob, |
| 755 | Root, |
| 756 | }; |
| 757 | |
| 758 | CGNode(const NodeKind K) : Kind(K) {} |
| 759 | CGNode(const CGNode &) = delete; |
| 760 | CGNode(CGNode &&) = delete; |
| 761 | CGNode &operator=(const CGNode &) = delete; |
| 762 | CGNode &operator=(CGNode &&) = delete; |
| 763 | virtual ~CGNode() = 0; |
| 764 | |
| 765 | NodeKind getKind() const { return Kind; } |
| 766 | |
| 767 | private: |
| 768 | NodeKind Kind; |
| 769 | }; |
| 770 | CGNode::~CGNode() = default; |
| 771 | |
| 772 | /// Subclass of CGNode representing the root node of the graph. |
| 773 | /// |
| 774 | /// The root node is a special node that connects to all other nodes with |
| 775 | /// no incoming edges, so that there is always a path from it to any node |
| 776 | /// in the graph. |
| 777 | /// |
| 778 | /// There should only be one such node in a given graph. |
| 779 | class RootNode : public CGNode { |
| 780 | public: |
| 781 | RootNode() : CGNode(NodeKind::Root) {} |
| 782 | ~RootNode() override = default; |
| 783 | |
| 784 | static bool classof(const CGNode *N) { |
| 785 | return N->getKind() == NodeKind::Root; |
| 786 | } |
| 787 | }; |
| 788 | |
| 789 | /// Base class for any CGNode type that represents a job. |
| 790 | class JobNode : public CGNode { |
| 791 | public: |
| 792 | JobNode(std::unique_ptr<Command> &&Job, NodeKind Kind) |
| 793 | : CGNode(Kind), Job(std::move(Job)) { |
| 794 | assert(this->Job && "Expected valid job!" ); |
| 795 | } |
| 796 | virtual ~JobNode() override = 0; |
| 797 | |
| 798 | std::unique_ptr<Command> Job; |
| 799 | |
| 800 | static bool classof(const CGNode *N) { |
| 801 | return N->getKind() != NodeKind::Root; |
| 802 | } |
| 803 | }; |
| 804 | JobNode::~JobNode() = default; |
| 805 | |
| 806 | /// Subclass of CGNode representing a -cc1 job which produces a Clang module. |
| 807 | class ClangModuleJobNode : public JobNode { |
| 808 | public: |
| 809 | ClangModuleJobNode(std::unique_ptr<Command> &&Job, deps::ModuleDeps &&MD) |
| 810 | : JobNode(std::move(Job), NodeKind::ClangModuleCC1Job), |
| 811 | MD(std::move(MD)) {} |
| 812 | ~ClangModuleJobNode() override = default; |
| 813 | |
| 814 | deps::ModuleDeps MD; |
| 815 | |
| 816 | static bool classof(const CGNode *N) { |
| 817 | return N->getKind() == NodeKind::ClangModuleCC1Job; |
| 818 | } |
| 819 | }; |
| 820 | |
| 821 | /// Base class for any CGNode type that represents any scanned -cc1 job. |
| 822 | class ScannedJobNode : public JobNode { |
| 823 | public: |
| 824 | ScannedJobNode(std::unique_ptr<Command> &&Job, InputDependencies &&InputDeps, |
| 825 | NodeKind Kind) |
| 826 | : JobNode(std::move(Job), Kind), InputDeps(std::move(InputDeps)) {} |
| 827 | ~ScannedJobNode() override = default; |
| 828 | |
| 829 | InputDependencies InputDeps; |
| 830 | |
| 831 | static bool classof(const CGNode *N) { |
| 832 | return N->getKind() == NodeKind::NamedModuleCC1Job || |
| 833 | N->getKind() == NodeKind::NonModuleCC1Job; |
| 834 | } |
| 835 | }; |
| 836 | |
| 837 | /// Subclass of CGNode representing a -cc1 job which produces a C++20 named |
| 838 | /// module. |
| 839 | class NamedModuleJobNode : public ScannedJobNode { |
| 840 | public: |
| 841 | NamedModuleJobNode(std::unique_ptr<Command> &&Job, |
| 842 | InputDependencies &&InputDeps) |
| 843 | : ScannedJobNode(std::move(Job), std::move(InputDeps), |
| 844 | NodeKind::NamedModuleCC1Job) {} |
| 845 | ~NamedModuleJobNode() override = default; |
| 846 | |
| 847 | static bool classof(const CGNode *N) { |
| 848 | return N->getKind() == NodeKind::NamedModuleCC1Job; |
| 849 | } |
| 850 | }; |
| 851 | |
| 852 | /// Subclass of CGNode representing a -cc1 job which does not produce any |
| 853 | /// module, but might still have module imports. |
| 854 | class NonModuleTUJobNode : public ScannedJobNode { |
| 855 | public: |
| 856 | NonModuleTUJobNode(std::unique_ptr<Command> &&Job, |
| 857 | InputDependencies &&InputDeps) |
| 858 | : ScannedJobNode(std::move(Job), std::move(InputDeps), |
| 859 | NodeKind::NonModuleCC1Job) {} |
| 860 | ~NonModuleTUJobNode() override = default; |
| 861 | |
| 862 | static bool classof(const CGNode *N) { |
| 863 | return N->getKind() == NodeKind::NonModuleCC1Job; |
| 864 | } |
| 865 | }; |
| 866 | |
| 867 | /// Subclass of CGNode representing a job which produces an image file, such as |
| 868 | /// a linker or interface stub merge job. |
| 869 | class ImageJobNode : public JobNode { |
| 870 | public: |
| 871 | ImageJobNode(std::unique_ptr<Command> &&Job) |
| 872 | : JobNode(std::move(Job), NodeKind::ImageJob) {} |
| 873 | ~ImageJobNode() override = default; |
| 874 | |
| 875 | static bool classof(const CGNode *N) { |
| 876 | return N->getKind() == NodeKind::ImageJob; |
| 877 | } |
| 878 | }; |
| 879 | |
| 880 | /// Subclass of CGNode representing any job not covered by the other node types. |
| 881 | /// |
| 882 | /// Jobs represented by this node type are not modified by the modules driver. |
| 883 | class MiscJobNode : public JobNode { |
| 884 | public: |
| 885 | MiscJobNode(std::unique_ptr<Command> &&Job) |
| 886 | : JobNode(std::move(Job), NodeKind::MiscJob) {} |
| 887 | ~MiscJobNode() override = default; |
| 888 | |
| 889 | static bool classof(const CGNode *N) { |
| 890 | return N->getKind() == NodeKind::MiscJob; |
| 891 | } |
| 892 | }; |
| 893 | |
| 894 | /// Compilation Graph Edge |
| 895 | /// |
| 896 | /// Edges connect the producer of an output to its consumer, except for edges |
| 897 | /// stemming from the root node. |
| 898 | class CGEdge : public CGEdgeBase { |
| 899 | public: |
| 900 | enum class EdgeKind { |
| 901 | Regular, |
| 902 | ModuleDependency, |
| 903 | Rooted, |
| 904 | }; |
| 905 | |
| 906 | CGEdge(CGNode &N, EdgeKind K) : CGEdgeBase(N), Kind(K) {} |
| 907 | CGEdge(const CGEdge &) = delete; |
| 908 | CGEdge &operator=(const CGEdge &) = delete; |
| 909 | CGEdge(CGEdge &&) = delete; |
| 910 | CGEdge &operator=(CGEdge &&) = delete; |
| 911 | |
| 912 | EdgeKind getKind() const { return Kind; } |
| 913 | |
| 914 | private: |
| 915 | EdgeKind Kind; |
| 916 | }; |
| 917 | |
| 918 | /// Compilation Graph |
| 919 | /// |
| 920 | /// The graph owns all of its components. |
| 921 | /// All nodes and edges created by the graph have the same livetime as the |
| 922 | /// graph, even if removed from the graph's node list. |
| 923 | class CompilationGraph : public CGBase { |
| 924 | public: |
| 925 | CompilationGraph() = default; |
| 926 | CompilationGraph(const CompilationGraph &) = delete; |
| 927 | CompilationGraph &operator=(const CompilationGraph &) = delete; |
| 928 | CompilationGraph(CompilationGraph &&G) = default; |
| 929 | CompilationGraph &operator=(CompilationGraph &&) = default; |
| 930 | ~CompilationGraph() = default; |
| 931 | |
| 932 | CGNode &getRoot() const { |
| 933 | assert(Root && "Root node has not yet been created!" ); |
| 934 | return *Root; |
| 935 | } |
| 936 | |
| 937 | RootNode &createRoot() { |
| 938 | assert(!Root && "Root node has already been created!" ); |
| 939 | auto &RootRef = createNodeImpl<RootNode>(); |
| 940 | Root = &RootRef; |
| 941 | return RootRef; |
| 942 | } |
| 943 | |
| 944 | template <typename T, typename... Args> T &createJobNode(Args &&...Arg) { |
| 945 | static_assert(std::is_base_of<JobNode, T>::value, |
| 946 | "T must be derived from JobNode" ); |
| 947 | return createNodeImpl<T>(std::forward<Args>(Arg)...); |
| 948 | } |
| 949 | |
| 950 | CGEdge &createEdge(CGEdge::EdgeKind Kind, CGNode &Src, CGNode &Dst) { |
| 951 | auto Edge = std::make_unique<CGEdge>(args&: Dst, args&: Kind); |
| 952 | CGEdge &EdgeRef = *Edge; |
| 953 | AllEdges.push_back(Elt: std::move(Edge)); |
| 954 | connect(Src, Dst, E&: EdgeRef); |
| 955 | return EdgeRef; |
| 956 | } |
| 957 | |
| 958 | private: |
| 959 | using CGBase::addNode; |
| 960 | using CGBase::connect; |
| 961 | |
| 962 | template <typename T, typename... Args> T &createNodeImpl(Args &&...Arg) { |
| 963 | auto Node = std::make_unique<T>(std::forward<Args>(Arg)...); |
| 964 | T &NodeRef = *Node; |
| 965 | AllNodes.push_back(std::move(Node)); |
| 966 | addNode(N&: NodeRef); |
| 967 | return NodeRef; |
| 968 | } |
| 969 | |
| 970 | CGNode *Root = nullptr; |
| 971 | SmallVector<std::unique_ptr<CGNode>> AllNodes; |
| 972 | SmallVector<std::unique_ptr<CGEdge>> AllEdges; |
| 973 | }; |
| 974 | } // anonymous namespace |
| 975 | |
| 976 | static StringRef getFirstInputFilename(const Command &Job) { |
| 977 | return Job.getInputInfos().front().getFilename(); |
| 978 | } |
| 979 | |
| 980 | namespace llvm { |
| 981 | /// Non-const versions of the GraphTraits specializations for CompilationGraph. |
| 982 | template <> struct GraphTraits<CGNode *> { |
| 983 | using NodeRef = CGNode *; |
| 984 | |
| 985 | static NodeRef CGGetTargetNode(CGEdge *E) { return &E->getTargetNode(); } |
| 986 | |
| 987 | using ChildIteratorType = |
| 988 | mapped_iterator<CGNode::iterator, decltype(&CGGetTargetNode)>; |
| 989 | using ChildEdgeIteratorType = CGNode::iterator; |
| 990 | |
| 991 | static NodeRef getEntryNode(NodeRef N) { return N; } |
| 992 | |
| 993 | static ChildIteratorType child_begin(NodeRef N) { |
| 994 | return ChildIteratorType(N->begin(), &CGGetTargetNode); |
| 995 | } |
| 996 | |
| 997 | static ChildIteratorType child_end(NodeRef N) { |
| 998 | return ChildIteratorType(N->end(), &CGGetTargetNode); |
| 999 | } |
| 1000 | |
| 1001 | static ChildEdgeIteratorType child_edge_begin(NodeRef N) { |
| 1002 | return N->begin(); |
| 1003 | } |
| 1004 | static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } |
| 1005 | }; |
| 1006 | |
| 1007 | template <> struct GraphTraits<CompilationGraph *> : GraphTraits<CGNode *> { |
| 1008 | using GraphRef = CompilationGraph *; |
| 1009 | using NodeRef = CGNode *; |
| 1010 | |
| 1011 | using nodes_iterator = CompilationGraph::iterator; |
| 1012 | |
| 1013 | static NodeRef getEntryNode(GraphRef G) { return &G->getRoot(); } |
| 1014 | |
| 1015 | static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } |
| 1016 | |
| 1017 | static nodes_iterator nodes_end(GraphRef G) { return G->end(); } |
| 1018 | }; |
| 1019 | |
| 1020 | /// Const versions of the GraphTraits specializations for CompilationGraph. |
| 1021 | template <> struct GraphTraits<const CGNode *> { |
| 1022 | using NodeRef = const CGNode *; |
| 1023 | |
| 1024 | static NodeRef CGGetTargetNode(const CGEdge *E) { |
| 1025 | return &E->getTargetNode(); |
| 1026 | } |
| 1027 | |
| 1028 | using ChildIteratorType = |
| 1029 | mapped_iterator<CGNode::const_iterator, decltype(&CGGetTargetNode)>; |
| 1030 | using ChildEdgeIteratorType = CGNode::const_iterator; |
| 1031 | |
| 1032 | static NodeRef getEntryNode(NodeRef N) { return N; } |
| 1033 | |
| 1034 | static ChildIteratorType child_begin(NodeRef N) { |
| 1035 | return ChildIteratorType(N->begin(), &CGGetTargetNode); |
| 1036 | } |
| 1037 | |
| 1038 | static ChildIteratorType child_end(NodeRef N) { |
| 1039 | return ChildIteratorType(N->end(), &CGGetTargetNode); |
| 1040 | } |
| 1041 | |
| 1042 | static ChildEdgeIteratorType child_edge_begin(NodeRef N) { |
| 1043 | return N->begin(); |
| 1044 | } |
| 1045 | |
| 1046 | static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } |
| 1047 | }; |
| 1048 | |
| 1049 | template <> |
| 1050 | struct GraphTraits<const CompilationGraph *> : GraphTraits<const CGNode *> { |
| 1051 | using GraphRef = const CompilationGraph *; |
| 1052 | using NodeRef = const CGNode *; |
| 1053 | |
| 1054 | using nodes_iterator = CompilationGraph::const_iterator; |
| 1055 | |
| 1056 | static NodeRef getEntryNode(GraphRef G) { return &G->getRoot(); } |
| 1057 | |
| 1058 | static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } |
| 1059 | |
| 1060 | static nodes_iterator nodes_end(GraphRef G) { return G->end(); } |
| 1061 | }; |
| 1062 | |
| 1063 | template <> |
| 1064 | struct DOTGraphTraits<const CompilationGraph *> : DefaultDOTGraphTraits { |
| 1065 | explicit DOTGraphTraits(bool IsSimple = false) |
| 1066 | : DefaultDOTGraphTraits(IsSimple) {} |
| 1067 | using GraphRef = const CompilationGraph *; |
| 1068 | using NodeRef = const CGNode *; |
| 1069 | |
| 1070 | static std::string getGraphName(GraphRef) { |
| 1071 | return "Module Dependency Graph" ; |
| 1072 | } |
| 1073 | |
| 1074 | static std::string getGraphProperties(GraphRef) { |
| 1075 | return "\tnode [shape=Mrecord, colorscheme=set23, style=filled];\n" ; |
| 1076 | } |
| 1077 | |
| 1078 | static bool renderGraphFromBottomUp() { return true; } |
| 1079 | |
| 1080 | static bool isNodeHidden(NodeRef N, GraphRef) { |
| 1081 | // Only show nodes with module dependency relations. |
| 1082 | return !isa<ClangModuleJobNode, ScannedJobNode>(Val: N); |
| 1083 | } |
| 1084 | |
| 1085 | static std::string getNodeIdentifier(NodeRef N, GraphRef) { |
| 1086 | return llvm::TypeSwitch<NodeRef, std::string>(N) |
| 1087 | .Case(caseFn: [](const ClangModuleJobNode *ClangModuleNode) { |
| 1088 | const auto &ID = ClangModuleNode->MD.ID; |
| 1089 | return llvm::formatv(Fmt: "{0}-{1}" , Vals: ID.ModuleName, Vals: ID.ContextHash).str(); |
| 1090 | }) |
| 1091 | .Case(caseFn: [](const NamedModuleJobNode *NamedModuleNode) { |
| 1092 | return llvm::formatv(Fmt: "{0}-{1}" , Vals: NamedModuleNode->InputDeps.ModuleName, |
| 1093 | Vals: getTriple(Job: *NamedModuleNode->Job)) |
| 1094 | .str(); |
| 1095 | }) |
| 1096 | .Case(caseFn: [](const NonModuleTUJobNode *NonModuleTUNode) { |
| 1097 | const auto &Job = *NonModuleTUNode->Job; |
| 1098 | return llvm::formatv(Fmt: "{0}-{1}" , Vals: getFirstInputFilename(Job), |
| 1099 | Vals: getTriple(Job)) |
| 1100 | .str(); |
| 1101 | }) |
| 1102 | .DefaultUnreachable(message: "Unexpected node kind! Is this node hidden?" ); |
| 1103 | } |
| 1104 | |
| 1105 | static std::string getNodeLabel(NodeRef N, GraphRef) { |
| 1106 | return llvm::TypeSwitch<NodeRef, std::string>(N) |
| 1107 | .Case(caseFn: [](const ClangModuleJobNode *ClangModuleNode) { |
| 1108 | const auto &ID = ClangModuleNode->MD.ID; |
| 1109 | return llvm::formatv(Fmt: "Module type: Clang module \\| Module name: {0} " |
| 1110 | "\\| Hash: {1}" , |
| 1111 | Vals: ID.ModuleName, Vals: ID.ContextHash) |
| 1112 | .str(); |
| 1113 | }) |
| 1114 | .Case(caseFn: [](const NamedModuleJobNode *NamedModuleNode) { |
| 1115 | const auto &Job = *NamedModuleNode->Job; |
| 1116 | return llvm::formatv( |
| 1117 | Fmt: "Filename: {0} \\| Module type: Named module \\| " |
| 1118 | "Module name: {1} \\| Triple: {2}" , |
| 1119 | Vals: getFirstInputFilename(Job), |
| 1120 | Vals: NamedModuleNode->InputDeps.ModuleName, Vals: getTriple(Job)) |
| 1121 | .str(); |
| 1122 | }) |
| 1123 | .Case(caseFn: [](const NonModuleTUJobNode *NonModuleTUNode) { |
| 1124 | const auto &Job = *NonModuleTUNode->Job; |
| 1125 | return llvm::formatv(Fmt: "Filename: {0} \\| Triple: {1}" , |
| 1126 | Vals: getFirstInputFilename(Job), Vals: getTriple(Job)) |
| 1127 | .str(); |
| 1128 | }) |
| 1129 | .DefaultUnreachable(message: "Unexpected node kind! Is this node hidden?" ); |
| 1130 | } |
| 1131 | |
| 1132 | static std::string getNodeAttributes(NodeRef N, GraphRef) { |
| 1133 | switch (N->getKind()) { |
| 1134 | case CGNode::NodeKind::ClangModuleCC1Job: |
| 1135 | return "fillcolor=1" ; |
| 1136 | case CGNode::NodeKind::NamedModuleCC1Job: |
| 1137 | return "fillcolor=2" ; |
| 1138 | case CGNode::NodeKind::NonModuleCC1Job: |
| 1139 | return "fillcolor=3" ; |
| 1140 | default: |
| 1141 | llvm_unreachable("Unexpected node kind! Is this node hidden?" ); |
| 1142 | } |
| 1143 | } |
| 1144 | }; |
| 1145 | |
| 1146 | /// GraphWriter specialization for CompilationGraph that emits a more |
| 1147 | /// human-readable DOT graph. |
| 1148 | template <> |
| 1149 | class GraphWriter<const CompilationGraph *> |
| 1150 | : public GraphWriterBase<const CompilationGraph *, |
| 1151 | GraphWriter<const CompilationGraph *>> { |
| 1152 | public: |
| 1153 | using GraphType = const CompilationGraph *; |
| 1154 | using Base = GraphWriterBase<GraphType, GraphWriter<GraphType>>; |
| 1155 | |
| 1156 | GraphWriter(llvm::raw_ostream &O, const GraphType &G, bool IsSimple) |
| 1157 | : Base(O, G, IsSimple), EscapedIDByNodeRef(G->size()) {} |
| 1158 | |
| 1159 | void writeNodes() { |
| 1160 | auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; |
| 1161 | auto VisibleNodes = llvm::filter_to_vector(C: nodes(G), Pred&: IsNodeVisible); |
| 1162 | |
| 1163 | writeNodeDefinitions(VisibleNodes); |
| 1164 | O << "\n" ; |
| 1165 | writeNodeRelations(VisibleNodes); |
| 1166 | } |
| 1167 | |
| 1168 | private: |
| 1169 | using Base::DOTTraits; |
| 1170 | using Base::GTraits; |
| 1171 | using Base::NodeRef; |
| 1172 | |
| 1173 | void writeNodeDefinitions(ArrayRef<NodeRef> VisibleNodes) { |
| 1174 | for (NodeRef Node : VisibleNodes) { |
| 1175 | std::string EscapedNodeID = |
| 1176 | DOT::EscapeString(Label: DTraits.getNodeIdentifier(N: Node, G)); |
| 1177 | const std::string NodeLabel = DTraits.getNodeLabel(N: Node, G); |
| 1178 | const std::string NodeAttrs = DTraits.getNodeAttributes(N: Node, G); |
| 1179 | O << '\t' << '"' << EscapedNodeID << "\" [" << NodeAttrs << ", label=\"{ " |
| 1180 | << DOT::EscapeString(Label: NodeLabel) << " }\"];\n" ; |
| 1181 | EscapedIDByNodeRef.try_emplace(Key: Node, Args: std::move(EscapedNodeID)); |
| 1182 | } |
| 1183 | } |
| 1184 | |
| 1185 | void writeNodeRelations(ArrayRef<NodeRef> VisibleNodes) { |
| 1186 | auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; |
| 1187 | for (NodeRef Node : VisibleNodes) { |
| 1188 | auto DstNodes = llvm::make_range(x: GTraits::child_begin(N: Node), |
| 1189 | y: GTraits::child_end(N: Node)); |
| 1190 | auto VisibleDstNodes = llvm::make_filter_range(Range&: DstNodes, Pred: IsNodeVisible); |
| 1191 | StringRef EscapedSrcNodeID = EscapedIDByNodeRef.at(Val: Node); |
| 1192 | for (NodeRef DstNode : VisibleDstNodes) { |
| 1193 | StringRef EscapedTgtNodeID = EscapedIDByNodeRef.at(Val: DstNode); |
| 1194 | O << '\t' << '"' << EscapedSrcNodeID << "\" -> \"" << EscapedTgtNodeID |
| 1195 | << "\";\n" ; |
| 1196 | } |
| 1197 | } |
| 1198 | } |
| 1199 | |
| 1200 | DenseMap<NodeRef, std::string> EscapedIDByNodeRef; |
| 1201 | }; |
| 1202 | } // namespace llvm |
| 1203 | |
| 1204 | /// Validates that each module-defining source is of type \c TY_CXXModule. |
| 1205 | /// |
| 1206 | /// \returns false on error, with diagnostics emitted via \p Diags. |
| 1207 | static bool validateScannedJobInputKinds( |
| 1208 | ArrayRef<std::unique_ptr<Command>> ScannedJobs, |
| 1209 | ArrayRef<InputDependencies> InputDepsForScannedJobs, |
| 1210 | DiagnosticsEngine &Diags) { |
| 1211 | for (const auto &&[Job, InputDeps] : llvm::zip_equal( |
| 1212 | t: llvm::make_pointee_range(Range&: ScannedJobs), u&: InputDepsForScannedJobs)) { |
| 1213 | const auto &MainInput = Job.getInputInfos().front(); |
| 1214 | const bool DefinesNamedModule = !InputDeps.ModuleName.empty(); |
| 1215 | |
| 1216 | if (DefinesNamedModule && MainInput.getType() != types::TY_CXXModule && |
| 1217 | MainInput.getType() != types::TY_CXXStdModule) { |
| 1218 | Diags.Report(DiagID: diag::err_module_defined_outside_of_module_source) |
| 1219 | << InputDeps.ModuleName << MainInput.getFilename(); |
| 1220 | return false; |
| 1221 | } |
| 1222 | } |
| 1223 | return true; |
| 1224 | } |
| 1225 | |
| 1226 | static SmallVector<std::unique_ptr<Command>> |
| 1227 | takeJobsAtIndices(SmallVectorImpl<std::unique_ptr<Command>> &Jobs, |
| 1228 | ArrayRef<size_t> Indices) { |
| 1229 | SmallVector<std::unique_ptr<Command>> Out; |
| 1230 | for (const auto JobIndex : Indices) { |
| 1231 | assert(Jobs[JobIndex] && "Expected valid job!" ); |
| 1232 | Out.push_back(Elt: std::move(Jobs[JobIndex])); |
| 1233 | } |
| 1234 | return Out; |
| 1235 | } |
| 1236 | |
| 1237 | /// Creates nodes for all jobs that could not be scanned (e.g. image jobs, ...). |
| 1238 | static void createNodesForNonScannableJobs( |
| 1239 | CompilationGraph &Graph, |
| 1240 | SmallVectorImpl<std::unique_ptr<Command>> &&NonScannableJobs) { |
| 1241 | for (auto &Job : NonScannableJobs) { |
| 1242 | if (Job->getCreator().isLinkJob()) |
| 1243 | Graph.createJobNode<ImageJobNode>(Arg: std::move(Job)); |
| 1244 | else |
| 1245 | Graph.createJobNode<MiscJobNode>(Arg: std::move(Job)); |
| 1246 | } |
| 1247 | } |
| 1248 | |
| 1249 | /// Creates nodes for the Standard library module jobs not discovered as |
| 1250 | /// dependencies. |
| 1251 | /// |
| 1252 | /// These and any dependent (non-image) job nodes should be pruned from the |
| 1253 | /// graph later. |
| 1254 | static SmallVector<JobNode *> createNodesForUnusedStdlibModuleJobs( |
| 1255 | CompilationGraph &Graph, |
| 1256 | SmallVectorImpl<std::unique_ptr<Command>> &&UnusedStdlibModuleJobs) { |
| 1257 | SmallVector<JobNode *> StdlibModuleNodesToPrune; |
| 1258 | for (auto &Job : UnusedStdlibModuleJobs) { |
| 1259 | auto &NewNode = Graph.createJobNode<MiscJobNode>(Arg: std::move(Job)); |
| 1260 | StdlibModuleNodesToPrune.push_back(Elt: &NewNode); |
| 1261 | } |
| 1262 | return StdlibModuleNodesToPrune; |
| 1263 | } |
| 1264 | |
| 1265 | // Returns the derived argument list for the tool chain responsible |
| 1266 | // for creating \p Job. |
| 1267 | static const DerivedArgList &getToolChainArgs(Compilation &C, |
| 1268 | const Command &Job) { |
| 1269 | const auto &TC = Job.getCreator().getToolChain(); |
| 1270 | const auto &SourceAction = Job.getSource(); |
| 1271 | return C.getArgsForToolChain(TC: &TC, BA: SourceAction.getOffloadingArch(), |
| 1272 | DeviceOffloadKind: SourceAction.getOffloadingDeviceKind()); |
| 1273 | } |
| 1274 | |
| 1275 | /// Creates a job for the Clang module described by \p MD. |
| 1276 | static std::unique_ptr<Command> |
| 1277 | createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob, |
| 1278 | const deps::ModuleDeps &MD) { |
| 1279 | DerivedArgList &Args = C.getArgs(); |
| 1280 | const OptTable &Opts = C.getDriver().getOpts(); |
| 1281 | Arg *InputArg = makeInputArg(Args, Opts, Value: "<discovered clang module>" ); |
| 1282 | Action *IA = C.MakeAction<InputAction>(Arg&: *InputArg, Arg: types::ID::TY_ModuleFile); |
| 1283 | Action *PA = C.MakeAction<PrecompileJobAction>(Arg&: IA, Arg: types::ID::TY_ModuleFile); |
| 1284 | PA->propagateOffloadInfo(A: &ImportingJob.getSource()); |
| 1285 | |
| 1286 | const auto &TCArgs = getToolChainArgs(C, Job: ImportingJob); |
| 1287 | |
| 1288 | const auto &BuildArgs = MD.getBuildArguments(); |
| 1289 | ArgStringList JobArgs; |
| 1290 | JobArgs.reserve(N: BuildArgs.size()); |
| 1291 | for (const auto &Arg : BuildArgs) |
| 1292 | JobArgs.push_back(Elt: TCArgs.MakeArgString(Str: Arg)); |
| 1293 | |
| 1294 | const auto &D = C.getDriver(); |
| 1295 | return std::make_unique<Command>( |
| 1296 | args&: *PA, args: ImportingJob.getCreator(), args: ResponseFileSupport::AtFileUTF8(), |
| 1297 | args: D.getDriverProgramPath(), args&: JobArgs, |
| 1298 | /*Inputs=*/args: ArrayRef<InputInfo>{}, |
| 1299 | /*Outputs=*/args: ArrayRef<InputInfo>{}, args: D.getPrependArg()); |
| 1300 | } |
| 1301 | |
| 1302 | /// Creates a \c ClangModuleJobNode with associated job for each unique Clang |
| 1303 | /// module in \p ModuleDepGraphsForScannedJobs. |
| 1304 | /// |
| 1305 | /// \param ImportingJobs Jobs whose module dependencies were scanned. |
| 1306 | /// \param ModuleDepGraphsForScannedJobs Full Clang module dependency graphs |
| 1307 | /// corresponding to \p ImportingJobs, in order. |
| 1308 | static void createClangModuleJobsAndNodes( |
| 1309 | CompilationGraph &Graph, Compilation &C, |
| 1310 | ArrayRef<std::unique_ptr<Command>> ImportingJobs, |
| 1311 | SmallVectorImpl<deps::ModuleDepsGraph> &&ModuleDepGraphsForScannedJobs) { |
| 1312 | llvm::DenseSet<deps::ModuleID> AlreadySeen; |
| 1313 | for (auto &&[ImportingJob, ModuleDepsGraph] : |
| 1314 | llvm::zip_equal(t: llvm::make_pointee_range(Range&: ImportingJobs), |
| 1315 | u&: ModuleDepGraphsForScannedJobs)) { |
| 1316 | for (auto &MD : ModuleDepsGraph) { |
| 1317 | const auto Inserted = AlreadySeen.insert(V: MD.ID).second; |
| 1318 | if (!Inserted) |
| 1319 | continue; |
| 1320 | |
| 1321 | auto ClangModuleJob = createClangModulePrecompileJob(C, ImportingJob, MD); |
| 1322 | Graph.createJobNode<ClangModuleJobNode>(Arg: std::move(ClangModuleJob), |
| 1323 | Arg: std::move(MD)); |
| 1324 | } |
| 1325 | } |
| 1326 | } |
| 1327 | |
| 1328 | /// Installs the command lines produced by the dependency scan into |
| 1329 | /// \p ScannedJobs. |
| 1330 | static void |
| 1331 | installScanCommandLines(Compilation &C, |
| 1332 | MutableArrayRef<std::unique_ptr<Command>> ScannedJobs, |
| 1333 | ArrayRef<InputDependencies> InputDepsForScannedJobs) { |
| 1334 | for (auto &&[Job, InputDeps] : llvm::zip_equal( |
| 1335 | t: llvm::make_pointee_range(Range&: ScannedJobs), u&: InputDepsForScannedJobs)) { |
| 1336 | const auto &BuildArgs = InputDeps.BuildArgs; |
| 1337 | ArgStringList JobArgs; |
| 1338 | JobArgs.reserve(N: BuildArgs.size()); |
| 1339 | |
| 1340 | auto &TCArgs = getToolChainArgs(C, Job); |
| 1341 | for (const auto &Arg : BuildArgs) |
| 1342 | JobArgs.push_back(Elt: TCArgs.MakeArgString(Str: Arg)); |
| 1343 | |
| 1344 | Job.replaceArguments(List: std::move(JobArgs)); |
| 1345 | } |
| 1346 | } |
| 1347 | |
| 1348 | /// Creates nodes for all jobs which were scanned for dependencies. |
| 1349 | /// |
| 1350 | /// The updated command lines produced by the dependency scan are installed at a |
| 1351 | /// later point. |
| 1352 | static void createNodesForScannedJobs( |
| 1353 | CompilationGraph &Graph, |
| 1354 | SmallVectorImpl<std::unique_ptr<Command>> &&ScannedJobs, |
| 1355 | SmallVectorImpl<InputDependencies> &&InputDepsForScannedJobs) { |
| 1356 | for (auto &&[Job, InputDeps] : |
| 1357 | llvm::zip_equal(t&: ScannedJobs, u&: InputDepsForScannedJobs)) { |
| 1358 | if (InputDeps.ModuleName.empty()) |
| 1359 | Graph.createJobNode<NonModuleTUJobNode>(Arg: std::move(Job), |
| 1360 | Arg: std::move(InputDeps)); |
| 1361 | else |
| 1362 | Graph.createJobNode<NamedModuleJobNode>(Arg: std::move(Job), |
| 1363 | Arg: std::move(InputDeps)); |
| 1364 | } |
| 1365 | } |
| 1366 | |
| 1367 | template <typename LookupT, typename KeyRangeT> |
| 1368 | static void connectEdgesViaLookup(CompilationGraph &Graph, CGNode &TgtNode, |
| 1369 | const LookupT &SrcNodeLookup, |
| 1370 | const KeyRangeT &SrcNodeLookupKeys, |
| 1371 | CGEdge::EdgeKind Kind) { |
| 1372 | for (const auto &Key : SrcNodeLookupKeys) { |
| 1373 | const auto It = SrcNodeLookup.find(Key); |
| 1374 | if (It == SrcNodeLookup.end()) |
| 1375 | continue; |
| 1376 | |
| 1377 | auto &SrcNode = *It->second; |
| 1378 | Graph.createEdge(Kind, Src&: SrcNode, Dst&: TgtNode); |
| 1379 | } |
| 1380 | } |
| 1381 | |
| 1382 | /// Create edges for regular (non-module) dependencies in \p Graph. |
| 1383 | static void createRegularEdges(CompilationGraph &Graph) { |
| 1384 | llvm::DenseMap<StringRef, CGNode *> NodeByOutputFiles; |
| 1385 | for (auto *Node : Graph) { |
| 1386 | for (const auto &Output : cast<JobNode>(Val: Node)->Job->getOutputFilenames()) { |
| 1387 | [[maybe_unused]] const bool Inserted = |
| 1388 | NodeByOutputFiles.try_emplace(Key: Output, Args&: Node).second; |
| 1389 | assert(Inserted && |
| 1390 | "Driver should not produce multiple jobs with identical outputs!" ); |
| 1391 | } |
| 1392 | } |
| 1393 | |
| 1394 | for (auto *Node : Graph) { |
| 1395 | const auto &InputInfos = cast<JobNode>(Val: Node)->Job->getInputInfos(); |
| 1396 | auto InputFilenames = llvm::map_range( |
| 1397 | C: InputInfos, F: [](const auto &II) { return II.getFilename(); }); |
| 1398 | |
| 1399 | connectEdgesViaLookup(Graph, TgtNode&: *Node, SrcNodeLookup: NodeByOutputFiles, SrcNodeLookupKeys: InputFilenames, |
| 1400 | Kind: CGEdge::EdgeKind::Regular); |
| 1401 | } |
| 1402 | } |
| 1403 | |
| 1404 | /// Create edges for module dependencies in \p Graph. |
| 1405 | /// |
| 1406 | /// \returns false if there are multiple definitions for a named module, with |
| 1407 | /// diagnostics reported to \p Diags; otherwise returns true. |
| 1408 | static bool createModuleDependencyEdges(CompilationGraph &Graph, |
| 1409 | DiagnosticsEngine &Diags) { |
| 1410 | llvm::DenseMap<deps::ModuleID, CGNode *> ClangModuleNodeByID; |
| 1411 | llvm::DenseMap<ModuleNameAndTriple, CGNode *> NamedModuleNodeByID; |
| 1412 | |
| 1413 | // Map each module to the job that produces it. |
| 1414 | bool HasDuplicateModuleError = false; |
| 1415 | for (auto *Node : Graph) { |
| 1416 | llvm::TypeSwitch<CGNode *>(Node) |
| 1417 | .Case(caseFn: [&](ClangModuleJobNode *ClangModuleNode) { |
| 1418 | [[maybe_unused]] const bool Inserted = |
| 1419 | ClangModuleNodeByID.try_emplace(Key: ClangModuleNode->MD.ID, Args&: Node) |
| 1420 | .second; |
| 1421 | assert(Inserted && |
| 1422 | "Multiple Clang module nodes with the same module ID!" ); |
| 1423 | }) |
| 1424 | .Case(caseFn: [&](NamedModuleJobNode *NamedModuleNode) { |
| 1425 | StringRef ModuleName = NamedModuleNode->InputDeps.ModuleName; |
| 1426 | ModuleNameAndTriple ID{ModuleName, getTriple(Job: *NamedModuleNode->Job)}; |
| 1427 | const auto [It, Inserted] = NamedModuleNodeByID.try_emplace(Key: ID, Args&: Node); |
| 1428 | if (!Inserted) { |
| 1429 | // For scan input jobs, their first input is always a filename and |
| 1430 | // the scanned source. |
| 1431 | // We don't use InputDeps.FileDeps here because diagnostics should |
| 1432 | // refer to the filename as specified on the command line, not the |
| 1433 | // canonical absolute path. |
| 1434 | StringRef PrevFile = |
| 1435 | getFirstInputFilename(Job: *cast<JobNode>(Val: It->second)->Job); |
| 1436 | StringRef CurFile = getFirstInputFilename(Job: *NamedModuleNode->Job); |
| 1437 | Diags.Report(DiagID: diag::err_modules_driver_named_module_redefinition) |
| 1438 | << ModuleName << PrevFile << CurFile; |
| 1439 | HasDuplicateModuleError = true; |
| 1440 | } |
| 1441 | }); |
| 1442 | } |
| 1443 | if (HasDuplicateModuleError) |
| 1444 | return false; |
| 1445 | |
| 1446 | // Create edges from the module nodes to their importers. |
| 1447 | for (auto *Node : Graph) { |
| 1448 | llvm::TypeSwitch<CGNode *>(Node) |
| 1449 | .Case(caseFn: [&](ClangModuleJobNode *ClangModuleNode) { |
| 1450 | connectEdgesViaLookup(Graph, TgtNode&: *ClangModuleNode, SrcNodeLookup: ClangModuleNodeByID, |
| 1451 | SrcNodeLookupKeys: ClangModuleNode->MD.ClangModuleDeps, |
| 1452 | Kind: CGEdge::EdgeKind::ModuleDependency); |
| 1453 | }) |
| 1454 | .Case(caseFn: [&](ScannedJobNode *NodeWithInputDeps) { |
| 1455 | connectEdgesViaLookup(Graph, TgtNode&: *NodeWithInputDeps, SrcNodeLookup: ClangModuleNodeByID, |
| 1456 | SrcNodeLookupKeys: NodeWithInputDeps->InputDeps.ClangModuleDeps, |
| 1457 | Kind: CGEdge::EdgeKind::ModuleDependency); |
| 1458 | |
| 1459 | StringRef Triple = getTriple(Job: *NodeWithInputDeps->Job); |
| 1460 | const auto NamedModuleDepIDs = |
| 1461 | llvm::map_range(C&: NodeWithInputDeps->InputDeps.NamedModuleDeps, |
| 1462 | F: [&](StringRef ModuleName) { |
| 1463 | return ModuleNameAndTriple{ModuleName, Triple}; |
| 1464 | }); |
| 1465 | connectEdgesViaLookup(Graph, TgtNode&: *NodeWithInputDeps, SrcNodeLookup: NamedModuleNodeByID, |
| 1466 | SrcNodeLookupKeys: NamedModuleDepIDs, |
| 1467 | Kind: CGEdge::EdgeKind::ModuleDependency); |
| 1468 | }); |
| 1469 | } |
| 1470 | |
| 1471 | return true; |
| 1472 | } |
| 1473 | |
| 1474 | /// Prunes the compilation graph of any jobs which build Standard library |
| 1475 | /// modules not required in this compilation. |
| 1476 | static void |
| 1477 | pruneUnusedStdlibModuleJobs(CompilationGraph &Graph, |
| 1478 | ArrayRef<JobNode *> UnusedStdlibModuleJobNodes) { |
| 1479 | // Collect all reachable non-image job nodes. |
| 1480 | llvm::SmallPtrSet<JobNode *, 16> PrunableJobNodes; |
| 1481 | for (auto *PrunableJobNodeRoot : UnusedStdlibModuleJobNodes) { |
| 1482 | auto ReachableJobNodes = |
| 1483 | llvm::map_range(C: llvm::depth_first(G: cast<CGNode>(Val: PrunableJobNodeRoot)), |
| 1484 | F: llvm::CastTo<JobNode>); |
| 1485 | auto ReachableNonImageNodes = llvm::make_filter_range( |
| 1486 | Range&: ReachableJobNodes, Pred: [](auto *N) { return !llvm::isa<ImageJobNode>(N); }); |
| 1487 | PrunableJobNodes.insert_range(R&: ReachableNonImageNodes); |
| 1488 | } |
| 1489 | |
| 1490 | // Map image job nodes to the prunable job nodes that feed into them. |
| 1491 | llvm::DenseMap<ImageJobNode *, llvm::SmallPtrSet<JobNode *, 4>> |
| 1492 | PrunableJobNodesByImageNode; |
| 1493 | for (auto *PrunableJobNode : PrunableJobNodes) { |
| 1494 | auto ReachableJobNodes = llvm::depth_first(G: cast<CGNode>(Val: PrunableJobNode)); |
| 1495 | auto ReachableImageJobNodes = llvm::map_range( |
| 1496 | C: llvm::make_filter_range(Range&: ReachableJobNodes, Pred: llvm::IsaPred<ImageJobNode>), |
| 1497 | F: llvm::CastTo<ImageJobNode>); |
| 1498 | |
| 1499 | for (auto *ImageNode : ReachableImageJobNodes) |
| 1500 | PrunableJobNodesByImageNode[ImageNode].insert(Ptr: PrunableJobNode); |
| 1501 | } |
| 1502 | |
| 1503 | // Remove from each affected image job node any arguments corresponding to |
| 1504 | // outputs of the connected prunable job nodes. |
| 1505 | for (auto &[ImageNode, PrunableJobNodeInputs] : PrunableJobNodesByImageNode) { |
| 1506 | SmallVector<StringRef, 4> OutputsToRemove; |
| 1507 | for (auto *JN : PrunableJobNodeInputs) |
| 1508 | llvm::append_range(C&: OutputsToRemove, R: JN->Job->getOutputFilenames()); |
| 1509 | |
| 1510 | auto NewArgs = ImageNode->Job->getArguments(); |
| 1511 | llvm::erase_if(C&: NewArgs, P: [&](StringRef Arg) { |
| 1512 | return llvm::is_contained(Range&: OutputsToRemove, Element: Arg); |
| 1513 | }); |
| 1514 | ImageNode->Job->replaceArguments(List: NewArgs); |
| 1515 | } |
| 1516 | |
| 1517 | // Erase all prunable job nodes from the graph. |
| 1518 | for (auto *JN : PrunableJobNodes) { |
| 1519 | // Nodes are owned by the graph, but we can release the associated job. |
| 1520 | JN->Job.reset(); |
| 1521 | Graph.removeNode(N&: *JN); |
| 1522 | } |
| 1523 | } |
| 1524 | |
| 1525 | /// Creates the root node and connects it to all nodes with no incoming edges |
| 1526 | /// ensuring that every node in the graph is reachable from the root. |
| 1527 | static void createAndConnectRoot(CompilationGraph &Graph) { |
| 1528 | llvm::SmallPtrSet<CGNode *, 16> HasIncomingEdge; |
| 1529 | for (auto *Node : Graph) |
| 1530 | for (auto *Edge : Node->getEdges()) |
| 1531 | HasIncomingEdge.insert(Ptr: &Edge->getTargetNode()); |
| 1532 | |
| 1533 | auto AllNonRootNodes = llvm::iterator_range(Graph); |
| 1534 | auto &Root = Graph.createRoot(); |
| 1535 | |
| 1536 | for (auto *Node : AllNonRootNodes) { |
| 1537 | if (HasIncomingEdge.contains(Ptr: Node)) |
| 1538 | continue; |
| 1539 | Graph.createEdge(Kind: CGEdge::EdgeKind::Rooted, Src&: Root, Dst&: *Node); |
| 1540 | } |
| 1541 | } |
| 1542 | |
| 1543 | /// Creates a temporary output path for \p ModuleName. |
| 1544 | static std::string createModuleOutputPath(const Compilation &C, |
| 1545 | StringRef ModuleName) { |
| 1546 | // Sanitize the ':' included in parition names. It is illegal for filenames on |
| 1547 | // Windows. |
| 1548 | SmallString<32> SanitizedModuleName(ModuleName); |
| 1549 | llvm::replace(Range&: SanitizedModuleName, OldValue: ':', NewValue: '-'); |
| 1550 | auto ModuleOutputPath = C.getDriver().GetTemporaryPath( |
| 1551 | Prefix: SanitizedModuleName, Suffix: types::getTypeTempSuffix(Id: types::TY_ModuleFile)); |
| 1552 | return ModuleOutputPath; |
| 1553 | } |
| 1554 | |
| 1555 | /// Adds the '-fmodule-output=' argument for the module produced by \p Node. |
| 1556 | static void configureNamedModuleOutputArg(Compilation &C, |
| 1557 | NamedModuleJobNode &Node, |
| 1558 | StringRef ModuleOutputPath) { |
| 1559 | auto &Job = *Node.Job; |
| 1560 | const auto &TCArgs = getToolChainArgs(C, Job); |
| 1561 | auto JobArgs = Job.getArguments(); |
| 1562 | JobArgs.push_back( |
| 1563 | Elt: TCArgs.MakeArgString(Str: "-fmodule-output=" + ModuleOutputPath)); |
| 1564 | Job.replaceArguments(List: std::move(JobArgs)); |
| 1565 | } |
| 1566 | |
| 1567 | /// Propagates the '-fmodule-file=' mapping for the named module described by |
| 1568 | /// \p Node to each dependent job. |
| 1569 | static void propagateModuleFileMappingArg(Compilation &C, |
| 1570 | NamedModuleJobNode &Node, |
| 1571 | StringRef ModuleOutputPath) { |
| 1572 | const StringRef ModuleName = Node.InputDeps.ModuleName; |
| 1573 | |
| 1574 | auto DependentNodes = llvm::drop_begin(RangeOrContainer: llvm::depth_first<CGNode *>(G: &Node)); |
| 1575 | auto DependentScannedNodes = llvm::map_range( |
| 1576 | C: llvm::make_filter_range(Range&: DependentNodes, Pred: llvm::IsaPred<ScannedJobNode>), |
| 1577 | F: llvm::CastTo<ScannedJobNode>); |
| 1578 | |
| 1579 | for (ScannedJobNode *DependentNode : DependentScannedNodes) { |
| 1580 | auto &DependentJob = *DependentNode->Job; |
| 1581 | const auto &TCArgs = getToolChainArgs(C, Job: DependentJob); |
| 1582 | auto JobArgs = DependentJob.getArguments(); |
| 1583 | JobArgs.push_back(Elt: TCArgs.MakeArgString(Str: "-fmodule-file=" + ModuleName + "=" + |
| 1584 | ModuleOutputPath)); |
| 1585 | DependentJob.replaceArguments(List: std::move(JobArgs)); |
| 1586 | } |
| 1587 | } |
| 1588 | |
| 1589 | /// Finalizes command lines for C++20 named module dependencies. |
| 1590 | /// |
| 1591 | /// The command lines produced by dependency scanning are only adjusted to |
| 1592 | /// handle discovered Clang modules. For C++20 named modules, we update the |
| 1593 | /// command-lines here. |
| 1594 | static void fixupNamedModuleCommandLines(Compilation &C, |
| 1595 | CompilationGraph &Graph) { |
| 1596 | const auto NamedModuleNodes = llvm::map_range( |
| 1597 | C: llvm::make_filter_range(Range&: Graph, Pred: llvm::IsaPred<NamedModuleJobNode>), |
| 1598 | F: llvm::CastTo<NamedModuleJobNode>); |
| 1599 | |
| 1600 | for (NamedModuleJobNode *Node : NamedModuleNodes) { |
| 1601 | const auto &Job = *Node->Job; |
| 1602 | |
| 1603 | // For Standard library modules, the driver already creates the module |
| 1604 | // output as a temp file, so we can use that path directly. |
| 1605 | const bool IsStdModule = |
| 1606 | Job.getInputInfos().front().getType() == types::TY_CXXStdModule; |
| 1607 | if (IsStdModule) { |
| 1608 | StringRef ModuleOutputPath = Job.getOutputFilenames().front(); |
| 1609 | propagateModuleFileMappingArg(C, Node&: *Node, ModuleOutputPath); |
| 1610 | continue; |
| 1611 | } |
| 1612 | |
| 1613 | const StringRef ModuleName = Node->InputDeps.ModuleName; |
| 1614 | const auto ModuleOutputPath = createModuleOutputPath(C, ModuleName); |
| 1615 | C.addTempFile(Name: C.getArgs().MakeArgString(Str: ModuleOutputPath)); |
| 1616 | |
| 1617 | configureNamedModuleOutputArg(C, Node&: *Node, ModuleOutputPath); |
| 1618 | propagateModuleFileMappingArg(C, Node&: *Node, ModuleOutputPath); |
| 1619 | } |
| 1620 | } |
| 1621 | |
| 1622 | /// Moves jobs from \p Graph into \p C in the graph's topological order. |
| 1623 | static void feedJobsBackIntoCompilation(Compilation &C, |
| 1624 | CompilationGraph &&Graph) { |
| 1625 | llvm::ReversePostOrderTraversal<CompilationGraph *> TopologicallySortedNodes( |
| 1626 | &Graph); |
| 1627 | assert(isa<RootNode>(*TopologicallySortedNodes.begin()) && |
| 1628 | "First node in topological order must be the root!" ); |
| 1629 | auto TopologicallySortedJobNodes = llvm::map_range( |
| 1630 | C: llvm::drop_begin(RangeOrContainer&: TopologicallySortedNodes), F: llvm::CastTo<JobNode>); |
| 1631 | for (auto *JN : TopologicallySortedJobNodes) |
| 1632 | C.addCommand(Cmd: std::move(JN->Job)); |
| 1633 | } |
| 1634 | |
| 1635 | void driver::modules::runModulesDriver( |
| 1636 | Compilation &C, ArrayRef<StdModuleManifest::Module> ManifestEntries) { |
| 1637 | llvm::PrettyStackTraceString CrashInfo("Running modules driver." ); |
| 1638 | |
| 1639 | auto Jobs = C.getJobs().takeJobs(); |
| 1640 | |
| 1641 | const auto ManifestEntryBySource = buildManifestLookupMap(ManifestEntries); |
| 1642 | // Apply manifest-entry specific command-line modifications before the scan as |
| 1643 | // they might affect it. |
| 1644 | applyArgsForStdModuleManifestInputs(C, ManifestEntryBySource, Jobs); |
| 1645 | |
| 1646 | DiagnosticsEngine &Diags = C.getDriver().getDiags(); |
| 1647 | |
| 1648 | // Run the dependency scan. |
| 1649 | const auto MaybeModuleCachePath = getModuleCachePath(Args&: C.getArgs()); |
| 1650 | if (!MaybeModuleCachePath) { |
| 1651 | Diags.Report(DiagID: diag::err_default_modules_cache_not_available); |
| 1652 | return; |
| 1653 | } |
| 1654 | |
| 1655 | auto MaybeCWD = C.getDriver().getVFS().getCurrentWorkingDirectory(); |
| 1656 | const auto CWD = MaybeCWD ? std::move(*MaybeCWD) : "." ; |
| 1657 | |
| 1658 | auto MaybeScanResults = scanDependencies(Jobs, ManifestLookup: ManifestEntryBySource, |
| 1659 | ModuleCachePath: *MaybeModuleCachePath, WorkingDirectory: CWD, Diags); |
| 1660 | if (!MaybeScanResults) { |
| 1661 | Diags.Report(DiagID: diag::err_dependency_scan_failed); |
| 1662 | return; |
| 1663 | } |
| 1664 | auto &ScanResult = *MaybeScanResults; |
| 1665 | |
| 1666 | // Build the compilation graph. |
| 1667 | CompilationGraph Graph; |
| 1668 | createNodesForNonScannableJobs( |
| 1669 | Graph, NonScannableJobs: takeJobsAtIndices(Jobs, Indices: ScanResult.NonScannableJobIndices)); |
| 1670 | auto UnusedStdlibModuleJobNodes = createNodesForUnusedStdlibModuleJobs( |
| 1671 | Graph, UnusedStdlibModuleJobs: takeJobsAtIndices(Jobs, Indices: ScanResult.UnusedStdlibModuleJobIndices)); |
| 1672 | |
| 1673 | auto ScannedJobs = takeJobsAtIndices(Jobs, Indices: ScanResult.ScannedJobIndices); |
| 1674 | if (!validateScannedJobInputKinds(ScannedJobs, |
| 1675 | InputDepsForScannedJobs: ScanResult.InputDepsForScannedJobs, Diags)) |
| 1676 | return; |
| 1677 | installScanCommandLines(C, ScannedJobs, InputDepsForScannedJobs: ScanResult.InputDepsForScannedJobs); |
| 1678 | |
| 1679 | createClangModuleJobsAndNodes( |
| 1680 | Graph, C, /*ImportingJobs*/ ScannedJobs, |
| 1681 | ModuleDepGraphsForScannedJobs: std::move(ScanResult.ModuleDepGraphsForScannedJobs)); |
| 1682 | createNodesForScannedJobs(Graph, ScannedJobs: std::move(ScannedJobs), |
| 1683 | InputDepsForScannedJobs: std::move(ScanResult.InputDepsForScannedJobs)); |
| 1684 | |
| 1685 | createRegularEdges(Graph); |
| 1686 | pruneUnusedStdlibModuleJobs(Graph, UnusedStdlibModuleJobNodes); |
| 1687 | if (!createModuleDependencyEdges(Graph, Diags)) |
| 1688 | return; |
| 1689 | createAndConnectRoot(Graph); |
| 1690 | |
| 1691 | Diags.Report(DiagID: diag::remark_printing_module_graph); |
| 1692 | if (!Diags.isLastDiagnosticIgnored()) |
| 1693 | llvm::WriteGraph<const CompilationGraph *>(O&: llvm::errs(), G: &Graph); |
| 1694 | |
| 1695 | fixupNamedModuleCommandLines(C, Graph); |
| 1696 | feedJobsBackIntoCompilation(C, Graph: std::move(Graph)); |
| 1697 | } |
| 1698 | |