diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h --- a/clang/include/clang/Basic/LangOptions.h +++ b/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, }; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/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") diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h --- a/clang/include/clang/Basic/Module.h +++ b/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, diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -5640,6 +5640,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">, diff --git a/clang/include/clang/Frontend/FrontendActions.h b/clang/include/clang/Frontend/FrontendActions.h --- a/clang/include/clang/Frontend/FrontendActions.h +++ b/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 + 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 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/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, diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h --- a/clang/include/clang/Lex/ModuleMap.h +++ b/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 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 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2977,6 +2977,12 @@ 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 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -1561,6 +1561,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 @@ -1576,7 +1577,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: diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/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: diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/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 +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: diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -67,6 +67,8 @@ 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 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/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, diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -2468,8 +2468,9 @@ 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 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1030,9 +1030,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) + 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 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -97,6 +97,38 @@ 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 @@ 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,13 @@ 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 --- /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