[Mlir-commits] [mlir] [MLIR] Integrate LLVM Optimization Remarks Infrastructure (PR #152474)
Guray Ozen
llvmlistbot at llvm.org
Thu Aug 7 04:19:58 PDT 2025
https://github.com/grypp updated https://github.com/llvm/llvm-project/pull/152474
>From 6d6d8966ac3918c8f60226eff617033d74a3d98c Mon Sep 17 00:00:00 2001
From: Guray Ozen <gozen at nvidia.com>
Date: Thu, 7 Aug 2025 10:01:51 +0000
Subject: [PATCH 1/2] [MLIR] MLIR Integrate LLVM Optimization Remarks
Infrastructure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch hooks LLVM’s remarks infrastructure into MLIR.
* Disabled by default, zero-cost when off.
Enable per context with `MLIRContext::setupOptimizationRemarks(...)`.
* YAML or bitstream output: via new `MLIRRemarkStreamer`
(subclasses `llvm::remarks::RemarkStreamer`).
* Implements simple APIs for passes:
User-code can directly prints remarks like:
```
reportOptimizationPass(loc, categoryName, "MyPass") << "vectorized";
reportOptimizationMiss(loc, categoryLoopUnroll, "MyPass",
"try increasing unroll factor") << "...";
reportOptimizationFail(loc, categoryLoopUnroll, "MyPass") << "...";
reportOptimizationAnalysis(loc, categoryLoopUnroll, "MyPass")
<< "estimated trip count: " << tripCount;
```
---
mlir/include/mlir/IR/MLIRContext.h | 15 +
mlir/include/mlir/IR/Remarks.h | 454 +++++++++++++++++++++++++++++
mlir/lib/IR/CMakeLists.txt | 6 +
mlir/lib/IR/MLIRContext.cpp | 35 +++
mlir/lib/IR/Remarks.cpp | 296 +++++++++++++++++++
mlir/unittests/IR/CMakeLists.txt | 1 +
mlir/unittests/IR/RemarkTest.cpp | 106 +++++++
7 files changed, 913 insertions(+)
create mode 100644 mlir/include/mlir/IR/Remarks.h
create mode 100644 mlir/lib/IR/Remarks.cpp
create mode 100644 mlir/unittests/IR/RemarkTest.cpp
diff --git a/mlir/include/mlir/IR/MLIRContext.h b/mlir/include/mlir/IR/MLIRContext.h
index ef8dab87f131a..ab8f4aed3fc79 100644
--- a/mlir/include/mlir/IR/MLIRContext.h
+++ b/mlir/include/mlir/IR/MLIRContext.h
@@ -32,6 +32,7 @@ class InFlightDiagnostic;
class Location;
class MLIRContextImpl;
class RegisteredOperationName;
+class RemarkEngine;
class StorageUniquer;
class IRUnit;
@@ -212,6 +213,9 @@ class MLIRContext {
/// Returns the diagnostic engine for this context.
DiagnosticEngine &getDiagEngine();
+ /// Returns the remark engine for this context.
+ std::unique_ptr<RemarkEngine> &getRemarkEngine();
+
/// Returns the storage uniquer used for creating affine constructs.
StorageUniquer &getAffineUniquer();
@@ -245,6 +249,14 @@ class MLIRContext {
/// (attributes, operations, types, etc.).
llvm::hash_code getRegistryHash();
+ /// Set up optimization remarks for the context.
+ void setupOptimizationRemarks(
+ StringRef outputPath, StringRef outputFormat, bool printAsEmitRemarks,
+ const std::optional<std::string> &categoryPassName = std::nullopt,
+ const std::optional<std::string> &categoryMissName = std::nullopt,
+ const std::optional<std::string> &categoryAnalysisName = std::nullopt,
+ const std::optional<std::string> &categoryFailedName = std::nullopt);
+
//===--------------------------------------------------------------------===//
// Action API
//===--------------------------------------------------------------------===//
@@ -281,6 +293,9 @@ class MLIRContext {
}
private:
+ /// Set the remark engine for this context.
+ void setRemarkEngine(std::unique_ptr<RemarkEngine> engine);
+
/// Return true if the given dialect is currently loading.
bool isDialectLoading(StringRef dialectNamespace);
diff --git a/mlir/include/mlir/IR/Remarks.h b/mlir/include/mlir/IR/Remarks.h
new file mode 100644
index 0000000000000..8f7e7a900e7f1
--- /dev/null
+++ b/mlir/include/mlir/IR/Remarks.h
@@ -0,0 +1,454 @@
+//===- Remarks.h - MLIR Optimization Remark ----------------------*- C++-*-===//
+//
+// 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 defines utilities for emitting optimization remarks.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_IR_REMARKS_H
+#define MLIR_IR_REMARKS_H
+
+#include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/DiagnosticPrinter.h"
+#include "llvm/Remarks/Remark.h"
+#include "llvm/Remarks/RemarkStreamer.h"
+#include "llvm/Support/ToolOutputFile.h"
+
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/MLIRContext.h"
+
+namespace mlir {
+
+/// Defines different remark kinds that can be used to categorize remarks.
+enum class RemarkKind {
+ OptimizationRemarkUnknown = 0,
+ OptimizationRemarkPass,
+ OptimizationRemarkMissed,
+ OptimizationRemarkFailure,
+ OptimizationRemarkAnalysis,
+};
+
+//===----------------------------------------------------------------------===//
+// Remark Base Class
+//===----------------------------------------------------------------------===//
+class RemarkBase : public llvm::DiagnosticInfo {
+
+public:
+ RemarkBase(RemarkKind remarkKind, DiagnosticSeverity severity,
+ const char *passName, StringRef remarkName, Location loc,
+ std::optional<StringRef> functionName = std::nullopt)
+ : llvm::DiagnosticInfo(makeLLVMKind(remarkKind),
+ makeLLVMSeverity(severity)),
+ remarkKind(remarkKind), functionName(functionName), loc(loc),
+ passName(passName), remarkName(remarkName) {}
+
+ struct SetIsVerbose {};
+
+ struct SetExtraArgs {};
+
+ struct RemarkKeyValue {
+ std::string key;
+ std::string val;
+
+ explicit RemarkKeyValue(StringRef str = "") : key("String"), val(str) {}
+ RemarkKeyValue(StringRef key, Value value);
+ RemarkKeyValue(StringRef key, Type type);
+ RemarkKeyValue(StringRef key, StringRef s);
+ RemarkKeyValue(StringRef key, const char *s)
+ : RemarkKeyValue(key, StringRef(s)) {};
+ RemarkKeyValue(StringRef key, int n);
+ RemarkKeyValue(StringRef key, float n);
+ RemarkKeyValue(StringRef key, long n);
+ RemarkKeyValue(StringRef key, long long n);
+ RemarkKeyValue(StringRef key, unsigned n);
+ RemarkKeyValue(StringRef key, unsigned long n);
+ RemarkKeyValue(StringRef key, unsigned long long n);
+ RemarkKeyValue(StringRef key, bool b)
+ : key(key), val(b ? "true" : "false") {}
+ };
+
+ void insert(StringRef s);
+ void insert(RemarkKeyValue a);
+ void insert(SetIsVerbose v);
+ void insert(SetExtraArgs ea);
+
+ void print(llvm::DiagnosticPrinter &dp) const override;
+ void print() const;
+
+ virtual bool isEnabled() const = 0;
+ Location getLocation() const { return loc; }
+ llvm::remarks::RemarkLocation getRemarkLocation() const;
+ /// Diagnostic -> Remark
+ llvm::remarks::Remark generateRemark() const;
+
+ StringRef getFunction() const {
+ if (functionName)
+ return *functionName;
+ return "<unknown function>";
+ }
+ StringRef getPassName() const { return passName; }
+ StringRef getRemarkName() const { return remarkName; }
+ std::string getMsg() const;
+
+ bool isVerbose() const { return isVerboseRemark; }
+
+ ArrayRef<RemarkKeyValue> getArgs() const { return args; }
+
+ llvm::remarks::Type getRemarkType() const;
+
+protected:
+ /// Keeps the MLIR diagnostic kind, which is used to determine the
+ /// diagnostic kind in the LLVM remark streamer.
+ RemarkKind remarkKind;
+ /// Name of the convering function like interface
+ std::optional<std::string> functionName;
+
+ Location loc;
+ /// Name of the pass that triggers this report.
+ const char *passName;
+
+ /// Textual identifier for the remark (single-word, CamelCase). Can be used
+ /// by external tools reading the output file for optimization remarks to
+ /// identify the remark.
+ StringRef remarkName;
+
+ /// RemarkKeyValues collected via the streaming interface.
+ SmallVector<RemarkKeyValue, 4> args;
+
+ /// The remark is expected to be noisy.
+ bool isVerboseRemark = false;
+
+private:
+ /// Convert the MLIR diagnostic severity to LLVM diagnostic severity.
+ static llvm::DiagnosticSeverity
+ makeLLVMSeverity(DiagnosticSeverity severity) {
+ switch (severity) {
+ case DiagnosticSeverity::Note:
+ return llvm::DiagnosticSeverity::DS_Note;
+ case DiagnosticSeverity::Warning:
+ return llvm::DiagnosticSeverity::DS_Warning;
+ case DiagnosticSeverity::Error:
+ return llvm::DiagnosticSeverity::DS_Error;
+ case DiagnosticSeverity::Remark:
+ return llvm::DiagnosticSeverity::DS_Remark;
+ }
+ llvm_unreachable("Unknown diagnostic severity");
+ }
+ /// Convert the MLIR remark kind to LLVM diagnostic kind.
+ static llvm::DiagnosticKind makeLLVMKind(RemarkKind remarkKind) {
+ switch (remarkKind) {
+ case RemarkKind::OptimizationRemarkUnknown:
+ return llvm::DiagnosticKind::DK_Generic;
+ case RemarkKind::OptimizationRemarkPass:
+ return llvm::DiagnosticKind::DK_OptimizationRemark;
+ case RemarkKind::OptimizationRemarkMissed:
+ return llvm::DiagnosticKind::DK_OptimizationRemarkMissed;
+ case RemarkKind::OptimizationRemarkFailure:
+ return llvm::DiagnosticKind::DK_OptimizationFailure;
+ case RemarkKind::OptimizationRemarkAnalysis:
+ return llvm::DiagnosticKind::DK_OptimizationRemarkAnalysis;
+ }
+ llvm_unreachable("Unknown diagnostic kind");
+ }
+};
+
+// clang-format off
+template <class RemarkT>
+decltype(auto) operator<<(
+ RemarkT &&r,
+ std::enable_if_t<std::is_base_of_v<RemarkBase,
+ std::remove_reference_t<RemarkT>>,
+ StringRef>
+ s) {
+ r.insert(s);
+ return std::forward<RemarkT>(r);
+}
+
+template <class RemarkT>
+decltype(auto) operator<<(
+ RemarkT &&r,
+ std::enable_if_t<std::is_base_of_v<RemarkBase,
+ std::remove_reference_t<RemarkT>>,
+ RemarkBase::RemarkKeyValue>
+ a) {
+ r.insert(std::move(a));
+ return std::forward<RemarkT>(r);
+}
+
+template <class RemarkT>
+decltype(auto) operator<<(
+ RemarkT &&r,
+ std::enable_if_t<std::is_base_of_v<RemarkBase,
+ std::remove_reference_t<RemarkT>>,
+ RemarkBase::SetIsVerbose>
+ v) {
+ r.insert(v);
+ return std::forward<RemarkT>(r);
+}
+
+template <class RemarkT>
+decltype(auto) operator<<(
+ RemarkT &&r,
+ std::enable_if_t<std::is_base_of_v<RemarkBase,
+ std::remove_reference_t<RemarkT>>,
+ RemarkBase::SetExtraArgs>
+ ea) {
+ r.insert(ea);
+ return std::forward<RemarkT>(r);
+}
+// clang-format on
+
+//===----------------------------------------------------------------------===//
+// Shorthand aliases for different kinds of remarks.
+//===----------------------------------------------------------------------===//
+
+template <RemarkKind K, DiagnosticSeverity S>
+class OptRemarkBase final : public RemarkBase {
+public:
+ explicit OptRemarkBase(Location loc, StringRef passName, StringRef remarkName)
+ : RemarkBase(K, S, passName.data(), remarkName, loc) {}
+
+ bool isEnabled() const override { return true; }
+};
+
+using OptRemarkAnalysis = OptRemarkBase<RemarkKind::OptimizationRemarkAnalysis,
+ DiagnosticSeverity::Remark>;
+
+using OptRemarkPass = OptRemarkBase<RemarkKind::OptimizationRemarkPass,
+ DiagnosticSeverity::Remark>;
+
+using OptRemarkMissed = OptRemarkBase<RemarkKind::OptimizationRemarkMissed,
+ DiagnosticSeverity::Remark>;
+
+using OptRemarkFailure = OptRemarkBase<RemarkKind::OptimizationRemarkFailure,
+ DiagnosticSeverity::Remark>;
+
+class RemarkEngine;
+
+//===----------------------------------------------------------------------===//
+// InFlightRemark
+//===----------------------------------------------------------------------===//
+
+/// InFlightRemark is a RAII class that holds a reference to a RemarkBase
+/// instance and allows to build the remark using the << operator. The remark
+/// is emitted when the InFlightRemark instance is destroyed, which happens
+/// when the scope ends or when the InFlightRemark instance is moved.
+/// Similar to InFlightDiagnostic, but for remarks.
+class InFlightRemark {
+public:
+ explicit InFlightRemark(RemarkBase *diag) : remark(diag) {}
+ InFlightRemark(RemarkEngine &eng, RemarkBase *diag)
+ : owner(&eng), remark(diag) {}
+
+ InFlightRemark() = default; // empty ctor
+
+ template <typename T>
+ InFlightRemark &operator<<(T &&arg) {
+ if (remark)
+ *remark << std::forward<T>(arg);
+ return *this;
+ }
+
+ explicit operator bool() const { return remark != nullptr; }
+
+ ~InFlightRemark();
+
+ InFlightRemark(const InFlightRemark &) = delete;
+ InFlightRemark &operator=(const InFlightRemark &) = delete;
+ InFlightRemark(InFlightRemark &&) = default;
+ InFlightRemark &operator=(InFlightRemark &&) = default;
+
+private:
+ RemarkEngine *owner{nullptr};
+ std::unique_ptr<RemarkBase> remark;
+};
+
+//===----------------------------------------------------------------------===//
+// MLIR Remark Streamer
+//===----------------------------------------------------------------------===//
+
+class MLIRRemarkStreamer {
+ llvm::remarks::RemarkStreamer &remarkStreamer;
+
+public:
+ explicit MLIRRemarkStreamer(llvm::remarks::RemarkStreamer &remarkStreamer)
+ : remarkStreamer(remarkStreamer) {}
+
+ void streamOptimizationRemark(const RemarkBase &remark);
+};
+
+//===----------------------------------------------------------------------===//
+// Remark Engine (MLIR Context will own this class)
+//===----------------------------------------------------------------------===//
+
+class RemarkEngine {
+private:
+ /// The category for missed optimization remarks.
+ std::optional<llvm::Regex> missFilter;
+ /// The category for passed optimization remarks.
+ std::optional<llvm::Regex> passFilter;
+ /// The category for analysis remarks.
+ std::optional<llvm::Regex> analysisFilter;
+ /// The category for failed optimization remarks.
+ std::optional<llvm::Regex> failedFilter;
+ /// The output file for the remarks.
+ std::unique_ptr<llvm::ToolOutputFile> remarksFile;
+ /// The MLIR remark streamer that will be used to emit the remarks.
+ std::unique_ptr<MLIRRemarkStreamer> remarkStreamer;
+ std::unique_ptr<llvm::remarks::RemarkStreamer> llvmRemarkStreamer;
+ /// When is enabled, engine also prints remarks as mlir::emitRemarks.
+ bool printAsEmitRemarks;
+ /// The main MLIR remark streamer that will be used to emit the remarks.
+ MLIRRemarkStreamer *getLLVMRemarkStreamer() { return remarkStreamer.get(); }
+ const MLIRRemarkStreamer *getLLVMRemarkStreamer() const {
+ return remarkStreamer.get();
+ }
+ void
+ setLLVMRemarkStreamer(std::unique_ptr<MLIRRemarkStreamer> remarkStreamer) {
+ this->remarkStreamer = std::move(remarkStreamer);
+ }
+
+ /// Get the main MLIR remark streamer that will be used to emit the remarks.
+ llvm::remarks::RemarkStreamer *getMainRemarkStreamer() {
+ return llvmRemarkStreamer.get();
+ }
+ const llvm::remarks::RemarkStreamer *getMainRemarkStreamer() const {
+ return llvmRemarkStreamer.get();
+ }
+ /// Set the main remark streamer to be used by the engine.
+ void setMainRemarkStreamer(
+ std::unique_ptr<llvm::remarks::RemarkStreamer> mainRemarkStreamer) {
+ llvmRemarkStreamer = std::move(mainRemarkStreamer);
+ }
+
+ /// Return true if missed optimization remarks are enabled, override
+ /// to provide different implementation.
+ bool isMissedOptRemarkEnabled(StringRef categoryName) const;
+
+ /// Return true if passed optimization remarks are enabled, override
+ /// to provide different implementation.
+ bool isPassedOptRemarkEnabled(StringRef categoryName) const;
+
+ /// Return true if analysis optimization remarks are enabled, override
+ /// to provide different implementation.
+ bool isAnalysisOptRemarkEnabled(StringRef categoryName) const;
+
+ /// Return true if analysis optimization remarks are enabled, override
+ /// to provide different implementation.
+ bool isFailedOptRemarkEnabled(StringRef categoryName) const;
+
+ /// Return true if any type of remarks are enabled for this pass.
+ bool isAnyRemarkEnabled(StringRef categoryName) const {
+ return (isMissedOptRemarkEnabled(categoryName) ||
+ isPassedOptRemarkEnabled(categoryName) ||
+ isFailedOptRemarkEnabled(categoryName) ||
+ isAnalysisOptRemarkEnabled(categoryName));
+ }
+
+ /// Emit a remark using the given maker function, which should return
+ /// a RemarkBase instance. The remark will be emitted using the main
+ /// remark streamer.
+ template <typename RemarkT, typename... Args>
+ InFlightRemark makeRemark(Args &&...args);
+
+ template <typename RemarkT>
+ InFlightRemark
+ emitIfEnabled(Location loc, StringRef passName, StringRef category,
+ bool (RemarkEngine::*isEnabled)(StringRef) const);
+
+public:
+ RemarkEngine() = delete;
+ /// Constructs Remark engine with optional category names
+ RemarkEngine(bool printAsEmitRemarks,
+ std::optional<std::string> categoryPassName = std::nullopt,
+ std::optional<std::string> categoryMissName = std::nullopt,
+ std::optional<std::string> categoryAnalysisName = std::nullopt,
+ std::optional<std::string> categoryFailedName = std::nullopt);
+
+ /// Destructor that will close the output file and reset the
+ /// main remark streamer.
+ ~RemarkEngine();
+
+ /// Setup the remark engine with the given output path and format.
+ llvm::Error initialize(StringRef outputPath, StringRef outputFormat);
+
+ /// Report a diagnostic remark.
+ void report(const RemarkBase &&diag);
+
+ /// Report a successful remark, this will create an InFlightRemark
+ /// that can be used to build the remark using the << operator.
+ InFlightRemark emitOptimizationRemark(Location loc, StringRef passName,
+ StringRef category);
+ /// Report a missed optimization remark
+ /// that can be used to build the remark using the << operator.
+ InFlightRemark emitOptimizationRemarkMiss(Location loc, StringRef passName,
+ StringRef category);
+ /// Report a failed optimization remark, this will create an InFlightRemark
+ /// that can be used to build the remark using the << operator.
+ InFlightRemark emitOptimizationRemarkFailure(Location loc, StringRef passName,
+ StringRef category);
+ /// Report an analysis remark, this will create an InFlightRemark
+ /// that can be used to build the remark using the << operator.
+ InFlightRemark emitOptimizationRemarkAnalysis(Location loc,
+ StringRef passName,
+ StringRef category);
+};
+
+//===----------------------------------------------------------------------===//
+// Emitters
+//===----------------------------------------------------------------------===//
+
+using Suggestion = RemarkBase::RemarkKeyValue;
+inline Suggestion suggest(StringRef txt) { return {"Suggestion", txt}; }
+
+template <typename Fn, typename... Args>
+[[nodiscard]] inline InFlightRemark withEngine(Fn fn, Location loc,
+ Args &&...args) {
+ MLIRContext *ctx = loc->getContext();
+
+ auto &enginePtr = ctx->getRemarkEngine();
+
+ if (RemarkEngine *eng = enginePtr.get())
+ return (eng->*fn)(loc, std::forward<Args>(args)...);
+
+ return {};
+}
+
+/// Report an optimization remark that was passed.
+inline InFlightRemark reportOptimizationPass(Location loc, StringRef cat,
+ StringRef passName) {
+ return withEngine(&RemarkEngine::emitOptimizationRemark, loc, passName, cat);
+}
+
+/// Report an optimization remark that was missed.
+inline InFlightRemark reportOptimizationMiss(Location loc, StringRef cat,
+ StringRef passName,
+ StringRef suggestion) {
+ auto r =
+ withEngine(&RemarkEngine::emitOptimizationRemarkMiss, loc, passName, cat);
+ if (r)
+ r << suggest(suggestion);
+ return r;
+}
+/// Report an optimization failure remark.
+inline InFlightRemark reportOptimizationFail(Location loc, StringRef cat,
+ StringRef passName) {
+ return withEngine(&RemarkEngine::emitOptimizationRemarkFailure, loc, passName,
+ cat);
+}
+
+/// Report an optimization analysis remark.
+inline InFlightRemark reportOptimizationAnalysis(Location loc, StringRef cat,
+ StringRef passName) {
+ return withEngine(&RemarkEngine::emitOptimizationRemarkAnalysis, loc,
+ passName, cat);
+}
+
+} // namespace mlir
+
+#endif // MLIR_IR_REMARKS_H
\ No newline at end of file
diff --git a/mlir/lib/IR/CMakeLists.txt b/mlir/lib/IR/CMakeLists.txt
index 3ef69cea18f0a..438e486f2ad4c 100644
--- a/mlir/lib/IR/CMakeLists.txt
+++ b/mlir/lib/IR/CMakeLists.txt
@@ -33,6 +33,7 @@ add_mlir_library(MLIRIR
PatternMatch.cpp
Region.cpp
RegionKindInterface.cpp
+ Remarks.cpp
SymbolTable.cpp
TensorEncoding.cpp
Types.cpp
@@ -69,5 +70,10 @@ add_mlir_library(MLIRIR
LINK_LIBS PUBLIC
MLIRSupport
+
+ LINK_COMPONENTS
+ Remarks
+ Core
+ BitstreamReader
)
diff --git a/mlir/lib/IR/MLIRContext.cpp b/mlir/lib/IR/MLIRContext.cpp
index 2d5381d43f863..5a37de1481eb9 100644
--- a/mlir/lib/IR/MLIRContext.cpp
+++ b/mlir/lib/IR/MLIRContext.cpp
@@ -25,12 +25,14 @@
#include "mlir/IR/Location.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/OperationSupport.h"
+#include "mlir/IR/Remarks.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/RWMutex.h"
@@ -133,6 +135,11 @@ class MLIRContextImpl {
//===--------------------------------------------------------------------===//
DiagnosticEngine diagEngine;
+ //===--------------------------------------------------------------------===//
+ // Remark
+ //===--------------------------------------------------------------------===//
+ std::unique_ptr<RemarkEngine> remarkEngine;
+
//===--------------------------------------------------------------------===//
// Options
//===--------------------------------------------------------------------===//
@@ -387,6 +394,34 @@ bool MLIRContext::hasActionHandler() { return (bool)getImpl().actionHandler; }
/// Returns the diagnostic engine for this context.
DiagnosticEngine &MLIRContext::getDiagEngine() { return getImpl().diagEngine; }
+//===----------------------------------------------------------------------===//
+// Remark Handlers
+//===----------------------------------------------------------------------===//
+
+/// Returns the remark engine for this context.
+void MLIRContext::setRemarkEngine(std::unique_ptr<RemarkEngine> engine) {
+ getImpl().remarkEngine = std::move(engine);
+}
+
+std::unique_ptr<RemarkEngine> &MLIRContext::getRemarkEngine() {
+ return getImpl().remarkEngine;
+}
+
+void MLIRContext::setupOptimizationRemarks(
+ StringRef outputPath, StringRef outputFormat, bool printAsEmitRemarks,
+ const std::optional<std::string> &categoryPassName,
+ const std::optional<std::string> &categoryMissName,
+ const std::optional<std::string> &categoryAnalysisName,
+ const std::optional<std::string> &categoryFailedName) {
+ auto engine = std::make_unique<RemarkEngine>(
+ printAsEmitRemarks, categoryPassName, categoryMissName,
+ categoryAnalysisName, categoryFailedName);
+ llvm::Error e = engine->initialize(outputPath, outputFormat);
+ if (e)
+ llvm::report_fatal_error("Failed to initialize remark engine");
+ setRemarkEngine(std::move(engine));
+}
+
//===----------------------------------------------------------------------===//
// Dialect and Operation Registration
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/IR/Remarks.cpp b/mlir/lib/IR/Remarks.cpp
new file mode 100644
index 0000000000000..4d2aa95236f68
--- /dev/null
+++ b/mlir/lib/IR/Remarks.cpp
@@ -0,0 +1,296 @@
+//===- Remarks.cpp - MLIR Remarks ---------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/Remarks.h"
+
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/Value.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/IR/LLVMRemarkStreamer.h"
+#include "llvm/Remarks/RemarkFormat.h"
+#include "llvm/Support/FileSystem.h"
+
+using namespace mlir;
+
+//------------------------------------------------------------------------------
+// RemarkBase
+//------------------------------------------------------------------------------
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, Value value)
+ : key(std::string(key)) {
+
+ llvm::raw_string_ostream rss(val);
+ rss << value;
+}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, Type type)
+ : key(std::string(key)) {
+ llvm::raw_string_ostream os(val);
+ os << type;
+}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, StringRef s)
+ : key(std::string(key)), val(s.str()) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, int n)
+ : key(std::string(key)), val(llvm::itostr(n)) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, float n)
+ : key(std::string(key)), val(std::to_string(n)) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, long n)
+ : key(std::string(key)), val(llvm::itostr(n)) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, long long n)
+ : key(std::string(key)), val(llvm::itostr(n)) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, unsigned n)
+ : key(std::string(key)), val(llvm::utostr(n)) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, unsigned long n)
+ : key(std::string(key)), val(llvm::utostr(n)) {}
+
+RemarkBase::RemarkKeyValue::RemarkKeyValue(StringRef key, unsigned long long n)
+ : key(std::string(key)), val(llvm::utostr(n)) {}
+
+void RemarkBase::print(llvm::DiagnosticPrinter &dp) const {
+ std::string str;
+ llvm::raw_string_ostream os(str);
+ getLocation()->print(os);
+ os.flush();
+ dp << str << ": " << getMsg();
+}
+
+void RemarkBase::print() const { emitError(getLocation(), getMsg()); }
+
+void RemarkBase::insert(StringRef s) { args.emplace_back(s); }
+
+void RemarkBase::insert(RemarkKeyValue a) { args.push_back(std::move(a)); }
+
+void RemarkBase::insert(SetIsVerbose v) { isVerboseRemark = true; }
+
+std::string RemarkBase::getMsg() const {
+ std::string str;
+ llvm::raw_string_ostream rss(str);
+ for (const RemarkBase::RemarkKeyValue &arg :
+ llvm::make_range(args.begin(), args.end()))
+ rss << arg.val;
+ return str;
+}
+
+llvm::remarks::Type RemarkBase::getRemarkType() const {
+ switch (remarkKind) {
+ case RemarkKind::OptimizationRemarkUnknown:
+ return llvm::remarks::Type::Unknown;
+ case RemarkKind::OptimizationRemarkPass:
+ return llvm::remarks::Type::Passed;
+ case RemarkKind::OptimizationRemarkMissed:
+ return llvm::remarks::Type::Missed;
+ case RemarkKind::OptimizationRemarkFailure:
+ return llvm::remarks::Type::Failure;
+ case RemarkKind::OptimizationRemarkAnalysis:
+ return llvm::remarks::Type::Analysis;
+ }
+ llvm_unreachable("Unknown remark kind");
+}
+
+llvm::remarks::RemarkLocation RemarkBase::getRemarkLocation() const {
+ if (auto flc = dyn_cast<FileLineColLoc>(getLocation())) {
+ auto *buf = new std::string("./" + flc.getFilename().str());
+ return {*buf, flc.getLine(), flc.getColumn()};
+ }
+ return {"<unknown file>", 0, 0};
+}
+
+llvm::remarks::Remark RemarkBase::generateRemark() const {
+ llvm::remarks::Remark r; // The result.
+ r.RemarkType = getRemarkType();
+ r.PassName = getPassName();
+ r.RemarkName = getRemarkName();
+ r.FunctionName = getFunction();
+ r.Loc = getRemarkLocation();
+ for (const RemarkBase::RemarkKeyValue &arg : getArgs()) {
+ r.Args.emplace_back();
+ r.Args.back().Key = arg.key;
+ r.Args.back().Val = arg.val;
+ }
+ return r;
+}
+
+//===----------------------------------------------------------------------===//
+// InFlightRemark
+//===----------------------------------------------------------------------===//
+
+InFlightRemark::~InFlightRemark() {
+ if (remark)
+ if (owner) {
+ owner->report(std::move(*remark));
+ }
+ owner = nullptr;
+}
+
+//===----------------------------------------------------------------------===//
+// Remark Engine
+//===----------------------------------------------------------------------===//
+
+template <typename RemarkT, typename... Args>
+InFlightRemark RemarkEngine::makeRemark(Args &&...args) {
+ return InFlightRemark(*this, new RemarkT(std::forward<Args>(args)...));
+}
+
+template <typename RemarkT>
+InFlightRemark
+RemarkEngine::emitIfEnabled(Location loc, StringRef passName,
+ StringRef category,
+ bool (RemarkEngine::*isEnabled)(StringRef) const) {
+ return (this->*isEnabled)(category)
+ ? makeRemark<RemarkT>(loc, category, passName)
+ : InFlightRemark{};
+}
+
+bool RemarkEngine::isMissedOptRemarkEnabled(StringRef categoryName) const {
+ return missFilter && missFilter->match(categoryName);
+}
+
+bool RemarkEngine::isPassedOptRemarkEnabled(StringRef categoryName) const {
+ return passFilter && passFilter->match(categoryName);
+}
+
+bool RemarkEngine::isAnalysisOptRemarkEnabled(StringRef categoryName) const {
+ return analysisFilter && analysisFilter->match(categoryName);
+}
+
+bool RemarkEngine::isFailedOptRemarkEnabled(StringRef categoryName) const {
+ return failedFilter && failedFilter->match(categoryName);
+}
+
+InFlightRemark RemarkEngine::emitOptimizationRemark(Location loc,
+ StringRef passName,
+ StringRef category) {
+ return emitIfEnabled<OptRemarkPass>(loc, passName, category,
+ &RemarkEngine::isPassedOptRemarkEnabled);
+}
+
+InFlightRemark RemarkEngine::emitOptimizationRemarkMiss(Location loc,
+ StringRef passName,
+ StringRef category) {
+ return emitIfEnabled<OptRemarkMissed>(
+ loc, passName, category, &RemarkEngine::isMissedOptRemarkEnabled);
+}
+
+InFlightRemark RemarkEngine::emitOptimizationRemarkFailure(Location loc,
+ StringRef passName,
+ StringRef category) {
+ return emitIfEnabled<OptRemarkFailure>(
+ loc, passName, category, &RemarkEngine::isFailedOptRemarkEnabled);
+}
+
+InFlightRemark
+RemarkEngine::emitOptimizationRemarkAnalysis(Location loc, StringRef passName,
+ StringRef category) {
+ return emitIfEnabled<OptRemarkAnalysis>(
+ loc, passName, category, &RemarkEngine::isAnalysisOptRemarkEnabled);
+}
+
+//===----------------------------------------------------------------------===//
+// RemarkStreamer
+//===----------------------------------------------------------------------===//
+void MLIRRemarkStreamer::streamOptimizationRemark(const RemarkBase &remark) {
+ if (!remarkStreamer.matchesFilter(remark.getPassName()))
+ return;
+
+ // First, convert the diagnostic to a remark.
+ llvm::remarks::Remark r = remark.generateRemark();
+ // Then, emit the remark through the serializer.
+ remarkStreamer.getSerializer().emit(r);
+}
+//===----------------------------------------------------------------------===//
+// Remarkengine
+//===----------------------------------------------------------------------===//
+
+void RemarkEngine::report(const RemarkBase &&diag) {
+ // Stream the remark
+ if (getLLVMRemarkStreamer() && remarksFile)
+ getLLVMRemarkStreamer()->streamOptimizationRemark(diag);
+
+ // Print using MLIR's diagnostic
+ if (printAsEmitRemarks) {
+ emitRemark(diag.getLocation(), diag.getMsg());
+ }
+}
+
+RemarkEngine::~RemarkEngine() {
+ if (remarksFile) {
+ remarksFile->keep();
+ remarksFile.reset();
+ }
+ setMainRemarkStreamer(nullptr);
+ setLLVMRemarkStreamer(nullptr);
+}
+
+llvm::Error RemarkEngine::initialize(StringRef outputPath,
+ StringRef outputFormat) {
+ if (remarksFile) {
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "RemarkEngine is already initialized with an output file");
+ }
+
+ auto fFormatOrErr = llvm::remarks::parseFormat(outputFormat);
+ if (!fFormatOrErr) {
+ return fFormatOrErr.takeError();
+ }
+ llvm::remarks::Format fFormat = *fFormatOrErr;
+ std::error_code errCode;
+ llvm::sys::fs::OpenFlags fFlags = (fFormat == llvm::remarks::Format::YAML)
+ ? llvm::sys::fs::OF_Text
+ : llvm::sys::fs::OF_None;
+
+ remarksFile =
+ std::make_unique<llvm::ToolOutputFile>(outputPath, errCode, fFlags);
+ if (errCode) {
+ remarksFile.reset(); // force an empty optional
+ return llvm::createStringError(errCode, "Failed to open remarks file");
+ }
+
+ auto remarkSerializer = llvm::remarks::createRemarkSerializer(
+ *fFormatOrErr, llvm::remarks::SerializerMode::Separate,
+ remarksFile->os());
+ if (llvm::Error err = remarkSerializer.takeError()) {
+ return err;
+ }
+
+ // Create the main remark streamer.
+ setMainRemarkStreamer(std::make_unique<llvm::remarks::RemarkStreamer>(
+ std::move(*remarkSerializer), outputFormat));
+
+ setLLVMRemarkStreamer(
+ std::make_unique<MLIRRemarkStreamer>(*getMainRemarkStreamer()));
+
+ return llvm::ErrorSuccess();
+}
+
+RemarkEngine::RemarkEngine(bool printAsEmitRemarks,
+ std::optional<std::string> categoryPassName,
+ std::optional<std::string> categoryMissName,
+ std::optional<std::string> categoryAnalysisName,
+ std::optional<std::string> categoryFailedName)
+ : printAsEmitRemarks(printAsEmitRemarks) {
+ if (categoryPassName)
+ passFilter = llvm::Regex(categoryPassName.value());
+ if (categoryMissName)
+ missFilter = llvm::Regex(categoryMissName.value());
+ if (categoryAnalysisName)
+ analysisFilter = llvm::Regex(categoryAnalysisName.value());
+ if (categoryFailedName)
+ failedFilter = llvm::Regex(categoryFailedName.value());
+}
diff --git a/mlir/unittests/IR/CMakeLists.txt b/mlir/unittests/IR/CMakeLists.txt
index a46e64718dab9..07b242f7440bb 100644
--- a/mlir/unittests/IR/CMakeLists.txt
+++ b/mlir/unittests/IR/CMakeLists.txt
@@ -14,6 +14,7 @@ add_mlir_unittest(MLIRIRTests
MemrefLayoutTest.cpp
OperationSupportTest.cpp
PatternMatchTest.cpp
+ RemarkTest.cpp
ShapedTypeTest.cpp
SymbolTableTest.cpp
TypeTest.cpp
diff --git a/mlir/unittests/IR/RemarkTest.cpp b/mlir/unittests/IR/RemarkTest.cpp
new file mode 100644
index 0000000000000..8abddb44fd428
--- /dev/null
+++ b/mlir/unittests/IR/RemarkTest.cpp
@@ -0,0 +1,106 @@
+//===- RemarkTest.cpp - Remark unit tests -------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/Remarks.h"
+#include "mlir/Support/TypeID.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/YAMLParser.h"
+#include "gtest/gtest.h"
+#include <optional>
+
+using namespace llvm;
+using namespace mlir;
+using namespace mlir::detail;
+
+namespace {
+
+TEST(Remark, TestOutputOptimizationRemark) {
+ const auto *pass1Msg = "My message";
+ const auto *pass2Msg = "My another message";
+ const auto *pass3Msg = "Do not show this message";
+
+ auto *context = new MLIRContext();
+
+ std::string categoryLoopunroll("LoopUnroll");
+ std::string categoryInline("Inliner");
+ std::string myPassname1("myPass1");
+ std::string myPassname2("myPass2");
+ std::string funcName("myFunc");
+ std::string yamlFile = "/tmp/remarks.yaml";
+
+ Location loc = UnknownLoc::get(context);
+
+ // Setup the remark engine
+ context->setupOptimizationRemarks(yamlFile, "yaml", true, categoryLoopunroll,
+ std::nullopt, std::nullopt,
+ categoryLoopunroll);
+
+ // Remark 1: pass, category LoopUnroll
+ reportOptimizationPass(loc, categoryLoopunroll, myPassname1) << pass1Msg;
+ // Remark 2: failure, category LoopUnroll
+ reportOptimizationFail(loc, categoryLoopunroll, myPassname2) << pass2Msg;
+ // Remark 3: pass, category Inline (should not be printed)
+ reportOptimizationPass(loc, categoryInline, myPassname1) << pass3Msg;
+
+ delete context;
+
+ // Read the file
+ auto bufferOrErr = MemoryBuffer::getFile(yamlFile);
+ ASSERT_TRUE(static_cast<bool>(bufferOrErr)) << "Failed to open remarks file";
+ std::string content = bufferOrErr.get()->getBuffer().str();
+
+ // Remark 1: pass, should be printed
+ EXPECT_NE(content.find("--- !Passed"), std::string::npos);
+ EXPECT_NE(content.find("Pass: " + categoryLoopunroll),
+ std::string::npos);
+ EXPECT_NE(content.find("Name: " + myPassname1), std::string::npos);
+ EXPECT_NE(content.find("String: " + std::string(pass1Msg)),
+ std::string::npos);
+
+ // Remark 2: failure, should be printed
+ EXPECT_NE(content.find("--- !Failure"), std::string::npos);
+ EXPECT_NE(content.find("Name: " + myPassname2), std::string::npos);
+ EXPECT_NE(content.find("String: " + std::string(pass2Msg)),
+ std::string::npos);
+
+ // Remark 3: pass, category Inline (should not be printed)
+ EXPECT_EQ(content.find("String: " + std::string(pass3Msg)),
+ std::string::npos);
+}
+
+TEST(Remark, TestNoOutputOptimizationRemark) {
+ const auto *pass1Msg = "My message";
+
+ auto *context = new MLIRContext();
+
+ std::string categoryFailName("myImportantCategory");
+ std::string myPassname1("myPass1");
+ std::string funcName("myFunc");
+ std::string yamlFile = "/tmp/remarks.yaml";
+
+ std::error_code ec =
+ llvm::sys::fs::remove(yamlFile, /*IgnoreNonExisting=*/true);
+ if (ec) {
+ FAIL() << "Failed to remove file " << yamlFile << ": " << ec.message();
+ }
+
+ Location loc = UnknownLoc::get(context);
+ reportOptimizationFail(loc, categoryFailName, myPassname1) << pass1Msg;
+
+ delete context;
+
+ // No setup, so no output file should be created
+ // check!
+ bool fileExists = llvm::sys::fs::exists(yamlFile);
+ EXPECT_FALSE(fileExists)
+ << "Expected no YAML file to be created without setupOptimizationRemarks";
+}
+
+} // namespace
>From 0aae05128946d52e5f5dd88fed5baaca8b510088 Mon Sep 17 00:00:00 2001
From: Guray Ozen <gozen at nvidia.com>
Date: Thu, 7 Aug 2025 11:19:47 +0000
Subject: [PATCH 2/2] address copilot
---
mlir/include/mlir/IR/Remarks.h | 1 -
mlir/lib/IR/Remarks.cpp | 16 +++++++---------
mlir/unittests/IR/RemarkTest.cpp | 17 ++++++++++++-----
3 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/mlir/include/mlir/IR/Remarks.h b/mlir/include/mlir/IR/Remarks.h
index 8f7e7a900e7f1..240857b388587 100644
--- a/mlir/include/mlir/IR/Remarks.h
+++ b/mlir/include/mlir/IR/Remarks.h
@@ -82,7 +82,6 @@ class RemarkBase : public llvm::DiagnosticInfo {
virtual bool isEnabled() const = 0;
Location getLocation() const { return loc; }
- llvm::remarks::RemarkLocation getRemarkLocation() const;
/// Diagnostic -> Remark
llvm::remarks::Remark generateRemark() const;
diff --git a/mlir/lib/IR/Remarks.cpp b/mlir/lib/IR/Remarks.cpp
index 4d2aa95236f68..c0bae3915f0e0 100644
--- a/mlir/lib/IR/Remarks.cpp
+++ b/mlir/lib/IR/Remarks.cpp
@@ -103,21 +103,19 @@ llvm::remarks::Type RemarkBase::getRemarkType() const {
llvm_unreachable("Unknown remark kind");
}
-llvm::remarks::RemarkLocation RemarkBase::getRemarkLocation() const {
- if (auto flc = dyn_cast<FileLineColLoc>(getLocation())) {
- auto *buf = new std::string("./" + flc.getFilename().str());
- return {*buf, flc.getLine(), flc.getColumn()};
- }
- return {"<unknown file>", 0, 0};
-}
-
llvm::remarks::Remark RemarkBase::generateRemark() const {
+ auto locLambda = [&]() -> llvm::remarks::RemarkLocation {
+ if (auto flc = dyn_cast<FileLineColLoc>(getLocation()))
+ return {flc.getFilename(), flc.getLine(), flc.getColumn()};
+ return {"<unknown file>", 0, 0};
+ };
+
llvm::remarks::Remark r; // The result.
r.RemarkType = getRemarkType();
r.PassName = getPassName();
r.RemarkName = getRemarkName();
r.FunctionName = getFunction();
- r.Loc = getRemarkLocation();
+ r.Loc = locLambda();
for (const RemarkBase::RemarkKeyValue &arg : getArgs()) {
r.Args.emplace_back();
r.Args.back().Key = arg.key;
diff --git a/mlir/unittests/IR/RemarkTest.cpp b/mlir/unittests/IR/RemarkTest.cpp
index 8abddb44fd428..91bb6d82a6920 100644
--- a/mlir/unittests/IR/RemarkTest.cpp
+++ b/mlir/unittests/IR/RemarkTest.cpp
@@ -33,8 +33,12 @@ TEST(Remark, TestOutputOptimizationRemark) {
std::string myPassname1("myPass1");
std::string myPassname2("myPass2");
std::string funcName("myFunc");
- std::string yamlFile = "/tmp/remarks.yaml";
-
+ SmallString<64> tmpPathStorage;
+ sys::fs::createUniquePath("remarks-%%%%%%.yaml", tmpPathStorage,
+ /*MakeAbsolute=*/true);
+ std::string yamlFile =
+ std::string(tmpPathStorage.data(), tmpPathStorage.size());
+ ASSERT_FALSE(yamlFile.empty());
Location loc = UnknownLoc::get(context);
// Setup the remark engine
@@ -77,14 +81,17 @@ TEST(Remark, TestOutputOptimizationRemark) {
TEST(Remark, TestNoOutputOptimizationRemark) {
const auto *pass1Msg = "My message";
-
auto *context = new MLIRContext();
std::string categoryFailName("myImportantCategory");
std::string myPassname1("myPass1");
std::string funcName("myFunc");
- std::string yamlFile = "/tmp/remarks.yaml";
-
+ SmallString<64> tmpPathStorage;
+ sys::fs::createUniquePath("remarks-%%%%%%.yaml", tmpPathStorage,
+ /*MakeAbsolute=*/true);
+ std::string yamlFile =
+ std::string(tmpPathStorage.data(), tmpPathStorage.size());
+ ASSERT_FALSE(yamlFile.empty());
std::error_code ec =
llvm::sys::fs::remove(yamlFile, /*IgnoreNonExisting=*/true);
if (ec) {
More information about the Mlir-commits
mailing list