[clang] 0687578 - [C++20][Modules][HU 2/5] Support searching Header Units in user or system search paths.

Iain Sandoe via cfe-commits cfe-commits at lists.llvm.org
Sat Mar 26 03:17:53 PDT 2022


Author: Iain Sandoe
Date: 2022-03-26T10:17:17Z
New Revision: 0687578728ea1985cbab0de14d4eeb4e89cdf210

URL: https://github.com/llvm/llvm-project/commit/0687578728ea1985cbab0de14d4eeb4e89cdf210
DIFF: https://github.com/llvm/llvm-project/commit/0687578728ea1985cbab0de14d4eeb4e89cdf210.diff

LOG: [C++20][Modules][HU 2/5] Support searching Header Units in user or system search paths.

This is support for the user-facing options to create importable header units
from headers in the user or system search paths (or to be given an absolute path).

This means that an incomplete header path will be passed by the driver and the
lookup carried out using the search paths present when the front end is run.

To support this, we introduce file fypes for c++-{user,system,header-unit}-header.
These terms are the same as the ones used by GCC, to minimise the differences for
tooling (and users).

The preprocessor checks for headers before issuing a warning for
"#pragma once" in a header build.  We ensure that the importable header units
are recognised as headers in order to avoid such warnings.

Differential Revision: https://reviews.llvm.org/D121096

Added: 
    clang/test/Modules/cxx20-hu-02.cpp
    clang/test/Modules/cxx20-hu-03.cpp
    clang/test/Modules/cxx20-hu-bad-input.cpp

Modified: 
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/Frontend/FrontendOptions.h
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Frontend/FrontendAction.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 59862555cc8f6..63913b933c8fb 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -266,6 +266,8 @@ def err_drv_cc_print_options_failure : Error<
 def err_drv_lto_without_lld : Error<"LTO requires -fuse-ld=lld">;
 def err_drv_preamble_format : Error<
     "incorrect format for -preamble-bytes=N,END">;
+def err_drv_header_unit_extra_inputs : Error<
+    "multiple inputs are not valid for header units (first extra '%0')">;
 def warn_invalid_ios_deployment_target : Warning<
   "invalid iOS deployment version '%0', iOS 10 is the maximum deployment "
   "target for 32-bit targets">, InGroup<InvalidIOSDeploymentTarget>,

diff  --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h
index 160dc71505e2f..ff5a9c5c77f4d 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -153,6 +153,8 @@ class InputKind {
   Language Lang;
   unsigned Fmt : 3;
   unsigned Preprocessed : 1;
+  unsigned HeaderUnit : 3;
+  unsigned IsHeader : 1;
 
 public:
   /// The input file format.
@@ -162,13 +164,29 @@ class InputKind {
     Precompiled
   };
 
+  // If we are building a header unit, what kind it is; this affects whether
+  // we look for the file in the user or system include search paths before
+  // flagging a missing input.
+  enum HeaderUnitKind {
+    HeaderUnit_None,
+    HeaderUnit_User,
+    HeaderUnit_System,
+    HeaderUnit_Abs
+  };
+
   constexpr InputKind(Language L = Language::Unknown, Format F = Source,
-                      bool PP = false)
-      : Lang(L), Fmt(F), Preprocessed(PP) {}
+                      bool PP = false, HeaderUnitKind HU = HeaderUnit_None,
+                      bool HD = false)
+      : Lang(L), Fmt(F), Preprocessed(PP), HeaderUnit(HU), IsHeader(HD) {}
 
   Language getLanguage() const { return static_cast<Language>(Lang); }
   Format getFormat() const { return static_cast<Format>(Fmt); }
+  HeaderUnitKind getHeaderUnitKind() const {
+    return static_cast<HeaderUnitKind>(HeaderUnit);
+  }
   bool isPreprocessed() const { return Preprocessed; }
+  bool isHeader() const { return IsHeader; }
+  bool isHeaderUnit() const { return HeaderUnit != HeaderUnit_None; }
 
   /// Is the input kind fully-unknown?
   bool isUnknown() const { return Lang == Language::Unknown && Fmt == Source; }
@@ -179,11 +197,23 @@ class InputKind {
   }
 
   InputKind getPreprocessed() const {
-    return InputKind(getLanguage(), getFormat(), true);
+    return InputKind(getLanguage(), getFormat(), true, getHeaderUnitKind(),
+                     isHeader());
+  }
+
+  InputKind getHeader() const {
+    return InputKind(getLanguage(), getFormat(), isPreprocessed(),
+                     getHeaderUnitKind(), true);
+  }
+
+  InputKind withHeaderUnit(HeaderUnitKind HU) const {
+    return InputKind(getLanguage(), getFormat(), isPreprocessed(), HU,
+                     isHeader());
   }
 
   InputKind withFormat(Format F) const {
-    return InputKind(getLanguage(), F, isPreprocessed());
+    return InputKind(getLanguage(), F, isPreprocessed(), getHeaderUnitKind(),
+                     isHeader());
   }
 };
 
@@ -218,6 +248,10 @@ class FrontendInputFile {
   bool isFile() const { return !isBuffer(); }
   bool isBuffer() const { return Buffer != None; }
   bool isPreprocessed() const { return Kind.isPreprocessed(); }
+  bool isHeader() const { return Kind.isHeader(); }
+  InputKind::HeaderUnitKind getHeaderUnitKind() const {
+    return Kind.getHeaderUnitKind();
+  }
 
   StringRef getFile() const {
     assert(isFile());

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index be7828ba2e937..5bb480a599713 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2567,6 +2567,20 @@ static void GenerateFrontendArgs(const FrontendOptions &Opts,
     StringRef Preprocessed = Opts.DashX.isPreprocessed() ? "-cpp-output" : "";
     StringRef ModuleMap =
         Opts.DashX.getFormat() == InputKind::ModuleMap ? "-module-map" : "";
+    StringRef HeaderUnit = "";
+    switch (Opts.DashX.getHeaderUnitKind()) {
+    case InputKind::HeaderUnit_None:
+      break;
+    case InputKind::HeaderUnit_User:
+      HeaderUnit = "-user";
+      break;
+    case InputKind::HeaderUnit_System:
+      HeaderUnit = "-system";
+      break;
+    case InputKind::HeaderUnit_Abs:
+      HeaderUnit = "-header-unit";
+      break;
+    }
     StringRef Header = IsHeader ? "-header" : "";
 
     StringRef Lang;
@@ -2611,7 +2625,8 @@ static void GenerateFrontendArgs(const FrontendOptions &Opts,
       break;
     }
 
-    GenerateArg(Args, OPT_x, Lang + Header + ModuleMap + Preprocessed, SA);
+    GenerateArg(Args, OPT_x,
+                Lang + HeaderUnit + Header + ModuleMap + Preprocessed, SA);
   }
 
   // OPT_INPUT has a unique class, generate it directly.
@@ -2756,13 +2771,32 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
   if (const Arg *A = Args.getLastArg(OPT_x)) {
     StringRef XValue = A->getValue();
 
-    // Parse suffixes: '<lang>(-header|[-module-map][-cpp-output])'.
+    // Parse suffixes:
+    // '<lang>(-[{header-unit,user,system}-]header|[-module-map][-cpp-output])'.
     // FIXME: Supporting '<lang>-header-cpp-output' would be useful.
     bool Preprocessed = XValue.consume_back("-cpp-output");
     bool ModuleMap = XValue.consume_back("-module-map");
-    IsHeaderFile = !Preprocessed && !ModuleMap &&
-                   XValue != "precompiled-header" &&
-                   XValue.consume_back("-header");
+    // Detect and consume the header indicator.
+    bool IsHeader =
+        XValue != "precompiled-header" && XValue.consume_back("-header");
+
+    // If we have c++-{user,system}-header, that indicates a header unit input
+    // likewise, if the user put -fmodule-header together with a header with an
+    // absolute path (header-unit-header).
+    InputKind::HeaderUnitKind HUK = InputKind::HeaderUnit_None;
+    if (IsHeader || Preprocessed) {
+      if (XValue.consume_back("-header-unit"))
+        HUK = InputKind::HeaderUnit_Abs;
+      else if (XValue.consume_back("-system"))
+        HUK = InputKind::HeaderUnit_System;
+      else if (XValue.consume_back("-user"))
+        HUK = InputKind::HeaderUnit_User;
+    }
+
+    // The value set by this processing is an un-preprocessed source which is
+    // not intended to be a module map or header unit.
+    IsHeaderFile = IsHeader && !Preprocessed && !ModuleMap &&
+                   HUK == InputKind::HeaderUnit_None;
 
     // Principal languages.
     DashX = llvm::StringSwitch<InputKind>(XValue)
@@ -2779,14 +2813,16 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
 
     // "objc[++]-cpp-output" is an acceptable synonym for
     // "objective-c[++]-cpp-output".
-    if (DashX.isUnknown() && Preprocessed && !IsHeaderFile && !ModuleMap)
+    if (DashX.isUnknown() && Preprocessed && !IsHeaderFile && !ModuleMap &&
+        HUK == InputKind::HeaderUnit_None)
       DashX = llvm::StringSwitch<InputKind>(XValue)
                   .Case("objc", Language::ObjC)
                   .Case("objc++", Language::ObjCXX)
                   .Default(Language::Unknown);
 
     // Some special cases cannot be combined with suffixes.
-    if (DashX.isUnknown() && !Preprocessed && !ModuleMap && !IsHeaderFile)
+    if (DashX.isUnknown() && !Preprocessed && !IsHeaderFile && !ModuleMap &&
+        HUK == InputKind::HeaderUnit_None)
       DashX = llvm::StringSwitch<InputKind>(XValue)
                   .Case("cpp-output", InputKind(Language::C).getPreprocessed())
                   .Case("assembler-with-cpp", Language::Asm)
@@ -2801,6 +2837,12 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
 
     if (Preprocessed)
       DashX = DashX.getPreprocessed();
+    // A regular header is considered mutually exclusive with a header unit.
+    if (HUK != InputKind::HeaderUnit_None) {
+      DashX = DashX.withHeaderUnit(HUK);
+      IsHeaderFile = true;
+    } else if (IsHeaderFile)
+      DashX = DashX.getHeader();
     if (ModuleMap)
       DashX = DashX.withFormat(InputKind::ModuleMap);
   }
@@ -2810,6 +2852,11 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
   Opts.Inputs.clear();
   if (Inputs.empty())
     Inputs.push_back("-");
+
+  if (DashX.getHeaderUnitKind() != InputKind::HeaderUnit_None &&
+      Inputs.size() > 1)
+    Diags.Report(diag::err_drv_header_unit_extra_inputs) << Inputs[1];
+
   for (unsigned i = 0, e = Inputs.size(); i != e; ++i) {
     InputKind IK = DashX;
     if (IK.isUnknown()) {
@@ -3857,6 +3904,7 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
   }
   if (Opts.FastRelaxedMath)
     Opts.setDefaultFPContractMode(LangOptions::FPM_Fast);
+
   llvm::sort(Opts.ModuleFeatures);
 
   // -mrtd option

diff  --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index b8f92381613a3..68b823252130c 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -798,7 +798,48 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI,
                                            &CI.getPreprocessor());
   HasBegunSourceFile = true;
 
-  // Initialize the main file entry.
+  // Handle C++20 header units.
+  // Here, the user has the option to specify that the header name should be
+  // looked up in the pre-processor search paths (and the main filename as
+  // passed by the driver might therefore be incomplete until that look-up).
+  if (CI.getLangOpts().CPlusPlusModules && Input.getKind().isHeaderUnit() &&
+      !Input.getKind().isPreprocessed()) {
+    StringRef FileName = Input.getFile();
+    InputKind Kind = Input.getKind();
+    if (Kind.getHeaderUnitKind() != InputKind::HeaderUnit_Abs) {
+      assert(CI.hasPreprocessor() &&
+             "trying to build a header unit without a Pre-processor?");
+      HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo();
+      // Relative searches begin from CWD.
+      const DirectoryEntry *Dir = nullptr;
+      if (auto DirOrErr = CI.getFileManager().getDirectory("."))
+        Dir = *DirOrErr;
+      SmallVector<std::pair<const FileEntry *, const DirectoryEntry *>, 1> CWD;
+      CWD.push_back({nullptr, Dir});
+      Optional<FileEntryRef> FE =
+          HS.LookupFile(FileName, SourceLocation(),
+                        /*Angled*/ Input.getKind().getHeaderUnitKind() ==
+                            InputKind::HeaderUnit_System,
+                        nullptr, nullptr, CWD, nullptr, nullptr, nullptr,
+                        nullptr, nullptr, nullptr);
+      if (!FE) {
+        CI.getDiagnostics().Report(diag::err_module_header_file_not_found)
+            << FileName;
+        return false;
+      }
+      // We now have the filename...
+      FileName = FE->getFileEntry().getName();
+      // ... still a header unit, but now use the path as written.
+      Kind = Input.getKind().withHeaderUnit(InputKind::HeaderUnit_Abs);
+      Input = FrontendInputFile(FileName, Kind, Input.isSystem());
+    }
+    // Unless the user has overridden the name, the header unit module name is
+    // the pathname for the file.
+    if (CI.getLangOpts().ModuleName.empty())
+      CI.getLangOpts().ModuleName = std::string(FileName);
+    CI.getLangOpts().CurrentModule = CI.getLangOpts().ModuleName;
+  }
+
   if (!CI.InitializeSourceManager(Input))
     return false;
 

diff  --git a/clang/test/Modules/cxx20-hu-02.cpp b/clang/test/Modules/cxx20-hu-02.cpp
new file mode 100644
index 0000000000000..f51eaf1a84bfd
--- /dev/null
+++ b/clang/test/Modules/cxx20-hu-02.cpp
@@ -0,0 +1,77 @@
+// Test generation and import of user and system C++20 Header Units.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: cd %t
+
+// check user path
+// RUN: %clang_cc1 -std=c++20 -emit-header-unit -I user \
+// RUN: -xc++-user-header hu-01.h -o hu-01.pcm
+
+// RUN: %clang_cc1 -std=c++20 -module-file-info hu-01.pcm | \
+// RUN: FileCheck --check-prefix=CHECK-HU %s -DTDIR=%t
+
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface imp-hu-01.cpp \
+// RUN:  -I user -fmodule-file=hu-01.pcm -o B.pcm -Rmodule-import \
+// RUN: 2>&1  | FileCheck --check-prefix=CHECK-IMP %s -DTDIR=%t
+
+// check system path
+// RUN: %clang_cc1 -std=c++20 -emit-header-unit -isystem system \
+// RUN: -xc++-system-header hu-02.h -o hu-02.pcm
+
+// RUN: %clang_cc1 -std=c++20 -module-file-info hu-02.pcm | \
+// RUN: FileCheck --check-prefix=CHECK-HU2 %s -DTDIR=%t
+
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface imp-hu-02.cpp \
+// RUN:  -isystem system -fmodule-file=hu-02.pcm -o C.pcm \
+// RUN: -Rmodule-import 2>&1 | \
+// RUN: FileCheck --check-prefix=CHECK-SYS-IMP %s -DTDIR=%t
+
+// check absolute path
+// RUN: %clang_cc1 -std=c++20 -emit-header-unit  \
+// RUN: -xc++-header-unit-header %t/hu-03.h -o hu-03.pcm
+
+// RUN: %clang_cc1 -std=c++20 -module-file-info hu-03.pcm | \
+// RUN: FileCheck --check-prefix=CHECK-HU3 %s -DTDIR=%t
+
+//--- user/hu-01.h
+int foo(int);
+
+// CHECK-HU:  ====== C++20 Module structure ======
+// CHECK-HU-NEXT:  Header Unit 'user{{[/\\]}}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 'user{{[/\\]}}hu-01.h' from 'hu-01.pcm'
+// expected-no-diagnostics
+
+//--- system/hu-02.h
+int baz(int);
+
+// CHECK-HU2:  ====== C++20 Module structure ======
+// CHECK-HU2-NEXT:  Header Unit 'system{{[/\\]}}hu-02.h' is the Primary Module at index #1
+
+//--- imp-hu-02.cpp
+module;
+import <hu-02.h>;
+
+export module C;
+
+int bar(int x) {
+  return baz(x);
+}
+// CHECK-SYS-IMP: remark: importing module 'system{{[/\\]}}hu-02.h' from 'hu-02.pcm'
+// expected-no-diagnostics
+
+//--- hu-03.h
+int curly(int);
+
+// CHECK-HU3:  ====== C++20 Module structure ======
+// CHECK-HU3-NEXT:  Header Unit '[[TDIR]]/hu-03.h' is the Primary Module at index #1
+// expected-no-diagnostics

diff  --git a/clang/test/Modules/cxx20-hu-03.cpp b/clang/test/Modules/cxx20-hu-03.cpp
new file mode 100644
index 0000000000000..81916e97aaa16
--- /dev/null
+++ b/clang/test/Modules/cxx20-hu-03.cpp
@@ -0,0 +1,57 @@
+// Test check that processing headers as C++20 units allows #pragma once.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: cd %t
+
+// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header hu-01.h \
+// RUN: -Werror -o hu-01.pcm
+
+// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header hu-02.h \
+// RUN: -fmodule-file=%t/hu-01.pcm -o hu-02.pcm
+
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only imports-01.cpp \
+// RUN: -fmodule-file=%t/hu-01.pcm
+
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only imports-02.cpp \
+// RUN: -fmodule-file=%t/hu-02.pcm
+
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only imports-03.cpp \
+// RUN: -fmodule-file=%t/hu-02.pcm
+
+//--- hu-01.h
+#pragma once
+struct HU {
+  int a;
+};
+// expected-no-diagnostics
+
+//--- hu-02.h
+export import "hu-01.h";
+// expected-no-diagnostics
+
+//--- imports-01.cpp
+import "hu-01.h";
+
+HU foo(int x) {
+  return {x};
+}
+// expected-no-diagnostics
+
+//--- imports-02.cpp
+import "hu-02.h";
+
+HU foo(int x) {
+  return {x};
+}
+// expected-no-diagnostics
+
+//--- imports-03.cpp
+import "hu-01.h";
+import "hu-02.h";
+
+HU foo(int x) {
+  return {x};
+}
+// expected-no-diagnostics

diff  --git a/clang/test/Modules/cxx20-hu-bad-input.cpp b/clang/test/Modules/cxx20-hu-bad-input.cpp
new file mode 100644
index 0000000000000..6c72d7d41646b
--- /dev/null
+++ b/clang/test/Modules/cxx20-hu-bad-input.cpp
@@ -0,0 +1,19 @@
+// Test generation and import of simple C++20 Header Units.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: cd %t
+
+// RUN: not %clang_cc1 -std=c++20 -emit-header-unit \
+// RUN:  -xc++-header-unit-header hu-01.hh \
+// RUN:  -xc++-header-unit-header hu-02.hh \
+// RUN:  -o hu-01.pcm -verify  2>&1 | FileCheck %s
+
+// CHECK: (frontend): multiple inputs are not valid for header units (first extra 'hu-02.hh')
+
+//--- hu-01.hh
+int foo(int);
+
+//--- hu-02.hh
+int bar(int);


        


More information about the cfe-commits mailing list