diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -266,6 +266,8 @@ 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, 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 @@ -153,6 +153,8 @@ Language Lang; unsigned Fmt : 3; unsigned Preprocessed : 1; + unsigned HeaderUnit : 3; + unsigned IsHeader : 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), IsHeader(HD) {} Language getLanguage() const { return static_cast(Lang); } Format getFormat() const { return static_cast(Fmt); } + HeaderUnitKind getHeaderUnitKind() const { + return static_cast(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 @@ } 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 @@ 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 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/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.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 @@ 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 @@ if (const Arg *A = Args.getLastArg(OPT_x)) { StringRef XValue = A->getValue(); - // Parse suffixes: '(-header|[-module-map][-cpp-output])'. + // Parse suffixes: + // '(-[{header-unit,user,system}-]header|[-module-map][-cpp-output])'. // FIXME: Supporting '-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(XValue) @@ -2779,14 +2813,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(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(XValue) .Case("cpp-output", InputKind(Language::C).getPreprocessed()) .Case("assembler-with-cpp", Language::Asm) @@ -2801,6 +2837,12 @@ 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 @@ 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 @@ } 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 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/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.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, 1> CWD; + CWD.push_back({nullptr, Dir}); + Optional 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 --- /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 ; + +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 --- /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 --- /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);