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,58 @@ /// The format used for serializing remarks (default: YAML) std::string OptRecordFormat; + // The RemarkKind enum class and OptRemark struct are identical to what Clang + // has + // TODO: Share with clang instead of re-implementing here + 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); + } + }; + + // The OptRemark fields provided here are identical to Clang. + + /// 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,36 @@ return true; } +static CodeGenOptions::OptRemark +parseOptimizationRemark(clang::DiagnosticsEngine &diags, + llvm::opt::ArgList &args, llvm::opt::OptSpecifier optEq, + llvm::StringRef name) { + CodeGenOptions::OptRemark result; + + 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.split('-') == std::make_pair(llvm::StringRef("no"), name)) + result.Kind = CodeGenOptions::RemarkKind::RK_Disabled; + else + continue; + + if (result.Kind == CodeGenOptions::RemarkKind::RK_Disabled) { + result.Pattern = ""; + result.Regex = nullptr; + } else { + result.Pattern = ".*"; + result.Regex = std::make_shared(result.Pattern); + } + } + } + + return result; +} + static void parseCodeGenArgs(Fortran::frontend::CodeGenOptions &opts, llvm::opt::ArgList &args, clang::DiagnosticsEngine &diags) { @@ -194,13 +224,45 @@ args.getLastArg(clang::driver::options::OPT_opt_record_file)) opts.OptRecordFile = a->getValue(); + // Specifies what passes to include. If not provided + // -fsave-optimization-record will include all passes. + if (const llvm::opt::Arg *a = + args.getLastArg(clang::driver::options::OPT_opt_record_passes)) + opts.OptRecordPasses = a->getValue(); + + // Optimization file format. Defaults to yaml if (const llvm::opt::Arg *a = args.getLastArg(clang::driver::options::OPT_opt_record_format)) opts.OptRecordFormat = a->getValue(); - if (const llvm::opt::Arg *a = - args.getLastArg(clang::driver::options::OPT_opt_record_passes)) - opts.OptRecordPasses = a->getValue(); + // Specify which successful passes should be reported using a regex. + opts.OptimizationRemark = parseOptimizationRemark( + diags, args, clang::driver::options::OPT_Rpass_EQ, "pass"); + + // Specify which missed passes should be reported using a regex. + opts.OptimizationRemarkMissed = parseOptimizationRemark( + diags, args, clang::driver::options::OPT_Rpass_missed_EQ, "pass-missed"); + + // Specify which passes, with additional information, + // should be reported using a regex. + opts.OptimizationRemarkAnalysis = parseOptimizationRemark( + diags, args, clang::driver::options::OPT_Rpass_analysis_EQ, + "pass-analysis"); + + if (opts.getDebugInfo() == llvm::codegenoptions::NoDebugInfo) { + // If the user requested a flag that requires source locations available in + // the backend, make sure that the backend tracks source location + // information. + bool needLocTracking = !opts.OptRecordFile.empty() || + !opts.OptRecordPasses.empty() || + !opts.OptRecordFormat.empty() || + opts.OptimizationRemark.hasValidPattern() || + opts.OptimizationRemarkMissed.hasValidPattern() || + opts.OptimizationRemarkAnalysis.hasValidPattern(); + + if (needLocTracking) + opts.setDebugInfo(llvm::codegenoptions::LocTrackingOnly); + } if (auto *a = args.getLastArg(clang::driver::options::OPT_save_temps_EQ)) opts.SaveTempsDir = a->getValue(); @@ -959,6 +1021,13 @@ res.loweringOpts.setNoPPCNativeVecElemOrder(true); } + // Add the remark option requested i.e. pass, pass-missed or pass-analysis. + // This will be used later during processing warnings and remarks to give + // messages specific to a remark argument. That happens in + // processWarningOptions in ExecuteCompilerInvocation.cpp + for (auto *a : args.filtered(clang::driver::options::OPT_R_Group)) + res.getDiagnosticOpts().Remarks.push_back(a->getValue()); + 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,116 @@ mpm.run(*llvmModule, mam); } +// This class handles optimization remark messages requested if +// any of -Rpass, -Rpass-analysis or -Rpass-missed flags were provided +class BackendRemarkConsumer : public llvm::DiagnosticHandler { + + const CodeGenOptions &codeGenOpts; + clang::DiagnosticsEngine &diags; + +public: + BackendRemarkConsumer(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) { + auto passName = d.getPassName(); + if (d.isPassed() && + codeGenOpts.OptimizationRemark.patternMatches(passName)) { + // Optimization remarks are active only if the -Rpass flag has a regular + // expression that matches the name of the pass name in \p d. + emitOptimizationMessage( + d, clang::diag::remark_fe_backend_optimization_remark); + + return; + } + + if (d.isMissed() && + codeGenOpts.OptimizationRemarkMissed.patternMatches(passName)) { + // 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. + emitOptimizationMessage( + d, clang::diag::remark_fe_backend_optimization_remark_missed); + + return; + } + + assert(d.isAnalysis() && "Unknown remark type"); + + auto *ora = llvm::dyn_cast(&d); + if (!ora) + return; + + bool shouldAlwaysPrint = ora->shouldAlwaysPrint(); + + if (shouldAlwaysPrint || + codeGenOpts.OptimizationRemarkAnalysis.patternMatches(passName)) + 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 +1140,11 @@ if (!codeGenOpts.OffloadObjects.empty()) embedOffloadObjects(); + BackendRemarkConsumer remarkConsumer(diags, codeGenOpts); + + llvmModule->getContext().setDiagnosticHandler( + std::make_unique(remarkConsumer)); + // 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,23 @@ 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) { + llvm::StringRef opt = + clang::DiagnosticIDs::getWarningOptionForDiag(info.getID()); + if (!opt.empty()) { + os << " [" << (level == clang::DiagnosticsEngine::Remark ? "-R" : "-W") + << opt; + llvm::StringRef optValue = info.getDiags()->getFlagValue(); + if (!optValue.empty()) + os << "=" << optValue; + os << ']'; + } +} + void TextDiagnosticPrinter::HandleDiagnostic( clang::DiagnosticsEngine::Level level, const clang::Diagnostic &info) { // Default implementation (Warnings/errors count). @@ -40,6 +61,7 @@ info.FormatDiagnostic(outStr); llvm::raw_svector_ostream diagMessageStream(outStr); + printDiagnosticOptions(diagMessageStream, level, info, *diagOpts); if (!prefix.empty()) os << prefix << ": "; @@ -48,12 +70,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 BackendRemarkConsumer + std::string diagMsg = std::string(diagMessageStream.str()); + std::string delimiter = ";;"; + + size_t pos = 0; + std::vector tokens; + while ((pos = diagMsg.find(delimiter)) != std::string::npos) { + tokens.push_back(diagMsg.substr(0, pos)); + diagMsg.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, diagMsg, + 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 @@ -27,6 +27,7 @@ #include "llvm/Option/Option.h" #include "llvm/Support/BuryPointer.h" #include "llvm/Support/CommandLine.h" +#include namespace Fortran::frontend { @@ -100,6 +101,61 @@ llvm_unreachable("Invalid program action!"); } +// Emit a warning and typo hint for unknown warning opts +static void emitUnknownDiagWarning(clang::DiagnosticsEngine &diags, + clang::diag::Flavor flavor, + llvm::StringRef prefix, + llvm::StringRef opt) { + llvm::StringRef suggestion = + clang::DiagnosticIDs::getNearestOption(flavor, opt); + diags.Report(clang::diag::warn_unknown_diag_option) + << (flavor == clang::diag::Flavor::WarningOrError ? 0 : 1) + << (prefix.str() += std::string(opt)) << !suggestion.empty() + << (prefix.str() += std::string(suggestion)); +} + +static void processWarningOptions(clang::DiagnosticsEngine &diags, + const clang::DiagnosticOptions &opts, + bool reportDiags = true) { + + llvm::SmallVector _diags; + const llvm::IntrusiveRefCntPtr diagIDs = + diags.getDiagnosticIDs(); + // We parse the warning options twice. The first pass sets diagnostic state, + // while the second pass reports warnings/errors. This has the effect that + // we follow the more canonical "last option wins" paradigm when there are + // conflicting options. + for (unsigned report = 0, reportEnd = 2; report != reportEnd; ++report) { + bool setDiagnostic = (report == 0); + + // If we've set the diagnostic state and are not reporting diagnostics then + // we're done. + if (!setDiagnostic && !reportDiags) + break; + + for (unsigned i = 0, e = opts.Remarks.size(); i != e; ++i) { + llvm::StringRef opt = opts.Remarks[i]; + const auto flavor = clang::diag::Flavor::Remark; + + // Check to see if this opt starts with "no-", if so, this is a + // negative form of the option. + bool isPositive = !opt.startswith("no-"); + if (!isPositive) + opt = opt.substr(3); + + if (report) { + if (diagIDs->getDiagnosticsInGroup(flavor, opt, _diags)) + emitUnknownDiagWarning(diags, flavor, isPositive ? "-R" : "-Rno-", + opt); + } else { + diags.setSeverityForGroup(flavor, opt, + isPositive ? clang::diag::Severity::Remark + : clang::diag::Severity::Ignored); + } + } + } +} + bool executeCompilerInvocation(CompilerInstance *flang) { // Honor -help. if (flang->getFrontendOpts().showHelp) { @@ -166,6 +222,8 @@ // Honor color diagnostics. flang->getDiagnosticOpts().ShowColors = flang->getFrontendOpts().showColors; + processWarningOptions(flang->getDiagnostics(), flang->getDiagnosticOpts()); + // Create and execute the frontend action. std::unique_ptr act(createFrontendAction(*flang)); if (!act) 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,52 @@ +! 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 -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS +! RUN: %flang_fc1 %s -O1 -Rpass -Rno-pass -emit-llvm -o - 2>&1 | FileCheck %s --check-prefix=CHECK-NO-REMARKS + +! 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 -Rno-pass, -Rno-pass-analysis, -Rno-pass-missed nothing emitted +! RUN: %flang %s -O1 -Rno-pass 2>&1 | FileCheck %s --allow-empty --check-prefix=CHECK-NO-REMARKS +! RUN: %flang %s -O1 -Rno-pass-missed 2>&1 | FileCheck %s --allow-empty --check-prefix=CHECK-NO-REMARKS +! RUN: %flang %s -O1 -Rno-pass-analysis 2>&1 | FileCheck %s --allow-empty --check-prefix=CHECK-NO-REMARKS + +! Check full -Rpass message is emitted +! RUN: %flang %s -O1 -Rpass 2>&1 | FileCheck %s + +! Check full -Rpass-missed message is emitted +! RUN: %flang %s -O1 -Rpass-missed 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS-MISSED + +! Check full -Rpass-analysis message is emitted +! RUN: %flang %s -O1 -Rpass-analysis 2>&1 | FileCheck %s --check-prefix=CHECK-REMARKS-ANALYSIS + +! CHECK: optimization-remark.f90:48:5: remark: Loop deleted because it is invariant [-Rpass=loop-delete] +! CHECK-REMARKS-MISSED: optimization-remark.f90:43:5: remark: loop not vectorized [-Rpass-missed=loop-vectorize] +! CHECK-REMARKS-ANALYSIS: optimization-remark.f90:43:5: remark: loop not vectorized: instruction cannot be vectorized [-Rpass-analysis=loop-vectorize] +! CHECK-REMARKS: remark: +! CHECK-NO-REMARKS-NOT: remark: + +! 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