[MLIR] Add remark flags to mlir-opt (#156825)
GitOrigin-RevId: 50f539c858aa4d7e71d9b5d5d7da7c30ffaf4bea
diff --git a/include/mlir/IR/Remarks.h b/include/mlir/IR/Remarks.h
index 26d6547..20e84ec 100644
--- a/include/mlir/IR/Remarks.h
+++ b/include/mlir/IR/Remarks.h
@@ -29,7 +29,7 @@
/// 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<std::string> passed, missed, analysis, failed;
+ std::optional<std::string> all, passed, missed, analysis, failed;
};
/// Categories describe the outcome of an transformation, not the mechanics of
diff --git a/include/mlir/Tools/mlir-opt/MlirOptMain.h b/include/mlir/Tools/mlir-opt/MlirOptMain.h
index 9423122..c3ac9d9 100644
--- a/include/mlir/Tools/mlir-opt/MlirOptMain.h
+++ b/include/mlir/Tools/mlir-opt/MlirOptMain.h
@@ -38,6 +38,12 @@
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 @@
}
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/lib/IR/Remarks.cpp b/lib/IR/Remarks.cpp
index 78c9644..29088bd 100644
--- a/lib/IR/Remarks.cpp
+++ b/lib/IR/Remarks.cpp
@@ -248,17 +248,56 @@
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<llvm::Regex>
+buildFilter(const mlir::remark::RemarkCategories &cats,
+ const std::optional<std::string> &specific) {
+ llvm::SmallVector<llvm::StringRef, 2> 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/lib/Tools/mlir-opt/CMakeLists.txt b/lib/Tools/mlir-opt/CMakeLists.txt
index f24d4c6..858c9c1 100644
--- a/lib/Tools/mlir-opt/CMakeLists.txt
+++ b/lib/Tools/mlir-opt/CMakeLists.txt
@@ -13,4 +13,5 @@
MLIRPluginsLib
MLIRSupport
MLIRIRDL
+ MLIRRemarkStreamer
)
diff --git a/lib/Tools/mlir-opt/MlirOptMain.cpp b/lib/Tools/mlir-opt/MlirOptMain.cpp
index de714d8..4f3b2ed 100644
--- a/lib/Tools/mlir-opt/MlirOptMain.cpp
+++ b/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 @@
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, /*ExternalStorage=*/true> 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<std::string, /*ExternalStorage=*/true> remarksAll(
+ "remarks-filter",
+ cl::desc("Show all remarks: passed, missed, failed, analysis"),
+ cl::location(remarksAllFilterFlag), cl::init(""),
+ cl::cat(remarkCategory));
+
+ static cl::opt<std::string, /*ExternalStorage=*/true> 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<std::string, /*ExternalStorage=*/true> remarksPassed(
+ "remarks-filter-passed", cl::desc("Show passed remarks"),
+ cl::location(remarksPassedFilterFlag), cl::init(""),
+ cl::cat(remarkCategory));
+
+ static cl::opt<std::string, /*ExternalStorage=*/true> remarksFailed(
+ "remarks-filter-failed", cl::desc("Show failed remarks"),
+ cl::location(remarksFailedFilterFlag), cl::init(""),
+ cl::cat(remarkCategory));
+
+ static cl::opt<std::string, /*ExternalStorage=*/true> remarksMissed(
+ "remarks-filter-missed", cl::desc("Show missed remarks"),
+ cl::location(remarksMissedFilterFlag), cl::init(""),
+ cl::cat(remarkCategory));
+
+ static cl::opt<std::string, /*ExternalStorage=*/true> 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 @@
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 @@
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 @@
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 @@
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/test/Pass/remarks.mlir b/test/Pass/remarks.mlir
new file mode 100644
index 0000000..8aa04e3
--- /dev/null
+++ b/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/test/lib/Pass/CMakeLists.txt b/test/lib/Pass/CMakeLists.txt
index ab52f62..04c9163 100644
--- a/test/lib/Pass/CMakeLists.txt
+++ b/test/lib/Pass/CMakeLists.txt
@@ -4,6 +4,7 @@
TestConvertToSPIRVPass.cpp
TestDynamicPipeline.cpp
TestPassManager.cpp
+ TestRemarksPass.cpp
TestSPIRVCPURunnerPipeline.cpp
TestVulkanRunnerPipeline.cpp
diff --git a/test/lib/Pass/TestRemarksPass.cpp b/test/lib/Pass/TestRemarksPass.cpp
new file mode 100644
index 0000000..3b25686
--- /dev/null
+++ b/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<TestRemarkPass, OperationPass<>> {
+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<ModuleOp>(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<TestRemarkPass>(); }
+} // namespace test
+} // namespace mlir
diff --git a/tools/mlir-opt/mlir-opt.cpp b/tools/mlir-opt/mlir-opt.cpp
index 7b992b4..e4620c0 100644
--- a/tools/mlir-opt/mlir-opt.cpp
+++ b/tools/mlir-opt/mlir-opt.cpp
@@ -97,6 +97,7 @@
void registerTestDiagnosticsMetadataPass();
void registerTestDominancePass();
void registerTestDynamicPipelinePass();
+void registerTestRemarkPass();
void registerTestEmulateNarrowTypePass();
void registerTestFooAnalysisPass();
void registerTestComposeSubView();
@@ -243,6 +244,7 @@
mlir::test::registerTestDiagnosticsMetadataPass();
mlir::test::registerTestDominancePass();
mlir::test::registerTestDynamicPipelinePass();
+ mlir::test::registerTestRemarkPass();
mlir::test::registerTestEmulateNarrowTypePass();
mlir::test::registerTestFooAnalysisPass();
mlir::test::registerTestComposeSubView();
diff --git a/unittests/IR/CMakeLists.txt b/unittests/IR/CMakeLists.txt
index 75cd2d6..dd3b110 100644
--- a/unittests/IR/CMakeLists.txt
+++ b/unittests/IR/CMakeLists.txt
@@ -14,7 +14,7 @@
MemrefLayoutTest.cpp
OperationSupportTest.cpp
PatternMatchTest.cpp
- RemarkTest.cpp
+ RemarkTest.cpp
ShapedTypeTest.cpp
SymbolTableTest.cpp
TypeTest.cpp
diff --git a/unittests/IR/RemarkTest.cpp b/unittests/IR/RemarkTest.cpp
index 65e1e08..5bfca25 100644
--- a/unittests/IR/RemarkTest.cpp
+++ b/unittests/IR/RemarkTest.cpp
@@ -48,7 +48,8 @@
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 @@
});
// 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 @@
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};