[Mlir-commits] [mlir] [MLIR][CAPI][Python] Add C API and Python bindings for the remark engine (PR #229094)
Guray Ozen
llvmlistbot at llvm.org
Tue Oct 6 01:23:12 PDT 2026
https://github.com/grypp updated https://github.com/llvm/llvm-project/pull/229094
>From 9d2b7caf1408a7bb02d3452c5047357d2a8eb7b7 Mon Sep 17 00:00:00 2001
From: Guray Ozen <gozen at nvidia.com>
Date: Mon, 5 Oct 2026 16:09:25 +0200
Subject: [PATCH 1/2] [MLIR][CAPI][Python] Add C API and Python bindings for
the remark engine
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, filters, callback,
print_as_emit_remarks), Context.finalize_remarks(), Context.remarks_enabled.
- ir.Remark and ir.RemarkKind, wrapping a remark 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=).
Tests: mlir/test/CAPI/remarks.c (mlir-capi-remarks-test) and
mlir/test/python/ir/remarks.py.
Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
---
mlir/include/mlir-c/Remarks.h | 186 +++++++++++
mlir/include/mlir/Bindings/Python/IRCore.h | 55 ++++
mlir/include/mlir/CAPI/Remarks.h | 23 ++
mlir/lib/Bindings/Python/IRCore.cpp | 340 +++++++++++++++++++++
mlir/lib/CAPI/IR/CMakeLists.txt | 2 +
mlir/lib/CAPI/IR/Remarks.cpp | 235 ++++++++++++++
mlir/test/CAPI/CMakeLists.txt | 6 +
mlir/test/CAPI/remarks.c | 252 +++++++++++++++
mlir/test/CMakeLists.txt | 1 +
mlir/test/lit.cfg.py | 1 +
mlir/test/python/ir/remarks.py | 303 ++++++++++++++++++
11 files changed, 1404 insertions(+)
create mode 100644 mlir/include/mlir-c/Remarks.h
create mode 100644 mlir/include/mlir/CAPI/Remarks.h
create mode 100644 mlir/lib/CAPI/IR/Remarks.cpp
create mode 100644 mlir/test/CAPI/remarks.c
create mode 100644 mlir/test/python/ir/remarks.py
diff --git a/mlir/include/mlir-c/Remarks.h b/mlir/include/mlir-c/Remarks.h
new file mode 100644
index 0000000000000..1bd93ba395af4
--- /dev/null
+++ b/mlir/include/mlir-c/Remarks.h
@@ -0,0 +1,186 @@
+//===-- 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, ...`, optionally followed by its location.
+MLIR_CAPI_EXPORTED void mlirRemarkPrint(MlirRemark remark, bool printLocation,
+ 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..027201a9d95a5 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"
@@ -253,6 +254,28 @@ 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. `policy` is
+ /// "all" or "final"; `format` is "emit" (remarks become MLIR remark
+ /// diagnostics, or go to `callback` when one is given), "yaml" or
+ /// "bitstream" (remarks are streamed to `outputFile`). Raises ValueError on
+ /// invalid options or when an engine is already enabled.
+ void enableRemarks(const std::string &policy, const std::string &format,
+ const std::string &outputFile,
+ const std::string &allFilter,
+ const std::string &passedFilter,
+ const std::string &missedFilter,
+ const std::string &analysisFilter,
+ const std::string &failedFilter, nanobind::object callback,
+ nanobind::object 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 the
+ /// wrapper is being destroyed, which is when a remark engine finalizing
+ /// with the context must not call back into Python.
+ 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; }
@@ -353,6 +376,14 @@ enum class PyDiagnosticSeverity : std::underlying_type_t<
Remark = MlirDiagnosticRemark
};
+enum class PyRemarkKind : std::underlying_type_t<MlirRemarkKind> {
+ Unknown = MlirRemarkKindUnknown,
+ Passed = MlirRemarkKindPassed,
+ Missed = MlirRemarkKindMissed,
+ Failure = MlirRemarkKindFailure,
+ Analysis = MlirRemarkKindAnalysis
+};
+
enum class PyWalkResult : std::underlying_type_t<MlirWalkResult> {
Advance = MlirWalkResultAdvance,
Interrupt = MlirWalkResultInterrupt,
@@ -410,6 +441,30 @@ 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:
+ PyRemark(MlirRemark remark) : remark(remark) {}
+ void invalidate() { valid = false; }
+ bool isValid() { return valid; }
+ PyRemarkKind getKind();
+ nanobind::str getRemarkName();
+ nanobind::str getCategoryName();
+ nanobind::str getFullCategoryName();
+ nanobind::str getFunctionName();
+ nanobind::typed<nanobind::object, PyLocation> getLocation();
+ uint64_t getId();
+ nanobind::list getArgs();
+ nanobind::str getMessage();
+
+private:
+ void checkValid();
+ 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
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..7caddb6156423 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>
@@ -714,6 +715,217 @@ void PyThreadContextEntry::popLocation(PyLocation &location) {
// PyDiagnostic*
//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+// PyRemark and the remark engine of a context.
+//------------------------------------------------------------------------------
+
+namespace {
+/// User data of the remark callback streamer: the Python callable and the
+/// context it was installed on.
+struct PyRemarkCallbackData {
+ MlirContext context;
+ nb::object callback;
+};
+} // namespace
+
+static nb::str remarkStr(MlirStringRef ref) {
+ return nb::str(ref.data, ref.length);
+}
+
+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;
+ // Postponed remarks are reported when the engine is destroyed. When that
+ // happens because the context itself is being destroyed, its Python wrapper
+ // is already gone and the remark cannot be delivered.
+ 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(
+ const std::string &policy, const std::string &format,
+ const std::string &outputFile, const std::string &allFilter,
+ const std::string &passedFilter, const std::string &missedFilter,
+ const std::string &analysisFilter, const std::string &failedFilter,
+ nb::object callback, nb::object printAsEmitRemarks) {
+ MlirRemarkPolicy remarkPolicy;
+ if (policy == "all") {
+ remarkPolicy = MlirRemarkPolicyAll;
+ } else if (policy == "final") {
+ remarkPolicy = MlirRemarkPolicyFinal;
+ } else {
+ throw nb::value_error(
+ ("unknown remark policy '" + policy + "'; expected 'all' or 'final'")
+ .c_str());
+ }
+ bool toFile = false;
+ MlirRemarkFileFormat fileFormat = MlirRemarkFileFormatYAML;
+ if (format == "yaml") {
+ toFile = true;
+ } else if (format == "bitstream") {
+ toFile = true;
+ fileFormat = MlirRemarkFileFormatBitstream;
+ } else if (format != "emit") {
+ throw nb::value_error(("unknown remark format '" + format +
+ "'; expected 'emit', 'yaml' or 'bitstream'")
+ .c_str());
+ }
+ bool hasCallback = !callback.is_none();
+ if (toFile && hasCallback)
+ throw nb::value_error(
+ ("a remark callback cannot be combined with format='" + format +
+ "'; use format='emit'")
+ .c_str());
+ 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)};
+ // Without a sink of its own, the engine prints as MLIR remark diagnostics.
+ bool emit = printAsEmitRemarks.is_none() ? (!toFile && !hasCallback)
+ : nb::cast<bool>(printAsEmitRemarks);
+
+ MlirLogicalResult result;
+ if (toFile) {
+ std::string path = outputFile;
+ if (path.empty())
+ path = fileFormat == MlirRemarkFileFormatYAML ? "mlir-remarks.yaml"
+ : "mlir-remarks.bitstream";
+ result = mlirContextEnableOptimizationRemarksToFile(
+ get(), categories, remarkPolicy, fileFormat, toMlirStringRef(path),
+ emit);
+ if (mlirLogicalResultIsFailure(result))
+ throw nb::value_error(
+ ("failed to enable remarks: cannot write '" + path + "'").c_str());
+ } else if (hasCallback) {
+ auto *data = new PyRemarkCallbackData{get(), std::move(callback)};
+ result = mlirContextEnableOptimizationRemarksWithCallback(
+ get(), categories, remarkPolicy, pyRemarkCallback, data,
+ pyRemarkCallbackDelete, emit);
+ } else {
+ result = mlirContextEnableOptimizationRemarks(get(), categories,
+ remarkPolicy, emit);
+ }
+ 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() {
+ if (!valid)
+ throw std::invalid_argument("Remark is invalid (used outside of callback)");
+}
+
+PyRemarkKind PyRemark::getKind() {
+ checkValid();
+ return static_cast<PyRemarkKind>(mlirRemarkGetKind(remark));
+}
+
+nb::str PyRemark::getRemarkName() {
+ checkValid();
+ return remarkStr(mlirRemarkGetRemarkName(remark));
+}
+
+nb::str PyRemark::getCategoryName() {
+ checkValid();
+ return remarkStr(mlirRemarkGetCategoryName(remark));
+}
+
+nb::str PyRemark::getFullCategoryName() {
+ checkValid();
+ return remarkStr(mlirRemarkGetFullCategoryName(remark));
+}
+
+nb::str PyRemark::getFunctionName() {
+ checkValid();
+ return remarkStr(mlirRemarkGetFunctionName(remark));
+}
+
+nb::typed<nb::object, PyLocation> PyRemark::getLocation() {
+ checkValid();
+ MlirLocation loc = mlirRemarkGetLocation(remark);
+ MlirContext context = mlirLocationGetContext(loc);
+ return PyLocation(PyMlirContext::forContext(context), loc).maybeDownCast();
+}
+
+uint64_t PyRemark::getId() {
+ checkValid();
+ return mlirRemarkGetId(remark);
+}
+
+nb::list PyRemark::getArgs() {
+ 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() {
+ checkValid();
+ std::string message;
+ auto callback = +[](MlirStringRef ref, void *userData) {
+ static_cast<std::string *>(userData)->append(ref.data, ref.length);
+ };
+ mlirRemarkPrint(remark, /*printLocation=*/false, callback, &message);
+ return nb::str(message.c_str(), message.size());
+}
+
+/// Converts the `kind` argument of `Location.emit_remark`, a RemarkKind or one
+/// of the strings "passed", "missed", "failure"/"failed" and "analysis".
+static MlirRemarkKind parseRemarkKind(nb::handle kind) {
+ if (nb::isinstance<PyRemarkKind>(kind))
+ return static_cast<MlirRemarkKind>(nb::cast<PyRemarkKind>(kind));
+ std::string name;
+ if (nb::try_cast<std::string>(kind, name)) {
+ if (name == "passed")
+ return MlirRemarkKindPassed;
+ if (name == "missed")
+ return MlirRemarkKindMissed;
+ if (name == "failure" || name == "failed")
+ return MlirRemarkKindFailure;
+ if (name == "analysis")
+ return MlirRemarkKindAnalysis;
+ throw nb::value_error(("unknown remark kind '" + name +
+ "'; expected 'passed', 'missed', 'failure' or "
+ "'analysis'")
+ .c_str());
+ }
+ throw nb::type_error("remark kind must be a RemarkKind or a string");
+}
+
void PyDiagnostic::invalidate() {
valid = false;
if (materializedNotes) {
@@ -3326,6 +3538,13 @@ void populateIRCore(nb::module_ &m) {
.value("NOTE", PyDiagnosticSeverity::Note)
.value("REMARK", PyDiagnosticSeverity::Remark);
+ 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_<PyWalkOrder>(m, "WalkOrder")
.value("PRE_ORDER", PyWalkOrder::PreOrder)
.value("POST_ORDER", PyWalkOrder::PostOrder);
@@ -3401,6 +3620,37 @@ void populateIRCore(nb::module_ &m) {
"exc_value"_a.none(), "traceback"_a.none(),
"Exits the diagnostic handler context manager.");
+ 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.");
+
// Expose DefaultThreadPool to python
nb::class_<PyThreadPool>(m, "ThreadPool")
.def(
@@ -3499,6 +3749,55 @@ 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 = "all", "format"_a = "emit", "output_file"_a = "",
+ "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.
+
+ Args:
+ policy: "all" reports remarks as they are emitted; "final"
+ postpones them until `finalize_remarks()` and reports the final
+ set, grouping related remarks under their parents.
+ format: "emit" delivers remarks as MLIR remark diagnostics, or to
+ `callback` when one is given; "yaml" and "bitstream" stream
+ them to `output_file`, written on `finalize_remarks()`.
+ output_file: The output path for the file formats; defaults to
+ "mlir-remarks.yaml" / "mlir-remarks.bitstream".
+ 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. Requires
+ format="emit".
+ print_as_emit_remarks: Also emit every remark as an MLIR remark
+ diagnostic; defaults to True only for format="emit" without a
+ callback.
+
+ Raises:
+ ValueError: On an unknown policy, format or kind combination,
+ 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 +4152,47 @@ void populateIRCore(nb::module_ &m) {
Args:
message: The error message to emit.)")
+ .def(
+ "emit_remark",
+ [](PyLocation &self, nb::object 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 {
+ MlirRemarkKind remarkKind = parseRemarkKind(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: A `RemarkKind`, or one of "passed", "missed", "failure"
+ and "analysis".
+ 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/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..5a54e5b38895c
--- /dev/null
+++ b/mlir/lib/CAPI/IR/Remarks.cpp
@@ -0,0 +1,235 @@
+//===- 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"
+
+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
+
+/// Like mlir-opt, every filter is handed to the engine as a (possibly empty)
+/// string: the engine then reports a kind when its own filter or `all`
+/// matches, and reports nothing for a kind whose filters are both empty.
+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 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();
+ llvm::remarks::Format llvmFormat = format == MlirRemarkFileFormatBitstream
+ ? llvm::remarks::Format::Bitstream
+ : llvm::remarks::Format::YAML;
+ return wrap(remark::enableOptimizationRemarksWithLLVMStreamer(
+ *ctx, unwrap(path), llvmFormat, 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()) {
+ 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 finalizes the policy (reporting postponed remarks)
+ // and then the streamer (writing the file / releasing the user data).
+ 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) {
+ return wrap(llvm::StringRef(unwrap(remark)->getArgs()[pos].key));
+}
+
+MlirStringRef mlirRemarkGetArgValue(MlirRemark remark, intptr_t pos) {
+ return wrap(llvm::StringRef(unwrap(remark)->getArgs()[pos].val));
+}
+
+void mlirRemarkPrint(MlirRemark remark, bool printLocation,
+ MlirStringCallback callback, void *userData) {
+ detail::CallbackOstream stream(callback, userData);
+ unwrap(remark)->print(stream, printLocation);
+}
+
+//===----------------------------------------------------------------------===//
+// 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) {
+ 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/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..1b74ac624c0f2
--- /dev/null
+++ b/mlir/test/CAPI/remarks.c
@@ -0,0 +1,252 @@
+//===- 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;
+}
+
+/// The callback streamer: prints every field of the remark it receives and
+/// counts the deliveries in userData.
+static void remarkCallback(MlirRemark remark, void *userData) {
+ int *count = (int *)userData;
+ ++*count;
+ const char *kind = "?";
+ 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:
+ kind = "unknown";
+ 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, /*printLocation=*/true, 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 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);
+
+ // Without an engine nothing is emitted.
+ // 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));
+
+ // Enabling twice fails and leaves the first engine in place.
+ // CHECK: second enable ok=0
+ res = mlirContextEnableOptimizationRemarks(ctx, allCategories(".*"),
+ MlirRemarkPolicyAll, true);
+ fprintf(stderr, "second enable ok=%d\n", 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 | @"remarks.c":7:3{{ ?}}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);
+
+ // A category the filter does not match is dropped before the streamer.
+ // 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");
+
+ // Finalize drops the engine and releases the user data.
+ // 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: @testEmitRemarkForm
+static void testEmitRemarkForm(void) {
+ fprintf(stderr, "@testEmitRemarkForm\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 path[] = "mlir-capi-remarks-test.yaml";
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarksToFile(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, MlirRemarkFileFormatYAML,
+ str(path), false);
+ // CHECK: file enable ok=1
+ fprintf(stderr, "file enable ok=%d\n", mlirLogicalResultIsSuccess(res));
+ emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "to file");
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ FILE *f = fopen(path, "r");
+ bool found = false;
+ if (f) {
+ char line[256];
+ while (fgets(line, sizeof(line), f))
+ if (strstr(line, "Unroll"))
+ found = true;
+ fclose(f);
+ remove(path);
+ }
+ // CHECK: yaml has remark=1
+ fprintf(stderr, "yaml has remark=%d\n", found);
+ mlirContextDestroy(ctx);
+}
+
+int main(void) {
+ testCallbackStreamer();
+ testFinalPolicy();
+ testEmitRemarkForm();
+ testFileStreamer();
+ 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..dcf8e6329c9ba
--- /dev/null
+++ b/mlir/test/python/ir/remarks.py
@@ -0,0 +1,303 @@
+# 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="passed", name="Unroll", category="Loop", **kwargs):
+ return loc.emit_remark(kind, name, category=category, **kwargs)
+
+
+# CHECK-LABEL: TEST: testNoEngine
+ at run
+def testNoEngine():
+ ctx = Context()
+ with ctx:
+ loc = Location.file("test.mlir", 1, 2)
+ # 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-LABEL: TEST: testCallback
+ at run
+def testCallback():
+ ctx = Context()
+ collected = []
+
+ def callback(remark):
+ collected.append(remark)
+ print(
+ "kind:",
+ remark.kind,
+ "name:",
+ remark.remark_name,
+ "category:",
+ remark.category_name,
+ "full:",
+ remark.full_category_name,
+ "function:",
+ remark.function_name,
+ )
+ print("args:", remark.args)
+ print("message:", remark.message)
+ print("str:", str(remark))
+ print("location:", remark.location)
+ print("id:", remark.remark_id)
+
+ ctx.enable_remarks(all_filter=".*", callback=callback)
+ # CHECK: enabled: True
+ print("enabled:", ctx.remarks_enabled)
+ with ctx:
+ loc = Location.file("test.mlir", 3, 4)
+ # CHECK: kind: RemarkKind.PASSED name: Unroll category: Loop full: Loop:Inner function: main
+ # 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)
+ # CHECK: id: 1
+ # CHECK: emitted: True
+ emitted = emit(
+ loc,
+ RemarkKind.PASSED,
+ sub_category="Inner",
+ function_name="main",
+ message="unrolled by 4",
+ args=[("factor", "4")],
+ )
+ print("emitted:", emitted)
+ # A remark is invalid once the callback has returned.
+ try:
+ collected[0].remark_name
+ except ValueError as e:
+ # CHECK: invalid: Remark is invalid (used outside of callback)
+ print("invalid:", e)
+ # CHECK: invalid str: <Invalid Remark>
+ print("invalid str:", str(collected[0]))
+ collected.clear()
+ ctx.finalize_remarks()
+ # CHECK: enabled after finalize: False
+ print("enabled after finalize:", ctx.remarks_enabled)
+
+
+# CHECK-LABEL: TEST: testFilters
+ at run
+def testFilters():
+ ctx = Context()
+ names = []
+ ctx.enable_remarks(
+ all_filter="Loop",
+ passed_filter="Vector",
+ callback=lambda r: names.append(r.remark_name),
+ )
+ with ctx:
+ loc = Location.unknown()
+ # The all-filter admits every kind of the Loop category.
+ # CHECK: loop passed: True
+ print("loop passed:", emit(loc, "passed", "LoopPassed", "Loop"))
+ # CHECK: loop missed: True
+ print("loop missed:", emit(loc, "missed", "LoopMissed", "Loop"))
+ # The passed-filter admits only passed remarks of the Vector category.
+ # CHECK: vector passed: True
+ print("vector passed:", emit(loc, "passed", "VectorPassed", "Vector"))
+ # CHECK: vector missed: False
+ print("vector missed:", emit(loc, "missed", "VectorMissed", "Vector"))
+ # CHECK: other: False
+ print("other:", emit(loc, "analysis", "Other", "Memory"))
+ # Unknown kinds are never emitted.
+ # 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="final", all_filter=".*", callback=lambda r: names.append(r.remark_name)
+ )
+ with ctx:
+ loc = Location.unknown()
+ emit(loc, "passed", "First")
+ emit(loc, "missed", "Second")
+ emit(loc, "failed", "Third")
+ emit(loc, "analysis", "Fourth")
+ # Nothing is delivered until finalize.
+ # CHECK: before finalize: []
+ print("before finalize:", names)
+ ctx.finalize_remarks()
+ # CHECK: after finalize: 4
+ print("after finalize:", len(names))
+ assert sorted(names) == ["First", "Fourth", "Second", "Third"]
+ # The engine can be enabled again after finalize.
+ ctx.enable_remarks(all_filter=".*", callback=lambda r: names.append(r.remark_name))
+ with ctx:
+ emit(Location.unknown(), "passed", "Fifth")
+ # CHECK: re-enabled: Fifth
+ print("re-enabled:", names[-1])
+ ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testYamlFile
+ at run
+def testYamlFile():
+ ctx = Context()
+ directory = tempfile.mkdtemp()
+ path = os.path.join(directory, "remarks.yaml")
+ ctx.enable_remarks(format="yaml", output_file=path, all_filter=".*")
+ with ctx:
+ loc = Location.file("test.mlir", 5, 6)
+ # CHECK: emitted: True
+ print("emitted:", emit(loc, "missed", "NotUnrolled", "Loop", function_name="f"))
+ ctx.finalize_remarks()
+ with open(path) as f:
+ content = f.read()
+ # CHECK: has file: True
+ print("has file:", os.path.exists(path))
+ # CHECK: has name: True
+ print("has name:", "NotUnrolled" in content)
+ # CHECK: has pass: True
+ print("has pass:", "Loop" in content)
+ os.remove(path)
+ os.rmdir(directory)
+
+
+# CHECK-LABEL: TEST: testEmitFormat
+ at run
+def testEmitFormat():
+ ctx = Context()
+ messages = []
+
+ def handler(d):
+ messages.append((d.severity, str(d.message)))
+ return True
+
+ handler_handle = ctx.attach_diagnostic_handler(handler)
+ ctx.enable_remarks(all_filter=".*")
+ with ctx:
+ emit(
+ Location.unknown(),
+ "analysis",
+ "TripCount",
+ "Loop",
+ message="trip count is 4",
+ )
+ ctx.finalize_remarks()
+ handler_handle.detach()
+ # CHECK: diagnostics: [(DiagnosticSeverity.REMARK, '[Analysis] TripCount | Category:Loop | Remark="trip count is 4", RemarkId=1')]
+ print("diagnostics:", messages)
+
+
+# CHECK-LABEL: TEST: testCallbackAndEmit
+ at run
+def testCallbackAndEmit():
+ ctx = Context()
+ seen = []
+
+ def handler(d):
+ seen.append("diagnostic")
+ return True
+
+ handler_handle = ctx.attach_diagnostic_handler(handler)
+ ctx.enable_remarks(
+ all_filter=".*",
+ callback=lambda r: seen.append("callback"),
+ print_as_emit_remarks=True,
+ )
+ with ctx:
+ emit(Location.unknown())
+ ctx.finalize_remarks()
+ handler_handle.detach()
+ # CHECK: seen: ['callback', 'diagnostic']
+ print("seen:", seen)
+
+
+# CHECK-LABEL: TEST: testErrors
+ at run
+def testErrors():
+ ctx = Context()
+ for kwargs in (
+ dict(policy="sometimes"),
+ dict(format="xml"),
+ dict(format="yaml", callback=lambda r: None),
+ dict(format="yaml", output_file="/nonexistent-dir/remarks.yaml"),
+ ):
+ try:
+ ctx.enable_remarks(all_filter=".*", **kwargs)
+ print("no error (unexpected)")
+ except ValueError as e:
+ print("ValueError:", e)
+ # CHECK: ValueError: unknown remark policy 'sometimes'; expected 'all' or 'final'
+ # CHECK: ValueError: unknown remark format 'xml'; expected 'emit', 'yaml' or 'bitstream'
+ # CHECK: ValueError: a remark callback cannot be combined with format='yaml'; use format='emit'
+ # CHECK: ValueError: failed to enable remarks: cannot write '/nonexistent-dir/remarks.yaml'
+ ctx.enable_remarks(all_filter=".*")
+ try:
+ ctx.enable_remarks(all_filter=".*")
+ except ValueError as e:
+ # CHECK: ValueError: remarks are already enabled on this context; call finalize_remarks() first
+ print("ValueError:", e)
+ with ctx:
+ loc = Location.unknown()
+ try:
+ loc.emit_remark("sideways", "Name")
+ except ValueError as e:
+ # CHECK: ValueError: unknown remark kind 'sideways'; expected 'passed', 'missed', 'failure' or 'analysis'
+ print("ValueError:", e)
+ try:
+ loc.emit_remark(3, "Name")
+ except TypeError as e:
+ # CHECK: TypeError: remark kind must be a RemarkKind or a string
+ print("TypeError:", e)
+ ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testCallbackException
+ at run
+def testCallbackException():
+ ctx = Context()
+
+ def callback(remark):
+ raise RuntimeError("boom")
+
+ ctx.enable_remarks(all_filter=".*", callback=callback)
+ with ctx:
+ # The exception is printed to stderr and dropped; emission still succeeds.
+ # CHECK: emitted: True
+ print("emitted:", emit(Location.unknown()))
+ ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testContextDestroyedWithEngine
+ at run
+def testContextDestroyedWithEngine():
+ ctx = Context()
+ names = []
+ ctx.enable_remarks(
+ policy="final", all_filter=".*", callback=lambda r: names.append(r.remark_name)
+ )
+ with ctx:
+ emit(Location.unknown(), "passed", "Pending")
+ # Destroying the context finalizes the engine; a postponed remark cannot
+ # reach Python anymore and is dropped, without crashing.
+ ctx = None
+ gc.collect()
+ # CHECK: dropped: []
+ print("dropped:", names)
>From 6ea2a31b5cfeac72fca9f5acb5071e1abaca2d17 Mon Sep 17 00:00:00 2001
From: Guray Ozen <gozen at nvidia.com>
Date: Tue, 6 Oct 2026 10:22:26 +0200
Subject: [PATCH 2/2] fixes, address issues
---
mlir/include/mlir-c/Remarks.h | 7 +-
mlir/include/mlir/Bindings/Python/IRCore.h | 77 ++--
mlir/lib/Bindings/Python/IRCore.cpp | 294 +-------------
mlir/lib/Bindings/Python/IRRemarks.cpp | 232 +++++++++++
mlir/lib/Bindings/Python/MainModule.cpp | 3 +
mlir/lib/CAPI/IR/Remarks.cpp | 49 ++-
mlir/python/CMakeLists.txt | 1 +
mlir/test/CAPI/remarks.c | 141 +++++--
mlir/test/python/ir/remarks.py | 426 +++++++++++++--------
9 files changed, 726 insertions(+), 504 deletions(-)
create mode 100644 mlir/lib/Bindings/Python/IRRemarks.cpp
diff --git a/mlir/include/mlir-c/Remarks.h b/mlir/include/mlir-c/Remarks.h
index 1bd93ba395af4..f60051d422a96 100644
--- a/mlir/include/mlir-c/Remarks.h
+++ b/mlir/include/mlir-c/Remarks.h
@@ -159,10 +159,9 @@ MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetArgValue(MlirRemark remark,
intptr_t pos);
/// Prints the remark in its textual form, `[Kind] name | Category:... |
-/// Function=... | key=value, ...`, optionally followed by its location.
-MLIR_CAPI_EXPORTED void mlirRemarkPrint(MlirRemark remark, bool printLocation,
- MlirStringCallback callback,
- void *userData);
+/// Function=... | key=value, ...` (without its location).
+MLIR_CAPI_EXPORTED void
+mlirRemarkPrint(MlirRemark remark, MlirStringCallback callback, void *userData);
//===----------------------------------------------------------------------===//
// Emission.
diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index 027201a9d95a5..3edf12fbafee1 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -207,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 {
@@ -254,26 +274,24 @@ 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. `policy` is
- /// "all" or "final"; `format` is "emit" (remarks become MLIR remark
- /// diagnostics, or go to `callback` when one is given), "yaml" or
- /// "bitstream" (remarks are streamed to `outputFile`). Raises ValueError on
- /// invalid options or when an engine is already enabled.
- void enableRemarks(const std::string &policy, const std::string &format,
- const std::string &outputFile,
- const std::string &allFilter,
+ /// 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,
- nanobind::object printAsEmitRemarks);
+ 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 the
- /// wrapper is being destroyed, which is when a remark engine finalizing
- /// with the context must not call back into Python.
+ /// 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
@@ -376,14 +394,6 @@ enum class PyDiagnosticSeverity : std::underlying_type_t<
Remark = MlirDiagnosticRemark
};
-enum class PyRemarkKind : std::underlying_type_t<MlirRemarkKind> {
- Unknown = MlirRemarkKindUnknown,
- Passed = MlirRemarkKindPassed,
- Missed = MlirRemarkKindMissed,
- Failure = MlirRemarkKindFailure,
- Analysis = MlirRemarkKindAnalysis
-};
-
enum class PyWalkResult : std::underlying_type_t<MlirWalkResult> {
Advance = MlirWalkResultAdvance,
Interrupt = MlirWalkResultInterrupt,
@@ -446,21 +456,23 @@ class MLIR_PYTHON_API_EXPORTED PyDiagnostic {
/// an invalidated remark raises ValueError.
class MLIR_PYTHON_API_EXPORTED PyRemark {
public:
- PyRemark(MlirRemark remark) : remark(remark) {}
+ explicit PyRemark(MlirRemark remark) : remark(remark) {}
void invalidate() { valid = false; }
- bool isValid() { return valid; }
- PyRemarkKind getKind();
- nanobind::str getRemarkName();
- nanobind::str getCategoryName();
- nanobind::str getFullCategoryName();
- nanobind::str getFunctionName();
- nanobind::typed<nanobind::object, PyLocation> getLocation();
- uint64_t getId();
- nanobind::list getArgs();
- nanobind::str getMessage();
+ 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();
+ void checkValid() const;
MlirRemark remark;
bool valid = true;
};
@@ -2073,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/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 7caddb6156423..bb0a49ba93ce2 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -77,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) {
@@ -715,217 +714,6 @@ void PyThreadContextEntry::popLocation(PyLocation &location) {
// PyDiagnostic*
//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-// PyRemark and the remark engine of a context.
-//------------------------------------------------------------------------------
-
-namespace {
-/// User data of the remark callback streamer: the Python callable and the
-/// context it was installed on.
-struct PyRemarkCallbackData {
- MlirContext context;
- nb::object callback;
-};
-} // namespace
-
-static nb::str remarkStr(MlirStringRef ref) {
- return nb::str(ref.data, ref.length);
-}
-
-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;
- // Postponed remarks are reported when the engine is destroyed. When that
- // happens because the context itself is being destroyed, its Python wrapper
- // is already gone and the remark cannot be delivered.
- 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(
- const std::string &policy, const std::string &format,
- const std::string &outputFile, const std::string &allFilter,
- const std::string &passedFilter, const std::string &missedFilter,
- const std::string &analysisFilter, const std::string &failedFilter,
- nb::object callback, nb::object printAsEmitRemarks) {
- MlirRemarkPolicy remarkPolicy;
- if (policy == "all") {
- remarkPolicy = MlirRemarkPolicyAll;
- } else if (policy == "final") {
- remarkPolicy = MlirRemarkPolicyFinal;
- } else {
- throw nb::value_error(
- ("unknown remark policy '" + policy + "'; expected 'all' or 'final'")
- .c_str());
- }
- bool toFile = false;
- MlirRemarkFileFormat fileFormat = MlirRemarkFileFormatYAML;
- if (format == "yaml") {
- toFile = true;
- } else if (format == "bitstream") {
- toFile = true;
- fileFormat = MlirRemarkFileFormatBitstream;
- } else if (format != "emit") {
- throw nb::value_error(("unknown remark format '" + format +
- "'; expected 'emit', 'yaml' or 'bitstream'")
- .c_str());
- }
- bool hasCallback = !callback.is_none();
- if (toFile && hasCallback)
- throw nb::value_error(
- ("a remark callback cannot be combined with format='" + format +
- "'; use format='emit'")
- .c_str());
- 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)};
- // Without a sink of its own, the engine prints as MLIR remark diagnostics.
- bool emit = printAsEmitRemarks.is_none() ? (!toFile && !hasCallback)
- : nb::cast<bool>(printAsEmitRemarks);
-
- MlirLogicalResult result;
- if (toFile) {
- std::string path = outputFile;
- if (path.empty())
- path = fileFormat == MlirRemarkFileFormatYAML ? "mlir-remarks.yaml"
- : "mlir-remarks.bitstream";
- result = mlirContextEnableOptimizationRemarksToFile(
- get(), categories, remarkPolicy, fileFormat, toMlirStringRef(path),
- emit);
- if (mlirLogicalResultIsFailure(result))
- throw nb::value_error(
- ("failed to enable remarks: cannot write '" + path + "'").c_str());
- } else if (hasCallback) {
- auto *data = new PyRemarkCallbackData{get(), std::move(callback)};
- result = mlirContextEnableOptimizationRemarksWithCallback(
- get(), categories, remarkPolicy, pyRemarkCallback, data,
- pyRemarkCallbackDelete, emit);
- } else {
- result = mlirContextEnableOptimizationRemarks(get(), categories,
- remarkPolicy, emit);
- }
- 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() {
- if (!valid)
- throw std::invalid_argument("Remark is invalid (used outside of callback)");
-}
-
-PyRemarkKind PyRemark::getKind() {
- checkValid();
- return static_cast<PyRemarkKind>(mlirRemarkGetKind(remark));
-}
-
-nb::str PyRemark::getRemarkName() {
- checkValid();
- return remarkStr(mlirRemarkGetRemarkName(remark));
-}
-
-nb::str PyRemark::getCategoryName() {
- checkValid();
- return remarkStr(mlirRemarkGetCategoryName(remark));
-}
-
-nb::str PyRemark::getFullCategoryName() {
- checkValid();
- return remarkStr(mlirRemarkGetFullCategoryName(remark));
-}
-
-nb::str PyRemark::getFunctionName() {
- checkValid();
- return remarkStr(mlirRemarkGetFunctionName(remark));
-}
-
-nb::typed<nb::object, PyLocation> PyRemark::getLocation() {
- checkValid();
- MlirLocation loc = mlirRemarkGetLocation(remark);
- MlirContext context = mlirLocationGetContext(loc);
- return PyLocation(PyMlirContext::forContext(context), loc).maybeDownCast();
-}
-
-uint64_t PyRemark::getId() {
- checkValid();
- return mlirRemarkGetId(remark);
-}
-
-nb::list PyRemark::getArgs() {
- 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() {
- checkValid();
- std::string message;
- auto callback = +[](MlirStringRef ref, void *userData) {
- static_cast<std::string *>(userData)->append(ref.data, ref.length);
- };
- mlirRemarkPrint(remark, /*printLocation=*/false, callback, &message);
- return nb::str(message.c_str(), message.size());
-}
-
-/// Converts the `kind` argument of `Location.emit_remark`, a RemarkKind or one
-/// of the strings "passed", "missed", "failure"/"failed" and "analysis".
-static MlirRemarkKind parseRemarkKind(nb::handle kind) {
- if (nb::isinstance<PyRemarkKind>(kind))
- return static_cast<MlirRemarkKind>(nb::cast<PyRemarkKind>(kind));
- std::string name;
- if (nb::try_cast<std::string>(kind, name)) {
- if (name == "passed")
- return MlirRemarkKindPassed;
- if (name == "missed")
- return MlirRemarkKindMissed;
- if (name == "failure" || name == "failed")
- return MlirRemarkKindFailure;
- if (name == "analysis")
- return MlirRemarkKindAnalysis;
- throw nb::value_error(("unknown remark kind '" + name +
- "'; expected 'passed', 'missed', 'failure' or "
- "'analysis'")
- .c_str());
- }
- throw nb::type_error("remark kind must be a RemarkKind or a string");
-}
-
void PyDiagnostic::invalidate() {
valid = false;
if (materializedNotes) {
@@ -1144,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>
@@ -1156,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,
@@ -2934,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) {
@@ -3095,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;
@@ -3538,13 +3321,6 @@ void populateIRCore(nb::module_ &m) {
.value("NOTE", PyDiagnosticSeverity::Note)
.value("REMARK", PyDiagnosticSeverity::Remark);
- 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_<PyWalkOrder>(m, "WalkOrder")
.value("PRE_ORDER", PyWalkOrder::PreOrder)
.value("POST_ORDER", PyWalkOrder::PostOrder);
@@ -3620,37 +3396,6 @@ void populateIRCore(nb::module_ &m) {
"exc_value"_a.none(), "traceback"_a.none(),
"Exits the diagnostic handler context manager.");
- 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.");
-
// Expose DefaultThreadPool to python
nb::class_<PyThreadPool>(m, "ThreadPool")
.def(
@@ -3750,8 +3495,10 @@ void populateIRCore(nb::module_ &m) {
"callback"_a,
"Attaches a diagnostic handler that will receive callbacks.")
.def("enable_remarks", &PyMlirContext::enableRemarks, nb::kw_only(),
- "policy"_a = "all", "format"_a = "emit", "output_file"_a = "",
- "all_filter"_a = "", "passed_filter"_a = "", "missed_filter"_a = "",
+ "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(),
@@ -3763,29 +3510,31 @@ void populateIRCore(nb::module_ &m) {
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: "all" reports remarks as they are emitted; "final"
- postpones them until `finalize_remarks()` and reports the final
- set, grouping related remarks under their parents.
- format: "emit" delivers remarks as MLIR remark diagnostics, or to
- `callback` when one is given; "yaml" and "bitstream" stream
- them to `output_file`, written on `finalize_remarks()`.
- output_file: The output path for the file formats; defaults to
- "mlir-remarks.yaml" / "mlir-remarks.bitstream".
+ 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. Requires
- format="emit".
+ 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 for format="emit" without a
+ diagnostic; defaults to True only without an output file or
callback.
Raises:
- ValueError: On an unknown policy, format or kind combination,
+ 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,
@@ -4154,12 +3903,12 @@ void populateIRCore(nb::module_ &m) {
message: The error message to emit.)")
.def(
"emit_remark",
- [](PyLocation &self, nb::object kind, const std::string &remarkName,
+ [](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 {
- MlirRemarkKind remarkKind = parseRemarkKind(kind);
+ auto remarkKind = static_cast<MlirRemarkKind>(kind);
std::vector<MlirStringRef> keys, values;
keys.reserve(args.size());
values.reserve(args.size());
@@ -4181,8 +3930,7 @@ void populateIRCore(nb::module_ &m) {
engine of the context (see `Context.enable_remarks`).
Args:
- kind: A `RemarkKind`, or one of "passed", "missed", "failure"
- and "analysis".
+ 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.
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/Remarks.cpp b/mlir/lib/CAPI/IR/Remarks.cpp
index 5a54e5b38895c..a05a9cbe74ca8 100644
--- a/mlir/lib/CAPI/IR/Remarks.cpp
+++ b/mlir/lib/CAPI/IR/Remarks.cpp
@@ -15,6 +15,10 @@
#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;
@@ -48,9 +52,8 @@ class CallbackRemarkStreamer : public remark::detail::MLIRRemarkStreamerBase {
};
} // namespace
-/// Like mlir-opt, every filter is handed to the engine as a (possibly empty)
-/// string: the engine then reports a kind when its own filter or `all`
-/// matches, and reports nothing for a kind whose filters are both empty.
+/// 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(),
@@ -69,6 +72,16 @@ createPolicy(MlirRemarkPolicy policy) {
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:
@@ -107,12 +120,9 @@ MlirLogicalResult mlirContextEnableOptimizationRemarksToFile(
MLIRContext *ctx = unwrap(context);
if (ctx->getRemarkEngine())
return mlirLogicalResultFailure();
- llvm::remarks::Format llvmFormat = format == MlirRemarkFileFormatBitstream
- ? llvm::remarks::Format::Bitstream
- : llvm::remarks::Format::YAML;
return wrap(remark::enableOptimizationRemarksWithLLVMStreamer(
- *ctx, unwrap(path), llvmFormat, createPolicy(policy), unwrap(categories),
- printAsEmitRemarks));
+ *ctx, unwrap(path), unwrap(format), createPolicy(policy),
+ unwrap(categories), printAsEmitRemarks));
}
MlirLogicalResult mlirContextEnableOptimizationRemarksWithCallback(
@@ -122,6 +132,7 @@ MlirLogicalResult mlirContextEnableOptimizationRemarksWithCallback(
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();
@@ -134,8 +145,8 @@ MlirLogicalResult mlirContextEnableOptimizationRemarksWithCallback(
}
void mlirContextFinalizeOptimizationRemarks(MlirContext context) {
- // The engine destructor finalizes the policy (reporting postponed remarks)
- // and then the streamer (writing the file / releasing the user data).
+ // The engine destructor reports the postponed remarks, then destroys the
+ // streamer.
unwrap(context)->setRemarkEngine(nullptr);
}
@@ -180,17 +191,23 @@ intptr_t mlirRemarkGetNumArgs(MlirRemark remark) {
}
MlirStringRef mlirRemarkGetArgKey(MlirRemark remark, intptr_t pos) {
- return wrap(llvm::StringRef(unwrap(remark)->getArgs()[pos].key));
+ 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) {
- return wrap(llvm::StringRef(unwrap(remark)->getArgs()[pos].val));
+ 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, bool printLocation,
- MlirStringCallback callback, void *userData) {
+void mlirRemarkPrint(MlirRemark remark, MlirStringCallback callback,
+ void *userData) {
detail::CallbackOstream stream(callback, userData);
- unwrap(remark)->print(stream, printLocation);
+ unwrap(remark)->print(stream);
}
//===----------------------------------------------------------------------===//
@@ -202,6 +219,8 @@ bool mlirEmitOptimizationRemark(
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))
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/remarks.c b/mlir/test/CAPI/remarks.c
index 1b74ac624c0f2..0e8d753c1d3b4 100644
--- a/mlir/test/CAPI/remarks.c
+++ b/mlir/test/CAPI/remarks.c
@@ -37,12 +37,11 @@ static MlirRemarkCategories allCategories(const char *filter) {
return cats;
}
-/// The callback streamer: prints every field of the remark it receives and
-/// counts the deliveries in userData.
+/// 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 = "?";
+ const char *kind = "unknown";
switch (mlirRemarkGetKind(remark)) {
case MlirRemarkKindPassed:
kind = "passed";
@@ -57,7 +56,6 @@ static void remarkCallback(MlirRemark remark, void *userData) {
kind = "analysis";
break;
case MlirRemarkKindUnknown:
- kind = "unknown";
break;
}
MlirStringRef name = mlirRemarkGetRemarkName(remark);
@@ -78,7 +76,7 @@ static void remarkCallback(MlirRemark remark, void *userData) {
(int)value.length, value.data);
}
fprintf(stderr, " print: ");
- mlirRemarkPrint(remark, /*printLocation=*/true, printToStderr, NULL);
+ mlirRemarkPrint(remark, printToStderr, NULL);
fprintf(stderr, "\n location: ");
mlirLocationPrint(mlirRemarkGetLocation(remark), printToStderr, NULL);
fprintf(stderr, "\n");
@@ -88,6 +86,11 @@ 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")};
@@ -103,7 +106,6 @@ static void testCallbackStreamer(void) {
MlirContext ctx = mlirContextCreate();
MlirLocation loc = mlirLocationFileLineColGet(ctx, str("remarks.c"), 7, 3);
- // Without an engine nothing is emitted.
// CHECK: no engine: enabled=0 emitted=0
bool emitted = emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "dropped");
fprintf(stderr, "no engine: enabled=%d emitted=%d\n",
@@ -117,25 +119,18 @@ static void testCallbackStreamer(void) {
fprintf(stderr, "enabled=%d ok=%d\n", mlirContextHasRemarkEngine(ctx),
mlirLogicalResultIsSuccess(res));
- // Enabling twice fails and leaves the first engine in place.
- // CHECK: second enable ok=0
- res = mlirContextEnableOptimizationRemarks(ctx, allCategories(".*"),
- MlirRemarkPolicyAll, true);
- fprintf(stderr, "second enable ok=%d\n", 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 | @"remarks.c":7:3{{ ?}}Remark=unrolled, RemarkId=1, 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);
- // A category the filter does not match is dropped before the streamer.
// CHECK-NOT: name=Vectorize
// CHECK: count after filtered emit=1
emit(loc, MlirRemarkKindMissed, "Vectorize", "Vector", "not matched");
@@ -148,7 +143,6 @@ static void testCallbackStreamer(void) {
emit(loc, MlirRemarkKindFailure, "Fuse", "Loop", "no");
emit(loc, MlirRemarkKindAnalysis, "TripCount", "Loop", "4");
- // Finalize drops the engine and releases the user data.
// CHECK: deleteUserData called (count=4)
// CHECK: after finalize: enabled=0 count=4
mlirContextFinalizeOptimizationRemarks(ctx);
@@ -195,9 +189,9 @@ static MlirLogicalResult diagnosticHandler(MlirDiagnostic diagnostic,
return mlirLogicalResultSuccess();
}
-// CHECK-LABEL: @testEmitRemarkForm
-static void testEmitRemarkForm(void) {
- fprintf(stderr, "@testEmitRemarkForm\n");
+// CHECK-LABEL: @testEmitAsDiagnostics
+static void testEmitAsDiagnostics(void) {
+ fprintf(stderr, "@testEmitAsDiagnostics\n");
MlirContext ctx = mlirContextCreate();
MlirLocation loc = mlirLocationUnknownGet(ctx);
MlirDiagnosticHandlerID id =
@@ -220,15 +214,16 @@ static void testFileStreamer(void) {
fprintf(stderr, "@testFileStreamer\n");
MlirContext ctx = mlirContextCreate();
MlirLocation loc = mlirLocationUnknownGet(ctx);
- char path[] = "mlir-capi-remarks-test.yaml";
+
+ char yamlPath[] = "mlir-capi-remarks-test.yaml";
MlirLogicalResult res = mlirContextEnableOptimizationRemarksToFile(
ctx, allCategories(".*"), MlirRemarkPolicyAll, MlirRemarkFileFormatYAML,
- str(path), false);
- // CHECK: file enable ok=1
- fprintf(stderr, "file enable ok=%d\n", mlirLogicalResultIsSuccess(res));
+ 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(path, "r");
+ FILE *f = fopen(yamlPath, "r");
bool found = false;
if (f) {
char line[256];
@@ -236,17 +231,113 @@ static void testFileStreamer(void) {
if (strstr(line, "Unroll"))
found = true;
fclose(f);
- remove(path);
+ 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();
- testEmitRemarkForm();
+ testEmitAsDiagnostics();
testFileStreamer();
+ testAccessorDefaults();
+ testEnableWhileEnabled();
return 0;
}
diff --git a/mlir/test/python/ir/remarks.py b/mlir/test/python/ir/remarks.py
index dcf8e6329c9ba..49fbe4ebb6767 100644
--- a/mlir/test/python/ir/remarks.py
+++ b/mlir/test/python/ir/remarks.py
@@ -14,32 +14,55 @@ def run(f):
return f
-def emit(loc, kind="passed", name="Unroll", category="Loop", **kwargs):
+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
@run
def testNoEngine():
ctx = Context()
- with ctx:
- loc = Location.file("test.mlir", 1, 2)
- # 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()
+ 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
@run
def testCallback():
ctx = Context()
- collected = []
def callback(remark):
- collected.append(remark)
print(
"kind:",
remark.kind,
@@ -51,75 +74,108 @@ def callback(remark):
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)
- print("id:", remark.remark_id)
+ 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)
- with ctx:
- loc = Location.file("test.mlir", 3, 4)
- # CHECK: kind: RemarkKind.PASSED name: Unroll category: Loop full: Loop:Inner function: main
- # 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)
- # CHECK: id: 1
- # CHECK: emitted: True
- emitted = emit(
- loc,
- RemarkKind.PASSED,
- sub_category="Inner",
- function_name="main",
- message="unrolled by 4",
- args=[("factor", "4")],
- )
- print("emitted:", emitted)
- # A remark is invalid once the callback has returned.
- try:
- collected[0].remark_name
- except ValueError as e:
- # CHECK: invalid: Remark is invalid (used outside of callback)
- print("invalid:", e)
- # CHECK: invalid str: <Invalid Remark>
- print("invalid str:", str(collected[0]))
- collected.clear()
+ 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
@run
def testFilters():
ctx = Context()
names = []
ctx.enable_remarks(
- all_filter="Loop",
- passed_filter="Vector",
- callback=lambda r: names.append(r.remark_name),
+ all_filter="Loop", passed_filter="Vector", callback=collect(names)
)
- with ctx:
- loc = Location.unknown()
- # The all-filter admits every kind of the Loop category.
- # CHECK: loop passed: True
- print("loop passed:", emit(loc, "passed", "LoopPassed", "Loop"))
- # CHECK: loop missed: True
- print("loop missed:", emit(loc, "missed", "LoopMissed", "Loop"))
- # The passed-filter admits only passed remarks of the Vector category.
- # CHECK: vector passed: True
- print("vector passed:", emit(loc, "passed", "VectorPassed", "Vector"))
- # CHECK: vector missed: False
- print("vector missed:", emit(loc, "missed", "VectorMissed", "Vector"))
- # CHECK: other: False
- print("other:", emit(loc, "analysis", "Other", "Memory"))
- # Unknown kinds are never emitted.
- # CHECK: unknown: False
- print("unknown:", emit(loc, RemarkKind.UNKNOWN, "Unknown", "Loop"))
+ 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)
@@ -131,28 +187,18 @@ def testFinalPolicy():
ctx = Context()
names = []
ctx.enable_remarks(
- policy="final", all_filter=".*", callback=lambda r: names.append(r.remark_name)
+ policy=RemarkPolicy.FINAL, all_filter=".*", callback=collect(names)
)
- with ctx:
- loc = Location.unknown()
- emit(loc, "passed", "First")
- emit(loc, "missed", "Second")
- emit(loc, "failed", "Third")
- emit(loc, "analysis", "Fourth")
- # Nothing is delivered until finalize.
+ 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: 4
- print("after finalize:", len(names))
- assert sorted(names) == ["First", "Fourth", "Second", "Third"]
- # The engine can be enabled again after finalize.
- ctx.enable_remarks(all_filter=".*", callback=lambda r: names.append(r.remark_name))
- with ctx:
- emit(Location.unknown(), "passed", "Fifth")
- # CHECK: re-enabled: Fifth
- print("re-enabled:", names[-1])
- ctx.finalize_remarks()
+ # CHECK: after finalize: ['First', 'Fourth', 'Second', 'Third']
+ print("after finalize:", sorted(names))
# CHECK-LABEL: TEST: testYamlFile
@@ -161,111 +207,153 @@ def testYamlFile():
ctx = Context()
directory = tempfile.mkdtemp()
path = os.path.join(directory, "remarks.yaml")
- ctx.enable_remarks(format="yaml", output_file=path, all_filter=".*")
- with ctx:
- loc = Location.file("test.mlir", 5, 6)
- # CHECK: emitted: True
- print("emitted:", emit(loc, "missed", "NotUnrolled", "Loop", function_name="f"))
+ 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 = f.read()
- # CHECK: has file: True
- print("has file:", os.path.exists(path))
- # CHECK: has name: True
- print("has name:", "NotUnrolled" in content)
- # CHECK: has pass: True
- print("has pass:", "Loop" in content)
+ 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: testEmitFormat
+# CHECK-LABEL: TEST: testBitstreamFile
@run
-def testEmitFormat():
+def testBitstreamFile():
ctx = Context()
- messages = []
-
- def handler(d):
- messages.append((d.severity, str(d.message)))
- return True
-
- handler_handle = ctx.attach_diagnostic_handler(handler)
- ctx.enable_remarks(all_filter=".*")
- with ctx:
- emit(
- Location.unknown(),
- "analysis",
- "TripCount",
- "Loop",
- message="trip count is 4",
- )
+ 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()
- handler_handle.detach()
- # CHECK: diagnostics: [(DiagnosticSeverity.REMARK, '[Analysis] TripCount | Category:Loop | Remark="trip count is 4", RemarkId=1')]
- print("diagnostics:", messages)
+ # 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: testCallbackAndEmit
+# CHECK-LABEL: TEST: testEmitAsDiagnostics
@run
-def testCallbackAndEmit():
+def testEmitAsDiagnostics():
ctx = Context()
seen = []
def handler(d):
- seen.append("diagnostic")
+ seen.append((d.severity, str(d.message)))
return True
- handler_handle = ctx.attach_diagnostic_handler(handler)
+ 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,
)
- with ctx:
- emit(Location.unknown())
+ emit(loc)
ctx.finalize_remarks()
- handler_handle.detach()
- # CHECK: seen: ['callback', 'diagnostic']
- print("seen:", seen)
+ handle.detach()
+ # CHECK: both: ['callback', (DiagnosticSeverity.REMARK, '[Passed] Unroll | Category:Loop | RemarkId=1')]
+ print("both:", seen)
# CHECK-LABEL: TEST: testErrors
@run
def testErrors():
ctx = Context()
- for kwargs in (
- dict(policy="sometimes"),
- dict(format="xml"),
- dict(format="yaml", callback=lambda r: None),
- dict(format="yaml", output_file="/nonexistent-dir/remarks.yaml"),
- ):
+ 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(all_filter=".*", **kwargs)
+ ctx.enable_remarks(**kwargs)
print("no error (unexpected)")
- except ValueError as e:
- print("ValueError:", e)
- # CHECK: ValueError: unknown remark policy 'sometimes'; expected 'all' or 'final'
- # CHECK: ValueError: unknown remark format 'xml'; expected 'emit', 'yaml' or 'bitstream'
- # CHECK: ValueError: a remark callback cannot be combined with format='yaml'; use format='emit'
- # CHECK: ValueError: failed to enable remarks: cannot write '/nonexistent-dir/remarks.yaml'
+ 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)
- with ctx:
- loc = Location.unknown()
- try:
- loc.emit_remark("sideways", "Name")
- except ValueError as e:
- # CHECK: ValueError: unknown remark kind 'sideways'; expected 'passed', 'missed', 'failure' or 'analysis'
- print("ValueError:", e)
- try:
- loc.emit_remark(3, "Name")
- except TypeError as e:
- # CHECK: TypeError: remark kind must be a RemarkKind or a string
- print("TypeError:", 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()
@@ -273,30 +361,58 @@ def testErrors():
@run
def testCallbackException():
ctx = Context()
+ names = []
def callback(remark):
- raise RuntimeError("boom")
+ names.append(remark.remark_name)
+ if remark.remark_name == "Boom":
+ raise RuntimeError("boom")
ctx.enable_remarks(all_filter=".*", callback=callback)
- with ctx:
- # The exception is printed to stderr and dropped; emission still succeeds.
- # CHECK: emitted: True
- print("emitted:", emit(Location.unknown()))
+ 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
@run
def testContextDestroyedWithEngine():
ctx = Context()
names = []
ctx.enable_remarks(
- policy="final", all_filter=".*", callback=lambda r: names.append(r.remark_name)
+ policy=RemarkPolicy.FINAL, all_filter=".*", callback=collect(names)
)
- with ctx:
- emit(Location.unknown(), "passed", "Pending")
- # Destroying the context finalizes the engine; a postponed remark cannot
- # reach Python anymore and is dropped, without crashing.
+ emit(Location.unknown(context=ctx), RemarkKind.PASSED, "Pending")
+ # A remark postponed until the context's destruction is dropped.
ctx = None
gc.collect()
# CHECK: dropped: []
More information about the Mlir-commits
mailing list