llvm-project/clang/lib/Driver/ModulesDriver.cpp

1536 lines
55 KiB
C++

//===--- ModulesDriver.cpp - Driver managed module builds -----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file defines functionality to support driver managed builds for
/// compilations which use Clang modules or standard C++20 named modules.
///
//===----------------------------------------------------------------------===//
#include "clang/Driver/ModulesDriver.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LLVM.h"
#include "clang/DependencyScanning/DependencyScanningUtils.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/Job.h"
#include "clang/Driver/Tool.h"
#include "clang/Driver/ToolChain.h"
#include "clang/Frontend/StandaloneDiagnostic.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/DirectedGraph.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVectorExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/GraphWriter.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/ThreadPool.h"
#include "llvm/Support/VirtualFileSystem.h"
#include <utility>
namespace deps = clang::dependencies;
using namespace llvm::opt;
using namespace clang;
using namespace driver;
using namespace modules;
namespace clang::driver::modules {
static bool fromJSON(const llvm::json::Value &Params,
StdModuleManifest::Module::LocalArguments &LocalArgs,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O.mapOptional("system-include-directories",
LocalArgs.SystemIncludeDirs);
}
static bool fromJSON(const llvm::json::Value &Params,
StdModuleManifest::Module &ModuleEntry,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O.map("is-std-library", ModuleEntry.IsStdlib) &&
O.map("logical-name", ModuleEntry.LogicalName) &&
O.map("source-path", ModuleEntry.SourcePath) &&
O.mapOptional("local-arguments", ModuleEntry.LocalArgs);
}
static bool fromJSON(const llvm::json::Value &Params,
StdModuleManifest &Manifest, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O.map("modules", Manifest.Modules);
}
} // namespace clang::driver::modules
/// Parses the Standard library module manifest from \p Buffer.
static Expected<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_CXXModule, 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;
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) {}
EdgeKind getKind() const { return Kind; }
private:
EdgeKind Kind;
};
/// Compilation Graph
///
/// The graph owns all of its components.
/// All nodes and edges created by the graph have the same livetime as the
/// graph, even if removed from the graph's node list.
class CompilationGraph : public CGBase {
public:
CompilationGraph() = default;
CompilationGraph(const CompilationGraph &) = delete;
CompilationGraph(CompilationGraph &&G) = default;
CGNode &getRoot() const {
assert(Root && "Root node has not yet been created!");
return *Root;
}
RootNode &createRoot() {
assert(!Root && "Root node has already been created!");
auto &RootRef = createNodeImpl<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
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;
}
/// Creates a job for the Clang module described by \p MD.
static std::unique_ptr<CC1Command>
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 &TC = ImportingJob.getCreator().getToolChain();
const auto &TCArgs = C.getArgsForToolChain(&TC, PA->getOffloadingArch(),
PA->getOffloadingDeviceKind());
const auto &BuildArgs = MD.getBuildArguments();
ArgStringList JobArgs;
JobArgs.reserve(BuildArgs.size());
for (const auto &Arg : BuildArgs)
JobArgs.push_back(TCArgs.MakeArgString(Arg));
return std::make_unique<CC1Command>(
*PA, ImportingJob.getCreator(), ResponseFileSupport::AtFileUTF8(),
C.getDriver().getClangProgramPath(), JobArgs,
/*Inputs=*/ArrayRef<InputInfo>{},
/*Outputs=*/ArrayRef<InputInfo>{});
}
/// Creates a ClangModuleJobNode and its job for each unique Clang module
/// in \p ModuleDepGraphsForScannedJobs.
///
/// Only the jobs at indices \p ScannedJobIndices in \p Jobs are expected to be
/// non-null.
static void createClangModuleJobsAndNodes(
CompilationGraph &Graph, Compilation &C,
ArrayRef<std::unique_ptr<Command>> Jobs, ArrayRef<size_t> ScannedJobIndices,
SmallVectorImpl<deps::ModuleDepsGraph> &&ModuleDepGraphsForScannedJobs) {
llvm::DenseSet<deps::ModuleID> AlreadySeen;
for (auto &&[ScanIndex, ModuleDepsGraph] :
llvm::enumerate(ModuleDepGraphsForScannedJobs)) {
const auto &ImportingJob = *Jobs[ScannedJobIndices[ScanIndex]];
for (auto &MD : ModuleDepsGraph) {
const auto Inserted = AlreadySeen.insert(MD.ID).second;
if (!Inserted)
continue;
auto ClangModuleJob = createClangModulePrecompileJob(C, ImportingJob, MD);
Graph.createJobNode<ClangModuleJobNode>(std::move(ClangModuleJob),
std::move(MD));
}
}
}
/// Creates nodes for all jobs which were scanned for dependencies.
///
/// The updated command lines produced by the dependency scan are installed at a
/// later point.
static void createNodesForScannedJobs(
CompilationGraph &Graph,
SmallVectorImpl<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);
}
}
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));
createClangModuleJobsAndNodes(
Graph, C, Jobs, ScanResult.ScannedJobIndices,
std::move(ScanResult.ModuleDepGraphsForScannedJobs));
createNodesForScannedJobs(
Graph, takeJobsAtIndices(Jobs, ScanResult.ScannedJobIndices),
std::move(ScanResult.InputDepsForScannedJobs));
createRegularEdges(Graph);
pruneUnusedStdlibModuleJobs(Graph, UnusedStdlibModuleJobNodes);
if (!createModuleDependencyEdges(Graph, Diags))
return;
createAndConnectRoot(Graph);
Diags.Report(diag::remark_printing_module_graph);
if (!Diags.isLastDiagnosticIgnored())
llvm::WriteGraph<const CompilationGraph *>(llvm::errs(), &Graph);
// TODO: Install all updated command-lines produced by the dependency scan.
// TODO: Fix-up command-lines for named module imports.
// TODO: Sort the graph topologically before adding jobs back to the
// Compilation being built.
for (auto *N : Graph)
if (auto *JN = dyn_cast<JobNode>(N))
C.addCommand(std::move(JN->Job));
}