iains created this revision. Herald added subscribers: dexonsmith, dang. Herald added a project: All. iains added reviewers: rsmith, urnathan, ChuanqiXu. iains added a subscriber: clang-modules. iains edited the summary of this revision. iains added a comment. iains published this revision for review. Herald added a project: clang. Herald added a subscriber: cfe-commits.
This is a small patch set that implements the initial front end changes to support C++20 importable modules. A second path series will follow that makes the driver changes to drive the fronted support. This implementation makes the user-facing names, command line options and behaviour match the existing GCC implementation. There is no apparent engineering reason for divergence and this strategy minimises user learning curves and tooling differences. 1/5 - introduces the module type and build actions to compile them 2/5 - allows the user to specify that a header unit should be found from either the user or system search paths in force. In particular this avoids the user having to know the install path for system headers. 3/5 - handles emitting macros that are live at the end of the header unit TU, as required. 4/5 - handles pre-processed header unit sources 5/5 - introduces "-fdirectives-only" that can be combined with -E to produce pre-processor output that can be consumed as a second job to build an importable header (it can also be used to provide a scannable preprocesed file that has actioned any directives). iains added a comment. please see first comment for a description of the patch series. 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 one 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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D121095 Files: clang/include/clang/Basic/LangOptions.def clang/include/clang/Basic/LangOptions.h clang/include/clang/Basic/Module.h clang/include/clang/Driver/Options.td clang/include/clang/Frontend/FrontendActions.h clang/include/clang/Frontend/FrontendOptions.h clang/include/clang/Lex/ModuleMap.h clang/include/clang/Sema/Sema.h clang/lib/AST/Decl.cpp clang/lib/Frontend/CompilerInvocation.cpp clang/lib/Frontend/FrontendActions.cpp clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp clang/lib/Lex/ModuleMap.cpp clang/lib/Parse/Parser.cpp clang/lib/Sema/Sema.cpp clang/lib/Sema/SemaModule.cpp clang/test/Modules/cxx20-hu-01.cpp
Index: clang/test/Modules/cxx20-hu-01.cpp =================================================================== --- /dev/null +++ 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 Index: clang/lib/Sema/SemaModule.cpp =================================================================== --- clang/lib/Sema/SemaModule.cpp +++ clang/lib/Sema/SemaModule.cpp @@ -97,6 +97,38 @@ return nullptr; } +void Sema::ActOnStartOfHeaderUnit() { + 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 @@ 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 @@ 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,9 @@ 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 || + ModuleScopes.back().Module->isGlobalModule())) { // 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. Index: clang/lib/Sema/Sema.cpp =================================================================== --- clang/lib/Sema/Sema.cpp +++ clang/lib/Sema/Sema.cpp @@ -1021,9 +1021,13 @@ /// 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) + ActOnStartOfHeaderUnit(); + 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()); Index: clang/lib/Parse/Parser.cpp =================================================================== --- clang/lib/Parse/Parser.cpp +++ clang/lib/Parse/Parser.cpp @@ -2474,7 +2474,7 @@ 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) + if (!HeaderUnit || HeaderUnit->Kind != Module::ModuleKind::ModuleHeaderUnit) Diag(ImportLoc, diag::err_import_in_wrong_fragment) << IsPartition << 0; else SeenError = false; Index: clang/lib/Lex/ModuleMap.cpp =================================================================== --- clang/lib/Lex/ModuleMap.cpp +++ clang/lib/Lex/ModuleMap.cpp @@ -905,6 +905,19 @@ 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, Index: clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp =================================================================== --- clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -66,6 +66,8 @@ 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>(); Index: clang/lib/Frontend/FrontendActions.cpp =================================================================== --- clang/lib/Frontend/FrontendActions.cpp +++ clang/lib/Frontend/FrontendActions.cpp @@ -336,6 +336,21 @@ 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 @@ 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: Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -2420,6 +2420,7 @@ {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 @@ {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 @@ case frontend::GenerateModule: case frontend::GenerateModuleInterface: case frontend::GenerateHeaderModule: + case frontend::GenerateHeaderUnit: case frontend::GeneratePCH: case frontend::GenerateInterfaceStubs: case frontend::ParseSyntaxOnly: Index: clang/lib/AST/Decl.cpp =================================================================== --- clang/lib/AST/Decl.cpp +++ clang/lib/AST/Decl.cpp @@ -1554,6 +1554,7 @@ 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 @@ -1569,7 +1570,8 @@ InternalLinkage = !ND->hasExternalFormalLinkage(); else InternalLinkage = isInAnonymousNamespace(); - return InternalLinkage ? M->Parent : nullptr; + return InternalLinkage ? M->Kind == Module::ModuleHeaderUnit ? M : M->Parent + : nullptr; } case Module::PrivateModuleFragment: Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -2975,6 +2975,10 @@ ModuleIdPath Path, ModuleIdPath Partition, ModuleImportState &ImportState); + /// The parser has begun a translation unit to be compiled as a C++20 + /// Header Unit. + void ActOnStartOfHeaderUnit(); + /// The parser has processed a global-module-fragment declaration that begins /// the definition of the global module fragment of the current module unit. /// \param ModuleLoc The location of the 'module' keyword. Index: clang/include/clang/Lex/ModuleMap.h =================================================================== --- clang/include/clang/Lex/ModuleMap.h +++ clang/include/clang/Lex/ModuleMap.h @@ -564,6 +564,10 @@ /// 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, Index: clang/include/clang/Frontend/FrontendOptions.h =================================================================== --- clang/include/clang/Frontend/FrontendOptions.h +++ clang/include/clang/Frontend/FrontendOptions.h @@ -90,6 +90,9 @@ /// 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, Index: clang/include/clang/Frontend/FrontendActions.h =================================================================== --- clang/include/clang/Frontend/FrontendActions.h +++ clang/include/clang/Frontend/FrontendActions.h @@ -168,6 +168,15 @@ 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, Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -5630,6 +5630,8 @@ 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">, Index: clang/include/clang/Basic/Module.h =================================================================== --- clang/include/clang/Basic/Module.h +++ clang/include/clang/Basic/Module.h @@ -109,6 +109,9 @@ /// 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, Index: clang/include/clang/Basic/LangOptions.h =================================================================== --- clang/include/clang/Basic/LangOptions.h +++ clang/include/clang/Basic/LangOptions.h @@ -90,6 +90,9 @@ /// 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, }; Index: clang/include/clang/Basic/LangOptions.def =================================================================== --- clang/include/clang/Basic/LangOptions.def +++ clang/include/clang/Basic/LangOptions.def @@ -166,7 +166,7 @@ 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")
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits