diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index 29f6480ba935c..9ed50b6807d2a 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -577,6 +577,19 @@ def err_drv_reduced_module_output_overrided : Warning< "please consider use '-fmodule-output=' to specify the output file for reduced BMI explicitly">, InGroup>; +def remark_fmodules_driver_enabled : Remark< + "support for explicit module builds enabled (experimental)">, + InGroup; +def remark_found_cxx20_module_usage : Remark< + "found C++20 module usage in file '%0'">, + InGroup; +def remark_performing_explicit_module_build : Remark< + "performing explicit module build">, + InGroup; +def warn_modules_driver_unsupported_standard : Warning< + "'-fmodules-driver' is not supported before C++20">, + InGroup; + def warn_drv_delayed_template_parsing_after_cxx20 : Warning< "-fdelayed-template-parsing is deprecated after C++20">, InGroup>; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index beda73e675fc6..1baede2d0c324 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -625,6 +625,7 @@ def ModuleConflict : DiagGroup<"module-conflict">; def ModuleFileExtension : DiagGroup<"module-file-extension">; def ModuleIncludeDirectiveTranslation : DiagGroup<"module-include-translation">; def ModuleMap : DiagGroup<"module-map">; +def ModulesDriver : DiagGroup<"modules-driver">; def RoundTripCC1Args : DiagGroup<"round-trip-cc1-args">; def NewlineEOF : DiagGroup<"newline-eof">; def Nullability : DiagGroup<"nullability">; diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h index 7ca848f11b561..9f9518942270a 100644 --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -505,6 +505,8 @@ class Driver { /// BuildActions - Construct the list of actions to perform for the /// given arguments, which are only done for a single architecture. + /// If the compilation is an explicit module build, delegates to + /// BuildExplicitModuleBuildActions. Otherwise, uses BuildDefaultActions. /// /// \param C - The compilation that is being built. /// \param Args - The input arguments. @@ -790,6 +792,35 @@ class Driver { /// compilation based on which -f(no-)?lto(=.*)? option occurs last. void setLTOMode(const llvm::opt::ArgList &Args); + /// BuildDefaultActions - Constructs the list of actions to perform + /// for the provided arguments, which are only done for a single architecture. + /// + /// \param C - The compilation that is being built. + /// \param Args - The input arguments. + /// \param Actions - The list to store the resulting actions onto. + void BuildDefaultActions(Compilation &C, llvm::opt::DerivedArgList &Args, + const InputList &Inputs, ActionList &Actions) const; + + /// BuildExplicitModuleBuildActions - Performs a dependency scan and + /// constructs the list of actions to perform for dependency order and + /// the provided arguments. This is only done for a single a architecture. + /// + /// \param C - The compilation that is being built. + /// \param Args - The input arguments. + /// \param Actions - The list to store the resulting actions onto. + void BuildExplicitModuleBuildActions(Compilation &C, + llvm::opt::DerivedArgList &Args, + const InputList &Inputs, + ActionList &Actions) const; + + /// Scans the leading lines of the C++ source inputs to detect C++20 module + /// usage. + /// + /// \returns True if module usage is detected, false otherwise, or an error on + /// read failure. + llvm::ErrorOr + ScanInputsForCXXModuleUsage(const InputList &Inputs) const; + /// Retrieves a ToolChain for a particular \p Target triple. /// /// Will cache ToolChains for the life of the driver object, and create them diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 152df89118a6a..48d7d520bbfab 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3240,6 +3240,13 @@ def modules_reduced_bmi : Flag<["-"], "fmodules-reduced-bmi">, HelpText<"Generate the reduced BMI">, MarshallingInfoFlag>; +def fmodules_driver : Flag<["-"], "fmodules-driver">, + Group, Visibility<[ClangOption]>, + HelpText<"Enable support for explicit module builds from the driver (experimental)">; +def fno_modules_driver : Flag<["-"], "fno-modules-driver">, + Group, Visibility<[ClangOption]>, + HelpText<"Disable support for explicit module builds from the driver (experimental)">; + def experimental_modules_reduced_bmi : Flag<["-"], "fexperimental-modules-reduced-bmi">, Group, Visibility<[ClangOption, CC1Option]>, Alias; diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index eb60d907d2218..382db8a8ebd80 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -53,6 +53,7 @@ #include "ToolChains/WebAssembly.h" #include "ToolChains/XCore.h" #include "ToolChains/ZOS.h" +#include "clang/Basic/CharInfo.h" #include "clang/Basic/DiagnosticDriver.h" #include "clang/Basic/TargetID.h" #include "clang/Basic/Version.h" @@ -4285,6 +4286,13 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, YcArg = nullptr; } + if (Args.hasArgNoClaim(options::OPT_fmodules_driver)) + // TODO: Check against all incompatible -fmodules-driver arguments + if (!ModulesModeCXX20) { + Diag(diag::warn_modules_driver_unsupported_standard); + Args.eraseArg(options::OPT_fmodules_driver); + } + Arg *FinalPhaseArg; phases::ID FinalPhase = getFinalPhase(Args, &FinalPhaseArg); @@ -4403,6 +4411,177 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, } } +namespace { +static void skipWhitespace(const char *&Ptr) { + while (isWhitespace(*Ptr)) + ++Ptr; +} + +// Returns the length of EOL, either 0 (no end-of-line), 1 (\n) or 2 (\r\n). +static unsigned isEOL(const char *Ptr) { + if (*Ptr == '\0') + return 0; + if (*(Ptr + 1) != '\0' && isVerticalWhitespace(Ptr[0]) && + isVerticalWhitespace(Ptr[1]) && Ptr[0] != Ptr[1]) + return 2; + return !!isVerticalWhitespace(Ptr[0]); +} + +static void skipLine(const char *&Ptr) { + for (;;) { + char LastNonWhitespace = ' '; + while (!isVerticalWhitespace(*Ptr) && *Ptr != '\0') { + if (!isHorizontalWhitespace(*Ptr)) + LastNonWhitespace = *Ptr; + ++Ptr; + } + + const unsigned Len = isEOL(Ptr); + if (!Len) + return; + + Ptr += Len; + if (LastNonWhitespace != '\\') + break; + } +} + +// Returns the length of a line splice sequence (including trailing +// whitespace), or 0 if no line splice is found. +static unsigned isLineSplice(const char *Start) { + if (*Start != '\\') + return 0; + + const char *Ptr = Start + 1; + while (isHorizontalWhitespace(*Ptr)) + ++Ptr; + + if (unsigned Len = isEOL(Ptr)) + return Ptr - Start + Len; + return 0; +} + +static bool trySkipLineSplice(const char *&Ptr) { + if (unsigned Len = isLineSplice(Ptr); Len) { + Ptr += Len; + return true; + } + return false; +} + +static bool trySkipDirective(const char *&Ptr) { + if (*Ptr != '#') + return false; + + ++Ptr; + skipLine(Ptr); + return true; +} + +static bool trySkipLineComment(const char *&Ptr) { + if (Ptr[0] != '/' || Ptr[1] != '/') + return false; + + Ptr += 2; + skipLine(Ptr); + return true; +} + +static bool trySkipBlockComment(const char *&Ptr) { + if (Ptr[0] != '/' || Ptr[1] != '*') + return false; + + Ptr += 2; + while (*Ptr != '\0') { + if (Ptr[0] == '*' && Ptr[1] == '/') { + Ptr += 2; // '*/' + return true; + } + ++Ptr; + } + return true; +} + +static bool trySkipComment(const char *&Ptr) { + return trySkipLineComment(Ptr) || trySkipBlockComment(Ptr); +} + +// Skipps over comments and (non-module) directives +static void skipToRelevantCXXModuleText(const char *&Ptr) { + while (*Ptr != '\0') { + skipWhitespace(Ptr); + if (trySkipComment(Ptr) || trySkipDirective(Ptr) || trySkipLineSplice(Ptr)) + continue; + break; // Found relevant text! + } +} + +static bool scanBufferForCXXModuleUsage(const llvm::MemoryBuffer &Buffer) { + const char *Ptr = Buffer.getBufferStart(); + skipToRelevantCXXModuleText(Ptr); + + // Check if buffer has enough bytes left to check for the module-related + // declaration fragment we want to check without making potentially + // memory-mapped buffer load unnecessary pages. + constexpr int MinKeywordLength = 6; + const char *Begin = Ptr; + for (int i = 0; i < MinKeywordLength; ++i) { + if (*Ptr == '\0') + return false; + ++Ptr; + } + StringRef Text(Begin, MinKeywordLength); + + const bool IsGlobalModule = Text.starts_with("module"); + if (!IsGlobalModule && !Text.starts_with("import") && + !Text.starts_with("export")) + return false; + + // Ensure the keyword has a proper ending and isn't part of a identifier + // or namespace. For this we might have to skip comments and line + // continuations. + while (*Ptr != '\0') { + if (isWhitespace(*Ptr) || (IsGlobalModule && *Ptr == ';')) + return true; + if (trySkipBlockComment(Ptr) || trySkipLineSplice(Ptr)) + continue; + return false; + } + + return false; +} + +static bool hasCXXModuleInputType(const Driver::InputList &Inputs) { + const auto IsTypeCXXModule = [](const auto &Input) -> bool { + const auto TypeID = Input.first; + return (TypeID == types::TY_CXXModule); + }; + return llvm::any_of(Inputs, IsTypeCXXModule); +} + +} // anonymous namespace + +llvm::ErrorOr +Driver::ScanInputsForCXXModuleUsage(const InputList &Inputs) const { + const auto CXXInputs = llvm::make_filter_range( + Inputs, [](const auto &Input) { return types::isCXX(Input.first); }); + + for (const auto &Input : CXXInputs) { + StringRef Filename = Input.second->getSpelling(); + auto ErrOrBuffer = VFS->getBufferForFile(Filename); + if (!ErrOrBuffer) + return ErrOrBuffer.getError(); + const auto Buffer = std::move(*ErrOrBuffer); + + if (scanBufferForCXXModuleUsage(*Buffer)) { + Diags.Report(diag::remark_found_cxx20_module_usage) << Filename; + return true; + } + } + + return false; +} + void Driver::BuildActions(Compilation &C, DerivedArgList &Args, const InputList &Inputs, ActionList &Actions) const { llvm::PrettyStackTraceString CrashInfo("Building compilation actions"); @@ -4414,6 +4593,34 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, handleArguments(C, Args, Inputs, Actions); + if (Args.hasFlag(options::OPT_fmodules_driver, + options::OPT_fno_modules_driver, false)) { + Diags.Report(diag::remark_fmodules_driver_enabled); + // TODO: Move the logic for implicitly enabling explicit-module-builds out + // of -fmodules-driver once it is no longer experimental. + // Currently, this serves diagnostic purposes only. + bool UsesCXXModules = hasCXXModuleInputType(Inputs); + if (!UsesCXXModules) { + const auto ErrOrScanResult = ScanInputsForCXXModuleUsage(Inputs); + if (!ErrOrScanResult) { + Diags.Report(diag::err_cannot_open_file) + << ErrOrScanResult.getError().message(); + return; + } + UsesCXXModules = *ErrOrScanResult; + } + if (UsesCXXModules) + BuildExplicitModuleBuildActions(C, Args, Inputs, Actions); + return; + } + + Driver::BuildDefaultActions(C, Args, Inputs, Actions); +} + +void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args, + const InputList &Inputs, + ActionList &Actions) const { + bool UseNewOffloadingDriver = C.isOffloadingHostKind(Action::OFK_OpenMP) || C.isOffloadingHostKind(Action::OFK_SYCL) || @@ -4693,6 +4900,14 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, Args.ClaimAllArgs(options::OPT_cl_ignored_Group); } +void Driver::BuildExplicitModuleBuildActions(Compilation &C, + llvm::opt::DerivedArgList &Args, + const InputList &Inputs, + ActionList &Actions) const { + Diags.Report(diag::remark_performing_explicit_module_build); + return; +} + /// Returns the canonical name for the offloading architecture when using a HIP /// or CUDA architecture. static StringRef getCanonicalArchString(Compilation &C, diff --git a/clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp b/clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp new file mode 100644 index 0000000000000..9e0fc832003dd --- /dev/null +++ b/clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp @@ -0,0 +1,171 @@ +// The driver never checks to implicitly enable the explicit module build +// support unless at least two input files are provided. +// To trigger the C++20 module usage check, we always pass a second dummy file +// as input. +// TODO: Remove -fmodules everywhere once implicitly enabled explicit module +// builds are supported. + +// RUN: split-file %s %t +//--- empty.cpp +// Nothing here + +//--- only-global.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/only-global.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK1 +// CHECK1: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +module; + +//--- only-import.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/only-import.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK2 +// CHECK2: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +import A; + +//--- only-export.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/only-export.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK3 +// CHECK3: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +export module A; + +//--- leading-line-comment.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-line-comment.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK4 +// CHECK4: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// My line comment +import A; + +//--- leading-block-comment1.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment1.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK5 +// CHECK5: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +/*My block comment */ +import A; + +//--- leading-block-comment2.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment2.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK6 +// CHECK6: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +/*My line comment */ import A; + +//--- inline-block-comment1.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment1.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK7 +// CHECK7: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +export/*a comment*/module/*another comment*/A; + +//--- inline-block-comment2.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment2.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK8 +// CHECK8: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +module/*a comment*/; + +//--- leading-directives.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-directives.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK9 +// CHECK9: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +#define A +#undef A +#if A +#ifdef A +#elifdef A +#elifndef A +#endif +#ifndef A +#elif A +#else +#endif +#endif +#pragma once; +#include +export module m; + +//--- multiline-directive.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/multiline-directive.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK10 +// CHECK10: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +#define MACRO(a, \ + b) \ + call((a), \ + (b) +module; + +//--- leading-line-splice.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-line-splice.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK11 +// CHECK11: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +\ +module; + +//--- leading-line-splice-trailing-whitespace.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-line-splice-trailing-whitespace.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK12 +// CHECK12: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// v This backslash has trailing whitespace. + \ +export module A; + +//--- comment-line-splice.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/comment-line-splice.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK13 +// CHECK13-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// My comment continues next-line!\ +import A; + +//--- comment-line-splice-trailing-whitespace.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/comment-line-splice-trailing-whitespace.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK14 +// CHECK14-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// My comment continues next-line! This backslash has trailing whitespace. -> \ +module; + +//--- line-splice-in-directive1.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/line-splice-in-directive1.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK15 +// CHECK15: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] + +module\ +; + +//--- line-splice-in-directive2.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/line-splice-in-directive2.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK16 +// CHECK16: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] + +export\ + module\ + A; + +//--- no-module-usage1.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver -ccc-print-phases \ +// RUN: %t/no-module-usage1.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK17 +// CHECK17-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +auto main() -> int {} + +//--- no-module-usage2.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver -ccc-print-phases \ +// RUN: %t/no-module-usage2.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK18 +// CHECK18-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +moduleStruct{}; + +//--- no-module-usage3.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver -ccc-print-phases \ +// RUN: %t/no-module-usage3.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK19 +// CHECK19-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +export_struct{}; + +//--- no-module-usage-namespace-import.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver -ccc-print-phases \ +// RUN: %t/no-module-usage-namespace-import.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK20 +// CHECK20-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +import::inner xi = {}; + +//--- no-module-usage-namespace-module.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver -ccc-print-phases \ +// RUN: %t/no-module-usage-namespace-module.cpp %t/empty.cpp 2>&1 | FileCheck %s --check-prefix=CHECK21 +// CHECK21-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +module::inner yi = {}; + +// RUN: not %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver -ccc-print-phases \ +// RUN: imaginary-file.cpp %t/empty.cpp 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-NON-EXISTING-FILE-ERR +// CHECK-NON-EXISTING-FILE-ERR: clang: error: no such file or directory: 'imaginary-file.cpp' diff --git a/clang/test/Frontend/warning-options.cpp b/clang/test/Frontend/warning-options.cpp index 444733c8b7f36..21863cac2e788 100644 --- a/clang/test/Frontend/warning-options.cpp +++ b/clang/test/Frontend/warning-options.cpp @@ -3,6 +3,6 @@ // CHECK: unknown warning option '-Wmonkey' // CHECK: unknown warning option '-Wno-monkey' // CHECK: unknown warning option '-Wno-unused-command-line-arguments'; did you mean '-Wno-unused-command-line-argument'? -// CHECK: unknown warning option '-Wmodule-build'; did you mean '-Wmodule-conflict'? +// CHECK: unknown warning option '-Wmodule-build'; did you mean '-Wmodules-driver'? // CHECK-NEXT: unknown -Werror warning specifier: '-Werror-vla' // CHECK: unknown remark option '-Rmodule-built'; did you mean '-Rmodule-build'?