Skip to content

Commit 8e126e9

Browse files
committed
[clang][modules-driver] Add dependency scan and dependency graph
This patch is part of a series to support driver-managed module builds. It adds support for the discovery of module dependencies from within the driver and for generation of the module dependency graph. Source inputs provided by the command-line can import modules defined in the standard library module manifest, which are then scanned on demand. The dependency scan and graph support both Clang modules and C++ named modules. The generated dependency graph can be output in Graphviz DOT format as a remark.
1 parent 63f1c03 commit 8e126e9

16 files changed

+2127
-95
lines changed

clang/include/clang/Basic/DiagnosticDriverKinds.td

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,24 @@ def remark_found_cxx20_module_usage : Remark<
587587
def remark_performing_driver_managed_module_build : Remark<
588588
"performing driver managed module build">,
589589
InGroup<ModulesDriver>;
590+
def remark_modules_manifest_not_found : Remark<
591+
"standard modules manifest file not found; import of standard library "
592+
"modules not supported">,
593+
InGroup<ModulesDriver>;
594+
def remark_using_modules_manifest : Remark<
595+
"using standard modules manifest file '%0'">,
596+
InGroup<ModulesDriver>;
597+
def err_modules_manifest_failed_parse : Error<
598+
"failure while parsing standard modules manifest: '%0'">;
599+
def err_failed_dependency_scan : Error<
600+
"failed to perform dependency scan">;
601+
def err_mod_graph_named_module_redefinition : Error<
602+
"duplicate definitions of C++20 named module '%0' in '%1' and '%2'">;
603+
def err_building_dependency_graph : Error<
604+
"failed to construct the module dependency graph">;
605+
def remark_printing_module_graph : Remark<
606+
"printing module dependency graph">,
607+
InGroup<ModulesDriver>;
590608

591609
def warn_drv_delayed_template_parsing_after_cxx20 : Warning<
592610
"-fdelayed-template-parsing is deprecated after C++20">,

clang/include/clang/Driver/Driver.h

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -512,9 +512,6 @@ class Driver {
512512

513513
/// BuildActions - Construct the list of actions to perform for the
514514
/// given arguments, which are only done for a single architecture.
515-
/// If the compilation is an explicit module build, delegates to
516-
/// BuildDriverManagedModuleBuildActions. Otherwise, BuildDefaultActions is
517-
/// used.
518515
///
519516
/// \param C - The compilation that is being built.
520517
/// \param Args - The input arguments.
@@ -799,35 +796,6 @@ class Driver {
799796
/// compilation based on which -f(no-)?lto(=.*)? option occurs last.
800797
void setLTOMode(const llvm::opt::ArgList &Args);
801798

802-
/// BuildDefaultActions - Constructs the list of actions to perform
803-
/// for the provided arguments, which are only done for a single architecture.
804-
///
805-
/// \param C - The compilation that is being built.
806-
/// \param Args - The input arguments.
807-
/// \param Actions - The list to store the resulting actions onto.
808-
void BuildDefaultActions(Compilation &C, llvm::opt::DerivedArgList &Args,
809-
const InputList &Inputs, ActionList &Actions) const;
810-
811-
/// BuildDriverManagedModuleBuildActions - Performs a dependency
812-
/// scan and constructs the list of actions to perform for dependency order
813-
/// and the provided arguments. This is only done for a single a architecture.
814-
///
815-
/// \param C - The compilation that is being built.
816-
/// \param Args - The input arguments.
817-
/// \param Actions - The list to store the resulting actions onto.
818-
void BuildDriverManagedModuleBuildActions(Compilation &C,
819-
llvm::opt::DerivedArgList &Args,
820-
const InputList &Inputs,
821-
ActionList &Actions) const;
822-
823-
/// Scans the leading lines of the C++ source inputs to detect C++20 module
824-
/// usage.
825-
///
826-
/// \returns True if module usage is detected, false otherwise, or an error on
827-
/// read failure.
828-
llvm::ErrorOr<bool>
829-
ScanInputsForCXX20ModulesUsage(const InputList &Inputs) const;
830-
831799
/// Retrieves a ToolChain for a particular \p Target triple.
832800
///
833801
/// Will cache ToolChains for the life of the driver object, and create them

clang/include/clang/Driver/Job.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ class Command {
221221

222222
const char *getExecutable() const { return Executable; }
223223

224+
llvm::opt::ArgStringList &getArguments() { return Arguments; }
225+
224226
const llvm::opt::ArgStringList &getArguments() const { return Arguments; }
225227

226228
const std::vector<InputInfo> &getInputInfos() const { return InputInfoList; }
@@ -279,6 +281,9 @@ class JobList {
279281

280282
const list_type &getJobs() const { return Jobs; }
281283

284+
// Returns and transfers ownership of all jobs, leaving this list empty.
285+
list_type takeJobs();
286+
282287
bool empty() const { return Jobs.empty(); }
283288
size_type size() const { return Jobs.size(); }
284289
iterator begin() { return Jobs.begin(); }
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===- ModulesDriver.h - Driver managed module builds --------*- C++ -*-===//
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+
#ifndef LLVM_CLANG_DRIVER_MODULESDRIVER_H
16+
#define LLVM_CLANG_DRIVER_MODULESDRIVER_H
17+
18+
#include "clang/Driver/Types.h"
19+
#include "llvm/Support/Error.h"
20+
21+
namespace llvm {
22+
namespace vfs {
23+
class FileSystem;
24+
} // namespace vfs
25+
namespace opt {
26+
class Arg;
27+
} // namespace opt
28+
} // namespace llvm
29+
30+
namespace clang {
31+
class DiagnosticsEngine;
32+
namespace driver {
33+
class Compilation;
34+
} // namespace driver
35+
} // namespace clang
36+
37+
namespace clang::driver::modules {
38+
39+
/// A list of inputs and their types for the given arguments.
40+
/// Identical to Driver::InputTy.
41+
using InputTy = std::pair<types::ID, const llvm::opt::Arg *>;
42+
43+
/// A list of inputs and their types for the given arguments.
44+
/// Identical to Driver::InputList.
45+
using InputList = llvm::SmallVector<InputTy, 16>;
46+
47+
/// Checks whether the -fmodules-driver feature should be implicitly enabled.
48+
///
49+
/// The modules driver should be enabled if both:
50+
/// 1) the compilation has more than two C++ source inputs; and
51+
/// 2) any C++ source inputs uses C++20 named modules.
52+
///
53+
/// \param Inputs The input list for the compilation being built.
54+
/// \param VFS The virtual file system to use for all reads.
55+
/// \param Diags The diagnostics engine used for emitting remarks only.
56+
///
57+
/// \returns True if the modules driver should be enabled, false otherwise,
58+
/// or a llvm::FileError on failure to read a source input.
59+
llvm::Expected<bool> shouldUseModulesDriver(const InputList &Inputs,
60+
llvm::vfs::FileSystem &VFS,
61+
DiagnosticsEngine &Diags);
62+
63+
/// The parsed Standard library module manifest.
64+
struct StdModuleManifest {
65+
struct LocalModuleArgs {
66+
std::vector<std::string> SystemIncludeDirs;
67+
};
68+
69+
struct Module {
70+
bool IsStdlib = false;
71+
std::string LogicalName;
72+
std::string SourcePath;
73+
std::optional<LocalModuleArgs> LocalArgs;
74+
};
75+
76+
std::vector<Module> ModuleEntries;
77+
};
78+
79+
/// Reads the Standard library module manifest from the specified path.
80+
///
81+
/// All source file paths in the returned manifest are made absolute.
82+
///
83+
/// \param StdModuleManifestPath Path to the manifest file.
84+
/// \param VFS The llvm::vfs::FileSystem to be used for all file accesses.
85+
///
86+
/// \returns The parsed manifest on success, a llvm::FileError on failure to
87+
/// read the manifest, or a llvm::json::ParseError on failure to parse it.
88+
llvm::Expected<StdModuleManifest>
89+
readStdModuleManifest(llvm::StringRef StdModuleManifestPath,
90+
llvm::vfs::FileSystem &VFS);
91+
92+
/// Constructs compilation inputs for each module listed in the provided
93+
/// Standard library module manifest.
94+
///
95+
/// \param Manifest The standard modules manifest
96+
/// \param C The compilation being built.
97+
/// \param Inputs The input list to which the corresponding input entries are
98+
/// appended.
99+
void buildStdModuleManifestInputs(const StdModuleManifest &Manifest,
100+
Compilation &C, InputList &Inputs);
101+
102+
/// Reorders and builds compilation jobs based on discovered module
103+
/// dependencies.
104+
///
105+
/// \param C The compilation being built.
106+
/// \param Manifest The standard modules manifest
107+
void planDriverManagedModuleCompilation(Compilation &C,
108+
const StdModuleManifest &Manifest);
109+
110+
} // namespace clang::driver::modules
111+
112+
#endif

clang/include/clang/Lex/DependencyDirectivesScanner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ void printDependencyDirectivesAsSource(
140140
/// \param Source The input source buffer.
141141
///
142142
/// \returns true if any C++20 named modules related directive was found.
143-
bool scanInputForCXX20ModulesUsage(StringRef Source);
143+
bool scanInputForCXXNamedModulesUsage(StringRef Source);
144144

145145
/// Functor that returns the dependency directives for a given file.
146146
class DependencyDirectivesGetter {

clang/lib/Driver/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_clang_library(clangDriver
2121
Driver.cpp
2222
DriverOptions.cpp
2323
Job.cpp
24+
ModulesDriver.cpp
2425
Multilib.cpp
2526
MultilibBuilder.cpp
2627
OffloadBundler.cpp
@@ -99,5 +100,6 @@ add_clang_library(clangDriver
99100
LINK_LIBS
100101
clangBasic
101102
clangLex
103+
clangDependencyScanning
102104
${system_libs}
103105
)

clang/lib/Driver/Driver.cpp

Lines changed: 54 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "clang/Driver/Compilation.h"
6161
#include "clang/Driver/InputInfo.h"
6262
#include "clang/Driver/Job.h"
63+
#include "clang/Driver/ModulesDriver.h"
6364
#include "clang/Driver/Options.h"
6465
#include "clang/Driver/Phases.h"
6566
#include "clang/Driver/SanitizerArgs.h"
@@ -87,6 +88,7 @@
8788
#include "llvm/Support/FileSystem.h"
8889
#include "llvm/Support/FileUtilities.h"
8990
#include "llvm/Support/FormatVariadic.h"
91+
#include "llvm/Support/JSON.h"
9092
#include "llvm/Support/MD5.h"
9193
#include "llvm/Support/Path.h"
9294
#include "llvm/Support/PrettyStackTrace.h"
@@ -1829,6 +1831,53 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
18291831
// Populate the tool chains for the offloading devices, if any.
18301832
CreateOffloadingDeviceToolChains(*C, Inputs);
18311833

1834+
modules::StdModuleManifest Manifest;
1835+
if (C->getArgs().hasFlag(options::OPT_fmodules_driver,
1836+
options::OPT_fno_modules_driver, false)) {
1837+
Diags.Report(diag::remark_performing_driver_managed_module_build);
1838+
// TODO: Once -fmodules-driver is no longer experimental, move
1839+
// TODO: The detection logic to implicitly enable -fmodules-driver is kept
1840+
// here only for diagnostics until the feature is no longer experimental.
1841+
auto EnableOrErr = modules::shouldUseModulesDriver(Inputs, getVFS(), Diags);
1842+
if (!EnableOrErr) {
1843+
llvm::handleAllErrors(
1844+
EnableOrErr.takeError(), [&](const llvm::FileError &Err) {
1845+
Diags.Report(diag::err_cannot_open_file)
1846+
<< Err.getFileName() << Err.messageWithoutFileInfo();
1847+
});
1848+
return C;
1849+
}
1850+
1851+
// Read the standard modules manifest, and if available, add all discovered
1852+
// modules to the compilation. Compilation jobs for modules discovered from
1853+
// the manifest, which are not imported by any other source input, are
1854+
// pruned later.
1855+
const auto StdModuleManifestPath =
1856+
GetStdModuleManifestPath(*C, C->getDefaultToolChain());
1857+
if (StdModuleManifestPath == "<NOT PRESENT>") {
1858+
Diags.Report(diag::remark_modules_manifest_not_found);
1859+
} else {
1860+
Diags.Report(diag::remark_using_modules_manifest)
1861+
<< StdModuleManifestPath;
1862+
if (auto ManifestOrErr =
1863+
modules::readStdModuleManifest(StdModuleManifestPath, getVFS())) {
1864+
Manifest = std::move(*ManifestOrErr);
1865+
} else {
1866+
llvm::handleAllErrors(
1867+
ManifestOrErr.takeError(),
1868+
[&](llvm::json::ParseError &Err) {
1869+
Diags.Report(diag::err_modules_manifest_failed_parse)
1870+
<< Err.message();
1871+
},
1872+
[&](llvm::FileError &Err) {
1873+
Diags.Report(diag::err_cannot_open_file)
1874+
<< Err.getFileName() << Err.messageWithoutFileInfo();
1875+
});
1876+
}
1877+
}
1878+
modules::buildStdModuleManifestInputs(Manifest, *C, Inputs);
1879+
}
1880+
18321881
// Construct the list of abstract actions to perform for this compilation. On
18331882
// MachO targets this uses the driver-driver and universal actions.
18341883
if (TC.getTriple().isOSBinFormatMachO())
@@ -1843,6 +1892,11 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
18431892

18441893
BuildJobs(*C);
18451894

1895+
if (C->getArgs().hasFlag(options::OPT_fmodules_driver,
1896+
options::OPT_fno_modules_driver, false)) {
1897+
modules::planDriverManagedModuleCompilation(*C, Manifest);
1898+
}
1899+
18461900
return C;
18471901
}
18481902

@@ -4320,33 +4374,6 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args,
43204374
}
43214375
}
43224376

4323-
static bool hasCXXModuleInputType(const Driver::InputList &Inputs) {
4324-
const auto IsTypeCXXModule = [](const auto &Input) -> bool {
4325-
const auto TypeID = Input.first;
4326-
return (TypeID == types::TY_CXXModule);
4327-
};
4328-
return llvm::any_of(Inputs, IsTypeCXXModule);
4329-
}
4330-
4331-
llvm::ErrorOr<bool>
4332-
Driver::ScanInputsForCXX20ModulesUsage(const InputList &Inputs) const {
4333-
const auto CXXInputs = llvm::make_filter_range(
4334-
Inputs, [](const auto &Input) { return types::isCXX(Input.first); });
4335-
for (const auto &Input : CXXInputs) {
4336-
StringRef Filename = Input.second->getSpelling();
4337-
auto ErrOrBuffer = VFS->getBufferForFile(Filename);
4338-
if (!ErrOrBuffer)
4339-
return ErrOrBuffer.getError();
4340-
const auto Buffer = std::move(*ErrOrBuffer);
4341-
4342-
if (scanInputForCXX20ModulesUsage(Buffer->getBuffer())) {
4343-
Diags.Report(diag::remark_found_cxx20_module_usage) << Filename;
4344-
return true;
4345-
}
4346-
}
4347-
return false;
4348-
}
4349-
43504377
void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
43514378
const InputList &Inputs, ActionList &Actions) const {
43524379
llvm::PrettyStackTraceString CrashInfo("Building compilation actions");
@@ -4358,33 +4385,6 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
43584385

43594386
handleArguments(C, Args, Inputs, Actions);
43604387

4361-
if (Args.hasFlag(options::OPT_fmodules_driver,
4362-
options::OPT_fno_modules_driver, false)) {
4363-
// TODO: Move the logic for implicitly enabling explicit-module-builds out
4364-
// of -fmodules-driver once it is no longer experimental.
4365-
// Currently, this serves diagnostic purposes only.
4366-
bool UsesCXXModules = hasCXXModuleInputType(Inputs);
4367-
if (!UsesCXXModules) {
4368-
const auto ErrOrScanResult = ScanInputsForCXX20ModulesUsage(Inputs);
4369-
if (!ErrOrScanResult) {
4370-
Diags.Report(diag::err_cannot_open_file)
4371-
<< ErrOrScanResult.getError().message();
4372-
return;
4373-
}
4374-
UsesCXXModules = *ErrOrScanResult;
4375-
}
4376-
if (UsesCXXModules || Args.hasArg(options::OPT_fmodules))
4377-
BuildDriverManagedModuleBuildActions(C, Args, Inputs, Actions);
4378-
return;
4379-
}
4380-
4381-
BuildDefaultActions(C, Args, Inputs, Actions);
4382-
}
4383-
4384-
void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args,
4385-
const InputList &Inputs,
4386-
ActionList &Actions) const {
4387-
43884388
bool UseNewOffloadingDriver =
43894389
C.isOffloadingHostKind(Action::OFK_OpenMP) ||
43904390
C.isOffloadingHostKind(Action::OFK_SYCL) ||
@@ -4680,12 +4680,6 @@ void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args,
46804680
Args.ClaimAllArgs(options::OPT_cl_ignored_Group);
46814681
}
46824682

4683-
void Driver::BuildDriverManagedModuleBuildActions(
4684-
Compilation &C, llvm::opt::DerivedArgList &Args, const InputList &Inputs,
4685-
ActionList &Actions) const {
4686-
Diags.Report(diag::remark_performing_driver_managed_module_build);
4687-
}
4688-
46894683
/// Returns the canonical name for the offloading architecture when using a HIP
46904684
/// or CUDA architecture.
46914685
static StringRef getCanonicalArchString(Compilation &C,

clang/lib/Driver/Job.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,5 @@ void JobList::Print(raw_ostream &OS, const char *Terminator, bool Quote,
453453
}
454454

455455
void JobList::clear() { Jobs.clear(); }
456+
457+
JobList::list_type JobList::takeJobs() { return std::exchange(Jobs, {}); }

0 commit comments

Comments
 (0)