From 6c0e60e884a20016ccc0d7c7e6f06df089a0de86 Mon Sep 17 00:00:00 2001 From: Iain Sandoe Date: Sun, 20 Sep 2020 09:29:14 +0100 Subject: [PATCH] [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 --- clang/include/clang/Basic/LangOptions.def | 2 +- clang/include/clang/Basic/LangOptions.h | 3 + clang/include/clang/Basic/Module.h | 3 + clang/include/clang/Driver/Options.td | 2 + .../include/clang/Frontend/FrontendActions.h | 9 ++ .../include/clang/Frontend/FrontendOptions.h | 3 + clang/include/clang/Lex/ModuleMap.h | 4 + clang/include/clang/Sema/Sema.h | 6 + clang/lib/AST/Decl.cpp | 4 +- clang/lib/Frontend/CompilerInvocation.cpp | 4 +- clang/lib/Frontend/FrontendActions.cpp | 17 +++ .../ExecuteCompilerInvocation.cpp | 2 + clang/lib/Lex/ModuleMap.cpp | 13 +++ clang/lib/Parse/Parser.cpp | 5 +- clang/lib/Sema/Sema.cpp | 10 +- clang/lib/Sema/SemaModule.cpp | 42 ++++++- clang/test/Modules/cxx20-hu-01.cpp | 104 ++++++++++++++++++ 17 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 clang/test/Modules/cxx20-hu-01.cpp diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 3745740d3d29..05b969114299 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -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") diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index 6aa24d2facc2..96fd6049efec 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -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, }; diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index 3d1af45c48f3..9752ff61e4a2 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -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, diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 6ed87f9a464d..784751a1a686 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -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">, diff --git a/clang/include/clang/Frontend/FrontendActions.h b/clang/include/clang/Frontend/FrontendActions.h index 9b5b757034a6..ae829d741152 100644 --- a/clang/include/clang/Frontend/FrontendActions.h +++ b/clang/include/clang/Frontend/FrontendActions.h @@ -168,6 +168,15 @@ private: CreateOutputFile(CompilerInstance &CI, StringRef InFile) override; }; +class GenerateHeaderUnitAction : public GenerateModuleAction { + +private: + bool BeginSourceFileAction(CompilerInstance &CI) override; + + std::unique_ptr + CreateOutputFile(CompilerInstance &CI, StringRef InFile) override; +}; + class SyntaxOnlyAction : public ASTFrontendAction { protected: std::unique_ptr CreateASTConsumer(CompilerInstance &CI, diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index db4da799481f..160dc71505e2 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -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, diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h index 538dcc7c4bec..3e14009d1027 100644 --- a/clang/include/clang/Lex/ModuleMap.h +++ b/clang/include/clang/Lex/ModuleMap.h @@ -564,6 +564,10 @@ public: /// Create a header module from the specified list of headers. Module *createHeaderModule(StringRef Name, ArrayRef 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, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 4c44ee3cc931..5573621f6120 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -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, diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 6b68e511a932..9b8585bbe312 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -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: diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index cac443de75da..e18c7ed81873 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -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: diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index 65cbc946179f..2cd209640aaf 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -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 +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: diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index 4525a2c97785..6927d2ed47aa 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -67,6 +67,8 @@ CreateFrontendBaseAction(CompilerInstance &CI) { return std::make_unique(); case GenerateHeaderModule: return std::make_unique(); + case GenerateHeaderUnit: + return std::make_unique(); case GeneratePCH: return std::make_unique(); case GenerateInterfaceStubs: return std::make_unique(); diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index a5eca402c43b..75a0e6bcdfe6 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -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, diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 8bba3d480358..9c8892ebc4b8 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -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; diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index b0e47c0e54aa..52f38c06de36 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -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()); diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index 9de95e2e6087..a115834d334c 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -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(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. diff --git a/clang/test/Modules/cxx20-hu-01.cpp b/clang/test/Modules/cxx20-hu-01.cpp new file mode 100644 index 000000000000..022c49fecb32 --- /dev/null +++ b/clang/test/Modules/cxx20-hu-01.cpp @@ -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