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

Reply via email to