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 @@ -69,6 +69,53 @@ /// The format used for serializing remarks (default: YAML) std::string OptRecordFormat; + enum class 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 = RemarkKind::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::RemarkKind::RK_Enabled; + else if (value == "everything") + result.Kind = CodeGenOptions::RemarkKind::RK_EnabledEverything; + else if (value.split('-') == std::make_pair(llvm::StringRef("no"), name)) + result.Kind = CodeGenOptions::RemarkKind::RK_Disabled; + else if (value == "no-everything") + result.Kind = CodeGenOptions::RemarkKind::RK_DisabledEverything; + else + continue; + + if (result.Kind == CodeGenOptions::RemarkKind::RK_Disabled || + result.Kind == CodeGenOptions::RemarkKind::RK_DisabledEverything) { + result.Pattern = ""; + result.Regex = nullptr; + } else { + initializeResultPattern(a, ".*"); + } + } else if (a->getOption().matches(optEq)) { + result.Kind = CodeGenOptions::RemarkKind::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) { @@ -194,13 +251,42 @@ args.getLastArg(clang::driver::options::OPT_opt_record_file)) opts.OptRecordFile = a->getValue(); - if (const llvm::opt::Arg *a = - args.getLastArg(clang::driver::options::OPT_opt_record_format)) - opts.OptRecordFormat = a->getValue(); + bool needLocTracking = false; + + if (!opts.OptRecordFile.empty()) + needLocTracking = true; if (const llvm::opt::Arg *a = - args.getLastArg(clang::driver::options::OPT_opt_record_passes)) + args.getLastArg(clang::driver::options::OPT_opt_record_passes)) { opts.OptRecordPasses = a->getValue(); + needLocTracking = true; + } + + if (const llvm::opt::Arg *a = + args.getLastArg(clang::driver::options::OPT_opt_record_format)) { + opts.OptRecordFormat = a->getValue(); + needLocTracking = 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"); + + needLocTracking |= opts.OptimizationRemark.hasValidPattern() || + opts.OptimizationRemarkMissed.hasValidPattern() || + opts.OptimizationRemarkAnalysis.hasValidPattern(); + + // If the user requested a flag that requires source locations available in + // the backend, make sure that the backend tracks source location information. + if (needLocTracking && + opts.getDebugInfo() == llvm::codegenoptions::NoDebugInfo) + opts.setDebugInfo(llvm::codegenoptions::LocTrackingOnly); if (auto *a = args.getLastArg(clang::driver::options::OPT_save_temps_EQ)) opts.SaveTempsDir = a->getValue(); @@ -697,6 +783,7 @@ // Default to off for `flang-new -fc1`. res.getFrontendOpts().showColors = parseShowColorsArgs(args, /*defaultDiagColor=*/false); + res.getDiagnosticOpts().ShowColors = res.getFrontendOpts().showColors; return diags.getNumErrors() == numErrorsBefore; } @@ -903,6 +990,28 @@ return true; } +static void addDiagnosticArgs(const llvm::opt::ArgList &args, + llvm::opt::OptSpecifier group, + llvm::opt::OptSpecifier groupWithValue, + std::vector &diagnostics) { + for (auto *a : args.filtered(group)) { + if (a->getOption().getKind() == llvm::opt::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()); + } + } +} + bool CompilerInvocation::createFromArgs( CompilerInvocation &res, llvm::ArrayRef commandLineArgs, clang::DiagnosticsEngine &diags, const char *argv0) { @@ -959,6 +1068,10 @@ res.loweringOpts.setNoPPCNativeVecElemOrder(true); } + addDiagnosticArgs(args, clang::driver::options::OPT_R_Group, + clang::driver::options::OPT_R_value_Group, + res.getDiagnosticOpts().Remarks); + success &= parseFrontendArgs(res.getFrontendOpts(), args, diags); parseTargetArgs(res.getTargetOpts(), args); parsePreprocessorArgs(res.getPreprocessorOpts(), args); 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 @@ -48,6 +48,7 @@ #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/Bitcode/BitcodeWriterPass.h" +#include "llvm/IR/DiagnosticPrinter.h" #include "llvm/IR/LLVMRemarkStreamer.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/Verifier.h" @@ -919,6 +920,107 @@ 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); + + std::string msg; + llvm::raw_string_ostream msgStream(msg); + + if (d.isLocationAvailable()) { + // Since sourceMgr isn't available, send file name and absolute path + // through msgStream, to use for printing + msgStream << d.getLocationStr() << ";;" << d.getAbsolutePath() << ";;"; + msgStream << d.getMsg(); + } else { + msgStream << d.getMsg(); + llvm::DiagnosticPrinterRawOStream dp(msgStream); + d.print(dp); + } + + // Emit message. + diags.Report(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(); @@ -1029,6 +1131,11 @@ if (!codeGenOpts.OffloadObjects.empty()) embedOffloadObjects(); + StandaloneBackendConsumer m(diags, codeGenOpts); + + llvmModule->getContext().setDiagnosticHandler( + std::make_unique(m)); + // write optimization-record llvm::Expected> optRecordFileOrErr = setupLLVMOptimizationRemarks( diff --git a/flang/lib/Frontend/TextDiagnosticPrinter.cpp b/flang/lib/Frontend/TextDiagnosticPrinter.cpp --- a/flang/lib/Frontend/TextDiagnosticPrinter.cpp +++ b/flang/lib/Frontend/TextDiagnosticPrinter.cpp @@ -20,6 +20,10 @@ #include "llvm/ADT/SmallString.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include using namespace Fortran::frontend; @@ -29,6 +33,28 @@ TextDiagnosticPrinter::~TextDiagnosticPrinter() {} +// Print any diagnostic option information to a raw_ostream. +static void printDiagnosticOptions(llvm::raw_ostream &os, + clang::DiagnosticsEngine::Level level, + const clang::Diagnostic &info, + const clang::DiagnosticOptions &diagOpts) { + bool started = false; + if (diagOpts.ShowOptionNames) { + llvm::StringRef opt = + clang::DiagnosticIDs::getWarningOptionForDiag(info.getID()); + if (!opt.empty()) { + os << (started ? "," : " [") + << (level == clang::DiagnosticsEngine::Remark ? "-R" : "-W") << opt; + llvm::StringRef optValue = info.getDiags()->getFlagValue(); + if (!optValue.empty()) + os << "=" << optValue; + started = true; + } + } + if (started) + os << ']'; +} + void TextDiagnosticPrinter::HandleDiagnostic( clang::DiagnosticsEngine::Level level, const clang::Diagnostic &info) { // Default implementation (Warnings/errors count). @@ -40,6 +66,7 @@ info.FormatDiagnostic(outStr); llvm::raw_svector_ostream diagMessageStream(outStr); + printDiagnosticOptions(diagMessageStream, level, info, *diagOpts); if (!prefix.empty()) os << prefix << ": "; @@ -48,12 +75,46 @@ assert(!info.getLocation().isValid() && "Diagnostics with valid source location are not supported"); + // split incoming string to get the absolute path and filename in the + // case we are receiving optimization remarks from StandaloneBackendConsumer + std::string s = std::string(diagMessageStream.str()); + std::string delimiter = ";;"; + + size_t pos = 0; + std::vector tokens; + while ((pos = s.find(delimiter)) != std::string::npos) { + tokens.push_back(s.substr(0, pos)); + s.erase(0, pos + delimiter.length()); + } + + // tokens will always be of size 2 in the case of optimization + // remark message received, in this format; + // [file location with line and column];;[path to file];;[the remark message] + if (tokens.size() == 2) { + // extract absolute path from the provided relative path + std::filesystem::path absPath(tokens[1]); + std::filesystem::path canonicalPath = + std::filesystem::weakly_canonical(absPath); + + // we don't need the filename since we will append tokens[0] + // which is the filename, line and column number + canonicalPath.remove_filename(); + absPath = canonicalPath.make_preferred().string(); + + // used for changing only the bold attribute + if (diagOpts->ShowColors) + os.changeColor(llvm::raw_ostream::SAVEDCOLOR, true); + + // print absolute path, file name, line and column + os << absPath << tokens[0] << ": "; + } + Fortran::frontend::TextDiagnostic::printDiagnosticLevel(os, level, diagOpts->ShowColors); Fortran::frontend::TextDiagnostic::printDiagnosticMessage( os, - /*IsSupplemental=*/level == clang::DiagnosticsEngine::Note, - diagMessageStream.str(), diagOpts->ShowColors); + /*IsSupplemental=*/level == clang::DiagnosticsEngine::Note, s, + diagOpts->ShowColors); os.flush(); } diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp --- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -165,6 +165,10 @@ // Honor color diagnostics. flang->getDiagnosticOpts().ShowColors = flang->getFrontendOpts().showColors; + flang->getDiagnosticOpts().ShowOptionNames = 1; + + clang::ProcessWarningOptions(flang->getDiagnostics(), + flang->getDiagnosticOpts()); // Create and execute the frontend action. std::unique_ptr act(createFrontendAction(*flang)); diff --git a/flang/test/Driver/optimization-remark.f90 b/flang/test/Driver/optimization-remark.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Driver/optimization-remark.f90 @@ -0,0 +1,59 @@ +! This file tests the -Rpass family of flags (-Rpass, -Rpass-missed +! and -Rpass-analysis) +! loop-delete isn't enabled at O0 so we use at least O1 + +! Check that we can override -Rpass= with -Rno-pass. +! RUN: %flang_fc1 %s -O1 -Rpass=loop-delete -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS +! RUN: %flang_fc1 %s -O1 -Rpass=loop-delete -Rno-pass -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-NO-REMARKS +! RUN: %flang_fc1 %s -O1 -Rpass=loop-delete -Rno-everything -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-NO-REMARKS +! RUN: %flang_fc1 %s -O1 -Rpass=loop-delete -Rno-everything -Reverything -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS + +! -Reverything implies -Rpass=.*. +! RUN: %flang_fc1 %s -O1 -Reverything -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS + +! -Rpass implies -Rpass=.* +! RUN: %flang_fc1 %s -O1 -Rpass -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS + +! Check error on bad regex +! RUN: not %flang %s -O1 -Rpass=[ 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS-ERROR + +! Check "unknown remark option" warning +! RUN: %flang %s -O1 -R 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS-WARN + +! Check "unknown remark option" warning with suggestion +! RUN: %flang %s -O1 -Rpas 2>&1 | FileCheck %s --check-prefix=CHECK-WARN-SUGGEST + +! Check full -Rpass message is emitted +! RUN: %flang %s -O1 -Rpass=loop-delete 2>&1 | FileCheck %s + +! Check full -Rpass-missed message is emitted +! RUN: %flang %s -O1 -Rpass-missed=loop-vectorize 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS-MISSED + +! Check full -Rpass-analysis message is emitted +! RUN: %flang %s -O1 -Rpass-analysis=loop-vectorize 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS-ANALYSIS + +! CHECK: remark: Loop deleted because it is invariant +! CHECK-REMARKS-MISSED: remark: loop not vectorized +! CHECK-REMARKS-ANALYSIS: remark: loop not vectorized: call instruction cannot be vectorized +! CHECK-REMARKS: remark: +! CHECK-NO-REMARKS-NOT: remark: + +! CHECK-REMARKS-ERROR: error: in pattern '-Rpass=[': brackets ([ ]) not balanced +! CHECK-REMARKS-WARN: warning: unknown remark option '-R' [-Wunknown-warning-option] +! CHECK-WARN-SUGGEST: warning: unknown remark option '-Rpas'; did you mean '-Rpass'? [-Wunknown-warning-option] + +program forttest + implicit none + real, dimension(1:50) :: aR1 + integer :: n + + do n = 1,50 + aR1(n) = n * 1.34 + print *, "hello" + end do + + do n = 1,50 + aR1(n) = n * 1.34 + end do + +end program forttest