diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -1176,6 +1176,14 @@ Specify the prebuilt module path +.. option:: -fmodule-header + +Build a C++20 header unit from a header specified. + +.. option:: -fmodule-header=\[user,system\] + +Build a C++20 header unit, but search for the header in the user or system header search paths respectively. + .. option:: --hip-path= HIP runtime installation path, used for finding HIP version and adding HIP include path. diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -56,6 +56,16 @@ LTOK_Unknown }; +/// Whether headers used to construct C++20 module units should be looked +/// up by the path supplied on the command line, or in the user or system +/// search paths. +enum ModuleHeaderMode { + HeaderMode_None, + HeaderMode_Default, + HeaderMode_User, + HeaderMode_System +}; + /// Driver - Encapsulate logic for constructing compilation processes /// from a set of gcc-driver-like command line arguments. class Driver { @@ -84,6 +94,13 @@ EmbedBitcode } BitcodeEmbed; + /// Header unit mode set by -fmodule-header={user,system}. + ModuleHeaderMode CXX20HeaderType; + + /// Set if we should process inputs and jobs with C++20 module + /// interpretation. + bool ModulesModeCXX20; + /// LTO mode selected via -f(no-)?lto(=.*)? options. LTOKind LTOMode; @@ -574,6 +591,12 @@ /// ShouldEmitStaticLibrary - Should the linker emit a static library. bool ShouldEmitStaticLibrary(const llvm::opt::ArgList &Args) const; + /// Returns true if the user has indicated a C++20 header unit mode. + bool hasHeaderMode() const { return CXX20HeaderType != HeaderMode_None; } + + /// Get the mode for handling headers as set by fmodule-header{=}. + ModuleHeaderMode getModuleHeaderMode() const { return CXX20HeaderType; } + /// Returns true if we are performing any kind of LTO. bool isUsingLTO(bool IsOffload = false) const { return getLTOMode(IsOffload) != LTOK_None; 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 @@ -2296,6 +2296,11 @@ NegFlag, PosFlag, BothFlags<[NoXarchOption,CoreOption]>>; def fretain_comments_from_system_headers : Flag<["-"], "fretain-comments-from-system-headers">, Group, Flags<[CC1Option]>, MarshallingInfoFlag>; +def fmodule_header : Flag <["-"], "fmodule-header">, Group, + Flags<[NoXarchOption]>, HelpText<"Build a C++20 Header Unit from a header.">; +def fmodule_header_EQ : Joined<["-"], "fmodule-header=">, Group, + Flags<[NoXarchOption]>, MetaVarName<"">, + HelpText<"Build a C++20 Header Unit from a header that should be found in the user (fmodule-header=user) or system (fmodule-header=system) search path.">; def fno_knr_functions : Flag<["-"], "fno-knr-functions">, Group, MarshallingInfoFlag>, diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -191,13 +191,14 @@ DiagnosticsEngine &Diags, std::string Title, IntrusiveRefCntPtr VFS) : Diags(Diags), VFS(std::move(VFS)), Mode(GCCMode), - SaveTemps(SaveTempsNone), BitcodeEmbed(EmbedNone), LTOMode(LTOK_None), - ClangExecutable(ClangExecutable), SysRoot(DEFAULT_SYSROOT), - DriverTitle(Title), CCCPrintBindings(false), CCPrintOptions(false), - CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), - CCPrintProcessStats(false), TargetTriple(TargetTriple), Saver(Alloc), - CheckInputsExist(true), GenReproducer(false), - SuppressMissingInputWarning(false) { + SaveTemps(SaveTempsNone), BitcodeEmbed(EmbedNone), + CXX20HeaderType(HeaderMode_None), ModulesModeCXX20(false), + LTOMode(LTOK_None), ClangExecutable(ClangExecutable), + SysRoot(DEFAULT_SYSROOT), DriverTitle(Title), CCCPrintBindings(false), + CCPrintOptions(false), CCPrintHeaders(false), CCLogDiagnostics(false), + CCGenDiagnostics(false), CCPrintProcessStats(false), + TargetTriple(TargetTriple), Saver(Alloc), CheckInputsExist(true), + GenReproducer(false), SuppressMissingInputWarning(false) { // Provide a sane fallback if no VFS is specified. if (!this->VFS) this->VFS = llvm::vfs::getRealFileSystem(); @@ -337,9 +338,13 @@ CCGenDiagnostics) { FinalPhase = phases::Preprocess; - // --precompile only runs up to precompilation. + // --precompile only runs up to precompilation. + // Options that cause the output of C++20 compiled module interfaces or + // header units have the same effect. } else if ((PhaseArg = DAL.getLastArg(options::OPT__precompile)) || - (PhaseArg = DAL.getLastArg(options::OPT_extract_api))) { + (PhaseArg = DAL.getLastArg(options::OPT_extract_api)) || + (PhaseArg = DAL.getLastArg(options::OPT_fmodule_header, + options::OPT_fmodule_header_EQ))) { FinalPhase = phases::Precompile; // -{fsyntax-only,-analyze,emit-ast} only run up to the compiler. } else if ((PhaseArg = DAL.getLastArg(options::OPT_fsyntax_only)) || @@ -1251,6 +1256,37 @@ BitcodeEmbed = static_cast(Model); } + // Setting up the jobs for some precompile cases depends on whether we are + // treating them as PCH, implicit modules or C++20 ones. + // TODO: inferring the mode like this seems fragile (it meets the objective + // of not requiring anything new for operation, however). + const Arg *Std = Args.getLastArg(options::OPT_std_EQ); + ModulesModeCXX20 = + !Args.hasArg(options::OPT_fmodules) && Std && + (Std->containsValue("c++20") || Std->containsValue("c++2b") || + Std->containsValue("c++2a") || Std->containsValue("c++latest")); + + // Process -fmodule-header{=} flags. + if (Arg *A = Args.getLastArg(options::OPT_fmodule_header_EQ, + options::OPT_fmodule_header)) { + // These flags force C++20 handling of headers. + ModulesModeCXX20 = true; + if (A->getOption().matches(options::OPT_fmodule_header)) + CXX20HeaderType = HeaderMode_Default; + else { + StringRef ArgName = A->getValue(); + unsigned Kind = llvm::StringSwitch(ArgName) + .Case("user", HeaderMode_User) + .Case("system", HeaderMode_System) + .Default(~0U); + if (Kind == ~0U) { + Diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << ArgName; + } else + CXX20HeaderType = static_cast(Kind); + } + } + std::unique_ptr UArgs = std::make_unique(std::move(Args)); @@ -2220,8 +2256,11 @@ return true; // If it's a header to be found in the system or user search path, then defer - // complaints about its absence until those searches can be done. - if (Ty == types::TY_CXXSHeader || Ty == types::TY_CXXUHeader) + // complaints about its absence until those searches can be done. When we + // are definitely processing headers for C++20 header units, extend this to + // allow the user to put "-fmodule-header -xc++-header vector" for example. + if (Ty == types::TY_CXXSHeader || Ty == types::TY_CXXUHeader || + (ModulesModeCXX20 && Ty == types::TY_CXXHeader)) return true; if (getVFS().exists(Value)) @@ -2287,6 +2326,21 @@ return false; } +// Get the C++20 Header Unit type corresponding to the input type. +static types::ID CXXHeaderUnitType(ModuleHeaderMode HM) { + switch (HM) { + case HeaderMode_User: + return types::TY_CXXUHeader; + case HeaderMode_System: + return types::TY_CXXSHeader; + case HeaderMode_Default: + break; + case HeaderMode_None: + llvm_unreachable("should not be called in this case"); + } + return types::TY_CXXHUHeader; +} + // Construct a the list of inputs and their types. void Driver::BuildInputs(const ToolChain &TC, DerivedArgList &Args, InputList &Inputs) const { @@ -2406,6 +2460,11 @@ else if (Args.hasArg(options::OPT_ObjCXX)) Ty = types::TY_ObjCXX; } + + // Disambiguate headers that are meant to be header units from those + // intended to be PCH. + if (Ty == types::TY_CXXHeader && hasHeaderMode()) + Ty = CXXHeaderUnitType(CXX20HeaderType); } else { assert(InputTypeArg && "InputType set w/o InputTypeArg"); if (!InputTypeArg->getOption().matches(options::OPT_x)) { @@ -2457,6 +2516,11 @@ Diag(clang::diag::err_drv_unknown_language) << A->getValue(); InputType = types::TY_Object; } + + // If the user has put -fmodule-header{,=} then we treat C++ headers as + // header unit inputs. So we 'promote' -xc++-header appropriately. + if (InputType == types::TY_CXXHeader && hasHeaderMode()) + InputType = CXXHeaderUnitType(CXX20HeaderType); } else if (A->getOption().getID() == options::OPT_U) { assert(A->getNumValues() == 1 && "The /U option has one value."); StringRef Val = A->getValue(0); diff --git a/clang/test/Driver/cxx20-header-units-02.cpp b/clang/test/Driver/cxx20-header-units-02.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Driver/cxx20-header-units-02.cpp @@ -0,0 +1,32 @@ +// Test user-facing command line options to generate C++20 header units. + +// RUN: %clang -### -std=c++20 -fmodule-header=user foo.hh 2>&1 | \ +// RUN: FileCheck -check-prefix=CHECK-USER %s + +// RUN: %clang -### -std=c++20 -fmodule-header=system foo.hh 2>&1 | \ +// RUN: FileCheck -check-prefix=CHECK-SYS1 %s + +// RUN: %clang -### -std=c++20 -fmodule-header=system \ +// RUN: -xc++-system-header vector 2>&1 | FileCheck -check-prefix=CHECK-SYS2 %s + +// RUN: %clang -### -std=c++20 -fmodule-header=system \ +// RUN: -xc++-header vector 2>&1 | FileCheck -check-prefix=CHECK-SYS2 %s + +// RUN: %clang -### -std=c++20 -fmodule-header %/S/Inputs/header-unit-01.hh \ +// RUN: 2>&1 | FileCheck -check-prefix=CHECK-ABS %s -DTDIR=%/S/Inputs + +// CHECK-USER: "-emit-header-unit" +// CHECK-USER-SAME: "-o" "foo.pcm" +// CHECK-USER-SAME: "-x" "c++-user-header" "foo.hh" + +// CHECK-SYS1: "-emit-header-unit" +// CHECK-SYS1-SAME: "-o" "foo.pcm" +// CHECK-SYS1-SAME: "-x" "c++-system-header" "foo.hh" + +// CHECK-SYS2: "-emit-header-unit" +// CHECK-SYS2-SAME: "-o" "vector.pcm" +// CHECK-SYS2-SAME: "-x" "c++-system-header" "vector" + +// CHECK-ABS: "-emit-header-unit" +// CHECK-ABS-SAME: "-o" "header-unit-01.pcm" +// CHECK-ABS-SAME: "-x" "c++-header-unit-header" "[[TDIR]]/header-unit-01.hh"