Skip to content

Commit 9473873

Browse files
authored
Reapply "[clang][modules-driver] Add support for C++ named modules and import std" (#193815)
This reverts #193677 and relands #193312. This adds basic support for explicit C++ named module builds, managed natively by the Clang driver, including support for use of the Standard library modules. This follows #187606, which adds the same for Clang modules. Current limitations: - Standard library modules are still compiled to object files instead of using the provided shared library. (This will be addressed in a follow-up soon.) - Caching is not supported yet (but likely to be added during the upcoming GSoC cycle). - Importing C++ standard library modules into Clang modules is not supported (and not expected in the near term). RFC: https://discourse.llvm.org/t/rfc-modules-support-simple-c-20-modules-use-from-the-clang-driver-without-a-build-system
1 parent 689dc6c commit 9473873

8 files changed

Lines changed: 366 additions & 11 deletions

clang/include/clang/Basic/DiagnosticDriverKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,8 @@ def err_drv_reduced_module_output_overrided : Warning<
613613
"please consider use '-fmodule-output=' to specify the output file for reduced BMI explicitly">,
614614
InGroup<DiagGroup<"reduced-bmi-output-overrided">>;
615615

616+
def err_drv_modules_driver_requires_reduced_bmi : Error<
617+
"'-fmodules-driver' is currently incompatible with '-fno-modules-reduced-bmi'">;
616618
def remark_performing_driver_managed_module_build : Remark<
617619
"performing driver managed module build">, InGroup<ModulesDriver>;
618620
def remark_modules_manifest_not_found : Remark<

clang/include/clang/Driver/ModulesDriver.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class Compilation;
3232

3333
namespace clang::driver::modules {
3434

35+
/// Emits diagnostics for arguments incompatible with -fmodules-driver.
36+
void diagnoseModulesDriverArgs(llvm::opt::DerivedArgList &DAL,
37+
DiagnosticsEngine &Diags);
38+
3539
/// The parsed Standard library module manifest.
3640
struct StdModuleManifest {
3741
struct Module {

clang/lib/Driver/Driver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,8 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
18371837
if (UseModulesDriver) {
18381838
Diags.Report(diag::remark_performing_driver_managed_module_build);
18391839

1840+
modules::diagnoseModulesDriverArgs(C->getArgs(), Diags);
1841+
18401842
// Read the Standard library module manifest and, if available, add all
18411843
// discovered modules to this Compilation. Jobs for modules specified in
18421844
// the manifest that are not required by any command-line input are pruned

clang/lib/Driver/ModulesDriver.cpp

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang/Driver/Job.h"
2222
#include "clang/Driver/Tool.h"
2323
#include "clang/Driver/ToolChain.h"
24+
#include "clang/Driver/Types.h"
2425
#include "clang/Frontend/StandaloneDiagnostic.h"
2526
#include "llvm/ADT/DenseSet.h"
2627
#include "llvm/ADT/DepthFirstIterator.h"
@@ -47,6 +48,14 @@ using namespace clang;
4748
using namespace driver;
4849
using namespace modules;
4950

51+
void driver::modules::diagnoseModulesDriverArgs(llvm::opt::DerivedArgList &DAL,
52+
DiagnosticsEngine &Diags) {
53+
if (!DAL.hasFlag(options::OPT_fmodules_reduced_bmi,
54+
options::OPT_fno_modules_reduced_bmi, true)) {
55+
Diags.Report(diag::err_drv_modules_driver_requires_reduced_bmi);
56+
}
57+
}
58+
5059
namespace clang::driver::modules {
5160
static bool fromJSON(const llvm::json::Value &Params,
5261
StdModuleManifest::Module::LocalArguments &LocalArgs,
@@ -1252,6 +1261,16 @@ static SmallVector<JobNode *> createNodesForUnusedStdlibModuleJobs(
12521261
return StdlibModuleNodesToPrune;
12531262
}
12541263

1264+
// Returns the derived argument list for the tool chain responsible
1265+
// for creating \p Job.
1266+
static const DerivedArgList &getToolChainArgs(Compilation &C,
1267+
const Command &Job) {
1268+
const auto &TC = Job.getCreator().getToolChain();
1269+
const auto &SourceAction = Job.getSource();
1270+
return C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(),
1271+
SourceAction.getOffloadingDeviceKind());
1272+
}
1273+
12551274
/// Creates a job for the Clang module described by \p MD.
12561275
static std::unique_ptr<Command>
12571276
createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob,
@@ -1263,9 +1282,7 @@ createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob,
12631282
Action *PA = C.MakeAction<PrecompileJobAction>(IA, types::ID::TY_ModuleFile);
12641283
PA->propagateOffloadInfo(&ImportingJob.getSource());
12651284

1266-
const auto &TC = ImportingJob.getCreator().getToolChain();
1267-
const auto &TCArgs = C.getArgsForToolChain(&TC, PA->getOffloadingArch(),
1268-
PA->getOffloadingDeviceKind());
1285+
const auto &TCArgs = getToolChainArgs(C, ImportingJob);
12691286

12701287
const auto &BuildArgs = MD.getBuildArguments();
12711288
ArgStringList JobArgs;
@@ -1319,12 +1336,7 @@ installScanCommandLines(Compilation &C,
13191336
ArgStringList JobArgs;
13201337
JobArgs.reserve(BuildArgs.size());
13211338

1322-
const auto &SourceAction = Job.getSource();
1323-
const auto &TC = Job.getCreator().getToolChain();
1324-
auto &TCArgs =
1325-
C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(),
1326-
SourceAction.getOffloadingDeviceKind());
1327-
1339+
auto &TCArgs = getToolChainArgs(C, Job);
13281340
for (const auto &Arg : BuildArgs)
13291341
JobArgs.push_back(TCArgs.MakeArgString(Arg));
13301342

@@ -1527,6 +1539,73 @@ static void createAndConnectRoot(CompilationGraph &Graph) {
15271539
}
15281540
}
15291541

1542+
/// Creates a temporary output path for \p ModuleName.
1543+
static std::string createModuleOutputPath(const Compilation &C,
1544+
StringRef ModuleName) {
1545+
// Sanitize the ':' included in parition names. It is illegal for filenames on
1546+
// Windows.
1547+
SmallString<32> SanitizedModuleName(ModuleName);
1548+
llvm::replace(SanitizedModuleName, ':', '-');
1549+
auto ModuleOutputPath = C.getDriver().GetTemporaryPath(
1550+
SanitizedModuleName, types::getTypeTempSuffix(types::TY_ModuleFile));
1551+
return ModuleOutputPath;
1552+
}
1553+
1554+
/// Adds the '-fmodule-output=' argument for the module produced by \p Node.
1555+
static void configureNamedModuleOutputArg(Compilation &C,
1556+
NamedModuleJobNode &Node,
1557+
StringRef ModuleOutputPath) {
1558+
auto &Job = *Node.Job;
1559+
const auto &TCArgs = getToolChainArgs(C, Job);
1560+
auto JobArgs = Job.getArguments();
1561+
JobArgs.push_back(
1562+
TCArgs.MakeArgString("-fmodule-output=" + ModuleOutputPath));
1563+
Job.replaceArguments(std::move(JobArgs));
1564+
}
1565+
1566+
/// Propagates the '-fmodule-file=' mapping for the named module described by
1567+
/// \p Node to each dependent job.
1568+
static void propagateModuleFileMappingArg(Compilation &C,
1569+
NamedModuleJobNode &Node,
1570+
StringRef ModuleOutputPath) {
1571+
const StringRef ModuleName = Node.InputDeps.ModuleName;
1572+
1573+
auto DependentNodes = llvm::drop_begin(llvm::depth_first<CGNode *>(&Node));
1574+
auto DependentScannedNodes = llvm::map_range(
1575+
llvm::make_filter_range(DependentNodes, llvm::IsaPred<ScannedJobNode>),
1576+
llvm::CastTo<ScannedJobNode>);
1577+
1578+
for (ScannedJobNode *DependentNode : DependentScannedNodes) {
1579+
auto &DependentJob = *DependentNode->Job;
1580+
const auto &TCArgs = getToolChainArgs(C, DependentJob);
1581+
auto JobArgs = DependentJob.getArguments();
1582+
JobArgs.push_back(TCArgs.MakeArgString("-fmodule-file=" + ModuleName + "=" +
1583+
ModuleOutputPath));
1584+
DependentJob.replaceArguments(std::move(JobArgs));
1585+
}
1586+
}
1587+
1588+
/// Finalizes command lines for C++20 named module dependencies.
1589+
///
1590+
/// The command lines produced by dependency scanning are only adjusted to
1591+
/// handle discovered Clang modules. For C++20 named modules, we update the
1592+
/// command-lines here.
1593+
static void fixupNamedModuleCommandLines(Compilation &C,
1594+
CompilationGraph &Graph) {
1595+
const auto NamedModuleNodes = llvm::map_range(
1596+
llvm::make_filter_range(Graph, llvm::IsaPred<NamedModuleJobNode>),
1597+
llvm::CastTo<NamedModuleJobNode>);
1598+
1599+
for (NamedModuleJobNode *Node : NamedModuleNodes) {
1600+
const StringRef ModuleName = Node->InputDeps.ModuleName;
1601+
const auto ModuleOutputPath = createModuleOutputPath(C, ModuleName);
1602+
C.addTempFile(C.getArgs().MakeArgString(ModuleOutputPath));
1603+
1604+
configureNamedModuleOutputArg(C, *Node, ModuleOutputPath);
1605+
propagateModuleFileMappingArg(C, *Node, ModuleOutputPath);
1606+
}
1607+
}
1608+
15301609
/// Moves jobs from \p Graph into \p C in the graph's topological order.
15311610
static void feedJobsBackIntoCompilation(Compilation &C,
15321611
CompilationGraph &&Graph) {
@@ -1600,7 +1679,6 @@ void driver::modules::runModulesDriver(
16001679
if (!Diags.isLastDiagnosticIgnored())
16011680
llvm::WriteGraph<const CompilationGraph *>(llvm::errs(), &Graph);
16021681

1603-
// TODO: Fix-up command-lines for named module imports.
1604-
1682+
fixupNamedModuleCommandLines(C, Graph);
16051683
feedJobsBackIntoCompilation(C, std::move(Graph));
16061684
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Checks that -fmodules-driver correctly handles compilations using both
2+
// Standard C++20 modules and Clang modules.
3+
// Importing a Standard C++20 module into Clang module is not supported yet.
4+
5+
// RUN: split-file %s %t
6+
// RUN: rm -rf %t/modules-cache
7+
8+
// RUN: %clang -c -std=c++23 \
9+
// RUN: -fmodules-driver -Rmodules-driver \
10+
// RUN: -fmodules -Rmodule-import \
11+
// RUN: -fmodule-map-file=%t/module.modulemap \
12+
// RUN: -fmodules-cache-path=%t/modules-cache \
13+
// RUN: %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cppm 2>&1 \
14+
// RUN: | sed 's:\\\\\?:/:g' \
15+
// RUN: | FileCheck -DPREFIX=%/t --check-prefix=CHECK-REMARKS %s
16+
17+
// The scan itself will also produce [-Rmodule-import] remarks.
18+
// Let's skip past them, we only care about the final -cc1 commands.
19+
// CHECK-REMARKS: clang: remark: printing module dependency graph [-Rmodules-driver]
20+
// CHECK-REMARKS-NEXT: digraph "Module Dependency Graph" {
21+
// CHECK-REMARKS: }
22+
23+
// CHECK-REMARKS: [[PREFIX]]/A-part1-impl.cppm:2:2: remark: importing module 'root' from
24+
// CHECK-REMARKS: [[PREFIX]]/A-part1.cppm:2:2: remark: importing module 'root' from
25+
// CHECK-REMARKS: [[PREFIX]]/A.cppm:2:2: remark: importing module 'root' from
26+
// CHECK-REMARKS: [[PREFIX]]/A.cppm:4:8: remark: importing module 'A:part1' from
27+
// CHECK-REMARKS: [[PREFIX]]/A.cppm:4:8: remark: importing module 'root' into 'A:part1' from
28+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A' from
29+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'root' into 'A' from
30+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A:part1' into 'A' from
31+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'root' into 'A:part1' from
32+
33+
// RUN: %clang -std=c++23 \
34+
// RUN: -fmodules-driver -Rmodules-driver \
35+
// RUN: -fmodules -Rmodule-import \
36+
// RUN: -fmodule-map-file=%t/module.modulemap \
37+
// RUN: -fmodules-cache-path=%t/modules-cache \
38+
// RUN: %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cppm \
39+
// RUN: -### 2>&1 \
40+
// RUN: | sed 's:\\\\\?:/:g' \
41+
// RUN: | FileCheck -DPREFIX=%/t --check-prefix=CHECK-CC1 %s
42+
43+
// CHECK-CC1: "-cc1"
44+
// CHECK-CC1-SAME: "-o" "[[ROOTPCM:[^"]+]]"
45+
// CHECK-CC1-SAME: "-emit-module"
46+
// CHECK-CC1-SAME: "[[PREFIX]]/module.modulemap"
47+
// CHECK-CC1-SAME: "-fmodule-name=root"
48+
// CHECK-CC1-SAME: "-fno-implicit-modules"
49+
50+
// CHECK-CC1: "-cc1"
51+
// CHECK-CC1-SAME: "[[PREFIX]]/A-part1-impl.cppm"
52+
// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
53+
// CHECK-CC1-SAME: "-fno-implicit-modules"
54+
// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_IMPL_PCM:[^"]+]]"
55+
56+
// CHECK-CC1: "-cc1"
57+
// CHECK-CC1-SAME: "[[PREFIX]]/A-part1.cppm"
58+
// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
59+
// CHECK-CC1-SAME: "-fno-implicit-modules"
60+
// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_PCM:[^"]+]]"
61+
62+
// CHECK-CC1: "-cc1"
63+
// CHECK-CC1-SAME: "[[PREFIX]]/A.cppm"
64+
// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
65+
// CHECK-CC1-SAME: "-fno-implicit-modules"
66+
// CHECK-CC1-SAME: "-fmodule-output=[[A_PCM:[^"]+]]"
67+
// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
68+
69+
// CHECK-CC1: "-cc1"
70+
// CHECK-CC1-SAME: "[[PREFIX]]/main.cpp"
71+
// CHECK-CC1-SAME: "-fno-implicit-modules"
72+
// CHECK-CC1-SAME: "-fmodule-file=A=[[A_PCM]]"
73+
// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
74+
75+
//--- main.cpp
76+
import A;
77+
78+
int main() {
79+
a();
80+
}
81+
82+
//--- A.cppm
83+
module;
84+
#include "root.h"
85+
export module A;
86+
export import :part1;
87+
88+
export int a() {
89+
return part1() + root();
90+
}
91+
92+
//--- A-part1.cppm
93+
module;
94+
#include "root.h"
95+
export module A:part1;
96+
export int part1();
97+
98+
//--- A-part1-impl.cppm
99+
module;
100+
#include "root.h"
101+
module A:part1_impl;
102+
103+
int part1() {
104+
return root();
105+
}
106+
107+
//--- module.modulemap
108+
module root { header "root.h" export * }
109+
110+
//--- root.h
111+
inline int root() { return 1; }
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Checks that -fmodules-driver correctly handles compilations using
2+
// Standard C++20 modules.
3+
4+
// RUN: split-file %s %t
5+
6+
// RUN: %clang -c -std=c++23 \
7+
// RUN: -fmodules-driver -Rmodules-driver -Rmodule-import \
8+
// RUN: %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cppm %t/B.cppm 2>&1 \
9+
// RUN: | sed 's:\\\\\?:/:g' \
10+
// RUN: | FileCheck -DPREFIX=%/t --check-prefix=CHECK-REMARKS %s
11+
12+
// CHECK-REMARKS: [[PREFIX]]/A.cppm:2:8: remark: importing module 'A:part1' from
13+
// CHECK-REMARKS: [[PREFIX]]/A.cppm:3:1: remark: importing module 'B' from
14+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A' from
15+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A:part1' into 'A' from
16+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'B' into 'A' from
17+
// CHECK-REMARKS: [[PREFIX]]/main.cpp:2:1: remark: importing module 'B' from
18+
19+
// RUN: %clang -std=c++23 \
20+
// RUN: -fmodules-driver -Rmodules-driver -Rmodule-import \
21+
// RUN: %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cppm %t/B.cppm \
22+
// RUN: -### 2>&1 \
23+
// RUN: | sed 's:\\\\\?:/:g' \
24+
// RUN: | FileCheck --check-prefix=CHECK-CC1 %s
25+
26+
// CHECK-CC1: "-cc1"
27+
// CHECK-CC1-SAME: "{{.*}}/B.cppm"
28+
// CHECK-CC1-SAME: "-fno-implicit-modules"
29+
// CHECK-CC1-SAME: "-fmodule-output=[[B_PCM:[^"]+]]"
30+
31+
// CHECK-CC1: "-cc1"
32+
// CHECK-CC1-SAME: "{{.*}}/A-part1-impl.cppm"
33+
// CHECK-CC1-SAME: "-fno-implicit-modules"
34+
// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_IMPL_PCM:[^"]+]]"
35+
36+
// CHECK-CC1: "-cc1"
37+
// CHECK-CC1-SAME: "{{.*}}/A-part1.cppm"
38+
// CHECK-CC1-SAME: "-fno-implicit-modules"
39+
// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_PCM:[^"]+]]"
40+
41+
// CHECK-CC1: "-cc1"
42+
// CHECK-CC1-SAME: "{{.*}}/A.cppm"
43+
// CHECK-CC1-SAME: "-fno-implicit-modules"
44+
// CHECK-CC1-SAME: "-fmodule-output=[[A_PCM:[^"]+]]"
45+
// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
46+
// CHECK-CC1-SAME: "-fmodule-file=B=[[B_PCM]]"
47+
48+
// CHECK-CC1: "-cc1"
49+
// CHECK-CC1-SAME: "{{.*}}/main.cpp"
50+
// CHECK-CC1-SAME: "-fno-implicit-modules"
51+
// CHECK-CC1-SAME: "-fmodule-file=A=[[A_PCM]]"
52+
// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
53+
// CHECK-CC1-SAME: "-fmodule-file=B=[[B_PCM]]"
54+
55+
//--- main.cpp
56+
import A;
57+
import B;
58+
59+
int main() {
60+
return a() + b();
61+
}
62+
63+
//--- A.cppm
64+
export module A;
65+
export import :part1;
66+
import B;
67+
68+
export int a() {
69+
return part1() + b();
70+
}
71+
72+
//--- A-part1.cppm
73+
export module A:part1;
74+
export int part1();
75+
76+
//--- A-part1-impl.cppm
77+
module A:part1_impl;
78+
79+
int part1() {
80+
return 30;
81+
}
82+
83+
//--- B.cppm
84+
export module B;
85+
86+
export int b() {
87+
return 12;
88+
}

0 commit comments

Comments
 (0)