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 @@ -806,18 +806,18 @@ def Qunused_arguments : Flag<["-"], "Qunused-arguments">, Flags<[NoXarchOption, CoreOption]>, HelpText<"Don't emit warning for unused driver arguments">; def Q : Flag<["-"], "Q">, IgnoredGCCCompat; -def Rpass_EQ : Joined<["-"], "Rpass=">, Group, Flags<[CC1Option]>, +def Rpass_EQ : Joined<["-"], "Rpass=">, Group, Flags<[CC1Option, FlangOption, FC1Option]>, HelpText<"Report transformations performed by optimization passes whose " "name matches the given POSIX regular expression">; def Rpass_missed_EQ : Joined<["-"], "Rpass-missed=">, Group, - Flags<[CC1Option]>, + Flags<[CC1Option, FlangOption, FC1Option]>, HelpText<"Report missed transformations by optimization passes whose " "name matches the given POSIX regular expression">; def Rpass_analysis_EQ : Joined<["-"], "Rpass-analysis=">, Group, - Flags<[CC1Option]>, + Flags<[CC1Option, FlangOption, FC1Option]>, HelpText<"Report transformation analysis from optimization passes whose " "name matches the given POSIX regular expression">; -def R_Joined : Joined<["-"], "R">, Group, Flags<[CC1Option, CoreOption]>, +def R_Joined : Joined<["-"], "R">, Group, Flags<[CC1Option, CoreOption, FlangOption, FC1Option]>, MetaVarName<"">, HelpText<"Enable the specified remark">; def S : Flag<["-"], "S">, Flags<[NoXarchOption,CC1Option,FlangOption,FC1Option]>, Group, HelpText<"Only run preprocess and compilation steps">; diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp --- a/clang/lib/Driver/ToolChains/Flang.cpp +++ b/clang/lib/Driver/ToolChains/Flang.cpp @@ -387,6 +387,27 @@ CmdArgs.push_back("-freciprocal-math"); } +static void addDiagnosticArgs(const ArgList &Args, OptSpecifier Group, + OptSpecifier GroupWithValue, + std::vector &Diagnostics) { + for (auto *A : Args.filtered(Group)) { + if (A->getOption().getKind() == Option::FlagClass) { + // The argument is a pure flag (such as OPT_Wall or OPT_Wdeprecated). Add + // its name (minus the "W" or "R" at the beginning) to the diagnostics. + Diagnostics.push_back( + std::string(A->getOption().getName().drop_front(1))); + } else if (A->getOption().matches(GroupWithValue)) { + // This is -Wfoo= or -Rfoo=, where foo is the name of the diagnostic + // group. Add only the group name to the diagnostics. + Diagnostics.push_back( + std::string(A->getOption().getName().drop_front(1).rtrim("=-"))); + } else { + // Otherwise, add its value (for OPT_W_Joined and similar). + Diagnostics.push_back(A->getValue()); + } + } +} + void Flang::ConstructJob(Compilation &C, const JobAction &JA, const InputInfo &Output, const InputInfoList &Inputs, const ArgList &Args, const char *LinkingOutput) const { @@ -471,6 +492,12 @@ // Add Codegen options addCodegenOptions(Args, CmdArgs); + // -rpass flags + Args.AddAllArgs(CmdArgs, options::OPT_R_Group); + + addDiagnosticArgs(Args, options::OPT_R_Group, options::OPT_R_value_Group, + Diags.getDiagnosticOptions().Remarks); + // Add other compile options addOtherOptions(Args, CmdArgs); diff --git a/flang/include/flang/Frontend/CodeGenOptions.h b/flang/include/flang/Frontend/CodeGenOptions.h --- a/flang/include/flang/Frontend/CodeGenOptions.h +++ b/flang/include/flang/Frontend/CodeGenOptions.h @@ -58,6 +58,53 @@ /// The directory where temp files are stored if specified by -save-temps std::optional SaveTempsDir; + enum RemarkKind { + RK_Missing, // Remark argument not present on the command line. + RK_Enabled, // Remark enabled via '-Rgroup'. + RK_EnabledEverything, // Remark enabled via '-Reverything'. + RK_Disabled, // Remark disabled via '-Rno-group'. + RK_DisabledEverything, // Remark disabled via '-Rno-everything'. + RK_WithPattern, // Remark pattern specified via '-Rgroup=regexp'. + }; + + /// Optimization remark with an optional regular expression pattern. + struct OptRemark { + RemarkKind Kind = RK_Missing; + std::string Pattern; + std::shared_ptr Regex; + + /// By default, optimization remark is missing. + OptRemark() = default; + + /// Returns true iff the optimization remark holds a valid regular + /// expression. + bool hasValidPattern() const { return Regex != nullptr; } + + /// Matches the given string against the regex, if there is some. + bool patternMatches(llvm::StringRef String) const { + return hasValidPattern() && Regex->match(String); + } + }; + + /// Selected optimizations for which we should enable optimization remarks. + /// Transformation passes whose name matches the contained (optional) regular + /// expression (and support this feature), will emit a diagnostic whenever + /// they perform a transformation. + OptRemark OptimizationRemark; + + /// Selected optimizations for which we should enable missed optimization + /// remarks. Transformation passes whose name matches the contained (optional) + /// regular expression (and support this feature), will emit a diagnostic + /// whenever they tried but failed to perform a transformation. + OptRemark OptimizationRemarkMissed; + + /// Selected optimizations for which we should enable optimization analyses. + /// Transformation passes whose name matches the contained (optional) regular + /// expression (and support this feature), will emit a diagnostic whenever + /// they want to explain why they decided to apply or not apply a given + /// transformation. + OptRemark OptimizationRemarkAnalysis; + // Define accessors/mutators for code generation options of enumeration type. #define CODEGENOPT(Name, Bits, Default) #define ENUM_CODEGENOPT(Name, Type, Bits, Default) \ diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -153,6 +153,63 @@ return true; } +/// Parse a remark command line argument. It may be missing, disabled/enabled by +/// '-R[no-]group' or specified with a regular expression by '-Rgroup=regexp'. +/// On top of that, it can be disabled/enabled globally by '-R[no-]everything'. +static CodeGenOptions::OptRemark +parseOptimizationRemark(clang::DiagnosticsEngine &Diags, + llvm::opt::ArgList &Args, llvm::opt::OptSpecifier OptEQ, + llvm::StringRef Name) { + CodeGenOptions::OptRemark Result; + + auto InitializeResultPattern = [&Diags, &Args, + &Result](const llvm::opt::Arg *A, + llvm::StringRef Pattern) { + Result.Pattern = Pattern.str(); + + std::string RegexError; + Result.Regex = std::make_shared(Result.Pattern); + if (!Result.Regex->isValid(RegexError)) { + Diags.Report(clang::diag::err_drv_optimization_remark_pattern) + << RegexError << A->getAsString(Args); + return false; + } + + return true; + }; + + for (llvm::opt::Arg *A : Args) { + if (A->getOption().matches(clang::driver::options::OPT_R_Joined)) { + llvm::StringRef Value = A->getValue(); + + if (Value == Name) + Result.Kind = CodeGenOptions::RK_Enabled; + else if (Value == "everything") + Result.Kind = CodeGenOptions::RK_EnabledEverything; + else if (Value.split('-') == std::make_pair(llvm::StringRef("no"), Name)) + Result.Kind = CodeGenOptions::RK_Disabled; + else if (Value == "no-everything") + Result.Kind = CodeGenOptions::RK_DisabledEverything; + else + continue; + + if (Result.Kind == CodeGenOptions::RK_Disabled || + Result.Kind == CodeGenOptions::RK_DisabledEverything) { + Result.Pattern = ""; + Result.Regex = nullptr; + } else { + InitializeResultPattern(A, ".*"); + } + } else if (A->getOption().matches(OptEQ)) { + Result.Kind = CodeGenOptions::RK_WithPattern; + if (!InitializeResultPattern(A, A->getValue())) + return CodeGenOptions::OptRemark(); + } + } + + return Result; +} + static void parseCodeGenArgs(Fortran::frontend::CodeGenOptions &opts, llvm::opt::ArgList &args, clang::DiagnosticsEngine &diags) { @@ -190,6 +247,16 @@ opts.PrepareForThinLTO = true; } + opts.OptimizationRemark = parseOptimizationRemark( + diags, args, clang::driver::options::OPT_Rpass_EQ, "pass"); + + opts.OptimizationRemarkMissed = parseOptimizationRemark( + diags, args, clang::driver::options::OPT_Rpass_missed_EQ, "pass-missed"); + + opts.OptimizationRemarkAnalysis = parseOptimizationRemark( + diags, args, clang::driver::options::OPT_Rpass_analysis_EQ, + "pass-analysis"); + if (auto *a = args.getLastArg(clang::driver::options::OPT_save_temps_EQ)) opts.SaveTempsDir = a->getValue(); diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -42,11 +42,15 @@ #include "mlir/Target/LLVMIR/ModuleTranslation.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Driver/DriverDiagnostic.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/Bitcode/BitcodeWriterPass.h" +#include "llvm/IR/DiagnosticHandler.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/LLVMRemarkStreamer.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/IRReader/IRReader.h" @@ -55,6 +59,7 @@ #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -64,6 +69,7 @@ #include "llvm/TargetParser/TargetParser.h" #include "llvm/Transforms/Utils/ModuleUtils.h" #include +#include #include using namespace Fortran::frontend; @@ -920,6 +926,100 @@ mpm.run(*llvmModule, mam); } +class StandaloneBackendConsumer : public llvm::DiagnosticHandler { + + const CodeGenOptions &CodeGenOpts; + clang::DiagnosticsEngine &Diags; + +public: + StandaloneBackendConsumer(clang::DiagnosticsEngine &Diags, + const CodeGenOptions &CodeGenOpts) + : CodeGenOpts(CodeGenOpts), Diags(Diags) {} + + bool isAnalysisRemarkEnabled(llvm::StringRef PassName) const override { + return CodeGenOpts.OptimizationRemarkAnalysis.patternMatches(PassName); + } + bool isMissedOptRemarkEnabled(llvm::StringRef PassName) const override { + return CodeGenOpts.OptimizationRemarkMissed.patternMatches(PassName); + } + bool isPassedOptRemarkEnabled(llvm::StringRef PassName) const override { + return CodeGenOpts.OptimizationRemark.patternMatches(PassName); + } + + bool isAnyRemarkEnabled() const override { + return CodeGenOpts.OptimizationRemarkAnalysis.hasValidPattern() || + CodeGenOpts.OptimizationRemarkMissed.hasValidPattern() || + CodeGenOpts.OptimizationRemark.hasValidPattern(); + } + + void EmitOptimizationMessage(const llvm::DiagnosticInfoOptimizationBase &D, + unsigned DiagID) { + // We only support warnings and remarks. + assert(D.getSeverity() == llvm::DS_Remark || + D.getSeverity() == llvm::DS_Warning); + + clang::FullSourceLoc Loc; + + // Render message. + std::string Msg; + llvm::raw_string_ostream MsgStream(Msg); + MsgStream << D.getMsg(); + + // Emit message. + Diags.Report(Loc, DiagID) + << clang::AddFlagValue(D.getPassName()) << MsgStream.str(); + } + + void + OptimizationRemarkHandler(const llvm::DiagnosticInfoOptimizationBase &D) { + if (D.isPassed()) { + // Optimization remarks are active only if the -Rpass flag has a regular + // expression that matches the name of the pass name in \p D. + if (CodeGenOpts.OptimizationRemark.patternMatches(D.getPassName())) + EmitOptimizationMessage( + D, clang::diag::remark_fe_backend_optimization_remark); + + } else if (D.isMissed()) { + // Missed optimization remarks are active only if the -Rpass-missed + // flag has a regular expression that matches the name of the pass + // name in \p D. + if (CodeGenOpts.OptimizationRemarkMissed.patternMatches(D.getPassName())) + EmitOptimizationMessage( + D, clang::diag::remark_fe_backend_optimization_remark_missed); + } else { + assert(D.isAnalysis() && "Unknown remark type"); + + bool ShouldAlwaysPrint = false; + if (auto *ORA = llvm::dyn_cast(&D)) + ShouldAlwaysPrint = ORA->shouldAlwaysPrint(); + + if (ShouldAlwaysPrint || + CodeGenOpts.OptimizationRemarkAnalysis.patternMatches( + D.getPassName())) + EmitOptimizationMessage( + D, clang::diag::remark_fe_backend_optimization_remark_analysis); + } + } + + bool handleDiagnostics(const llvm::DiagnosticInfo &DI) override { + switch (DI.getKind()) { + case llvm::DK_OptimizationRemark: + OptimizationRemarkHandler(llvm::cast(DI)); + break; + case llvm::DK_OptimizationRemarkMissed: + OptimizationRemarkHandler(llvm::cast(DI)); + break; + case llvm::DK_OptimizationRemarkAnalysis: + OptimizationRemarkHandler( + llvm::cast(DI)); + break; + default: + break; + } + return true; + } +}; + void CodeGenAction::embedOffloadObjects() { CompilerInstance &ci = this->getInstance(); const auto &cgOpts = ci.getInvocation().getCodeGenOpts(); @@ -942,6 +1042,11 @@ void CodeGenAction::executeAction() { CompilerInstance &ci = this->getInstance(); + clang::DiagnosticsEngine &diags = ci.getDiagnostics(); + const CodeGenOptions &codeGenOpts = ci.getInvocation().getCodeGenOpts(); + Fortran::lower::LoweringOptions &loweringOpts = + ci.getInvocation().getLoweringOpts(); + // If the output stream is a file, generate it and define the corresponding // output stream. If a pre-defined output stream is available, we will use // that instead. @@ -959,15 +1064,15 @@ os = getOutputStream(ci, getCurrentFileOrBufferName(), action); if (!os) { - unsigned diagID = ci.getDiagnostics().getCustomDiagID( + unsigned diagID = diags.getCustomDiagID( clang::DiagnosticsEngine::Error, "failed to create the output file"); - ci.getDiagnostics().Report(diagID); + diags.Report(diagID); return; } } if (action == BackendActionTy::Backend_EmitFIR) { - if (ci.getInvocation().getLoweringOpts().getLowerToHighLevelFIR()) { + if (loweringOpts.getLowerToHighLevelFIR()) { lowerHLFIRToFIR(); } mlirModule->print(ci.isOutputStreamNull() ? *os : ci.getOutputStream()); @@ -975,7 +1080,7 @@ } if (action == BackendActionTy::Backend_EmitHLFIR) { - assert(ci.getInvocation().getLoweringOpts().getLowerToHighLevelFIR() && + assert(loweringOpts.getLowerToHighLevelFIR() && "Lowering must have been configured to emit HLFIR"); mlirModule->print(ci.isOutputStreamNull() ? *os : ci.getOutputStream()); return; @@ -994,8 +1099,7 @@ const std::string &theTriple = tm->getTargetTriple().str(); if (llvmModule->getTargetTriple() != theTriple) { - ci.getDiagnostics().Report(clang::diag::warn_fe_override_module) - << theTriple; + diags.Report(clang::diag::warn_fe_override_module) << theTriple; } // Always set the triple and data layout, to make sure they match and are set. @@ -1005,9 +1109,14 @@ llvmModule->setDataLayout(tm->createDataLayout()); // Embed offload objects specified with -fembed-offload-object - if (!ci.getInvocation().getCodeGenOpts().OffloadObjects.empty()) + if (!codeGenOpts.OffloadObjects.empty()) embedOffloadObjects(); + StandaloneBackendConsumer M(diags, codeGenOpts); + + llvmModule->getContext().setDiagnosticHandler( + std::make_unique(M)); + // Run LLVM's middle-end (i.e. the optimizer). runOptimizationPipeline(ci.isOutputStreamNull() ? *os : ci.getOutputStream()); @@ -1026,7 +1135,7 @@ if (action == BackendActionTy::Backend_EmitAssembly || action == BackendActionTy::Backend_EmitObj) { generateMachineCodeOrAssemblyImpl( - ci.getDiagnostics(), *tm, action, *llvmModule, + diags, *tm, action, *llvmModule, ci.isOutputStreamNull() ? *os : ci.getOutputStream()); return; } diff --git a/flang/test/Driver/driver-help-hidden.f90 b/flang/test/Driver/driver-help-hidden.f90 --- a/flang/test/Driver/driver-help-hidden.f90 +++ b/flang/test/Driver/driver-help-hidden.f90 @@ -83,6 +83,10 @@ ! CHECK-NEXT: -print-effective-triple Print the effective target triple ! CHECK-NEXT: -print-target-triple Print the normalized target triple ! CHECK-NEXT: -P Disable linemarker output in -E mode +! CHECK-NEXT: -Rpass-analysis= Report transformation analysis from optimization passes whose name matches the given POSIX regular expression +! CHECK-NEXT: -Rpass-missed= Report missed transformations by optimization passes whose name matches the given POSIX regular expression +! CHECK-NEXT: -Rpass= Report transformations performed by optimization passes whose name matches the given POSIX regular expression +! CHECK-NEXT: -R Enable the specified remark ! CHECK-NEXT: -save-temps= Save intermediate compilation results. ! CHECK-NEXT: -save-temps Save intermediate compilation results ! CHECK-NEXT: -std= Language standard to compile for diff --git a/flang/test/Driver/driver-help.f90 b/flang/test/Driver/driver-help.f90 --- a/flang/test/Driver/driver-help.f90 +++ b/flang/test/Driver/driver-help.f90 @@ -79,6 +79,10 @@ ! HELP-NEXT: -print-effective-triple Print the effective target triple ! HELP-NEXT: -print-target-triple Print the normalized target triple ! HELP-NEXT: -P Disable linemarker output in -E mode +! HELP-NEXT: -Rpass-analysis= Report transformation analysis from optimization passes whose name matches the given POSIX regular expression +! HELP-NEXT: -Rpass-missed= Report missed transformations by optimization passes whose name matches the given POSIX regular expression +! HELP-NEXT: -Rpass= Report transformations performed by optimization passes whose name matches the given POSIX regular expression +! HELP-NEXT: -R Enable the specified remark ! HELP-NEXT: -save-temps= Save intermediate compilation results. ! HELP-NEXT: -save-temps Save intermediate compilation results ! HELP-NEXT: -std= Language standard to compile for @@ -192,6 +196,10 @@ ! HELP-FC1-NEXT: -pic-level Value for __PIC__ ! HELP-FC1-NEXT: -plugin Use the named plugin action instead of the default action (use "help" to list available options) ! HELP-FC1-NEXT: -P Disable linemarker output in -E mode +! HELP-FC1-NEXT: -Rpass-analysis= Report transformation analysis from optimization passes whose name matches the given POSIX regular expression +! HELP-FC1-NEXT: -Rpass-missed= Report missed transformations by optimization passes whose name matches the given POSIX regular expression +! HELP-FC1-NEXT: -Rpass= Report transformations performed by optimization passes whose name matches the given POSIX regular expression +! HELP-FC1-NEXT: -R Enable the specified remark ! HELP-FC1-NEXT: -save-temps= Save intermediate compilation results. ! HELP-FC1-NEXT: -save-temps Save intermediate compilation results ! HELP-FC1-NEXT: -std= Language standard to compile for