diff --git a/mlir/include/mlir/IR/Remarks.h b/mlir/include/mlir/IR/Remarks.h index 26d65472f2b1c..20e84ec83cd01 100644 --- a/mlir/include/mlir/IR/Remarks.h +++ b/mlir/include/mlir/IR/Remarks.h @@ -29,7 +29,7 @@ namespace mlir::remark { /// Define an the set of categories to accept. By default none are, the provided /// regex matches against the category names for each kind of remark. struct RemarkCategories { - std::optional passed, missed, analysis, failed; + std::optional all, passed, missed, analysis, failed; }; /// Categories describe the outcome of an transformation, not the mechanics of diff --git a/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h b/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h index 94231227599c9..c3ac9d99c24bf 100644 --- a/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h +++ b/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h @@ -38,6 +38,12 @@ enum class VerbosityLevel { ErrorsWarningsAndRemarks }; +using RemarkFormat = enum { + REMARK_FORMAT_STDOUT, + REMARK_FORMAT_YAML, + REMARK_FORMAT_BITSTREAM, +}; + /// Configuration options for the mlir-opt tool. /// This is intended to help building tools like mlir-opt by collecting the /// supported options. @@ -221,15 +227,53 @@ class MlirOptMainConfig { } bool shouldVerifyRoundtrip() const { return verifyRoundtripFlag; } + /// Checks if any remark filters are set. + bool shouldEmitRemarks() const { + // Emit all remarks only when no filters are specified. + const bool hasFilters = + !getRemarksAllFilter().empty() || !getRemarksPassedFilter().empty() || + !getRemarksFailedFilter().empty() || + !getRemarksMissedFilter().empty() || !getRemarksAnalyseFilter().empty(); + return hasFilters; + } + /// Reproducer file generation (no crash required). StringRef getReproducerFilename() const { return generateReproducerFileFlag; } + /// Set the reproducer output filename + RemarkFormat getRemarkFormat() const { return remarkFormatFlag; } + /// Set the remark format to use. + std::string getRemarksAllFilter() const { return remarksAllFilterFlag; } + /// Set the remark output file. + std::string getRemarksOutputFile() const { return remarksOutputFileFlag; } + /// Set the remark passed filters. + std::string getRemarksPassedFilter() const { return remarksPassedFilterFlag; } + /// Set the remark failed filters. + std::string getRemarksFailedFilter() const { return remarksFailedFilterFlag; } + /// Set the remark missed filters. + std::string getRemarksMissedFilter() const { return remarksMissedFilterFlag; } + /// Set the remark analyse filters. + std::string getRemarksAnalyseFilter() const { + return remarksAnalyseFilterFlag; + } + protected: /// Allow operation with no registered dialects. /// This option is for convenience during testing only and discouraged in /// general. bool allowUnregisteredDialectsFlag = false; + /// Remark format + RemarkFormat remarkFormatFlag; + /// Remark file to output to + std::string remarksOutputFileFlag = ""; + /// Remark filters + std::string remarksAllFilterFlag = ""; + std::string remarksPassedFilterFlag = ""; + std::string remarksFailedFilterFlag = ""; + std::string remarksMissedFilterFlag = ""; + std::string remarksAnalyseFilterFlag = ""; + /// Configuration for the debugging hooks. tracing::DebugConfig debugConfig; diff --git a/mlir/lib/IR/Remarks.cpp b/mlir/lib/IR/Remarks.cpp index 78c964427868f..29088bd360e23 100644 --- a/mlir/lib/IR/Remarks.cpp +++ b/mlir/lib/IR/Remarks.cpp @@ -248,17 +248,56 @@ RemarkEngine::initialize(std::unique_ptr streamer, return success(); } +/// Returns true if filter is already anchored like ^...$ +static bool isAnchored(llvm::StringRef s) { + s = s.trim(); + return s.starts_with("^") && s.ends_with("$"); // note: startswith/endswith +} + +/// Anchor the entire pattern so it matches the whole string. +static std::string anchorWhole(llvm::StringRef filter) { + if (isAnchored(filter)) + return filter.str(); + return (llvm::Twine("^(") + filter + ")$").str(); +} + +/// Build a combined filter from cats.all and a category-specific pattern. +/// If neither is present, return std::nullopt. Otherwise "(all|specific)" +/// and anchor once. Also validate before returning. +static std::optional +buildFilter(const mlir::remark::RemarkCategories &cats, + const std::optional &specific) { + llvm::SmallVector parts; + if (cats.all && !cats.all->empty()) + parts.emplace_back(*cats.all); + if (specific && !specific->empty()) + parts.emplace_back(*specific); + + if (parts.empty()) + return std::nullopt; + + std::string joined = llvm::join(parts, "|"); + std::string anchored = anchorWhole(joined); + + llvm::Regex rx(anchored); + std::string err; + if (!rx.isValid(err)) + return std::nullopt; + + return rx; +} + RemarkEngine::RemarkEngine(bool printAsEmitRemarks, const RemarkCategories &cats) : printAsEmitRemarks(printAsEmitRemarks) { if (cats.passed) - passedFilter = llvm::Regex(cats.passed.value()); + passedFilter = buildFilter(cats, cats.passed); if (cats.missed) - missFilter = llvm::Regex(cats.missed.value()); + missFilter = buildFilter(cats, cats.missed); if (cats.analysis) - analysisFilter = llvm::Regex(cats.analysis.value()); + analysisFilter = buildFilter(cats, cats.analysis); if (cats.failed) - failedFilter = llvm::Regex(cats.failed.value()); + failedFilter = buildFilter(cats, cats.failed); } llvm::LogicalResult mlir::remark::enableOptimizationRemarks( diff --git a/mlir/lib/Tools/mlir-opt/CMakeLists.txt b/mlir/lib/Tools/mlir-opt/CMakeLists.txt index f24d4c60174ee..858c9c1f97f9c 100644 --- a/mlir/lib/Tools/mlir-opt/CMakeLists.txt +++ b/mlir/lib/Tools/mlir-opt/CMakeLists.txt @@ -13,4 +13,5 @@ add_mlir_library(MLIROptLib MLIRPluginsLib MLIRSupport MLIRIRDL + MLIRRemarkStreamer ) diff --git a/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp b/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp index de714d8b740af..4f3b2eda7e69b 100644 --- a/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp +++ b/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp @@ -23,9 +23,11 @@ #include "mlir/IR/Diagnostics.h" #include "mlir/IR/Location.h" #include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Remarks.h" #include "mlir/Parser/Parser.h" #include "mlir/Pass/PassManager.h" #include "mlir/Pass/PassRegistry.h" +#include "mlir/Remark/RemarkStreamer.h" #include "mlir/Support/FileUtilities.h" #include "mlir/Support/Timing.h" #include "mlir/Support/ToolUtilities.h" @@ -33,6 +35,7 @@ #include "mlir/Tools/Plugins/DialectPlugin.h" #include "mlir/Tools/Plugins/PassPlugin.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Remarks/RemarkFormat.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/LogicalResult.h" @@ -204,6 +207,58 @@ struct MlirOptMainConfigCLOptions : public MlirOptMainConfig { cl::location(generateReproducerFileFlag), cl::init(""), cl::value_desc("filename")); + static cl::OptionCategory remarkCategory( + "Remark Options", + "Filter remarks by regular expression (llvm::Regex syntax)."); + + static llvm::cl::opt remarkFormat{ + "remark-format", + llvm::cl::desc("Specify the format for remark output."), + cl::location(remarkFormatFlag), + llvm::cl::value_desc("format"), + llvm::cl::init(REMARK_FORMAT_STDOUT), + llvm::cl::values( + clEnumValN(REMARK_FORMAT_STDOUT, "emitRemark", + "Print as emitRemark to command-line"), + clEnumValN(REMARK_FORMAT_YAML, "yaml", "Print yaml file"), + clEnumValN(REMARK_FORMAT_BITSTREAM, "bitstream", + "Print bitstream file")), + llvm::cl::cat(remarkCategory)}; + + static cl::opt remarksAll( + "remarks-filter", + cl::desc("Show all remarks: passed, missed, failed, analysis"), + cl::location(remarksAllFilterFlag), cl::init(""), + cl::cat(remarkCategory)); + + static cl::opt remarksFile( + "remarks-output-file", + cl::desc( + "Output file for yaml and bitstream remark formats. Default is " + "mlir-remarks.yaml or mlir-remarks.bitstream"), + cl::location(remarksOutputFileFlag), cl::init(""), + cl::cat(remarkCategory)); + + static cl::opt remarksPassed( + "remarks-filter-passed", cl::desc("Show passed remarks"), + cl::location(remarksPassedFilterFlag), cl::init(""), + cl::cat(remarkCategory)); + + static cl::opt remarksFailed( + "remarks-filter-failed", cl::desc("Show failed remarks"), + cl::location(remarksFailedFilterFlag), cl::init(""), + cl::cat(remarkCategory)); + + static cl::opt remarksMissed( + "remarks-filter-missed", cl::desc("Show missed remarks"), + cl::location(remarksMissedFilterFlag), cl::init(""), + cl::cat(remarkCategory)); + + static cl::opt remarksAnalyse( + "remarks-filter-analyse", cl::desc("Show analysis remarks"), + cl::location(remarksAnalyseFilterFlag), cl::init(""), + cl::cat(remarkCategory)); + /// Set the callback to load a pass plugin. passPlugins.setCallback([&](const std::string &pluginPath) { auto plugin = PassPlugin::load(pluginPath); @@ -241,23 +296,23 @@ class DiagnosticFilter : public ScopedDiagnosticHandler { setHandler([verbosityLevel, showNotes](Diagnostic &diag) { auto severity = diag.getSeverity(); switch (severity) { - case DiagnosticSeverity::Error: + case mlir::DiagnosticSeverity::Error: // failure indicates that the error is not handled by the filter and // goes through to the default handler. Therefore, the error can be // successfully printed. return failure(); - case DiagnosticSeverity::Warning: + case mlir::DiagnosticSeverity::Warning: if (verbosityLevel == VerbosityLevel::ErrorsOnly) return success(); else return failure(); - case DiagnosticSeverity::Remark: + case mlir::DiagnosticSeverity::Remark: if (verbosityLevel == VerbosityLevel::ErrorsOnly || verbosityLevel == VerbosityLevel::ErrorsAndWarnings) return success(); else return failure(); - case DiagnosticSeverity::Note: + case mlir::DiagnosticSeverity::Note: if (showNotes) return failure(); else @@ -462,6 +517,41 @@ performActions(raw_ostream &os, context->enableMultithreading(wasThreadingEnabled); + remark::RemarkCategories cats{ + config.getRemarksAllFilter(), config.getRemarksPassedFilter(), + config.getRemarksMissedFilter(), config.getRemarksAnalyseFilter(), + config.getRemarksFailedFilter()}; + + mlir::MLIRContext &ctx = *context; + + switch (config.getRemarkFormat()) { + case REMARK_FORMAT_STDOUT: + if (failed(mlir::remark::enableOptimizationRemarks( + ctx, nullptr, cats, true /*printAsEmitRemarks*/))) + return failure(); + break; + + case REMARK_FORMAT_YAML: { + std::string file = config.getRemarksOutputFile().empty() + ? "mlir-remarks.yaml" + : config.getRemarksOutputFile(); + if (failed(mlir::remark::enableOptimizationRemarksWithLLVMStreamer( + ctx, file, llvm::remarks::Format::YAML, cats))) + return failure(); + break; + } + + case REMARK_FORMAT_BITSTREAM: { + std::string file = config.getRemarksOutputFile().empty() + ? "mlir-remarks.bitstream" + : config.getRemarksOutputFile(); + if (failed(mlir::remark::enableOptimizationRemarksWithLLVMStreamer( + ctx, file, llvm::remarks::Format::Bitstream, cats))) + return failure(); + break; + } + } + // Prepare the pass manager, applying command-line and reproducer options. PassManager pm(op.get()->getName(), PassManager::Nesting::Implicit); pm.enableVerifier(config.shouldVerifyPasses()); @@ -523,8 +613,8 @@ processBuffer(raw_ostream &os, std::unique_ptr ownedBuffer, SMLoc()); sourceMgr->AddNewSourceBuffer(std::move(ownedBuffer), SMLoc()); - // Create a context just for the current buffer. Disable threading on creation - // since we'll inject the thread-pool separately. + // Create a context just for the current buffer. Disable threading on + // creation since we'll inject the thread-pool separately. MLIRContext context(registry, MLIRContext::Threading::DISABLED); if (threadPool) context.setThreadPool(*threadPool); @@ -669,9 +759,9 @@ LogicalResult mlir::MlirOptMain(int argc, char **argv, if (config.shouldListPasses()) return printRegisteredPassesAndReturn(); - // When reading from stdin and the input is a tty, it is often a user mistake - // and the process "appears to be stuck". Print a message to let the user know - // about it! + // When reading from stdin and the input is a tty, it is often a user + // mistake and the process "appears to be stuck". Print a message to let the + // user know about it! if (inputFilename == "-" && sys::Process::FileDescriptorIsDisplayed(fileno(stdin))) llvm::errs() << "(processing input from stdin now, hit ctrl-c/ctrl-d to " diff --git a/mlir/test/Pass/remarks.mlir b/mlir/test/Pass/remarks.mlir new file mode 100644 index 0000000000000..8aa04e3c98d80 --- /dev/null +++ b/mlir/test/Pass/remarks.mlir @@ -0,0 +1,28 @@ +// RUN: mlir-opt %s --test-remark --remarks-filter-passed="category-1-passed" 2>&1 | FileCheck %s -check-prefix=CHECK-PASSED +// RUN: mlir-opt %s --test-remark --remarks-filter-missed="a-category-1-missed" 2>&1 | FileCheck %s -check-prefix=CHECK-MISSED +// RUN: mlir-opt %s --test-remark --remarks-filter-failed="category-2-failed" 2>&1 | FileCheck %s -check-prefix=CHECK-FAILED +// RUN: mlir-opt %s --test-remark --remarks-filter-analyse="category-2-analysis" 2>&1 | FileCheck %s -check-prefix=CHECK-ANALYSIS +// RUN: mlir-opt %s --test-remark --remarks-filter="category.*" 2>&1 | FileCheck %s -check-prefix=CHECK-ALL +// RUN: mlir-opt %s --test-remark --remarks-filter="category-1.*" 2>&1 | FileCheck %s -check-prefix=CHECK-ALL1 +module @foo { + "test.op"() : () -> () + +} + + +// CHECK-PASSED: remarks.mlir:8:3: remark: [Passed] test-remark | Category:category-1-passed | Reason="because we are testing the remark pipeline", Remark="This is a test passed remark", Suggestion="try using the remark pipeline feature" +// CHECK-MISSED:remarks.mlir:8:3: remark: [Missed] test-remark | Category:a-category-1-missed | Reason="because we are testing the remark pipeline", Remark="This is a test missed remark", Suggestion="try using the remark pipeline feature" +// CHECK-FAILED: remarks.mlir:8:3: remark: [Failure] test-remark | Category:category-2-failed | Reason="because we are testing the remark pipeline", Remark="This is a test failed remark", Suggestion="try using the remark pipeline feature" +// CHECK-ANALYSIS: remarks.mlir:8:3: remark: [Analysis] test-remark | Category:category-2-analysis | Remark="This is a test analysis remark" + + +// CHECK-ALL: remarks.mlir:8:3: remark: [Passed] test-remark | Category:category-1-passed | Reason="because we are testing the remark pipeline", Remark="This is a test passed remark", Suggestion="try using the remark pipeline feature" +// CHECK-ALL: remarks.mlir:8:3: remark: [Failure] test-remark | Category:category-2-failed | Reason="because we are testing the remark pipeline", Remark="This is a test failed remark", Suggestion="try using the remark pipeline feature" +// CHECK-ALL: remarks.mlir:8:3: remark: [Analysis] test-remark | Category:category-2-analysis | Remark="This is a test analysis remark" + +// CHECK-ALL1: remarks.mlir:8:3: remark: [Passed] test-remark | Category:category-1-passed | Reason="because we are testing the remark pipeline", Remark="This is a test passed remark", Suggestion="try using the remark pipeline feature" +// CHECK-ALL1-NOT: remarks.mlir:8:3: remark: [Missed] +// CHECK-ALL1-NOT: remarks.mlir:8:3: remark: [Failure] +// CHECK-ALL1-NOT: remarks.mlir:8:3: remark: [Analysis] + + diff --git a/mlir/test/lib/Pass/CMakeLists.txt b/mlir/test/lib/Pass/CMakeLists.txt index ab52f621c517e..04c91635def85 100644 --- a/mlir/test/lib/Pass/CMakeLists.txt +++ b/mlir/test/lib/Pass/CMakeLists.txt @@ -4,6 +4,7 @@ add_mlir_library(MLIRTestPass TestConvertToSPIRVPass.cpp TestDynamicPipeline.cpp TestPassManager.cpp + TestRemarksPass.cpp TestSPIRVCPURunnerPipeline.cpp TestVulkanRunnerPipeline.cpp diff --git a/mlir/test/lib/Pass/TestRemarksPass.cpp b/mlir/test/lib/Pass/TestRemarksPass.cpp new file mode 100644 index 0000000000000..3b25686b3dc14 --- /dev/null +++ b/mlir/test/lib/Pass/TestRemarksPass.cpp @@ -0,0 +1,74 @@ +//===------ TestRemarkPipeline.cpp --- dynamic pipeline test pass --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements a pass to test the dynamic pipeline feature. +// +//===----------------------------------------------------------------------===// + +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/Remarks.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Support/WalkResult.h" + +using namespace mlir; + +namespace { + +class TestRemarkPass : public PassWrapper> { +public: + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestRemarkPass) + + StringRef getArgument() const final { return "test-remark"; } + StringRef getDescription() const final { + return "Tests the remark pipeline feature"; + } + + TestRemarkPass() = default; + + void runOnOperation() override { + + getOperation()->walk([](Operation *op) { + if (isa(op)) + return WalkResult::advance(); + Location loc = op->getLoc(); + mlir::remark::missed(loc, remark::RemarkOpts::name("test-remark") + .category("a-category-1-missed")) + << remark::add("This is a test missed remark") + << remark::reason("because we are testing the remark pipeline") + << remark::suggest("try using the remark pipeline feature"); + + mlir::remark::passed( + loc, + remark::RemarkOpts::name("test-remark").category("category-1-passed")) + << remark::add("This is a test passed remark") + << remark::reason("because we are testing the remark pipeline") + << remark::suggest("try using the remark pipeline feature"); + + mlir::remark::failed( + loc, + remark::RemarkOpts::name("test-remark").category("category-2-failed")) + << remark::add("This is a test failed remark") + << remark::reason("because we are testing the remark pipeline") + << remark::suggest("try using the remark pipeline feature"); + + mlir::remark::analysis(loc, remark::RemarkOpts::name("test-remark") + .category("category-2-analysis")) + << remark::add("This is a test analysis remark"); + return WalkResult::advance(); + }); + } +}; +} // namespace + +namespace mlir { +namespace test { +void registerTestRemarkPass() { PassRegistration(); } +} // namespace test +} // namespace mlir diff --git a/mlir/tools/mlir-opt/mlir-opt.cpp b/mlir/tools/mlir-opt/mlir-opt.cpp index 7b992b4ee029b..e4620c009af8c 100644 --- a/mlir/tools/mlir-opt/mlir-opt.cpp +++ b/mlir/tools/mlir-opt/mlir-opt.cpp @@ -97,6 +97,7 @@ void registerTestDiagnosticsPass(); void registerTestDiagnosticsMetadataPass(); void registerTestDominancePass(); void registerTestDynamicPipelinePass(); +void registerTestRemarkPass(); void registerTestEmulateNarrowTypePass(); void registerTestFooAnalysisPass(); void registerTestComposeSubView(); @@ -243,6 +244,7 @@ void registerTestPasses() { mlir::test::registerTestDiagnosticsMetadataPass(); mlir::test::registerTestDominancePass(); mlir::test::registerTestDynamicPipelinePass(); + mlir::test::registerTestRemarkPass(); mlir::test::registerTestEmulateNarrowTypePass(); mlir::test::registerTestFooAnalysisPass(); mlir::test::registerTestComposeSubView(); diff --git a/mlir/unittests/IR/CMakeLists.txt b/mlir/unittests/IR/CMakeLists.txt index 75cd2d65ef5a1..dd3b110dcd295 100644 --- a/mlir/unittests/IR/CMakeLists.txt +++ b/mlir/unittests/IR/CMakeLists.txt @@ -14,7 +14,7 @@ add_mlir_unittest(MLIRIRTests MemrefLayoutTest.cpp OperationSupportTest.cpp PatternMatchTest.cpp - RemarkTest.cpp + RemarkTest.cpp ShapedTypeTest.cpp SymbolTableTest.cpp TypeTest.cpp diff --git a/mlir/unittests/IR/RemarkTest.cpp b/mlir/unittests/IR/RemarkTest.cpp index 65e1e08b83838..5bfca255c22ca 100644 --- a/mlir/unittests/IR/RemarkTest.cpp +++ b/mlir/unittests/IR/RemarkTest.cpp @@ -48,7 +48,8 @@ TEST(Remark, TestOutputOptimizationRemark) { context.printStackTraceOnDiagnostic(true); // Setup the remark engine - mlir::remark::RemarkCategories cats{/*passed=*/categoryVectorizer, + mlir::remark::RemarkCategories cats{/*all=*/"", + /*passed=*/categoryVectorizer, /*missed=*/categoryUnroll, /*analysis=*/categoryRegister, /*failed=*/categoryInliner}; @@ -197,7 +198,8 @@ TEST(Remark, TestOutputOptimizationRemarkDiagnostic) { }); // Setup the remark engine - mlir::remark::RemarkCategories cats{/*passed=*/categoryVectorizer, + mlir::remark::RemarkCategories cats{/*all=*/"", + /*passed=*/categoryVectorizer, /*missed=*/categoryUnroll, /*analysis=*/categoryRegister, /*failed=*/categoryUnroll}; @@ -278,7 +280,8 @@ TEST(Remark, TestCustomOptimizationRemarkDiagnostic) { Location loc = UnknownLoc::get(&context); // Setup the remark engine - mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll, + mlir::remark::RemarkCategories cats{/*all=*/"", + /*passed=*/categoryLoopunroll, /*missed=*/std::nullopt, /*analysis=*/std::nullopt, /*failed=*/categoryLoopunroll};