iains created this revision. Herald added a project: All. iains added reviewers: rsmith, urnathan, ChuanqiXu. iains published this revision for review. Herald added a project: clang. Herald added a subscriber: cfe-commits.
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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D121096 Files: clang/include/clang/Frontend/FrontendOptions.h clang/lib/Frontend/CompilerInvocation.cpp clang/lib/Frontend/FrontendAction.cpp clang/test/Modules/cxx20-hu-02.cpp clang/test/Modules/cxx20-hu-03.cpp
Index: clang/test/Modules/cxx20-hu-03.cpp =================================================================== --- /dev/null +++ clang/test/Modules/cxx20-hu-03.cpp @@ -0,0 +1,58 @@ +// 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 +#define FORTYTWO 42 +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 {FORTYTWO}; +} +// expected-no-diagnostics + +//--- imports-02.cpp +import "hu-02.h"; + +HU foo(int x) { + return {FORTYTWO}; +} +// expected-no-diagnostics + +//--- imports-03.cpp +import "hu-01.h"; +import "hu-02.h"; + +HU foo(int x) { + return {FORTYTWO}; +} +// expected-no-diagnostics Index: clang/test/Modules/cxx20-hu-02.cpp =================================================================== --- /dev/null +++ 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 Index: clang/lib/Frontend/FrontendAction.cpp =================================================================== --- clang/lib/Frontend/FrontendAction.cpp +++ clang/lib/Frontend/FrontendAction.cpp @@ -798,7 +798,48 @@ &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.getHeaderUnit() != 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().getHeaderUnit() == + 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; Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -2567,6 +2567,20 @@ StringRef Preprocessed = Opts.DashX.isPreprocessed() ? "-cpp-output" : ""; StringRef ModuleMap = Opts.DashX.getFormat() == InputKind::ModuleMap ? "-module-map" : ""; + StringRef HeaderUnit = ""; + switch (Opts.DashX.getHeaderUnit()) { + 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 @@ 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,30 @@ 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) { + HUK = + XValue.consume_back("-header-unit") ? InputKind::HeaderUnit_Abs : HUK; + HUK = XValue.consume_back("-system") ? InputKind::HeaderUnit_System : HUK; + HUK = XValue.consume_back("-user") ? InputKind::HeaderUnit_User : HUK; + } + + // 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 +2811,16 @@ // "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 +2835,13 @@ if (Preprocessed) DashX = DashX.getPreprocessed(); + // A regular header is considered mutually exclusive with a header unit + // one + 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 +2851,11 @@ Opts.Inputs.clear(); if (Inputs.empty()) Inputs.push_back("-"); + + assert((DashX.getHeaderUnit() == InputKind::HeaderUnit_None || + Inputs.size() == 1) && + "Expected only one input file for header unit"); + for (unsigned i = 0, e = Inputs.size(); i != e; ++i) { InputKind IK = DashX; if (IK.isUnknown()) { @@ -3857,6 +3903,7 @@ } if (Opts.FastRelaxedMath) Opts.setDefaultFPContractMode(LangOptions::FPM_Fast); + llvm::sort(Opts.ModuleFeatures); // -mrtd option Index: clang/include/clang/Frontend/FrontendOptions.h =================================================================== --- clang/include/clang/Frontend/FrontendOptions.h +++ clang/include/clang/Frontend/FrontendOptions.h @@ -153,6 +153,8 @@ Language Lang; unsigned Fmt : 3; unsigned Preprocessed : 1; + unsigned HeaderUnit : 3; + unsigned Header : 1; public: /// The input file format. @@ -162,13 +164,29 @@ 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), Header(HD) {} Language getLanguage() const { return static_cast<Language>(Lang); } Format getFormat() const { return static_cast<Format>(Fmt); } + HeaderUnitKind getHeaderUnit() const { + return static_cast<HeaderUnitKind>(HeaderUnit); + } bool isPreprocessed() const { return Preprocessed; } + bool isHeader() const { return Header; } + 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 @@ } InputKind getPreprocessed() const { - return InputKind(getLanguage(), getFormat(), true); + return InputKind(getLanguage(), getFormat(), true, getHeaderUnit(), + isHeader()); + } + + InputKind getHeader() const { + return InputKind(getLanguage(), getFormat(), isPreprocessed(), + getHeaderUnit(), 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(), getHeaderUnit(), + isHeader()); } }; @@ -218,6 +248,10 @@ 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 getHeaderUnit() const { + return Kind.getHeaderUnit(); + } StringRef getFile() const { assert(isFile());
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits