[C++20][Modules][HU 1/5] Introduce header units as a module type.

This is the first in a series of patches that introduce C++20 importable
header units.

These differ from clang header modules in that:
 (a) they are identifiable by an internal name
 (b) they represent the top level source for a single header - although
     that might include or import other headers.

We name importable header units with the path by which they are specified
(although that need not be the absolute path for the file).

So "foo/bar.h" would have a name "foo/bar.h".  Header units are made a
separate module type so that we can deal with diagnosing places where they
are permitted but a named module is not.

Differential Revision: https://reviews.llvm.org/D121095
This commit is contained in:
Iain Sandoe 2020-09-20 09:29:14 +01:00
parent c48b4641c7
commit 6c0e60e884
17 changed files with 224 additions and 9 deletions

View File

@ -166,7 +166,7 @@ BENIGN_LANGOPT(HeinousExtensions , 1, 0, "extensions that we really don't like a
LANGOPT(Modules , 1, 0, "modules semantics")
COMPATIBLE_LANGOPT(ModulesTS , 1, 0, "C++ Modules TS syntax")
COMPATIBLE_LANGOPT(CPlusPlusModules, 1, 0, "C++ modules syntax")
BENIGN_ENUM_LANGOPT(CompilingModule, CompilingModuleKind, 2, CMK_None,
BENIGN_ENUM_LANGOPT(CompilingModule, CompilingModuleKind, 3, CMK_None,
"compiling a module interface")
BENIGN_LANGOPT(CompilingPCH, 1, 0, "building a pch")
BENIGN_LANGOPT(BuildingPCHWithObjectFile, 1, 0, "building a pch which has a corresponding object file")

View File

@ -90,6 +90,9 @@ public:
/// Compiling a module from a list of header files.
CMK_HeaderModule,
/// Compiling a module header unit.
CMK_HeaderUnit,
/// Compiling a C++ modules TS module interface unit.
CMK_ModuleInterface,
};

View File

@ -109,6 +109,9 @@ public:
/// This is a C++20 module interface unit.
ModuleInterfaceUnit,
/// This is a C++ 20 header unit.
ModuleHeaderUnit,
/// This is a C++ 20 module partition interface.
ModulePartitionInterface,

View File

@ -5640,6 +5640,8 @@ def emit_module_interface : Flag<["-"], "emit-module-interface">,
HelpText<"Generate pre-compiled module file from a C++ module interface">;
def emit_header_module : Flag<["-"], "emit-header-module">,
HelpText<"Generate pre-compiled module file from a set of header files">;
def emit_header_unit : Flag<["-"], "emit-header-unit">,
HelpText<"Generate C++20 header units from header files">;
def emit_pch : Flag<["-"], "emit-pch">,
HelpText<"Generate pre-compiled header file">;
def emit_llvm_bc : Flag<["-"], "emit-llvm-bc">,

View File

@ -168,6 +168,15 @@ private:
CreateOutputFile(CompilerInstance &CI, StringRef InFile) override;
};
class GenerateHeaderUnitAction : public GenerateModuleAction {
private:
bool BeginSourceFileAction(CompilerInstance &CI) override;
std::unique_ptr<raw_pwrite_stream>
CreateOutputFile(CompilerInstance &CI, StringRef InFile) override;
};
class SyntaxOnlyAction : public ASTFrontendAction {
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,

View File

@ -90,6 +90,9 @@ enum ActionKind {
/// Generate pre-compiled module from a set of header files.
GenerateHeaderModule,
/// Generate a C++20 header unit module from a header file.
GenerateHeaderUnit,
/// Generate pre-compiled header.
GeneratePCH,

View File

@ -564,6 +564,10 @@ public:
/// Create a header module from the specified list of headers.
Module *createHeaderModule(StringRef Name, ArrayRef<Module::Header> Headers);
/// Create a C++20 header unit.
Module *createHeaderUnit(SourceLocation Loc, StringRef Name,
Module::Header H);
/// Infer the contents of a framework module map from the given
/// framework directory.
Module *inferFrameworkModule(const DirectoryEntry *FrameworkDir,

View File

@ -2977,6 +2977,12 @@ public:
NotACXX20Module ///< Not a C++20 TU, or an invalid state was found.
};
private:
/// The parser has begun a translation unit to be compiled as a C++20
/// Header Unit, helper for ActOnStartOfTranslationUnit() only.
void HandleStartOfHeaderUnit();
public:
/// The parser has processed a module-declaration that begins the definition
/// of a module interface or implementation.
DeclGroupPtrTy ActOnModuleDecl(SourceLocation StartLoc,

View File

@ -1561,6 +1561,7 @@ Module *Decl::getOwningModuleForLinkage(bool IgnoreLinkage) const {
case Module::ModulePartitionImplementation:
return M;
case Module::ModuleHeaderUnit:
case Module::GlobalModuleFragment: {
// External linkage declarations in the global module have no owning module
// for linkage purposes. But internal linkage declarations in the global
@ -1576,7 +1577,8 @@ Module *Decl::getOwningModuleForLinkage(bool IgnoreLinkage) const {
InternalLinkage = !ND->hasExternalFormalLinkage();
else
InternalLinkage = isInAnonymousNamespace();
return InternalLinkage ? M->Parent : nullptr;
return InternalLinkage ? M->Kind == Module::ModuleHeaderUnit ? M : M->Parent
: nullptr;
}
case Module::PrivateModuleFragment:

View File

@ -2420,6 +2420,7 @@ static const auto &getFrontendActionTable() {
{frontend::GenerateModule, OPT_emit_module},
{frontend::GenerateModuleInterface, OPT_emit_module_interface},
{frontend::GenerateHeaderModule, OPT_emit_header_module},
{frontend::GenerateHeaderUnit, OPT_emit_header_unit},
{frontend::GeneratePCH, OPT_emit_pch},
{frontend::GenerateInterfaceStubs, OPT_emit_interface_stubs},
{frontend::InitOnly, OPT_init_only},
@ -2436,7 +2437,7 @@ static const auto &getFrontendActionTable() {
{frontend::MigrateSource, OPT_migrate},
{frontend::RunPreprocessorOnly, OPT_Eonly},
{frontend::PrintDependencyDirectivesSourceMinimizerOutput,
OPT_print_dependency_directives_minimized_source},
OPT_print_dependency_directives_minimized_source},
};
return Table;
@ -4160,6 +4161,7 @@ static bool isStrictlyPreprocessorAction(frontend::ActionKind Action) {
case frontend::GenerateModule:
case frontend::GenerateModuleInterface:
case frontend::GenerateHeaderModule:
case frontend::GenerateHeaderUnit:
case frontend::GeneratePCH:
case frontend::GenerateInterfaceStubs:
case frontend::ParseSyntaxOnly:

View File

@ -336,6 +336,21 @@ GenerateHeaderModuleAction::CreateOutputFile(CompilerInstance &CI,
return CI.createDefaultOutputFile(/*Binary=*/true, InFile, "pcm");
}
bool GenerateHeaderUnitAction::BeginSourceFileAction(CompilerInstance &CI) {
if (!CI.getLangOpts().CPlusPlusModules) {
CI.getDiagnostics().Report(diag::err_module_interface_requires_cpp_modules);
return false;
}
CI.getLangOpts().setCompilingModule(LangOptions::CMK_HeaderUnit);
return GenerateModuleAction::BeginSourceFileAction(CI);
}
std::unique_ptr<raw_pwrite_stream>
GenerateHeaderUnitAction::CreateOutputFile(CompilerInstance &CI,
StringRef InFile) {
return CI.createDefaultOutputFile(/*Binary=*/true, InFile, "pcm");
}
SyntaxOnlyAction::~SyntaxOnlyAction() {
}
@ -818,6 +833,8 @@ static StringRef ModuleKindName(Module::ModuleKind MK) {
return "Partition Interface";
case Module::ModulePartitionImplementation:
return "Partition Implementation";
case Module::ModuleHeaderUnit:
return "Header Unit";
case Module::GlobalModuleFragment:
return "Global Module Fragment";
case Module::PrivateModuleFragment:

View File

@ -67,6 +67,8 @@ CreateFrontendBaseAction(CompilerInstance &CI) {
return std::make_unique<GenerateModuleInterfaceAction>();
case GenerateHeaderModule:
return std::make_unique<GenerateHeaderModuleAction>();
case GenerateHeaderUnit:
return std::make_unique<GenerateHeaderUnitAction>();
case GeneratePCH: return std::make_unique<GeneratePCHAction>();
case GenerateInterfaceStubs:
return std::make_unique<GenerateInterfaceStubsAction>();

View File

@ -905,6 +905,19 @@ Module *ModuleMap::createHeaderModule(StringRef Name,
return Result;
}
Module *ModuleMap::createHeaderUnit(SourceLocation Loc, StringRef Name,
Module::Header H) {
assert(LangOpts.CurrentModule == Name && "module name mismatch");
assert(!Modules[Name] && "redefining existing module");
auto *Result = new Module(Name, Loc, nullptr, /*IsFramework*/ false,
/*IsExplicit*/ false, NumCreatedModules++);
Result->Kind = Module::ModuleHeaderUnit;
Modules[Name] = SourceModule = Result;
addHeader(Result, H, NormalHeader);
return Result;
}
/// For a framework module, infer the framework against which we
/// should link.
static void inferFrameworkLink(Module *Mod, const DirectoryEntry *FrameworkDir,

View File

@ -2468,8 +2468,9 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc,
break;
case Sema::ModuleImportState::GlobalFragment:
// We can only have pre-processor directives in the global module
// fragment. We can, however have a header unit import here.
if (!HeaderUnit)
// fragment. We cannot import a named modules here, however we have a
// header unit import.
if (!HeaderUnit || HeaderUnit->Kind != Module::ModuleKind::ModuleHeaderUnit)
Diag(ImportLoc, diag::err_import_in_wrong_fragment) << IsPartition << 0;
else
SeenError = false;

View File

@ -1030,9 +1030,13 @@ void Sema::emitAndClearUnusedLocalTypedefWarnings() {
/// is parsed. Note that the ASTContext may have already injected some
/// declarations.
void Sema::ActOnStartOfTranslationUnit() {
if (getLangOpts().ModulesTS &&
(getLangOpts().getCompilingModule() == LangOptions::CMK_ModuleInterface ||
getLangOpts().getCompilingModule() == LangOptions::CMK_None)) {
if (getLangOpts().CPlusPlusModules &&
getLangOpts().getCompilingModule() == LangOptions::CMK_HeaderUnit)
HandleStartOfHeaderUnit();
else if (getLangOpts().ModulesTS &&
(getLangOpts().getCompilingModule() ==
LangOptions::CMK_ModuleInterface ||
getLangOpts().getCompilingModule() == LangOptions::CMK_None)) {
// We start in an implied global module fragment.
SourceLocation StartOfTU =
SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID());

View File

@ -97,6 +97,38 @@ Sema::ActOnGlobalModuleFragmentDecl(SourceLocation ModuleLoc) {
return nullptr;
}
void Sema::HandleStartOfHeaderUnit() {
assert(getLangOpts().CPlusPlusModules &&
"Header units are only valid for C++20 modules");
SourceLocation StartOfTU =
SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID());
StringRef HUName = getLangOpts().CurrentModule;
if (HUName.empty()) {
HUName = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID())->getName();
const_cast<LangOptions &>(getLangOpts()).CurrentModule = HUName.str();
}
auto &Map = PP.getHeaderSearchInfo().getModuleMap();
// TODO: Make the C++20 header lookup independent.
Module::Header H{getLangOpts().CurrentModule, getLangOpts().CurrentModule,
SourceMgr.getFileEntryForID(SourceMgr.getMainFileID())};
Module *Mod = Map.createHeaderUnit(StartOfTU, HUName, H);
assert(Mod && "module creation should not fail");
ModuleScopes.push_back({}); // No GMF
ModuleScopes.back().BeginLoc = StartOfTU;
ModuleScopes.back().Module = Mod;
ModuleScopes.back().ModuleInterface = true;
ModuleScopes.back().IsPartition = false;
VisibleModules.setVisible(Mod, StartOfTU);
// From now on, we have an owning module for all declarations we see.
// All of these are implicitly exported.
auto *TU = Context.getTranslationUnitDecl();
TU->setModuleOwnershipKind(Decl::ModuleOwnershipKind::Visible);
TU->setLocalOwningModule(Mod);
}
Sema::DeclGroupPtrTy
Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc,
ModuleDeclKind MDK, ModuleIdPath Path,
@ -149,6 +181,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc,
return nullptr;
case LangOptions::CMK_HeaderModule:
case LangOptions::CMK_HeaderUnit:
Diag(ModuleLoc, diag::err_module_decl_in_header_module);
return nullptr;
}
@ -310,6 +343,7 @@ Sema::ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc,
case Module::GlobalModuleFragment:
case Module::ModulePartitionImplementation:
case Module::ModulePartitionInterface:
case Module::ModuleHeaderUnit:
Diag(PrivateLoc, diag::err_private_module_fragment_not_module);
return nullptr;
@ -480,7 +514,13 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc,
Mod->Kind == Module::ModuleKind::ModulePartitionImplementation) {
Diag(ExportLoc, diag::err_export_partition_impl)
<< SourceRange(ExportLoc, Path.back().second);
} else if (!ModuleScopes.empty() && ModuleScopes.back().ModuleInterface) {
} else if (!ModuleScopes.empty() &&
(ModuleScopes.back().ModuleInterface ||
(getLangOpts().CPlusPlusModules &&
ModuleScopes.back().Module->isGlobalModule()))) {
assert((!ModuleScopes.back().Module->isGlobalModule() ||
Mod->Kind == Module::ModuleKind::ModuleHeaderUnit) &&
"should only be importing a header unit into the GMF");
// Re-export the module if the imported module is exported.
// Note that we don't need to add re-exported module to Imports field
// since `Exports` implies the module is imported already.

View File

@ -0,0 +1,104 @@
// Test generation and import of simple C++20 Header Units.
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-header %t/hu-01.h \
// RUN: -o %t/hu-01.pcm
// RUN: %clang_cc1 -std=c++20 -module-file-info %t/hu-01.pcm | \
// RUN: FileCheck --check-prefix=CHECK-HU %s -DTDIR=%t
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/imp-hu-01.cpp \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/B.pcm -Rmodule-import 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-IMP %s -DTDIR=%t
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/imp-hu-02.cpp \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/C.pcm -Rmodule-import 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-GMF-IMP %s -DTDIR=%t
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-header %t/hu-02.h \
// RUN: -o %t/hu-02.pcm
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/imp-hu-03.cpp \
// RUN: -fmodule-file=%t/hu-01.pcm -fmodule-file=%t/hu-02.pcm -o %t/D.pcm \
// RUN: -Rmodule-import 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-BOTH %s -DTDIR=%t
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-header %t/hu-03.h \
// RUN: -fmodule-file=%t/hu-01.pcm -o %t/hu-03.pcm
// RUN: %clang_cc1 -std=c++20 -module-file-info %t/hu-03.pcm | \
// RUN: FileCheck --check-prefix=CHECK-HU-HU %s -DTDIR=%t
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/imp-hu-04.cpp \
// RUN: -fmodule-file=%t/hu-03.pcm -o %t/E.pcm -Rmodule-import 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-NESTED %s -DTDIR=%t
//--- hu-01.h
int foo(int);
// CHECK-HU: ====== C++20 Module structure ======
// CHECK-HU-NEXT: Header Unit '[[TDIR]]/hu-01.h' is the Primary Module at index #1
//--- imp-hu-01.cpp
export module B;
import "hu-01.h";
int bar(int x) {
return foo(x);
}
// CHECK-IMP: remark: importing module '[[TDIR]]/hu-01.h' from '[[TDIR]]/hu-01.pcm'
// expected-no-diagnostics
//--- imp-hu-02.cpp
module;
import "hu-01.h";
export module C;
int bar(int x) {
return foo(x);
}
// CHECK-GMF-IMP: remark: importing module '[[TDIR]]/hu-01.h' from '[[TDIR]]/hu-01.pcm'
// expected-no-diagnostics
//--- hu-02.h
int baz(int);
//--- imp-hu-03.cpp
module;
export import "hu-01.h";
export module D;
import "hu-02.h";
int bar(int x) {
return foo(x) + baz(x);
}
// CHECK-BOTH: remark: importing module '[[TDIR]]/hu-01.h' from '[[TDIR]]/hu-01.pcm'
// CHECK-BOTH: remark: importing module '[[TDIR]]/hu-02.h' from '[[TDIR]]/hu-02.pcm'
// expected-no-diagnostics
//--- hu-03.h
export import "hu-01.h";
int baz(int);
// CHECK-HU-HU: ====== C++20 Module structure ======
// CHECK-HU-HU-NEXT: Header Unit '[[TDIR]]/hu-03.h' is the Primary Module at index #2
// CHECK-HU-HU-NEXT: Exports:
// CHECK-HU-HU-NEXT: Header Unit '[[TDIR]]/hu-01.h' is at index #1
// expected-no-diagnostics
//--- imp-hu-04.cpp
module;
import "hu-03.h";
export module E;
int bar(int x) {
return foo(x) + baz(x);
}
// CHECK-NESTED: remark: importing module '[[TDIR]]/hu-03.h' from '[[TDIR]]/hu-03.pcm'
// expected-no-diagnostics