[Mlir-commits] [mlir] 3d519f5 - [MLIR][CAPI][Python] Add C API and Python bindings for the remark engine (#229094)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Oct 10 04:34:17 PDT 2026


Author: Guray Ozen
Date: 2026-10-10T13:34:10+02:00
New Revision: 3d519f5eee97ad32db4882eab8fda86ebf8faee9

URL: https://github.com/llvm/llvm-project/commit/3d519f5eee97ad32db4882eab8fda86ebf8faee9
DIFF: https://github.com/llvm/llvm-project/commit/3d519f5eee97ad32db4882eab8fda86ebf8faee9.diff

LOG: [MLIR][CAPI][Python] Add C API and Python bindings for the remark engine (#229094)

Expose the optimization remark engine (`mlir/IR/Remarks.h`) through the
C API and the Python bindings, so clients outside C++ can enable it,
receive the reported remarks and emit their own.

### C API (`mlir-c/Remarks.h`)
- `mlirContextEnableOptimizationRemarks{,ToFile,WithCallback}` select
the categories (one regex per kind plus `all`), the emitting policy
(`all` or `final`) and the sink: MLIR remark diagnostics, a
YAML/bitstream file, or a callback invoked with an opaque `MlirRemark`.
- `mlirContextFinalizeOptimizationRemarks` flushes postponed remarks,
writes the file and drops the engine; `mlirContextHasRemarkEngine`
queries it.
- `MlirRemark` accessors (kind, remark/category/function names,
location, id, key/value arguments, print), valid for the duration of the
callback.
- `mlirEmitOptimizationRemark` emits a remark at a location through the
engine of its context.

### Python bindings
- `Context.enable_remarks(policy=, format=, output_file=, all_filter=,
passed_filter=, missed_filter=, analysis_filter=, failed_filter=,
callback=, print_as_emit_remarks=)`, `Context.finalize_remarks()`,
`Context.remarks_enabled`.
- `ir.Remark` and `ir.RemarkKind`: a remark wrapped for the duration of
the callback and invalidated afterwards, like `ir.Diagnostic`.
- `Location.emit_remark(kind, remark_name, category=, sub_category=,
function_name=, message=, args=)`.

The filter semantics follow `mlir-opt`'s `--remarks-filter` /
`--remark-policy` / `--remark-format`: every category filter reaches the
engine as a (possibly empty) string, so a kind is reported when its own
filter or `all` matches.

### Tests
`mlir/test/CAPI/remarks.c` (`mlir-capi-remarks-test`) and
`mlir/test/python/ir/remarks.py`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply at anthropic.com>

Added: 
    mlir/include/mlir-c/Remarks.h
    mlir/include/mlir/CAPI/Remarks.h
    mlir/lib/Bindings/Python/IRRemarks.cpp
    mlir/lib/CAPI/IR/Remarks.cpp
    mlir/test/CAPI/remarks.c
    mlir/test/python/ir/remarks.py

Modified: 
    mlir/include/mlir/Bindings/Python/IRCore.h
    mlir/lib/Bindings/Python/IRCore.cpp
    mlir/lib/Bindings/Python/MainModule.cpp
    mlir/lib/CAPI/IR/CMakeLists.txt
    mlir/python/CMakeLists.txt
    mlir/test/CAPI/CMakeLists.txt
    mlir/test/CMakeLists.txt
    mlir/test/lit.cfg.py

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir-c/Remarks.h b/mlir/include/mlir-c/Remarks.h
new file mode 100644
index 0000000000000..f60051d422a96
--- /dev/null
+++ b/mlir/include/mlir-c/Remarks.h
@@ -0,0 +1,185 @@
+//===-- mlir-c/Remarks.h - MLIR Remark Engine C API ---------------*- 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 header declares the C interface to the MLIR optimization remark engine:
+// enabling the engine on a context with category filters, an emitting policy
+// and a sink (MLIR diagnostics, a YAML/bitstream file, or a callback),
+// emitting remarks, and inspecting a remark from inside a callback.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_C_REMARKS_H
+#define MLIR_C_REMARKS_H
+
+#include "mlir-c/IR.h"
+#include "mlir-c/Support.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//===----------------------------------------------------------------------===//
+// Opaque types and enums.
+//===----------------------------------------------------------------------===//
+
+/// An opaque reference to a remark. A remark is only valid for the duration of
+/// the callback it is passed to; it must not be stored.
+struct MlirRemark {
+  const void *ptr;
+};
+typedef struct MlirRemark MlirRemark;
+
+/// The outcome a remark describes (see mlir::remark::RemarkKind).
+enum MlirRemarkKind {
+  MlirRemarkKindUnknown,
+  /// An optimization was applied.
+  MlirRemarkKindPassed,
+  /// A profitable optimization opportunity was found but not applied.
+  MlirRemarkKindMissed,
+  /// The optimization was attempted but failed.
+  MlirRemarkKindFailure,
+  /// Informational context without a pass/fail outcome.
+  MlirRemarkKindAnalysis
+};
+typedef enum MlirRemarkKind MlirRemarkKind;
+
+/// The emitting policy of the remark engine.
+enum MlirRemarkPolicy {
+  /// Report every remark as it is emitted.
+  MlirRemarkPolicyAll,
+  /// Postpone remarks and report the final set when the engine is finalized,
+  /// grouping related remarks under their parents.
+  MlirRemarkPolicyFinal
+};
+typedef enum MlirRemarkPolicy MlirRemarkPolicy;
+
+/// The serialization format of a remark output file.
+enum MlirRemarkFileFormat {
+  MlirRemarkFileFormatYAML,
+  MlirRemarkFileFormatBitstream
+};
+typedef enum MlirRemarkFileFormat MlirRemarkFileFormat;
+
+/// Regular expressions (llvm::Regex syntax, anchored by the engine) selecting
+/// the remark categories to report: `all` applies to every kind, the others to
+/// one kind each. A string of length 0 means the filter is not set, so the
+/// corresponding kind is not reported unless `all` matches.
+struct MlirRemarkCategories {
+  MlirStringRef all;
+  MlirStringRef passed;
+  MlirStringRef missed;
+  MlirStringRef analysis;
+  MlirStringRef failed;
+};
+typedef struct MlirRemarkCategories MlirRemarkCategories;
+
+/// Callback receiving every remark the engine reports.
+typedef void (*MlirRemarkCallback)(MlirRemark remark, void *userData);
+
+//===----------------------------------------------------------------------===//
+// Enabling and finalizing the engine of a context.
+//===----------------------------------------------------------------------===//
+
+/// Enables the remark engine on the context without a streamer; when
+/// `printAsEmitRemarks` is set, every reported remark is emitted as an MLIR
+/// remark diagnostic at its location. Fails when the context already has a
+/// remark engine (finalize it first).
+MLIR_CAPI_EXPORTED MlirLogicalResult mlirContextEnableOptimizationRemarks(
+    MlirContext context, MlirRemarkCategories categories,
+    MlirRemarkPolicy policy, bool printAsEmitRemarks);
+
+/// Enables the remark engine on the context, streaming the reported remarks to
+/// the file at `path` in `format`. The file is written when the engine is
+/// finalized. Fails when the file cannot be created or the context already has
+/// a remark engine.
+MLIR_CAPI_EXPORTED MlirLogicalResult mlirContextEnableOptimizationRemarksToFile(
+    MlirContext context, MlirRemarkCategories categories,
+    MlirRemarkPolicy policy, MlirRemarkFileFormat format, MlirStringRef path,
+    bool printAsEmitRemarks);
+
+/// Enables the remark engine on the context, delivering every reported remark
+/// to `callback`. `userData` is passed to the callback unchanged and released
+/// through `deleteUserData` (may be null) when the engine is finalized or the
+/// context is destroyed. Fails when the context already has a remark engine.
+MLIR_CAPI_EXPORTED MlirLogicalResult
+mlirContextEnableOptimizationRemarksWithCallback(
+    MlirContext context, MlirRemarkCategories categories,
+    MlirRemarkPolicy policy, MlirRemarkCallback callback, void *userData,
+    void (*deleteUserData)(void *), bool printAsEmitRemarks);
+
+/// Finalizes and removes the remark engine of the context: postponed remarks
+/// are reported, the output file is written and the callback's user data is
+/// released. Does nothing when no engine is enabled.
+MLIR_CAPI_EXPORTED void
+mlirContextFinalizeOptimizationRemarks(MlirContext context);
+
+/// Returns whether the context has a remark engine enabled.
+MLIR_CAPI_EXPORTED bool mlirContextHasRemarkEngine(MlirContext context);
+
+//===----------------------------------------------------------------------===//
+// Remark accessors (valid only inside a remark callback).
+//===----------------------------------------------------------------------===//
+
+/// Returns the kind of the remark.
+MLIR_CAPI_EXPORTED MlirRemarkKind mlirRemarkGetKind(MlirRemark remark);
+
+/// Returns the name identifying the remark.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetRemarkName(MlirRemark remark);
+
+/// Returns the category of the remark (the subject of the filters).
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetCategoryName(MlirRemark remark);
+
+/// Returns the combined `category:subcategory` name of the remark.
+MLIR_CAPI_EXPORTED MlirStringRef
+mlirRemarkGetFullCategoryName(MlirRemark remark);
+
+/// Returns the name of the function the remark refers to.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetFunctionName(MlirRemark remark);
+
+/// Returns the location of the remark.
+MLIR_CAPI_EXPORTED MlirLocation mlirRemarkGetLocation(MlirRemark remark);
+
+/// Returns the unique id of the remark within its engine (0 when unset).
+MLIR_CAPI_EXPORTED uint64_t mlirRemarkGetId(MlirRemark remark);
+
+/// Returns the number of key/value arguments attached to the remark.
+MLIR_CAPI_EXPORTED intptr_t mlirRemarkGetNumArgs(MlirRemark remark);
+
+/// Returns the key of the `pos`-th argument of the remark.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetArgKey(MlirRemark remark,
+                                                     intptr_t pos);
+
+/// Returns the value of the `pos`-th argument of the remark.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetArgValue(MlirRemark remark,
+                                                       intptr_t pos);
+
+/// Prints the remark in its textual form, `[Kind] name | Category:... |
+/// Function=... | key=value, ...` (without its location).
+MLIR_CAPI_EXPORTED void
+mlirRemarkPrint(MlirRemark remark, MlirStringCallback callback, void *userData);
+
+//===----------------------------------------------------------------------===//
+// Emission.
+//===----------------------------------------------------------------------===//
+
+/// Emits a remark of `kind` at `location` through the remark engine of the
+/// location's context. `message` (when non-empty) and the `numArgs` key/value
+/// pairs become the arguments of the remark. Returns false, emitting nothing,
+/// when the context has no remark engine, the category is filtered out, or
+/// `kind` is MlirRemarkKindUnknown.
+MLIR_CAPI_EXPORTED bool mlirEmitOptimizationRemark(
+    MlirLocation location, MlirRemarkKind kind, MlirStringRef remarkName,
+    MlirStringRef categoryName, MlirStringRef subCategoryName,
+    MlirStringRef functionName, MlirStringRef message, intptr_t numArgs,
+    const MlirStringRef *argKeys, const MlirStringRef *argValues);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MLIR_C_REMARKS_H

diff  --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index b28e6ec606d52..3edf12fbafee1 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -26,6 +26,7 @@
 #include "mlir-c/ExtensibleDialect.h"
 #include "mlir-c/IR.h"
 #include "mlir-c/IntegerSet.h"
+#include "mlir-c/Remarks.h"
 #include "mlir-c/Support.h"
 #include "mlir-c/Transforms.h"
 #include "mlir/Bindings/Python/Nanobind.h"
@@ -206,6 +207,26 @@ class MLIR_PYTHON_API_EXPORTED PyThreadPool {
   MlirLlvmThreadPool threadPool;
 };
 
+/// The kinds, emitting policies and file formats of the remark engine
+/// (mlir-c/Remarks.h).
+enum class PyRemarkKind : std::underlying_type_t<MlirRemarkKind> {
+  Unknown = MlirRemarkKindUnknown,
+  Passed = MlirRemarkKindPassed,
+  Missed = MlirRemarkKindMissed,
+  Failure = MlirRemarkKindFailure,
+  Analysis = MlirRemarkKindAnalysis
+};
+
+enum class PyRemarkPolicy : std::underlying_type_t<MlirRemarkPolicy> {
+  All = MlirRemarkPolicyAll,
+  Final = MlirRemarkPolicyFinal
+};
+
+enum class PyRemarkFormat : std::underlying_type_t<MlirRemarkFileFormat> {
+  YAML = MlirRemarkFileFormatYAML,
+  Bitstream = MlirRemarkFileFormatBitstream
+};
+
 /// Wrapper around MlirContext.
 using PyMlirContextRef = PyObjectRef<PyMlirContext>;
 class MLIR_PYTHON_API_EXPORTED PyMlirContext {
@@ -253,6 +274,26 @@ class MLIR_PYTHON_API_EXPORTED PyMlirContext {
   /// registration object (internally a PyDiagnosticHandler).
   nanobind::object attachDiagnosticHandler(nanobind::object callback);
 
+  /// Enables the optimization remark engine of the context, streaming to
+  /// `outputFile`, to `callback`, or (without either) to MLIR remark
+  /// diagnostics. Raises ValueError on an invalid combination, when an engine
+  /// is already enabled or when the file cannot be written.
+  void enableRemarks(PyRemarkPolicy policy,
+                     const std::optional<std::string> &outputFile,
+                     PyRemarkFormat format, const std::string &allFilter,
+                     const std::string &passedFilter,
+                     const std::string &missedFilter,
+                     const std::string &analysisFilter,
+                     const std::string &failedFilter, nanobind::object callback,
+                     std::optional<bool> printAsEmitRemarks);
+  /// Finalizes and removes the remark engine; no-op when none is enabled.
+  void finalizeRemarks();
+  /// Whether a remark engine is enabled on the context.
+  bool getRemarksEnabled();
+  /// Whether `context` still has a live Python wrapper (false while it is
+  /// being destroyed).
+  static bool isLiveContext(MlirContext context);
+
   /// Controls whether error diagnostics should be propagated to diagnostic
   /// handlers, instead of being captured by `ErrorCapture`.
   void setEmitErrorDiagnostics(bool value) { emitErrorDiagnostics = value; }
@@ -410,6 +451,32 @@ class MLIR_PYTHON_API_EXPORTED PyDiagnostic {
   bool valid = true;
 };
 
+/// Wrapper around an MlirRemark as delivered to a remark callback. Like a
+/// PyDiagnostic it is only valid for the duration of the callback; accessing
+/// an invalidated remark raises ValueError.
+class MLIR_PYTHON_API_EXPORTED PyRemark {
+public:
+  explicit PyRemark(MlirRemark remark) : remark(remark) {}
+  void invalidate() { valid = false; }
+  bool isValid() const { return valid; }
+  PyRemarkKind getKind() const;
+  nanobind::str getRemarkName() const;
+  nanobind::str getCategoryName() const;
+  nanobind::str getFullCategoryName() const;
+  nanobind::str getFunctionName() const;
+  nanobind::typed<nanobind::object, PyLocation> getLocation() const;
+  uint64_t getId() const;
+  /// The key/value arguments as a list of `(key, value)` tuples.
+  nanobind::list getArgs() const;
+  /// The textual form of the remark, without its location.
+  nanobind::str getMessage() const;
+
+private:
+  void checkValid() const;
+  MlirRemark remark;
+  bool valid = true;
+};
+
 /// Represents a diagnostic handler attached to the context. The handler's
 /// callback will be invoked with PyDiagnostic instances until the detach()
 /// method is called or the context is destroyed. A diagnostic handler can be
@@ -2018,6 +2085,7 @@ class MLIR_PYTHON_API_EXPORTED RecursiveMemoryEffects
 
 MLIR_PYTHON_API_EXPORTED MlirValue getUniqueResult(MlirOperation operation);
 MLIR_PYTHON_API_EXPORTED void populateIRCore(nanobind::module_ &m);
+MLIR_PYTHON_API_EXPORTED void populateIRRemarks(nanobind::module_ &m);
 MLIR_PYTHON_API_EXPORTED void populateRoot(nanobind::module_ &m);
 
 /// Helper for creating an @classmethod.

diff  --git a/mlir/include/mlir/CAPI/Remarks.h b/mlir/include/mlir/CAPI/Remarks.h
new file mode 100644
index 0000000000000..cb477ce0ca982
--- /dev/null
+++ b/mlir/include/mlir/CAPI/Remarks.h
@@ -0,0 +1,23 @@
+//===- Remarks.h - C API Utils for MLIR Remarks -----------------*- 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 declares the conversion between the C API remark type and its C++
+// counterpart.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_CAPI_REMARKS_H
+#define MLIR_CAPI_REMARKS_H
+
+#include "mlir-c/Remarks.h"
+#include "mlir/CAPI/Wrap.h"
+#include "mlir/IR/Remarks.h"
+
+DEFINE_C_API_PTR_METHODS(MlirRemark, const mlir::remark::detail::Remark)
+
+#endif // MLIR_CAPI_REMARKS_H

diff  --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index b6d92dc8064a2..bb0a49ba93ce2 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -17,6 +17,7 @@
 #include "mlir-c/Diagnostics.h"
 #include "mlir-c/ExtensibleDialect.h"
 #include "mlir-c/IR.h"
+#include "mlir-c/Remarks.h"
 #include "mlir-c/Support.h"
 
 #include <array>
@@ -76,7 +77,6 @@ createCustomDialectWrapper(const std::string &dialectNamespace,
 namespace mlir {
 namespace python {
 namespace MLIR_BINDINGS_PYTHON_DOMAIN {
-
 MlirBlock createBlock(
     const nb::typed<nb::sequence, PyType> &pyArgTypes,
     const std::optional<nb::typed<nb::sequence, PyLocation>> &pyArgLocs) {
@@ -932,7 +932,6 @@ PyOperation::~PyOperation() {
 }
 
 namespace {
-
 // Constructs a new object of type T in-place on the Python heap, returning a
 // PyObjectRef to it, loosely analogous to std::make_shared<T>().
 template <typename T, class... Args>
@@ -944,7 +943,6 @@ PyObjectRef<T> makeObjectRef(Args &&...args) {
   nb::inst_mark_ready(instance);
   return PyObjectRef<T>(ptr, std::move(instance));
 }
-
 } // namespace
 
 PyOperationRef PyOperation::createInstance(PyMlirContextRef contextRef,
@@ -2722,13 +2720,11 @@ void PyDynamicOpTraits::RecursiveMemoryEffects::bind(nb::module_ &m) {
       nb::arg("cls"), nb::arg("op_name"),
       nb::arg("context").none() = nb::none());
 }
-
 } // namespace MLIR_BINDINGS_PYTHON_DOMAIN
 } // namespace python
 } // namespace mlir
 
 namespace {
-
 using namespace mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN;
 
 MlirLocation tracebackToLocation(MlirContext ctx) {
@@ -2883,7 +2879,6 @@ maybeGetTracebackLocation(const std::optional<PyLocation> &location) {
 namespace mlir {
 namespace python {
 namespace MLIR_BINDINGS_PYTHON_DOMAIN {
-
 static std::string formatMLIRError(const MLIRError &e) {
   auto locStr = [](const PyLocation &loc) {
     PyPrintAccumulator accum;
@@ -3499,6 +3494,59 @@ void populateIRCore(nb::module_ &m) {
       .def("attach_diagnostic_handler", &PyMlirContext::attachDiagnosticHandler,
            "callback"_a,
            "Attaches a diagnostic handler that will receive callbacks.")
+      .def("enable_remarks", &PyMlirContext::enableRemarks, nb::kw_only(),
+           "policy"_a = PyRemarkPolicy::All,
+           "output_file"_a.none() = nb::none(),
+           "format"_a = PyRemarkFormat::YAML, "all_filter"_a = "",
+           "passed_filter"_a = "", "missed_filter"_a = "",
+           "analysis_filter"_a = "", "failed_filter"_a = "",
+           "callback"_a.none() = nb::none(),
+           "print_as_emit_remarks"_a.none() = nb::none(),
+           R"(
+            Enables the optimization remark engine on this context.
+
+            Remarks are reported by passes (and by `Location.emit_remark`) and
+            selected by the category filters, regular expressions anchored by
+            the engine: `all_filter` applies to every kind, the other filters to
+            one kind each; a kind without a matching filter is not reported.
+
+            The sink is selected by the arguments: `output_file` streams the
+            remarks to a file in `format`, `callback` delivers them to Python,
+            and without either they are emitted as MLIR remark diagnostics.
+
+            Args:
+              policy: `RemarkPolicy.ALL` reports remarks as they are emitted;
+                `RemarkPolicy.FINAL` postpones them until `finalize_remarks()`
+                and reports the final set, grouping related remarks under
+                their parents.
+              output_file: Path of the remark file, written on
+                `finalize_remarks()`.
+              format: The serialization format of `output_file`.
+              all_filter: Category regex applied to every remark kind.
+              passed_filter: Category regex for passed remarks.
+              missed_filter: Category regex for missed remarks.
+              analysis_filter: Category regex for analysis remarks.
+              failed_filter: Category regex for failed remarks.
+              callback: A callable receiving one `Remark` per reported remark;
+                the remark is only valid during the call.
+              print_as_emit_remarks: Also emit every remark as an MLIR remark
+                diagnostic; defaults to True only without an output file or
+                callback.
+
+            Raises:
+              ValueError: When both an output file and a callback are given,
+                when remarks are already enabled, or when the output file
+                cannot be written.)")
+      .def("finalize_remarks", &PyMlirContext::finalizeRemarks,
+           R"(
+            Finalizes and removes the remark engine of this context.
+
+            Postponed remarks (policy "final") are reported, the output file is
+            written and the callback is released. Does nothing when no engine
+            is enabled; `enable_remarks` may be called again afterwards.)")
+      .def_prop_ro(
+          "remarks_enabled", &PyMlirContext::getRemarksEnabled,
+          "Returns True if a remark engine is enabled on this context.")
       .def(
           "enable_multithreading",
           [](PyMlirContext &self, bool enable) {
@@ -3853,6 +3901,46 @@ void populateIRCore(nb::module_ &m) {
 
             Args:
               message: The error message to emit.)")
+      .def(
+          "emit_remark",
+          [](PyLocation &self, PyRemarkKind kind, const std::string &remarkName,
+             const std::string &category, const std::string &subCategory,
+             const std::string &functionName, const std::string &message,
+             const std::vector<std::pair<std::string, std::string>> &args)
+              -> bool {
+            auto remarkKind = static_cast<MlirRemarkKind>(kind);
+            std::vector<MlirStringRef> keys, values;
+            keys.reserve(args.size());
+            values.reserve(args.size());
+            for (const auto &[key, value] : args) {
+              keys.push_back(toMlirStringRef(key));
+              values.push_back(toMlirStringRef(value));
+            }
+            return mlirEmitOptimizationRemark(
+                self, remarkKind, toMlirStringRef(remarkName),
+                toMlirStringRef(category), toMlirStringRef(subCategory),
+                toMlirStringRef(functionName), toMlirStringRef(message),
+                static_cast<intptr_t>(keys.size()), keys.data(), values.data());
+          },
+          "kind"_a, "remark_name"_a, nb::kw_only(), "category"_a = "",
+          "sub_category"_a = "", "function_name"_a = "", "message"_a = "",
+          "args"_a = std::vector<std::pair<std::string, std::string>>(),
+          R"(
+            Emits an optimization remark at this location through the remark
+            engine of the context (see `Context.enable_remarks`).
+
+            Args:
+              kind: The `RemarkKind` of the remark.
+              remark_name: The name identifying the remark.
+              category: The category the filters are matched against.
+              sub_category: An optional sub-category.
+              function_name: The function the remark refers to.
+              message: Free text attached to the remark.
+              args: Key/value pairs attached to the remark.
+
+            Returns:
+              True if the remark was reported; False when no remark engine is
+              enabled or the category is filtered out.)")
       .def(
           "__str__",
           [](PyLocation &self) {

diff  --git a/mlir/lib/Bindings/Python/IRRemarks.cpp b/mlir/lib/Bindings/Python/IRRemarks.cpp
new file mode 100644
index 0000000000000..a870ef48ec672
--- /dev/null
+++ b/mlir/lib/Bindings/Python/IRRemarks.cpp
@@ -0,0 +1,232 @@
+//===- IRRemarks.cpp - Remark engine bindings -----------------------------===//
+//
+// 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-c/Remarks.h"
+#include "mlir/Bindings/Python/IRCore.h"
+
+#include <cstdio>
+#include <optional>
+#include <string>
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+namespace mlir {
+namespace python {
+namespace MLIR_BINDINGS_PYTHON_DOMAIN {
+namespace {
+/// User data of the remark callback installed by `Context.enable_remarks`.
+struct PyRemarkCallbackData {
+  MlirContext context;
+  nb::object callback;
+};
+} // namespace
+
+static nb::str remarkStr(MlirStringRef ref) {
+  return nb::str(ref.data, ref.length);
+}
+
+/// Forwards a reported remark to the Python callable in `userData`; the
+/// PyRemark is invalidated once the callable returns, like a PyDiagnostic.
+static void pyRemarkCallback(MlirRemark remark, void *userData) {
+  auto *data = static_cast<PyRemarkCallbackData *>(userData);
+  // Since this can be called from arbitrary C++ contexts, always get the gil.
+  nb::gil_scoped_acquire gil;
+  // A remark postponed until the context's destruction cannot be delivered:
+  // the Python wrapper of the context is already gone.
+  if (!PyMlirContext::isLiveContext(data->context))
+    return;
+  PyRemark *pyRemark = new PyRemark(remark);
+  nb::object pyRemarkObject = nb::cast(pyRemark, nb::rv_policy::take_ownership);
+  try {
+    data->callback(pyRemarkObject);
+  } catch (std::exception &e) {
+    fprintf(stderr, "MLIR Python remark callback raised exception: %s\n",
+            e.what());
+  }
+  pyRemark->invalidate();
+}
+
+static void pyRemarkCallbackDelete(void *userData) {
+  nb::gil_scoped_acquire gil;
+  delete static_cast<PyRemarkCallbackData *>(userData);
+}
+
+bool PyMlirContext::isLiveContext(MlirContext context) {
+  nb::ft_lock_guard lock(live_contexts_mutex);
+  return getLiveContexts().count(context.ptr) != 0;
+}
+
+void PyMlirContext::enableRemarks(
+    PyRemarkPolicy policy, const std::optional<std::string> &outputFile,
+    PyRemarkFormat format, const std::string &allFilter,
+    const std::string &passedFilter, const std::string &missedFilter,
+    const std::string &analysisFilter, const std::string &failedFilter,
+    nb::object callback, std::optional<bool> printAsEmitRemarks) {
+  bool hasCallback = !callback.is_none();
+  if (outputFile && hasCallback)
+    throw nb::value_error(
+        "a remark callback cannot be combined with an output file");
+  if (mlirContextHasRemarkEngine(get())) {
+    throw nb::value_error("remarks are already enabled on this context; call "
+                          "finalize_remarks() first");
+  }
+
+  MlirRemarkCategories categories{
+      toMlirStringRef(allFilter), toMlirStringRef(passedFilter),
+      toMlirStringRef(missedFilter), toMlirStringRef(analysisFilter),
+      toMlirStringRef(failedFilter)};
+  auto remarkPolicy = static_cast<MlirRemarkPolicy>(policy);
+  // Without a sink of its own, the engine prints as MLIR remark diagnostics.
+  bool printAsEmit =
+      printAsEmitRemarks.value_or(!outputFile.has_value() && !hasCallback);
+
+  MlirLogicalResult result;
+  if (outputFile) {
+    result = mlirContextEnableOptimizationRemarksToFile(
+        get(), categories, remarkPolicy,
+        static_cast<MlirRemarkFileFormat>(format), toMlirStringRef(*outputFile),
+        printAsEmit);
+    if (mlirLogicalResultIsFailure(result))
+      throw nb::value_error(
+          ("failed to enable remarks: cannot write '" + *outputFile + "'")
+              .c_str());
+  } else if (hasCallback) {
+    auto *data = new PyRemarkCallbackData{get(), std::move(callback)};
+    result = mlirContextEnableOptimizationRemarksWithCallback(
+        get(), categories, remarkPolicy, pyRemarkCallback, data,
+        pyRemarkCallbackDelete, printAsEmit);
+  } else {
+    result = mlirContextEnableOptimizationRemarks(get(), categories,
+                                                  remarkPolicy, printAsEmit);
+  }
+  if (mlirLogicalResultIsFailure(result))
+    throw nb::value_error("failed to enable remarks");
+}
+
+void PyMlirContext::finalizeRemarks() {
+  mlirContextFinalizeOptimizationRemarks(get());
+}
+
+bool PyMlirContext::getRemarksEnabled() {
+  return mlirContextHasRemarkEngine(get());
+}
+
+void PyRemark::checkValid() const {
+  if (!valid)
+    throw std::invalid_argument("Remark is invalid (used outside of callback)");
+}
+
+PyRemarkKind PyRemark::getKind() const {
+  checkValid();
+  return static_cast<PyRemarkKind>(mlirRemarkGetKind(remark));
+}
+
+nb::str PyRemark::getRemarkName() const {
+  checkValid();
+  return remarkStr(mlirRemarkGetRemarkName(remark));
+}
+
+nb::str PyRemark::getCategoryName() const {
+  checkValid();
+  return remarkStr(mlirRemarkGetCategoryName(remark));
+}
+
+nb::str PyRemark::getFullCategoryName() const {
+  checkValid();
+  return remarkStr(mlirRemarkGetFullCategoryName(remark));
+}
+
+nb::str PyRemark::getFunctionName() const {
+  checkValid();
+  return remarkStr(mlirRemarkGetFunctionName(remark));
+}
+
+nb::typed<nb::object, PyLocation> PyRemark::getLocation() const {
+  checkValid();
+  MlirLocation loc = mlirRemarkGetLocation(remark);
+  MlirContext context = mlirLocationGetContext(loc);
+  return PyLocation(PyMlirContext::forContext(context), loc).maybeDownCast();
+}
+
+uint64_t PyRemark::getId() const {
+  checkValid();
+  return mlirRemarkGetId(remark);
+}
+
+nb::list PyRemark::getArgs() const {
+  checkValid();
+  nb::list args;
+  intptr_t numArgs = mlirRemarkGetNumArgs(remark);
+  for (intptr_t i = 0; i < numArgs; ++i) {
+    args.append(nb::make_tuple(remarkStr(mlirRemarkGetArgKey(remark, i)),
+                               remarkStr(mlirRemarkGetArgValue(remark, i))));
+  }
+  return args;
+}
+
+nb::str PyRemark::getMessage() const {
+  checkValid();
+  std::string message;
+  auto callback = +[](MlirStringRef ref, void *userData) {
+    static_cast<std::string *>(userData)->append(ref.data, ref.length);
+  };
+  mlirRemarkPrint(remark, callback, &message);
+  return nb::str(message.c_str(), message.size());
+}
+
+void populateIRRemarks(nb::module_ &m) {
+  nb::enum_<PyRemarkKind>(m, "RemarkKind")
+      .value("UNKNOWN", PyRemarkKind::Unknown)
+      .value("PASSED", PyRemarkKind::Passed)
+      .value("MISSED", PyRemarkKind::Missed)
+      .value("FAILURE", PyRemarkKind::Failure)
+      .value("ANALYSIS", PyRemarkKind::Analysis);
+
+  nb::enum_<PyRemarkPolicy>(m, "RemarkPolicy")
+      .value("ALL", PyRemarkPolicy::All)
+      .value("FINAL", PyRemarkPolicy::Final);
+
+  nb::enum_<PyRemarkFormat>(m, "RemarkFormat")
+      .value("YAML", PyRemarkFormat::YAML)
+      .value("BITSTREAM", PyRemarkFormat::Bitstream);
+
+  nb::class_<PyRemark>(m, "Remark")
+      .def_prop_ro("kind", &PyRemark::getKind,
+                   "Returns the kind of the remark.")
+      .def_prop_ro("remark_name", &PyRemark::getRemarkName,
+                   "Returns the name identifying the remark.")
+      .def_prop_ro("category_name", &PyRemark::getCategoryName,
+                   "Returns the category of the remark (what the filters "
+                   "match).")
+      .def_prop_ro("full_category_name", &PyRemark::getFullCategoryName,
+                   "Returns the combined `category:subcategory` name.")
+      .def_prop_ro("function_name", &PyRemark::getFunctionName,
+                   "Returns the name of the function the remark refers to.")
+      .def_prop_ro("location", &PyRemark::getLocation,
+                   "Returns the location associated with the remark.")
+      .def_prop_ro("remark_id", &PyRemark::getId,
+                   "Returns the id of the remark within its engine (0 when "
+                   "unset).")
+      .def_prop_ro("args", &PyRemark::getArgs,
+                   "Returns the key/value arguments as a list of tuples.")
+      .def_prop_ro("message", &PyRemark::getMessage,
+                   "Returns the textual form of the remark, `[Kind] name | "
+                   "Category:... | key=value, ...`.")
+      .def(
+          "__str__",
+          [](PyRemark &self) -> nb::str {
+            if (!self.isValid())
+              return nb::str("<Invalid Remark>");
+            return self.getMessage();
+          },
+          "Returns the remark message as a string.");
+}
+} // namespace MLIR_BINDINGS_PYTHON_DOMAIN
+} // namespace python
+} // namespace mlir

diff  --git a/mlir/lib/Bindings/Python/MainModule.cpp b/mlir/lib/Bindings/Python/MainModule.cpp
index 93ce34b2e89f5..f8f6a223c2e46 100644
--- a/mlir/lib/Bindings/Python/MainModule.cpp
+++ b/mlir/lib/Bindings/Python/MainModule.cpp
@@ -25,6 +25,7 @@ void populateIRAttributes(nb::module_ &m);
 void populateIRInterfaces(nb::module_ &m);
 void populateIRTypes(nb::module_ &m);
 void populateIRCore(nb::module_ &m);
+void populateIRRemarks(nb::module_ &m);
 void populateRoot(nb::module_ &m);
 } // namespace MLIR_BINDINGS_PYTHON_DOMAIN
 } // namespace python
@@ -41,6 +42,8 @@ NB_MODULE(_mlir, m) {
   populateRoot(m);
   // Define and populate IR submodule.
   auto irModule = m.def_submodule("ir", "MLIR IR Bindings");
+  // The remark enums are default arguments of the Context bindings.
+  populateIRRemarks(irModule);
   populateIRCore(irModule);
   populateIRAffine(irModule);
   populateIRAttributes(irModule);

diff  --git a/mlir/lib/CAPI/IR/CMakeLists.txt b/mlir/lib/CAPI/IR/CMakeLists.txt
index 8bc76677d2bfd..32b8fa409e105 100644
--- a/mlir/lib/CAPI/IR/CMakeLists.txt
+++ b/mlir/lib/CAPI/IR/CMakeLists.txt
@@ -11,12 +11,14 @@ add_mlir_upstream_c_api_library(MLIRCAPIIR
   IntegerSet.cpp
   IR.cpp
   Pass.cpp
+  Remarks.cpp
   Support.cpp
 
   LINK_LIBS PUBLIC
   MLIRBytecodeWriter
   MLIRIR
   MLIRParser
+  MLIRRemarkStreamer
   MLIRSupport
   MLIRPass
 )

diff  --git a/mlir/lib/CAPI/IR/Remarks.cpp b/mlir/lib/CAPI/IR/Remarks.cpp
new file mode 100644
index 0000000000000..a05a9cbe74ca8
--- /dev/null
+++ b/mlir/lib/CAPI/IR/Remarks.cpp
@@ -0,0 +1,254 @@
+//===- Remarks.cpp - C Interface for the MLIR Remark Engine ---------------===//
+//
+// 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-c/Remarks.h"
+#include "mlir/CAPI/IR.h"
+#include "mlir/CAPI/Remarks.h"
+#include "mlir/CAPI/Support.h"
+#include "mlir/CAPI/Utils.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/Remarks.h"
+#include "mlir/Remark/RemarkStreamer.h"
+#include "llvm/Remarks/RemarkFormat.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#include <cassert>
+#include <memory>
+
+using namespace mlir;
+using mlir::remark::detail::Remark;
+
+//===----------------------------------------------------------------------===//
+// Helpers
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// A streamer forwarding every reported remark to a C callback.
+class CallbackRemarkStreamer : public remark::detail::MLIRRemarkStreamerBase {
+public:
+  CallbackRemarkStreamer(MlirRemarkCallback callback, void *userData,
+                         void (*deleteUserData)(void *))
+      : callback(callback), userData(userData), deleteUserData(deleteUserData) {
+  }
+
+  ~CallbackRemarkStreamer() override {
+    if (deleteUserData)
+      deleteUserData(userData);
+  }
+
+  void streamOptimizationRemark(const Remark &remark) override {
+    callback(wrap(&remark), userData);
+  }
+
+private:
+  MlirRemarkCallback callback;
+  void *userData;
+  void (*deleteUserData)(void *);
+};
+} // namespace
+
+/// Every filter reaches the engine as a (possibly empty) string, as with
+/// mlir-opt's options: a kind is reported when its own filter or `all` matches.
+static remark::RemarkCategories unwrap(MlirRemarkCategories categories) {
+  return remark::RemarkCategories{
+      unwrap(categories.all).str(), unwrap(categories.passed).str(),
+      unwrap(categories.missed).str(), unwrap(categories.analysis).str(),
+      unwrap(categories.failed).str()};
+}
+
+static std::unique_ptr<remark::detail::RemarkEmittingPolicyBase>
+createPolicy(MlirRemarkPolicy policy) {
+  switch (policy) {
+  case MlirRemarkPolicyAll:
+    return std::make_unique<remark::RemarkEmittingPolicyAll>();
+  case MlirRemarkPolicyFinal:
+    return std::make_unique<remark::RemarkEmittingPolicyFinal>();
+  }
+  llvm_unreachable("unknown remark policy");
+}
+
+static llvm::remarks::Format unwrap(MlirRemarkFileFormat format) {
+  switch (format) {
+  case MlirRemarkFileFormatYAML:
+    return llvm::remarks::Format::YAML;
+  case MlirRemarkFileFormatBitstream:
+    return llvm::remarks::Format::Bitstream;
+  }
+  llvm_unreachable("unknown remark file format");
+}
+
+static MlirRemarkKind wrap(remark::RemarkKind kind) {
+  switch (kind) {
+  case remark::RemarkKind::RemarkUnknown:
+    return MlirRemarkKindUnknown;
+  case remark::RemarkKind::RemarkPassed:
+    return MlirRemarkKindPassed;
+  case remark::RemarkKind::RemarkMissed:
+    return MlirRemarkKindMissed;
+  case remark::RemarkKind::RemarkFailure:
+    return MlirRemarkKindFailure;
+  case remark::RemarkKind::RemarkAnalysis:
+    return MlirRemarkKindAnalysis;
+  }
+  llvm_unreachable("unknown remark kind");
+}
+
+//===----------------------------------------------------------------------===//
+// Enabling and finalizing
+//===----------------------------------------------------------------------===//
+
+MlirLogicalResult mlirContextEnableOptimizationRemarks(
+    MlirContext context, MlirRemarkCategories categories,
+    MlirRemarkPolicy policy, bool printAsEmitRemarks) {
+  MLIRContext *ctx = unwrap(context);
+  if (ctx->getRemarkEngine())
+    return mlirLogicalResultFailure();
+  return wrap(remark::enableOptimizationRemarks(
+      *ctx, /*streamer=*/nullptr, createPolicy(policy), unwrap(categories),
+      printAsEmitRemarks));
+}
+
+MlirLogicalResult mlirContextEnableOptimizationRemarksToFile(
+    MlirContext context, MlirRemarkCategories categories,
+    MlirRemarkPolicy policy, MlirRemarkFileFormat format, MlirStringRef path,
+    bool printAsEmitRemarks) {
+  MLIRContext *ctx = unwrap(context);
+  if (ctx->getRemarkEngine())
+    return mlirLogicalResultFailure();
+  return wrap(remark::enableOptimizationRemarksWithLLVMStreamer(
+      *ctx, unwrap(path), unwrap(format), createPolicy(policy),
+      unwrap(categories), printAsEmitRemarks));
+}
+
+MlirLogicalResult mlirContextEnableOptimizationRemarksWithCallback(
+    MlirContext context, MlirRemarkCategories categories,
+    MlirRemarkPolicy policy, MlirRemarkCallback callback, void *userData,
+    void (*deleteUserData)(void *), bool printAsEmitRemarks) {
+  assert(callback && "unexpected null remark callback");
+  MLIRContext *ctx = unwrap(context);
+  if (ctx->getRemarkEngine()) {
+    // The caller handed over `userData`; release it as on success.
+    if (deleteUserData)
+      deleteUserData(userData);
+    return mlirLogicalResultFailure();
+  }
+  return wrap(remark::enableOptimizationRemarks(
+      *ctx,
+      std::make_unique<CallbackRemarkStreamer>(callback, userData,
+                                               deleteUserData),
+      createPolicy(policy), unwrap(categories), printAsEmitRemarks));
+}
+
+void mlirContextFinalizeOptimizationRemarks(MlirContext context) {
+  // The engine destructor reports the postponed remarks, then destroys the
+  // streamer.
+  unwrap(context)->setRemarkEngine(nullptr);
+}
+
+bool mlirContextHasRemarkEngine(MlirContext context) {
+  return unwrap(context)->getRemarkEngine() != nullptr;
+}
+
+//===----------------------------------------------------------------------===//
+// Remark accessors
+//===----------------------------------------------------------------------===//
+
+MlirRemarkKind mlirRemarkGetKind(MlirRemark remark) {
+  return wrap(unwrap(remark)->getRemarkKind());
+}
+
+MlirStringRef mlirRemarkGetRemarkName(MlirRemark remark) {
+  return wrap(unwrap(remark)->getRemarkName());
+}
+
+MlirStringRef mlirRemarkGetCategoryName(MlirRemark remark) {
+  return wrap(unwrap(remark)->getCategoryName());
+}
+
+MlirStringRef mlirRemarkGetFullCategoryName(MlirRemark remark) {
+  return wrap(unwrap(remark)->getCombinedCategoryName());
+}
+
+MlirStringRef mlirRemarkGetFunctionName(MlirRemark remark) {
+  return wrap(unwrap(remark)->getFunction());
+}
+
+MlirLocation mlirRemarkGetLocation(MlirRemark remark) {
+  return wrap(unwrap(remark)->getLocation());
+}
+
+uint64_t mlirRemarkGetId(MlirRemark remark) {
+  return unwrap(remark)->getId().getValue();
+}
+
+intptr_t mlirRemarkGetNumArgs(MlirRemark remark) {
+  return static_cast<intptr_t>(unwrap(remark)->getArgs().size());
+}
+
+MlirStringRef mlirRemarkGetArgKey(MlirRemark remark, intptr_t pos) {
+  ArrayRef<Remark::Arg> args = unwrap(remark)->getArgs();
+  assert(pos >= 0 && static_cast<size_t>(pos) < args.size() &&
+         "remark argument index out of range");
+  return wrap(llvm::StringRef(args[pos].key));
+}
+
+MlirStringRef mlirRemarkGetArgValue(MlirRemark remark, intptr_t pos) {
+  ArrayRef<Remark::Arg> args = unwrap(remark)->getArgs();
+  assert(pos >= 0 && static_cast<size_t>(pos) < args.size() &&
+         "remark argument index out of range");
+  return wrap(llvm::StringRef(args[pos].val));
+}
+
+void mlirRemarkPrint(MlirRemark remark, MlirStringCallback callback,
+                     void *userData) {
+  detail::CallbackOstream stream(callback, userData);
+  unwrap(remark)->print(stream);
+}
+
+//===----------------------------------------------------------------------===//
+// Emission
+//===----------------------------------------------------------------------===//
+
+bool mlirEmitOptimizationRemark(
+    MlirLocation location, MlirRemarkKind kind, MlirStringRef remarkName,
+    MlirStringRef categoryName, MlirStringRef subCategoryName,
+    MlirStringRef functionName, MlirStringRef message, intptr_t numArgs,
+    const MlirStringRef *argKeys, const MlirStringRef *argValues) {
+  assert((numArgs == 0 || (argKeys && argValues)) &&
+         "remark arguments require both keys and values");
+  Location loc = unwrap(location);
+  remark::RemarkOpts opts = remark::RemarkOpts::name(unwrap(remarkName))
+                                .category(unwrap(categoryName))
+                                .subCategory(unwrap(subCategoryName))
+                                .function(unwrap(functionName));
+  remark::detail::InFlightRemark inFlight;
+  switch (kind) {
+  case MlirRemarkKindUnknown:
+    return false;
+  case MlirRemarkKindPassed:
+    inFlight = remark::passed(loc, opts);
+    break;
+  case MlirRemarkKindMissed:
+    inFlight = remark::missed(loc, opts);
+    break;
+  case MlirRemarkKindFailure:
+    inFlight = remark::failed(loc, opts);
+    break;
+  case MlirRemarkKindAnalysis:
+    inFlight = remark::analysis(loc, opts);
+    break;
+  }
+  if (!inFlight)
+    return false;
+  if (message.length != 0)
+    inFlight << unwrap(message);
+  for (intptr_t i = 0; i < numArgs; ++i)
+    inFlight << Remark::Arg(unwrap(argKeys[i]), unwrap(argValues[i]));
+  // The remark is reported when `inFlight` goes out of scope.
+  return true;
+}

diff  --git a/mlir/python/CMakeLists.txt b/mlir/python/CMakeLists.txt
index aa0adea66d2fe..64554f0bac14a 100644
--- a/mlir/python/CMakeLists.txt
+++ b/mlir/python/CMakeLists.txt
@@ -838,6 +838,7 @@ declare_mlir_python_extension(MLIRPythonExtension.MLIRPythonSupport
     Globals.cpp
     IRAttributes.cpp
     IRCore.cpp
+    IRRemarks.cpp
     IRTypes.cpp
 )
 

diff  --git a/mlir/test/CAPI/CMakeLists.txt b/mlir/test/CAPI/CMakeLists.txt
index d29e7744527ef..bd8b58eb442bf 100644
--- a/mlir/test/CAPI/CMakeLists.txt
+++ b/mlir/test/CAPI/CMakeLists.txt
@@ -64,6 +64,12 @@ _add_capi_test_executable(mlir-capi-llvm-test
     MLIRCAPIRegisterEverything
 )
 
+_add_capi_test_executable(mlir-capi-remarks-test
+  remarks.c
+  LINK_LIBS PRIVATE
+    MLIRCAPIIR
+)
+
 _add_capi_test_executable(mlir-capi-pass-test
   pass.c
   LINK_LIBS PRIVATE

diff  --git a/mlir/test/CAPI/remarks.c b/mlir/test/CAPI/remarks.c
new file mode 100644
index 0000000000000..0e8d753c1d3b4
--- /dev/null
+++ b/mlir/test/CAPI/remarks.c
@@ -0,0 +1,343 @@
+//===- remarks.c - Test of the remark engine C API ------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+/* RUN: mlir-capi-remarks-test 2>&1 | FileCheck %s
+ */
+
+#include "mlir-c/Remarks.h"
+#include "mlir-c/Diagnostics.h"
+#include "mlir-c/IR.h"
+#include "mlir-c/Support.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void printToStderr(MlirStringRef str, void *userData) {
+  (void)userData;
+  fwrite(str.data, 1, str.length, stderr);
+}
+
+static MlirStringRef str(const char *s) {
+  return mlirStringRefCreateFromCString(s);
+}
+
+static MlirRemarkCategories allCategories(const char *filter) {
+  MlirRemarkCategories cats;
+  memset(&cats, 0, sizeof(cats));
+  cats.all = str(filter);
+  return cats;
+}
+
+/// Prints every field of the remark and counts the deliveries in userData.
+static void remarkCallback(MlirRemark remark, void *userData) {
+  int *count = (int *)userData;
+  ++*count;
+  const char *kind = "unknown";
+  switch (mlirRemarkGetKind(remark)) {
+  case MlirRemarkKindPassed:
+    kind = "passed";
+    break;
+  case MlirRemarkKindMissed:
+    kind = "missed";
+    break;
+  case MlirRemarkKindFailure:
+    kind = "failure";
+    break;
+  case MlirRemarkKindAnalysis:
+    kind = "analysis";
+    break;
+  case MlirRemarkKindUnknown:
+    break;
+  }
+  MlirStringRef name = mlirRemarkGetRemarkName(remark);
+  MlirStringRef category = mlirRemarkGetCategoryName(remark);
+  MlirStringRef fullCategory = mlirRemarkGetFullCategoryName(remark);
+  MlirStringRef function = mlirRemarkGetFunctionName(remark);
+  fprintf(stderr,
+          "remark #%d kind=%s name=%.*s category=%.*s full=%.*s "
+          "function=%.*s id=%" PRIu64 "\n",
+          *count, kind, (int)name.length, name.data, (int)category.length,
+          category.data, (int)fullCategory.length, fullCategory.data,
+          (int)function.length, function.data, mlirRemarkGetId(remark));
+  intptr_t numArgs = mlirRemarkGetNumArgs(remark);
+  for (intptr_t i = 0; i < numArgs; ++i) {
+    MlirStringRef key = mlirRemarkGetArgKey(remark, i);
+    MlirStringRef value = mlirRemarkGetArgValue(remark, i);
+    fprintf(stderr, "  arg %.*s=%.*s\n", (int)key.length, key.data,
+            (int)value.length, value.data);
+  }
+  fprintf(stderr, "  print: ");
+  mlirRemarkPrint(remark, printToStderr, NULL);
+  fprintf(stderr, "\n  location: ");
+  mlirLocationPrint(mlirRemarkGetLocation(remark), printToStderr, NULL);
+  fprintf(stderr, "\n");
+}
+
+static void deleteUserData(void *userData) {
+  fprintf(stderr, "deleteUserData called (count=%d)\n", *(int *)userData);
+}
+
+static void countRemark(MlirRemark remark, void *userData) {
+  (void)remark;
+  ++*(int *)userData;
+}
+
+static bool emit(MlirLocation loc, MlirRemarkKind kind, const char *name,
+                 const char *category, const char *message) {
+  MlirStringRef keys[1] = {str("factor")};
+  MlirStringRef values[1] = {str("4")};
+  return mlirEmitOptimizationRemark(loc, kind, str(name), str(category),
+                                    str("inner"), str("main"), str(message), 1,
+                                    keys, values);
+}
+
+// CHECK-LABEL: @testCallbackStreamer
+static void testCallbackStreamer(void) {
+  fprintf(stderr, "@testCallbackStreamer\n");
+  MlirContext ctx = mlirContextCreate();
+  MlirLocation loc = mlirLocationFileLineColGet(ctx, str("remarks.c"), 7, 3);
+
+  // CHECK: no engine: enabled=0 emitted=0
+  bool emitted = emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "dropped");
+  fprintf(stderr, "no engine: enabled=%d emitted=%d\n",
+          mlirContextHasRemarkEngine(ctx), emitted);
+
+  int count = 0;
+  MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+      ctx, allCategories("Loop"), MlirRemarkPolicyAll, remarkCallback, &count,
+      deleteUserData, /*printAsEmitRemarks=*/false);
+  // CHECK: enabled=1 ok=1
+  fprintf(stderr, "enabled=%d ok=%d\n", mlirContextHasRemarkEngine(ctx),
+          mlirLogicalResultIsSuccess(res));
+
+  // clang-format off
+  // CHECK: remark #1 kind=passed name=Unroll category=Loop full=Loop:inner function=main id=1
+  // CHECK:   arg RemarkId=1
+  // CHECK:   arg Remark=unrolled
+  // CHECK:   arg factor=4
+  // CHECK:   print: [Passed] Unroll | Category:Loop:inner | Function=main | Remark=unrolled, RemarkId=1, factor=4
+  // CHECK:   location: loc("remarks.c":7:3)
+  // CHECK: emitted=1
+  // clang-format on
+  emitted = emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "unrolled");
+  fprintf(stderr, "emitted=%d\n", emitted);
+
+  // CHECK-NOT: name=Vectorize
+  // CHECK: count after filtered emit=1
+  emit(loc, MlirRemarkKindMissed, "Vectorize", "Vector", "not matched");
+  fprintf(stderr, "count after filtered emit=%d\n", count);
+
+  // CHECK: remark #2 kind=missed name=Interchange
+  // CHECK: remark #3 kind=failure name=Fuse
+  // CHECK: remark #4 kind=analysis name=TripCount
+  emit(loc, MlirRemarkKindMissed, "Interchange", "Loop", "no");
+  emit(loc, MlirRemarkKindFailure, "Fuse", "Loop", "no");
+  emit(loc, MlirRemarkKindAnalysis, "TripCount", "Loop", "4");
+
+  // CHECK: deleteUserData called (count=4)
+  // CHECK: after finalize: enabled=0 count=4
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  fprintf(stderr, "after finalize: enabled=%d count=%d\n",
+          mlirContextHasRemarkEngine(ctx), count);
+  // Finalizing without an engine is a no-op.
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testFinalPolicy
+static void testFinalPolicy(void) {
+  fprintf(stderr, "@testFinalPolicy\n");
+  MlirContext ctx = mlirContextCreate();
+  MlirLocation loc = mlirLocationUnknownGet(ctx);
+  int count = 0;
+  MlirRemarkCategories cats;
+  memset(&cats, 0, sizeof(cats));
+  cats.passed = str("Loop");
+  MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+      ctx, cats, MlirRemarkPolicyFinal, remarkCallback, &count, NULL, false);
+  assert(mlirLogicalResultIsSuccess(res));
+  emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "postponed");
+  // Only the passed filter is set: a missed remark is not reported.
+  emit(loc, MlirRemarkKindMissed, "Interchange", "Loop", "dropped");
+  // CHECK: before finalize count=0
+  fprintf(stderr, "before finalize count=%d\n", count);
+  // CHECK: remark #1 kind=passed name=Unroll
+  // CHECK: after finalize count=1
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  fprintf(stderr, "after finalize count=%d\n", count);
+  mlirContextDestroy(ctx);
+}
+
+static MlirLogicalResult diagnosticHandler(MlirDiagnostic diagnostic,
+                                           void *userData) {
+  (void)userData;
+  const char *severity = "other";
+  if (mlirDiagnosticGetSeverity(diagnostic) == MlirDiagnosticRemark)
+    severity = "remark";
+  fprintf(stderr, "diagnostic %s: ", severity);
+  mlirDiagnosticPrint(diagnostic, printToStderr, NULL);
+  fprintf(stderr, "\n");
+  return mlirLogicalResultSuccess();
+}
+
+// CHECK-LABEL: @testEmitAsDiagnostics
+static void testEmitAsDiagnostics(void) {
+  fprintf(stderr, "@testEmitAsDiagnostics\n");
+  MlirContext ctx = mlirContextCreate();
+  MlirLocation loc = mlirLocationUnknownGet(ctx);
+  MlirDiagnosticHandlerID id =
+      mlirContextAttachDiagnosticHandler(ctx, diagnosticHandler, NULL, NULL);
+  MlirLogicalResult res = mlirContextEnableOptimizationRemarks(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll,
+      /*printAsEmitRemarks=*/true);
+  assert(mlirLogicalResultIsSuccess(res));
+  // clang-format off
+  // CHECK: diagnostic remark: [Analysis] TripCount | Category:Loop:inner | Function=main | Remark=4, RemarkId=1, factor=4
+  // clang-format on
+  emit(loc, MlirRemarkKindAnalysis, "TripCount", "Loop", "4");
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  mlirContextDetachDiagnosticHandler(ctx, id);
+  mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testFileStreamer
+static void testFileStreamer(void) {
+  fprintf(stderr, "@testFileStreamer\n");
+  MlirContext ctx = mlirContextCreate();
+  MlirLocation loc = mlirLocationUnknownGet(ctx);
+
+  char yamlPath[] = "mlir-capi-remarks-test.yaml";
+  MlirLogicalResult res = mlirContextEnableOptimizationRemarksToFile(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll, MlirRemarkFileFormatYAML,
+      str(yamlPath), /*printAsEmitRemarks=*/false);
+  // CHECK: yaml enable ok=1
+  fprintf(stderr, "yaml enable ok=%d\n", mlirLogicalResultIsSuccess(res));
+  emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "to file");
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  FILE *f = fopen(yamlPath, "r");
+  bool found = false;
+  if (f) {
+    char line[256];
+    while (fgets(line, sizeof(line), f))
+      if (strstr(line, "Unroll"))
+        found = true;
+    fclose(f);
+    remove(yamlPath);
+  }
+  // CHECK: yaml has remark=1
+  fprintf(stderr, "yaml has remark=%d\n", found);
+
+  // The bitstream file starts with the LLVM remark bitstream magic.
+  char bitstreamPath[] = "mlir-capi-remarks-test.bitstream";
+  res = mlirContextEnableOptimizationRemarksToFile(
+      ctx, allCategories(".*"), MlirRemarkPolicyFinal,
+      MlirRemarkFileFormatBitstream, str(bitstreamPath),
+      /*printAsEmitRemarks=*/false);
+  // CHECK: bitstream enable ok=1
+  fprintf(stderr, "bitstream enable ok=%d\n", mlirLogicalResultIsSuccess(res));
+  emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "to file");
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  char magic[5] = {0};
+  f = fopen(bitstreamPath, "rb");
+  if (f) {
+    if (fread(magic, 1, 4, f) != 4)
+      magic[0] = '\0';
+    fclose(f);
+    remove(bitstreamPath);
+  }
+  // CHECK: bitstream magic=RMRK
+  fprintf(stderr, "bitstream magic=%s\n", magic);
+  mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testAccessorDefaults
+static void testAccessorDefaults(void) {
+  fprintf(stderr, "@testAccessorDefaults\n");
+  MlirContext ctx = mlirContextCreate();
+  MlirLocation loc = mlirLocationUnknownGet(ctx);
+  int count = 0;
+  MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll, remarkCallback, &count,
+      NULL, false);
+  assert(mlirLogicalResultIsSuccess(res));
+  // Unset names fall back to placeholders, the only argument is the id, and
+  // null argument arrays are accepted when numArgs is 0.
+  // clang-format off
+  // CHECK: remark #1 kind=passed name=<unknown remark name> category= full= function=<unknown function> id=1
+  // CHECK:   arg RemarkId=1
+  // CHECK:   print: [Passed]  | RemarkId=1
+  // CHECK:   location: loc(unknown)
+  // CHECK: emitted=1
+  // clang-format on
+  bool emitted =
+      mlirEmitOptimizationRemark(loc, MlirRemarkKindPassed, str(""), str(""),
+                                 str(""), str(""), str(""), 0, NULL, NULL);
+  fprintf(stderr, "emitted=%d\n", emitted);
+  // A sub-category alone is the full category name.
+  // clang-format off
+  // CHECK: remark #2 kind=missed name=Name category= full=Sub function=<unknown function> id=2
+  // CHECK:   print: [Missed] Name | Category:Sub | RemarkId=2
+  // clang-format on
+  mlirEmitOptimizationRemark(loc, MlirRemarkKindMissed, str("Name"), str(""),
+                             str("Sub"), str(""), str(""), 0, NULL, NULL);
+  // CHECK: unknown emitted=0
+  emitted = emit(loc, MlirRemarkKindUnknown, "Name", "Loop", "");
+  fprintf(stderr, "unknown emitted=%d\n", emitted);
+  mlirContextFinalizeOptimizationRemarks(ctx);
+  mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testEnableWhileEnabled
+static void testEnableWhileEnabled(void) {
+  fprintf(stderr, "@testEnableWhileEnabled\n");
+  MlirContext ctx = mlirContextCreate();
+  MlirLocation loc = mlirLocationUnknownGet(ctx);
+  int first = 0, second = 0;
+  MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll, countRemark, &first,
+      deleteUserData, false);
+  // CHECK: first ok=1
+  fprintf(stderr, "first ok=%d\n", mlirLogicalResultIsSuccess(res));
+  // Every enable variant fails while an engine is active; the callback
+  // variant releases the user data it was handed right away.
+  // CHECK: deleteUserData called (count=0)
+  // CHECK: callback ok=0 file ok=0 plain ok=0
+  res = mlirContextEnableOptimizationRemarksWithCallback(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll, countRemark, &second,
+      deleteUserData, false);
+  MlirLogicalResult fileRes = mlirContextEnableOptimizationRemarksToFile(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll, MlirRemarkFileFormatYAML,
+      str("mlir-capi-remarks-test-unused.yaml"), false);
+  MlirLogicalResult plainRes = mlirContextEnableOptimizationRemarks(
+      ctx, allCategories(".*"), MlirRemarkPolicyAll, false);
+  fprintf(stderr, "callback ok=%d file ok=%d plain ok=%d\n",
+          mlirLogicalResultIsSuccess(res), mlirLogicalResultIsSuccess(fileRes),
+          mlirLogicalResultIsSuccess(plainRes));
+  emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "");
+  // CHECK: first=1 second=0
+  fprintf(stderr, "first=%d second=%d\n", first, second);
+  // Destroying the context with a live engine releases its user data.
+  // CHECK: deleteUserData called (count=1)
+  // CHECK: destroyed
+  mlirContextDestroy(ctx);
+  fprintf(stderr, "destroyed\n");
+}
+
+int main(void) {
+  testCallbackStreamer();
+  testFinalPolicy();
+  testEmitAsDiagnostics();
+  testFileStreamer();
+  testAccessorDefaults();
+  testEnableWhileEnabled();
+  return 0;
+}

diff  --git a/mlir/test/CMakeLists.txt b/mlir/test/CMakeLists.txt
index c79949737d64b..747d0cf68116b 100644
--- a/mlir/test/CMakeLists.txt
+++ b/mlir/test/CMakeLists.txt
@@ -126,6 +126,7 @@ set(MLIR_TEST_DEPENDS
   mlir-capi-llvm-test
   mlir-capi-pass-test
   mlir-capi-quant-test
+  mlir-capi-remarks-test
   mlir-capi-rewrite-test
   mlir-capi-smt-test
   mlir-capi-sparse-tensor-test

diff  --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py
index 3622fc0823fdf..dbd18f8e8b0f2 100644
--- a/mlir/test/lit.cfg.py
+++ b/mlir/test/lit.cfg.py
@@ -189,6 +189,7 @@ def find_real_python_interpreter():
     "mlir-capi-pass-test",
     "mlir-capi-pdl-test",
     "mlir-capi-quant-test",
+    "mlir-capi-remarks-test",
     "mlir-capi-rewrite-test",
     "mlir-capi-sparse-tensor-test",
     "mlir-capi-transform-test",

diff  --git a/mlir/test/python/ir/remarks.py b/mlir/test/python/ir/remarks.py
new file mode 100644
index 0000000000000..49fbe4ebb6767
--- /dev/null
+++ b/mlir/test/python/ir/remarks.py
@@ -0,0 +1,419 @@
+# RUN: %PYTHON %s | FileCheck %s
+
+import gc
+import os
+import tempfile
+from mlir.ir import *
+
+
+def run(f):
+    print("\nTEST:", f.__name__)
+    f()
+    gc.collect()
+    assert Context._get_live_count() == 0
+    return f
+
+
+def emit(loc, kind=RemarkKind.PASSED, name="Unroll", category="Loop", **kwargs):
+    return loc.emit_remark(kind, name, category=category, **kwargs)
+
+
+def collect(into, attribute="remark_name"):
+    """Returns a remark callback appending `attribute` of each remark to `into`."""
+    return lambda remark: into.append(getattr(remark, attribute))
+
+
+# CHECK-LABEL: TEST: testNoEngine
+ at run
+def testNoEngine():
+    ctx = Context()
+    loc = Location.file("test.mlir", 1, 2, context=ctx)
+    # CHECK: enabled: False
+    print("enabled:", ctx.remarks_enabled)
+    # CHECK: emitted: False
+    print("emitted:", emit(loc))
+    # Finalizing without an engine is a no-op.
+    ctx.finalize_remarks()
+    # CHECK: still disabled: False
+    print("still disabled:", ctx.remarks_enabled)
+
+
+# CHECK-LABEL: TEST: testEnableFinalize
+ at run
+def testEnableFinalize():
+    ctx = Context()
+    states = [ctx.remarks_enabled]
+    ctx.enable_remarks(all_filter=".*")
+    states.append(ctx.remarks_enabled)
+    ctx.finalize_remarks()
+    states.append(ctx.remarks_enabled)
+    ctx.finalize_remarks()
+    states.append(ctx.remarks_enabled)
+    ctx.enable_remarks(policy=RemarkPolicy.FINAL, print_as_emit_remarks=False)
+    states.append(ctx.remarks_enabled)
+    ctx.finalize_remarks()
+    states.append(ctx.remarks_enabled)
+    # CHECK: states: [False, True, False, False, True, False]
+    print("states:", states)
+
+
+# CHECK-LABEL: TEST: testCallback
+ at run
+def testCallback():
+    ctx = Context()
+
+    def callback(remark):
+        print(
+            "kind:",
+            remark.kind,
+            "name:",
+            remark.remark_name,
+            "category:",
+            remark.category_name,
+            "full:",
+            remark.full_category_name,
+            "function:",
+            remark.function_name,
+            "id:",
+            remark.remark_id,
+        )
+        print("args:", remark.args)
+        print("message:", remark.message)
+        print("str:", str(remark))
+        print(
+            "location:",
+            remark.location,
+            type(remark.location).__name__,
+            remark.location.context is ctx,
+        )
+
+    ctx.enable_remarks(all_filter=".*", callback=callback)
+    # CHECK: enabled: True
+    print("enabled:", ctx.remarks_enabled)
+    loc = Location.file("test.mlir", 3, 4, context=ctx)
+    # The arguments keep their insertion order (the engine's id, the message,
+    # the explicit pairs); the message sorts them by key.
+    # CHECK: kind: RemarkKind.PASSED name: Unroll category: Loop full: Loop:Inner function: main id: 1
+    # CHECK: args: [('RemarkId', '1'), ('Remark', 'unrolled by 4'), ('factor', '4')]
+    # CHECK: message: [Passed] Unroll | Category:Loop:Inner | Function=main | Remark="unrolled by 4", RemarkId=1, factor=4
+    # CHECK: str: [Passed] Unroll | Category:Loop:Inner | Function=main | Remark="unrolled by 4", RemarkId=1, factor=4
+    # CHECK: location: loc("test.mlir":3:4) FileLineColLoc True
+    # CHECK: emitted: True
+    emitted = emit(
+        loc,
+        sub_category="Inner",
+        function_name="main",
+        message="unrolled by 4",
+        args=[("factor", "4")],
+    )
+    print("emitted:", emitted)
+    # Unset names fall back to placeholders; the only argument is the id.
+    # CHECK: kind: RemarkKind.MISSED name: <unknown remark name> category:  full:  function: <unknown function> id: 2
+    # CHECK: args: [('RemarkId', '2')]
+    # CHECK: message: [Missed]  | RemarkId=2
+    Location.unknown(context=ctx).emit_remark(RemarkKind.MISSED, "")
+    ctx.finalize_remarks()
+    # CHECK: enabled after finalize: False
+    print("enabled after finalize:", ctx.remarks_enabled)
+
+
+# CHECK-LABEL: TEST: testRemarkInvalidatedAfterCallback
+ at run
+def testRemarkInvalidatedAfterCallback():
+    ctx = Context()
+    kept = []
+    ctx.enable_remarks(all_filter=".*", callback=kept.append)
+    emit(Location.unknown(context=ctx))
+    ctx.finalize_remarks()
+    remark = kept[0]
+    for attribute in (
+        "kind",
+        "remark_name",
+        "category_name",
+        "full_category_name",
+        "function_name",
+        "location",
+        "remark_id",
+        "args",
+        "message",
+    ):
+        try:
+            getattr(remark, attribute)
+            print(attribute, "-> no error (unexpected)")
+        except ValueError as e:
+            print(attribute, "->", e)
+    # CHECK: kind -> Remark is invalid (used outside of callback)
+    # CHECK: remark_name -> Remark is invalid (used outside of callback)
+    # CHECK: category_name -> Remark is invalid (used outside of callback)
+    # CHECK: full_category_name -> Remark is invalid (used outside of callback)
+    # CHECK: function_name -> Remark is invalid (used outside of callback)
+    # CHECK: location -> Remark is invalid (used outside of callback)
+    # CHECK: remark_id -> Remark is invalid (used outside of callback)
+    # CHECK: args -> Remark is invalid (used outside of callback)
+    # CHECK: message -> Remark is invalid (used outside of callback)
+    # CHECK: str: <Invalid Remark>
+    print("str:", str(remark))
+
+
+# CHECK-LABEL: TEST: testFilters
+ at run
+def testFilters():
+    ctx = Context()
+    names = []
+    ctx.enable_remarks(
+        all_filter="Loop", passed_filter="Vector", callback=collect(names)
+    )
+    loc = Location.unknown(context=ctx)
+    # CHECK: loop passed: True
+    print("loop passed:", emit(loc, RemarkKind.PASSED, "LoopPassed", "Loop"))
+    # CHECK: loop missed: True
+    print("loop missed:", emit(loc, RemarkKind.MISSED, "LoopMissed", "Loop"))
+    # CHECK: vector passed: True
+    print("vector passed:", emit(loc, RemarkKind.PASSED, "VectorPassed", "Vector"))
+    # CHECK: vector missed: False
+    print("vector missed:", emit(loc, RemarkKind.MISSED, "VectorMissed", "Vector"))
+    # CHECK: other: False
+    print("other:", emit(loc, RemarkKind.ANALYSIS, "Other", "Memory"))
+    # CHECK: unknown: False
+    print("unknown:", emit(loc, RemarkKind.UNKNOWN, "Unknown", "Loop"))
+    ctx.finalize_remarks()
+    # CHECK: names: ['LoopPassed', 'LoopMissed', 'VectorPassed']
+    print("names:", names)
+
+
+# CHECK-LABEL: TEST: testFinalPolicy
+ at run
+def testFinalPolicy():
+    ctx = Context()
+    names = []
+    ctx.enable_remarks(
+        policy=RemarkPolicy.FINAL, all_filter=".*", callback=collect(names)
+    )
+    loc = Location.unknown(context=ctx)
+    emit(loc, RemarkKind.PASSED, "First")
+    emit(loc, RemarkKind.MISSED, "Second")
+    emit(loc, RemarkKind.FAILURE, "Third")
+    emit(loc, RemarkKind.ANALYSIS, "Fourth")
+    # CHECK: before finalize: []
+    print("before finalize:", names)
+    ctx.finalize_remarks()
+    # CHECK: after finalize: ['First', 'Fourth', 'Second', 'Third']
+    print("after finalize:", sorted(names))
+
+
+# CHECK-LABEL: TEST: testYamlFile
+ at run
+def testYamlFile():
+    ctx = Context()
+    directory = tempfile.mkdtemp()
+    path = os.path.join(directory, "remarks.yaml")
+    ctx.enable_remarks(output_file=path, all_filter=".*")
+    loc = Location.file("test.mlir", 5, 6, context=ctx)
+    # CHECK: emitted: True
+    print(
+        "emitted:",
+        emit(
+            loc,
+            RemarkKind.MISSED,
+            "NotUnrolled",
+            sub_category="Inner",
+            function_name="f",
+            message="trip count too small",
+            args=[("tripCount", "4")],
+        ),
+    )
+    ctx.finalize_remarks()
+    with open(path) as f:
+        content = " ".join(f.read().split())
+    os.remove(path)
+    os.rmdir(directory)
+    for fragment in (
+        "--- !Missed",
+        "Pass: 'Loop:Inner'",
+        "Name: NotUnrolled",
+        "DebugLoc: { File: test.mlir, Line: 5, Column: 6 }",
+        "Function: f",
+        "- Remark: trip count too small",
+        "- tripCount: '4'",
+    ):
+        print(fragment, "->", fragment in content)
+    # CHECK: --- !Missed -> True
+    # CHECK: Pass: 'Loop:Inner' -> True
+    # CHECK: Name: NotUnrolled -> True
+    # CHECK: DebugLoc: { File: test.mlir, Line: 5, Column: 6 } -> True
+    # CHECK: Function: f -> True
+    # CHECK: - Remark: trip count too small -> True
+    # CHECK: - tripCount: '4' -> True
+
+
+# CHECK-LABEL: TEST: testBitstreamFile
+ at run
+def testBitstreamFile():
+    ctx = Context()
+    directory = tempfile.mkdtemp()
+    path = os.path.join(directory, "remarks.bitstream")
+    ctx.enable_remarks(
+        policy=RemarkPolicy.FINAL,
+        output_file=path,
+        format=RemarkFormat.BITSTREAM,
+        all_filter=".*",
+    )
+    # CHECK: emitted: True
+    print("emitted:", emit(Location.unknown(context=ctx)))
+    ctx.finalize_remarks()
+    # The file holds an LLVM remark bitstream once finalized.
+    with open(path, "rb") as f:
+        magic = f.read(4)
+    os.remove(path)
+    os.rmdir(directory)
+    # CHECK: magic: b'RMRK'
+    print("magic:", magic)
+
+
+# CHECK-LABEL: TEST: testEmitAsDiagnostics
+ at run
+def testEmitAsDiagnostics():
+    ctx = Context()
+    seen = []
+
+    def handler(d):
+        seen.append((d.severity, str(d.message)))
+        return True
+
+    handle = ctx.attach_diagnostic_handler(handler)
+    loc = Location.unknown(context=ctx)
+    # Without a sink of its own, the engine emits MLIR remark diagnostics.
+    ctx.enable_remarks(all_filter=".*")
+    emit(loc, RemarkKind.ANALYSIS, "TripCount", message="trip count is 4")
+    ctx.finalize_remarks()
+    # CHECK: diagnostics: [(DiagnosticSeverity.REMARK, '[Analysis] TripCount | Category:Loop | Remark="trip count is 4", RemarkId=1')]
+    print("diagnostics:", seen)
+    seen.clear()
+    ctx.enable_remarks(all_filter=".*", print_as_emit_remarks=False)
+    # CHECK: silent emitted: True []
+    print("silent emitted:", emit(loc), seen)
+    ctx.finalize_remarks()
+    # A callback is silent by default; combined with the diagnostics it runs first.
+    ctx.enable_remarks(
+        all_filter=".*",
+        callback=lambda r: seen.append("callback"),
+        print_as_emit_remarks=True,
+    )
+    emit(loc)
+    ctx.finalize_remarks()
+    handle.detach()
+    # CHECK: both: ['callback', (DiagnosticSeverity.REMARK, '[Passed] Unroll | Category:Loop | RemarkId=1')]
+    print("both:", seen)
+
+
+# CHECK-LABEL: TEST: testErrors
+ at run
+def testErrors():
+    ctx = Context()
+    try:
+        ctx.enable_remarks(output_file="remarks.yaml", callback=lambda r: None)
+        print("no error (unexpected)")
+    except ValueError as e:
+        # CHECK: ValueError: a remark callback cannot be combined with an output file
+        print("ValueError:", e)
+    try:
+        ctx.enable_remarks(output_file="/nonexistent-dir/remarks.yaml")
+        print("no error (unexpected)")
+    except ValueError as e:
+        # CHECK: ValueError: failed to enable remarks: cannot write '/nonexistent-dir/remarks.yaml'
+        print("ValueError:", e)
+    # CHECK: enabled after errors: False
+    print("enabled after errors:", ctx.remarks_enabled)
+    # The options are keyword-only and typed.
+    for kwargs in (dict(policy="final"), dict(format="yaml")):
+        try:
+            ctx.enable_remarks(**kwargs)
+            print("no error (unexpected)")
+        except TypeError as e:
+            print("TypeError:", str(e).splitlines()[0])
+    # CHECK-COUNT-2: TypeError: enable_remarks(): incompatible function arguments
+    ctx.enable_remarks(all_filter=".*")
+    try:
+        ctx.enable_remarks(all_filter=".*")
+        print("no error (unexpected)")
+    except ValueError as e:
+        # CHECK: ValueError: remarks are already enabled on this context; call finalize_remarks() first
+        print("ValueError:", e)
+    # CHECK: still enabled: True
+    print("still enabled:", ctx.remarks_enabled)
+    loc = Location.unknown(context=ctx)
+    try:
+        loc.emit_remark("passed", "Name")
+        print("no error (unexpected)")
+    except TypeError as e:
+        # CHECK: TypeError: emit_remark(): incompatible function arguments
+        print("TypeError:", str(e).splitlines()[0])
+    try:
+        Remark()
+        print("no error (unexpected)")
+    except TypeError as e:
+        # CHECK: TypeError: {{.*}}Remark: no constructor defined!
+        print("TypeError:", e)
+    ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testCallbackException
+ at run
+def testCallbackException():
+    ctx = Context()
+    names = []
+
+    def callback(remark):
+        names.append(remark.remark_name)
+        if remark.remark_name == "Boom":
+            raise RuntimeError("boom")
+
+    ctx.enable_remarks(all_filter=".*", callback=callback)
+    loc = Location.unknown(context=ctx)
+    # The exception is reported on stderr and dropped.
+    # CHECK: boom emitted: True enabled: True
+    print("boom emitted:", emit(loc, name="Boom"), "enabled:", ctx.remarks_enabled)
+    # CHECK: after emitted: True
+    print("after emitted:", emit(loc, name="After"))
+    # CHECK: names: ['Boom', 'After']
+    print("names:", names)
+    ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testMultipleContexts
+ at run
+def testMultipleContexts():
+    ctx1, ctx2 = Context(), Context()
+    seen1, seen2 = [], []
+    ctx1.enable_remarks(all_filter=".*", callback=collect(seen1))
+    ctx2.enable_remarks(passed_filter="Loop", callback=collect(seen2))
+    loc1 = Location.unknown(context=ctx1)
+    loc2 = Location.unknown(context=ctx2)
+    emit(loc1, RemarkKind.PASSED, "A")
+    emit(loc2, RemarkKind.PASSED, "B")
+    emit(loc2, RemarkKind.MISSED, "C")
+    ctx1.finalize_remarks()
+    # CHECK: enabled: False True
+    print("enabled:", ctx1.remarks_enabled, ctx2.remarks_enabled)
+    emit(loc2, RemarkKind.PASSED, "D")
+    ctx2.finalize_remarks()
+    # CHECK: seen1: ['A']
+    # CHECK: seen2: ['B', 'D']
+    print("seen1:", seen1)
+    print("seen2:", seen2)
+
+
+# CHECK-LABEL: TEST: testContextDestroyedWithEngine
+ at run
+def testContextDestroyedWithEngine():
+    ctx = Context()
+    names = []
+    ctx.enable_remarks(
+        policy=RemarkPolicy.FINAL, all_filter=".*", callback=collect(names)
+    )
+    emit(Location.unknown(context=ctx), RemarkKind.PASSED, "Pending")
+    # A remark postponed until the context's destruction is dropped.
+    ctx = None
+    gc.collect()
+    # CHECK: dropped: []
+    print("dropped:", names)


        


More information about the Mlir-commits mailing list