[Mlir-commits] [mlir] 3d519f5 - [MLIR][CAPI][Python] Add C API and Python bindings for the remark engine (#229094)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sat Oct 10 04:34:17 PDT 2026
Author: Guray Ozen
Date: 2026-10-10T13:34:10+02:00
New Revision: 3d519f5eee97ad32db4882eab8fda86ebf8faee9
URL: https://github.com/llvm/llvm-project/commit/3d519f5eee97ad32db4882eab8fda86ebf8faee9
DIFF: https://github.com/llvm/llvm-project/commit/3d519f5eee97ad32db4882eab8fda86ebf8faee9.diff
LOG: [MLIR][CAPI][Python] Add C API and Python bindings for the remark engine (#229094)
Expose the optimization remark engine (`mlir/IR/Remarks.h`) through the
C API and the Python bindings, so clients outside C++ can enable it,
receive the reported remarks and emit their own.
### C API (`mlir-c/Remarks.h`)
- `mlirContextEnableOptimizationRemarks{,ToFile,WithCallback}` select
the categories (one regex per kind plus `all`), the emitting policy
(`all` or `final`) and the sink: MLIR remark diagnostics, a
YAML/bitstream file, or a callback invoked with an opaque `MlirRemark`.
- `mlirContextFinalizeOptimizationRemarks` flushes postponed remarks,
writes the file and drops the engine; `mlirContextHasRemarkEngine`
queries it.
- `MlirRemark` accessors (kind, remark/category/function names,
location, id, key/value arguments, print), valid for the duration of the
callback.
- `mlirEmitOptimizationRemark` emits a remark at a location through the
engine of its context.
### Python bindings
- `Context.enable_remarks(policy=, format=, output_file=, all_filter=,
passed_filter=, missed_filter=, analysis_filter=, failed_filter=,
callback=, print_as_emit_remarks=)`, `Context.finalize_remarks()`,
`Context.remarks_enabled`.
- `ir.Remark` and `ir.RemarkKind`: a remark wrapped for the duration of
the callback and invalidated afterwards, like `ir.Diagnostic`.
- `Location.emit_remark(kind, remark_name, category=, sub_category=,
function_name=, message=, args=)`.
The filter semantics follow `mlir-opt`'s `--remarks-filter` /
`--remark-policy` / `--remark-format`: every category filter reaches the
engine as a (possibly empty) string, so a kind is reported when its own
filter or `all` matches.
### Tests
`mlir/test/CAPI/remarks.c` (`mlir-capi-remarks-test`) and
`mlir/test/python/ir/remarks.py`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5.1 <noreply at anthropic.com>
Added:
mlir/include/mlir-c/Remarks.h
mlir/include/mlir/CAPI/Remarks.h
mlir/lib/Bindings/Python/IRRemarks.cpp
mlir/lib/CAPI/IR/Remarks.cpp
mlir/test/CAPI/remarks.c
mlir/test/python/ir/remarks.py
Modified:
mlir/include/mlir/Bindings/Python/IRCore.h
mlir/lib/Bindings/Python/IRCore.cpp
mlir/lib/Bindings/Python/MainModule.cpp
mlir/lib/CAPI/IR/CMakeLists.txt
mlir/python/CMakeLists.txt
mlir/test/CAPI/CMakeLists.txt
mlir/test/CMakeLists.txt
mlir/test/lit.cfg.py
Removed:
################################################################################
diff --git a/mlir/include/mlir-c/Remarks.h b/mlir/include/mlir-c/Remarks.h
new file mode 100644
index 0000000000000..f60051d422a96
--- /dev/null
+++ b/mlir/include/mlir-c/Remarks.h
@@ -0,0 +1,185 @@
+//===-- mlir-c/Remarks.h - MLIR Remark Engine C API ---------------*- C -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This header declares the C interface to the MLIR optimization remark engine:
+// enabling the engine on a context with category filters, an emitting policy
+// and a sink (MLIR diagnostics, a YAML/bitstream file, or a callback),
+// emitting remarks, and inspecting a remark from inside a callback.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_C_REMARKS_H
+#define MLIR_C_REMARKS_H
+
+#include "mlir-c/IR.h"
+#include "mlir-c/Support.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//===----------------------------------------------------------------------===//
+// Opaque types and enums.
+//===----------------------------------------------------------------------===//
+
+/// An opaque reference to a remark. A remark is only valid for the duration of
+/// the callback it is passed to; it must not be stored.
+struct MlirRemark {
+ const void *ptr;
+};
+typedef struct MlirRemark MlirRemark;
+
+/// The outcome a remark describes (see mlir::remark::RemarkKind).
+enum MlirRemarkKind {
+ MlirRemarkKindUnknown,
+ /// An optimization was applied.
+ MlirRemarkKindPassed,
+ /// A profitable optimization opportunity was found but not applied.
+ MlirRemarkKindMissed,
+ /// The optimization was attempted but failed.
+ MlirRemarkKindFailure,
+ /// Informational context without a pass/fail outcome.
+ MlirRemarkKindAnalysis
+};
+typedef enum MlirRemarkKind MlirRemarkKind;
+
+/// The emitting policy of the remark engine.
+enum MlirRemarkPolicy {
+ /// Report every remark as it is emitted.
+ MlirRemarkPolicyAll,
+ /// Postpone remarks and report the final set when the engine is finalized,
+ /// grouping related remarks under their parents.
+ MlirRemarkPolicyFinal
+};
+typedef enum MlirRemarkPolicy MlirRemarkPolicy;
+
+/// The serialization format of a remark output file.
+enum MlirRemarkFileFormat {
+ MlirRemarkFileFormatYAML,
+ MlirRemarkFileFormatBitstream
+};
+typedef enum MlirRemarkFileFormat MlirRemarkFileFormat;
+
+/// Regular expressions (llvm::Regex syntax, anchored by the engine) selecting
+/// the remark categories to report: `all` applies to every kind, the others to
+/// one kind each. A string of length 0 means the filter is not set, so the
+/// corresponding kind is not reported unless `all` matches.
+struct MlirRemarkCategories {
+ MlirStringRef all;
+ MlirStringRef passed;
+ MlirStringRef missed;
+ MlirStringRef analysis;
+ MlirStringRef failed;
+};
+typedef struct MlirRemarkCategories MlirRemarkCategories;
+
+/// Callback receiving every remark the engine reports.
+typedef void (*MlirRemarkCallback)(MlirRemark remark, void *userData);
+
+//===----------------------------------------------------------------------===//
+// Enabling and finalizing the engine of a context.
+//===----------------------------------------------------------------------===//
+
+/// Enables the remark engine on the context without a streamer; when
+/// `printAsEmitRemarks` is set, every reported remark is emitted as an MLIR
+/// remark diagnostic at its location. Fails when the context already has a
+/// remark engine (finalize it first).
+MLIR_CAPI_EXPORTED MlirLogicalResult mlirContextEnableOptimizationRemarks(
+ MlirContext context, MlirRemarkCategories categories,
+ MlirRemarkPolicy policy, bool printAsEmitRemarks);
+
+/// Enables the remark engine on the context, streaming the reported remarks to
+/// the file at `path` in `format`. The file is written when the engine is
+/// finalized. Fails when the file cannot be created or the context already has
+/// a remark engine.
+MLIR_CAPI_EXPORTED MlirLogicalResult mlirContextEnableOptimizationRemarksToFile(
+ MlirContext context, MlirRemarkCategories categories,
+ MlirRemarkPolicy policy, MlirRemarkFileFormat format, MlirStringRef path,
+ bool printAsEmitRemarks);
+
+/// Enables the remark engine on the context, delivering every reported remark
+/// to `callback`. `userData` is passed to the callback unchanged and released
+/// through `deleteUserData` (may be null) when the engine is finalized or the
+/// context is destroyed. Fails when the context already has a remark engine.
+MLIR_CAPI_EXPORTED MlirLogicalResult
+mlirContextEnableOptimizationRemarksWithCallback(
+ MlirContext context, MlirRemarkCategories categories,
+ MlirRemarkPolicy policy, MlirRemarkCallback callback, void *userData,
+ void (*deleteUserData)(void *), bool printAsEmitRemarks);
+
+/// Finalizes and removes the remark engine of the context: postponed remarks
+/// are reported, the output file is written and the callback's user data is
+/// released. Does nothing when no engine is enabled.
+MLIR_CAPI_EXPORTED void
+mlirContextFinalizeOptimizationRemarks(MlirContext context);
+
+/// Returns whether the context has a remark engine enabled.
+MLIR_CAPI_EXPORTED bool mlirContextHasRemarkEngine(MlirContext context);
+
+//===----------------------------------------------------------------------===//
+// Remark accessors (valid only inside a remark callback).
+//===----------------------------------------------------------------------===//
+
+/// Returns the kind of the remark.
+MLIR_CAPI_EXPORTED MlirRemarkKind mlirRemarkGetKind(MlirRemark remark);
+
+/// Returns the name identifying the remark.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetRemarkName(MlirRemark remark);
+
+/// Returns the category of the remark (the subject of the filters).
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetCategoryName(MlirRemark remark);
+
+/// Returns the combined `category:subcategory` name of the remark.
+MLIR_CAPI_EXPORTED MlirStringRef
+mlirRemarkGetFullCategoryName(MlirRemark remark);
+
+/// Returns the name of the function the remark refers to.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetFunctionName(MlirRemark remark);
+
+/// Returns the location of the remark.
+MLIR_CAPI_EXPORTED MlirLocation mlirRemarkGetLocation(MlirRemark remark);
+
+/// Returns the unique id of the remark within its engine (0 when unset).
+MLIR_CAPI_EXPORTED uint64_t mlirRemarkGetId(MlirRemark remark);
+
+/// Returns the number of key/value arguments attached to the remark.
+MLIR_CAPI_EXPORTED intptr_t mlirRemarkGetNumArgs(MlirRemark remark);
+
+/// Returns the key of the `pos`-th argument of the remark.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetArgKey(MlirRemark remark,
+ intptr_t pos);
+
+/// Returns the value of the `pos`-th argument of the remark.
+MLIR_CAPI_EXPORTED MlirStringRef mlirRemarkGetArgValue(MlirRemark remark,
+ intptr_t pos);
+
+/// Prints the remark in its textual form, `[Kind] name | Category:... |
+/// Function=... | key=value, ...` (without its location).
+MLIR_CAPI_EXPORTED void
+mlirRemarkPrint(MlirRemark remark, MlirStringCallback callback, void *userData);
+
+//===----------------------------------------------------------------------===//
+// Emission.
+//===----------------------------------------------------------------------===//
+
+/// Emits a remark of `kind` at `location` through the remark engine of the
+/// location's context. `message` (when non-empty) and the `numArgs` key/value
+/// pairs become the arguments of the remark. Returns false, emitting nothing,
+/// when the context has no remark engine, the category is filtered out, or
+/// `kind` is MlirRemarkKindUnknown.
+MLIR_CAPI_EXPORTED bool mlirEmitOptimizationRemark(
+ MlirLocation location, MlirRemarkKind kind, MlirStringRef remarkName,
+ MlirStringRef categoryName, MlirStringRef subCategoryName,
+ MlirStringRef functionName, MlirStringRef message, intptr_t numArgs,
+ const MlirStringRef *argKeys, const MlirStringRef *argValues);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MLIR_C_REMARKS_H
diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index b28e6ec606d52..3edf12fbafee1 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -26,6 +26,7 @@
#include "mlir-c/ExtensibleDialect.h"
#include "mlir-c/IR.h"
#include "mlir-c/IntegerSet.h"
+#include "mlir-c/Remarks.h"
#include "mlir-c/Support.h"
#include "mlir-c/Transforms.h"
#include "mlir/Bindings/Python/Nanobind.h"
@@ -206,6 +207,26 @@ class MLIR_PYTHON_API_EXPORTED PyThreadPool {
MlirLlvmThreadPool threadPool;
};
+/// The kinds, emitting policies and file formats of the remark engine
+/// (mlir-c/Remarks.h).
+enum class PyRemarkKind : std::underlying_type_t<MlirRemarkKind> {
+ Unknown = MlirRemarkKindUnknown,
+ Passed = MlirRemarkKindPassed,
+ Missed = MlirRemarkKindMissed,
+ Failure = MlirRemarkKindFailure,
+ Analysis = MlirRemarkKindAnalysis
+};
+
+enum class PyRemarkPolicy : std::underlying_type_t<MlirRemarkPolicy> {
+ All = MlirRemarkPolicyAll,
+ Final = MlirRemarkPolicyFinal
+};
+
+enum class PyRemarkFormat : std::underlying_type_t<MlirRemarkFileFormat> {
+ YAML = MlirRemarkFileFormatYAML,
+ Bitstream = MlirRemarkFileFormatBitstream
+};
+
/// Wrapper around MlirContext.
using PyMlirContextRef = PyObjectRef<PyMlirContext>;
class MLIR_PYTHON_API_EXPORTED PyMlirContext {
@@ -253,6 +274,26 @@ class MLIR_PYTHON_API_EXPORTED PyMlirContext {
/// registration object (internally a PyDiagnosticHandler).
nanobind::object attachDiagnosticHandler(nanobind::object callback);
+ /// Enables the optimization remark engine of the context, streaming to
+ /// `outputFile`, to `callback`, or (without either) to MLIR remark
+ /// diagnostics. Raises ValueError on an invalid combination, when an engine
+ /// is already enabled or when the file cannot be written.
+ void enableRemarks(PyRemarkPolicy policy,
+ const std::optional<std::string> &outputFile,
+ PyRemarkFormat format, const std::string &allFilter,
+ const std::string &passedFilter,
+ const std::string &missedFilter,
+ const std::string &analysisFilter,
+ const std::string &failedFilter, nanobind::object callback,
+ std::optional<bool> printAsEmitRemarks);
+ /// Finalizes and removes the remark engine; no-op when none is enabled.
+ void finalizeRemarks();
+ /// Whether a remark engine is enabled on the context.
+ bool getRemarksEnabled();
+ /// Whether `context` still has a live Python wrapper (false while it is
+ /// being destroyed).
+ static bool isLiveContext(MlirContext context);
+
/// Controls whether error diagnostics should be propagated to diagnostic
/// handlers, instead of being captured by `ErrorCapture`.
void setEmitErrorDiagnostics(bool value) { emitErrorDiagnostics = value; }
@@ -410,6 +451,32 @@ class MLIR_PYTHON_API_EXPORTED PyDiagnostic {
bool valid = true;
};
+/// Wrapper around an MlirRemark as delivered to a remark callback. Like a
+/// PyDiagnostic it is only valid for the duration of the callback; accessing
+/// an invalidated remark raises ValueError.
+class MLIR_PYTHON_API_EXPORTED PyRemark {
+public:
+ explicit PyRemark(MlirRemark remark) : remark(remark) {}
+ void invalidate() { valid = false; }
+ bool isValid() const { return valid; }
+ PyRemarkKind getKind() const;
+ nanobind::str getRemarkName() const;
+ nanobind::str getCategoryName() const;
+ nanobind::str getFullCategoryName() const;
+ nanobind::str getFunctionName() const;
+ nanobind::typed<nanobind::object, PyLocation> getLocation() const;
+ uint64_t getId() const;
+ /// The key/value arguments as a list of `(key, value)` tuples.
+ nanobind::list getArgs() const;
+ /// The textual form of the remark, without its location.
+ nanobind::str getMessage() const;
+
+private:
+ void checkValid() const;
+ MlirRemark remark;
+ bool valid = true;
+};
+
/// Represents a diagnostic handler attached to the context. The handler's
/// callback will be invoked with PyDiagnostic instances until the detach()
/// method is called or the context is destroyed. A diagnostic handler can be
@@ -2018,6 +2085,7 @@ class MLIR_PYTHON_API_EXPORTED RecursiveMemoryEffects
MLIR_PYTHON_API_EXPORTED MlirValue getUniqueResult(MlirOperation operation);
MLIR_PYTHON_API_EXPORTED void populateIRCore(nanobind::module_ &m);
+MLIR_PYTHON_API_EXPORTED void populateIRRemarks(nanobind::module_ &m);
MLIR_PYTHON_API_EXPORTED void populateRoot(nanobind::module_ &m);
/// Helper for creating an @classmethod.
diff --git a/mlir/include/mlir/CAPI/Remarks.h b/mlir/include/mlir/CAPI/Remarks.h
new file mode 100644
index 0000000000000..cb477ce0ca982
--- /dev/null
+++ b/mlir/include/mlir/CAPI/Remarks.h
@@ -0,0 +1,23 @@
+//===- Remarks.h - C API Utils for MLIR Remarks -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares the conversion between the C API remark type and its C++
+// counterpart.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_CAPI_REMARKS_H
+#define MLIR_CAPI_REMARKS_H
+
+#include "mlir-c/Remarks.h"
+#include "mlir/CAPI/Wrap.h"
+#include "mlir/IR/Remarks.h"
+
+DEFINE_C_API_PTR_METHODS(MlirRemark, const mlir::remark::detail::Remark)
+
+#endif // MLIR_CAPI_REMARKS_H
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index b6d92dc8064a2..bb0a49ba93ce2 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -17,6 +17,7 @@
#include "mlir-c/Diagnostics.h"
#include "mlir-c/ExtensibleDialect.h"
#include "mlir-c/IR.h"
+#include "mlir-c/Remarks.h"
#include "mlir-c/Support.h"
#include <array>
@@ -76,7 +77,6 @@ createCustomDialectWrapper(const std::string &dialectNamespace,
namespace mlir {
namespace python {
namespace MLIR_BINDINGS_PYTHON_DOMAIN {
-
MlirBlock createBlock(
const nb::typed<nb::sequence, PyType> &pyArgTypes,
const std::optional<nb::typed<nb::sequence, PyLocation>> &pyArgLocs) {
@@ -932,7 +932,6 @@ PyOperation::~PyOperation() {
}
namespace {
-
// Constructs a new object of type T in-place on the Python heap, returning a
// PyObjectRef to it, loosely analogous to std::make_shared<T>().
template <typename T, class... Args>
@@ -944,7 +943,6 @@ PyObjectRef<T> makeObjectRef(Args &&...args) {
nb::inst_mark_ready(instance);
return PyObjectRef<T>(ptr, std::move(instance));
}
-
} // namespace
PyOperationRef PyOperation::createInstance(PyMlirContextRef contextRef,
@@ -2722,13 +2720,11 @@ void PyDynamicOpTraits::RecursiveMemoryEffects::bind(nb::module_ &m) {
nb::arg("cls"), nb::arg("op_name"),
nb::arg("context").none() = nb::none());
}
-
} // namespace MLIR_BINDINGS_PYTHON_DOMAIN
} // namespace python
} // namespace mlir
namespace {
-
using namespace mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN;
MlirLocation tracebackToLocation(MlirContext ctx) {
@@ -2883,7 +2879,6 @@ maybeGetTracebackLocation(const std::optional<PyLocation> &location) {
namespace mlir {
namespace python {
namespace MLIR_BINDINGS_PYTHON_DOMAIN {
-
static std::string formatMLIRError(const MLIRError &e) {
auto locStr = [](const PyLocation &loc) {
PyPrintAccumulator accum;
@@ -3499,6 +3494,59 @@ void populateIRCore(nb::module_ &m) {
.def("attach_diagnostic_handler", &PyMlirContext::attachDiagnosticHandler,
"callback"_a,
"Attaches a diagnostic handler that will receive callbacks.")
+ .def("enable_remarks", &PyMlirContext::enableRemarks, nb::kw_only(),
+ "policy"_a = PyRemarkPolicy::All,
+ "output_file"_a.none() = nb::none(),
+ "format"_a = PyRemarkFormat::YAML, "all_filter"_a = "",
+ "passed_filter"_a = "", "missed_filter"_a = "",
+ "analysis_filter"_a = "", "failed_filter"_a = "",
+ "callback"_a.none() = nb::none(),
+ "print_as_emit_remarks"_a.none() = nb::none(),
+ R"(
+ Enables the optimization remark engine on this context.
+
+ Remarks are reported by passes (and by `Location.emit_remark`) and
+ selected by the category filters, regular expressions anchored by
+ the engine: `all_filter` applies to every kind, the other filters to
+ one kind each; a kind without a matching filter is not reported.
+
+ The sink is selected by the arguments: `output_file` streams the
+ remarks to a file in `format`, `callback` delivers them to Python,
+ and without either they are emitted as MLIR remark diagnostics.
+
+ Args:
+ policy: `RemarkPolicy.ALL` reports remarks as they are emitted;
+ `RemarkPolicy.FINAL` postpones them until `finalize_remarks()`
+ and reports the final set, grouping related remarks under
+ their parents.
+ output_file: Path of the remark file, written on
+ `finalize_remarks()`.
+ format: The serialization format of `output_file`.
+ all_filter: Category regex applied to every remark kind.
+ passed_filter: Category regex for passed remarks.
+ missed_filter: Category regex for missed remarks.
+ analysis_filter: Category regex for analysis remarks.
+ failed_filter: Category regex for failed remarks.
+ callback: A callable receiving one `Remark` per reported remark;
+ the remark is only valid during the call.
+ print_as_emit_remarks: Also emit every remark as an MLIR remark
+ diagnostic; defaults to True only without an output file or
+ callback.
+
+ Raises:
+ ValueError: When both an output file and a callback are given,
+ when remarks are already enabled, or when the output file
+ cannot be written.)")
+ .def("finalize_remarks", &PyMlirContext::finalizeRemarks,
+ R"(
+ Finalizes and removes the remark engine of this context.
+
+ Postponed remarks (policy "final") are reported, the output file is
+ written and the callback is released. Does nothing when no engine
+ is enabled; `enable_remarks` may be called again afterwards.)")
+ .def_prop_ro(
+ "remarks_enabled", &PyMlirContext::getRemarksEnabled,
+ "Returns True if a remark engine is enabled on this context.")
.def(
"enable_multithreading",
[](PyMlirContext &self, bool enable) {
@@ -3853,6 +3901,46 @@ void populateIRCore(nb::module_ &m) {
Args:
message: The error message to emit.)")
+ .def(
+ "emit_remark",
+ [](PyLocation &self, PyRemarkKind kind, const std::string &remarkName,
+ const std::string &category, const std::string &subCategory,
+ const std::string &functionName, const std::string &message,
+ const std::vector<std::pair<std::string, std::string>> &args)
+ -> bool {
+ auto remarkKind = static_cast<MlirRemarkKind>(kind);
+ std::vector<MlirStringRef> keys, values;
+ keys.reserve(args.size());
+ values.reserve(args.size());
+ for (const auto &[key, value] : args) {
+ keys.push_back(toMlirStringRef(key));
+ values.push_back(toMlirStringRef(value));
+ }
+ return mlirEmitOptimizationRemark(
+ self, remarkKind, toMlirStringRef(remarkName),
+ toMlirStringRef(category), toMlirStringRef(subCategory),
+ toMlirStringRef(functionName), toMlirStringRef(message),
+ static_cast<intptr_t>(keys.size()), keys.data(), values.data());
+ },
+ "kind"_a, "remark_name"_a, nb::kw_only(), "category"_a = "",
+ "sub_category"_a = "", "function_name"_a = "", "message"_a = "",
+ "args"_a = std::vector<std::pair<std::string, std::string>>(),
+ R"(
+ Emits an optimization remark at this location through the remark
+ engine of the context (see `Context.enable_remarks`).
+
+ Args:
+ kind: The `RemarkKind` of the remark.
+ remark_name: The name identifying the remark.
+ category: The category the filters are matched against.
+ sub_category: An optional sub-category.
+ function_name: The function the remark refers to.
+ message: Free text attached to the remark.
+ args: Key/value pairs attached to the remark.
+
+ Returns:
+ True if the remark was reported; False when no remark engine is
+ enabled or the category is filtered out.)")
.def(
"__str__",
[](PyLocation &self) {
diff --git a/mlir/lib/Bindings/Python/IRRemarks.cpp b/mlir/lib/Bindings/Python/IRRemarks.cpp
new file mode 100644
index 0000000000000..a870ef48ec672
--- /dev/null
+++ b/mlir/lib/Bindings/Python/IRRemarks.cpp
@@ -0,0 +1,232 @@
+//===- IRRemarks.cpp - Remark engine bindings -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir-c/Remarks.h"
+#include "mlir/Bindings/Python/IRCore.h"
+
+#include <cstdio>
+#include <optional>
+#include <string>
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+namespace mlir {
+namespace python {
+namespace MLIR_BINDINGS_PYTHON_DOMAIN {
+namespace {
+/// User data of the remark callback installed by `Context.enable_remarks`.
+struct PyRemarkCallbackData {
+ MlirContext context;
+ nb::object callback;
+};
+} // namespace
+
+static nb::str remarkStr(MlirStringRef ref) {
+ return nb::str(ref.data, ref.length);
+}
+
+/// Forwards a reported remark to the Python callable in `userData`; the
+/// PyRemark is invalidated once the callable returns, like a PyDiagnostic.
+static void pyRemarkCallback(MlirRemark remark, void *userData) {
+ auto *data = static_cast<PyRemarkCallbackData *>(userData);
+ // Since this can be called from arbitrary C++ contexts, always get the gil.
+ nb::gil_scoped_acquire gil;
+ // A remark postponed until the context's destruction cannot be delivered:
+ // the Python wrapper of the context is already gone.
+ if (!PyMlirContext::isLiveContext(data->context))
+ return;
+ PyRemark *pyRemark = new PyRemark(remark);
+ nb::object pyRemarkObject = nb::cast(pyRemark, nb::rv_policy::take_ownership);
+ try {
+ data->callback(pyRemarkObject);
+ } catch (std::exception &e) {
+ fprintf(stderr, "MLIR Python remark callback raised exception: %s\n",
+ e.what());
+ }
+ pyRemark->invalidate();
+}
+
+static void pyRemarkCallbackDelete(void *userData) {
+ nb::gil_scoped_acquire gil;
+ delete static_cast<PyRemarkCallbackData *>(userData);
+}
+
+bool PyMlirContext::isLiveContext(MlirContext context) {
+ nb::ft_lock_guard lock(live_contexts_mutex);
+ return getLiveContexts().count(context.ptr) != 0;
+}
+
+void PyMlirContext::enableRemarks(
+ PyRemarkPolicy policy, const std::optional<std::string> &outputFile,
+ PyRemarkFormat format, const std::string &allFilter,
+ const std::string &passedFilter, const std::string &missedFilter,
+ const std::string &analysisFilter, const std::string &failedFilter,
+ nb::object callback, std::optional<bool> printAsEmitRemarks) {
+ bool hasCallback = !callback.is_none();
+ if (outputFile && hasCallback)
+ throw nb::value_error(
+ "a remark callback cannot be combined with an output file");
+ if (mlirContextHasRemarkEngine(get())) {
+ throw nb::value_error("remarks are already enabled on this context; call "
+ "finalize_remarks() first");
+ }
+
+ MlirRemarkCategories categories{
+ toMlirStringRef(allFilter), toMlirStringRef(passedFilter),
+ toMlirStringRef(missedFilter), toMlirStringRef(analysisFilter),
+ toMlirStringRef(failedFilter)};
+ auto remarkPolicy = static_cast<MlirRemarkPolicy>(policy);
+ // Without a sink of its own, the engine prints as MLIR remark diagnostics.
+ bool printAsEmit =
+ printAsEmitRemarks.value_or(!outputFile.has_value() && !hasCallback);
+
+ MlirLogicalResult result;
+ if (outputFile) {
+ result = mlirContextEnableOptimizationRemarksToFile(
+ get(), categories, remarkPolicy,
+ static_cast<MlirRemarkFileFormat>(format), toMlirStringRef(*outputFile),
+ printAsEmit);
+ if (mlirLogicalResultIsFailure(result))
+ throw nb::value_error(
+ ("failed to enable remarks: cannot write '" + *outputFile + "'")
+ .c_str());
+ } else if (hasCallback) {
+ auto *data = new PyRemarkCallbackData{get(), std::move(callback)};
+ result = mlirContextEnableOptimizationRemarksWithCallback(
+ get(), categories, remarkPolicy, pyRemarkCallback, data,
+ pyRemarkCallbackDelete, printAsEmit);
+ } else {
+ result = mlirContextEnableOptimizationRemarks(get(), categories,
+ remarkPolicy, printAsEmit);
+ }
+ if (mlirLogicalResultIsFailure(result))
+ throw nb::value_error("failed to enable remarks");
+}
+
+void PyMlirContext::finalizeRemarks() {
+ mlirContextFinalizeOptimizationRemarks(get());
+}
+
+bool PyMlirContext::getRemarksEnabled() {
+ return mlirContextHasRemarkEngine(get());
+}
+
+void PyRemark::checkValid() const {
+ if (!valid)
+ throw std::invalid_argument("Remark is invalid (used outside of callback)");
+}
+
+PyRemarkKind PyRemark::getKind() const {
+ checkValid();
+ return static_cast<PyRemarkKind>(mlirRemarkGetKind(remark));
+}
+
+nb::str PyRemark::getRemarkName() const {
+ checkValid();
+ return remarkStr(mlirRemarkGetRemarkName(remark));
+}
+
+nb::str PyRemark::getCategoryName() const {
+ checkValid();
+ return remarkStr(mlirRemarkGetCategoryName(remark));
+}
+
+nb::str PyRemark::getFullCategoryName() const {
+ checkValid();
+ return remarkStr(mlirRemarkGetFullCategoryName(remark));
+}
+
+nb::str PyRemark::getFunctionName() const {
+ checkValid();
+ return remarkStr(mlirRemarkGetFunctionName(remark));
+}
+
+nb::typed<nb::object, PyLocation> PyRemark::getLocation() const {
+ checkValid();
+ MlirLocation loc = mlirRemarkGetLocation(remark);
+ MlirContext context = mlirLocationGetContext(loc);
+ return PyLocation(PyMlirContext::forContext(context), loc).maybeDownCast();
+}
+
+uint64_t PyRemark::getId() const {
+ checkValid();
+ return mlirRemarkGetId(remark);
+}
+
+nb::list PyRemark::getArgs() const {
+ checkValid();
+ nb::list args;
+ intptr_t numArgs = mlirRemarkGetNumArgs(remark);
+ for (intptr_t i = 0; i < numArgs; ++i) {
+ args.append(nb::make_tuple(remarkStr(mlirRemarkGetArgKey(remark, i)),
+ remarkStr(mlirRemarkGetArgValue(remark, i))));
+ }
+ return args;
+}
+
+nb::str PyRemark::getMessage() const {
+ checkValid();
+ std::string message;
+ auto callback = +[](MlirStringRef ref, void *userData) {
+ static_cast<std::string *>(userData)->append(ref.data, ref.length);
+ };
+ mlirRemarkPrint(remark, callback, &message);
+ return nb::str(message.c_str(), message.size());
+}
+
+void populateIRRemarks(nb::module_ &m) {
+ nb::enum_<PyRemarkKind>(m, "RemarkKind")
+ .value("UNKNOWN", PyRemarkKind::Unknown)
+ .value("PASSED", PyRemarkKind::Passed)
+ .value("MISSED", PyRemarkKind::Missed)
+ .value("FAILURE", PyRemarkKind::Failure)
+ .value("ANALYSIS", PyRemarkKind::Analysis);
+
+ nb::enum_<PyRemarkPolicy>(m, "RemarkPolicy")
+ .value("ALL", PyRemarkPolicy::All)
+ .value("FINAL", PyRemarkPolicy::Final);
+
+ nb::enum_<PyRemarkFormat>(m, "RemarkFormat")
+ .value("YAML", PyRemarkFormat::YAML)
+ .value("BITSTREAM", PyRemarkFormat::Bitstream);
+
+ nb::class_<PyRemark>(m, "Remark")
+ .def_prop_ro("kind", &PyRemark::getKind,
+ "Returns the kind of the remark.")
+ .def_prop_ro("remark_name", &PyRemark::getRemarkName,
+ "Returns the name identifying the remark.")
+ .def_prop_ro("category_name", &PyRemark::getCategoryName,
+ "Returns the category of the remark (what the filters "
+ "match).")
+ .def_prop_ro("full_category_name", &PyRemark::getFullCategoryName,
+ "Returns the combined `category:subcategory` name.")
+ .def_prop_ro("function_name", &PyRemark::getFunctionName,
+ "Returns the name of the function the remark refers to.")
+ .def_prop_ro("location", &PyRemark::getLocation,
+ "Returns the location associated with the remark.")
+ .def_prop_ro("remark_id", &PyRemark::getId,
+ "Returns the id of the remark within its engine (0 when "
+ "unset).")
+ .def_prop_ro("args", &PyRemark::getArgs,
+ "Returns the key/value arguments as a list of tuples.")
+ .def_prop_ro("message", &PyRemark::getMessage,
+ "Returns the textual form of the remark, `[Kind] name | "
+ "Category:... | key=value, ...`.")
+ .def(
+ "__str__",
+ [](PyRemark &self) -> nb::str {
+ if (!self.isValid())
+ return nb::str("<Invalid Remark>");
+ return self.getMessage();
+ },
+ "Returns the remark message as a string.");
+}
+} // namespace MLIR_BINDINGS_PYTHON_DOMAIN
+} // namespace python
+} // namespace mlir
diff --git a/mlir/lib/Bindings/Python/MainModule.cpp b/mlir/lib/Bindings/Python/MainModule.cpp
index 93ce34b2e89f5..f8f6a223c2e46 100644
--- a/mlir/lib/Bindings/Python/MainModule.cpp
+++ b/mlir/lib/Bindings/Python/MainModule.cpp
@@ -25,6 +25,7 @@ void populateIRAttributes(nb::module_ &m);
void populateIRInterfaces(nb::module_ &m);
void populateIRTypes(nb::module_ &m);
void populateIRCore(nb::module_ &m);
+void populateIRRemarks(nb::module_ &m);
void populateRoot(nb::module_ &m);
} // namespace MLIR_BINDINGS_PYTHON_DOMAIN
} // namespace python
@@ -41,6 +42,8 @@ NB_MODULE(_mlir, m) {
populateRoot(m);
// Define and populate IR submodule.
auto irModule = m.def_submodule("ir", "MLIR IR Bindings");
+ // The remark enums are default arguments of the Context bindings.
+ populateIRRemarks(irModule);
populateIRCore(irModule);
populateIRAffine(irModule);
populateIRAttributes(irModule);
diff --git a/mlir/lib/CAPI/IR/CMakeLists.txt b/mlir/lib/CAPI/IR/CMakeLists.txt
index 8bc76677d2bfd..32b8fa409e105 100644
--- a/mlir/lib/CAPI/IR/CMakeLists.txt
+++ b/mlir/lib/CAPI/IR/CMakeLists.txt
@@ -11,12 +11,14 @@ add_mlir_upstream_c_api_library(MLIRCAPIIR
IntegerSet.cpp
IR.cpp
Pass.cpp
+ Remarks.cpp
Support.cpp
LINK_LIBS PUBLIC
MLIRBytecodeWriter
MLIRIR
MLIRParser
+ MLIRRemarkStreamer
MLIRSupport
MLIRPass
)
diff --git a/mlir/lib/CAPI/IR/Remarks.cpp b/mlir/lib/CAPI/IR/Remarks.cpp
new file mode 100644
index 0000000000000..a05a9cbe74ca8
--- /dev/null
+++ b/mlir/lib/CAPI/IR/Remarks.cpp
@@ -0,0 +1,254 @@
+//===- Remarks.cpp - C Interface for the MLIR Remark Engine ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir-c/Remarks.h"
+#include "mlir/CAPI/IR.h"
+#include "mlir/CAPI/Remarks.h"
+#include "mlir/CAPI/Support.h"
+#include "mlir/CAPI/Utils.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/Remarks.h"
+#include "mlir/Remark/RemarkStreamer.h"
+#include "llvm/Remarks/RemarkFormat.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#include <cassert>
+#include <memory>
+
+using namespace mlir;
+using mlir::remark::detail::Remark;
+
+//===----------------------------------------------------------------------===//
+// Helpers
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// A streamer forwarding every reported remark to a C callback.
+class CallbackRemarkStreamer : public remark::detail::MLIRRemarkStreamerBase {
+public:
+ CallbackRemarkStreamer(MlirRemarkCallback callback, void *userData,
+ void (*deleteUserData)(void *))
+ : callback(callback), userData(userData), deleteUserData(deleteUserData) {
+ }
+
+ ~CallbackRemarkStreamer() override {
+ if (deleteUserData)
+ deleteUserData(userData);
+ }
+
+ void streamOptimizationRemark(const Remark &remark) override {
+ callback(wrap(&remark), userData);
+ }
+
+private:
+ MlirRemarkCallback callback;
+ void *userData;
+ void (*deleteUserData)(void *);
+};
+} // namespace
+
+/// Every filter reaches the engine as a (possibly empty) string, as with
+/// mlir-opt's options: a kind is reported when its own filter or `all` matches.
+static remark::RemarkCategories unwrap(MlirRemarkCategories categories) {
+ return remark::RemarkCategories{
+ unwrap(categories.all).str(), unwrap(categories.passed).str(),
+ unwrap(categories.missed).str(), unwrap(categories.analysis).str(),
+ unwrap(categories.failed).str()};
+}
+
+static std::unique_ptr<remark::detail::RemarkEmittingPolicyBase>
+createPolicy(MlirRemarkPolicy policy) {
+ switch (policy) {
+ case MlirRemarkPolicyAll:
+ return std::make_unique<remark::RemarkEmittingPolicyAll>();
+ case MlirRemarkPolicyFinal:
+ return std::make_unique<remark::RemarkEmittingPolicyFinal>();
+ }
+ llvm_unreachable("unknown remark policy");
+}
+
+static llvm::remarks::Format unwrap(MlirRemarkFileFormat format) {
+ switch (format) {
+ case MlirRemarkFileFormatYAML:
+ return llvm::remarks::Format::YAML;
+ case MlirRemarkFileFormatBitstream:
+ return llvm::remarks::Format::Bitstream;
+ }
+ llvm_unreachable("unknown remark file format");
+}
+
+static MlirRemarkKind wrap(remark::RemarkKind kind) {
+ switch (kind) {
+ case remark::RemarkKind::RemarkUnknown:
+ return MlirRemarkKindUnknown;
+ case remark::RemarkKind::RemarkPassed:
+ return MlirRemarkKindPassed;
+ case remark::RemarkKind::RemarkMissed:
+ return MlirRemarkKindMissed;
+ case remark::RemarkKind::RemarkFailure:
+ return MlirRemarkKindFailure;
+ case remark::RemarkKind::RemarkAnalysis:
+ return MlirRemarkKindAnalysis;
+ }
+ llvm_unreachable("unknown remark kind");
+}
+
+//===----------------------------------------------------------------------===//
+// Enabling and finalizing
+//===----------------------------------------------------------------------===//
+
+MlirLogicalResult mlirContextEnableOptimizationRemarks(
+ MlirContext context, MlirRemarkCategories categories,
+ MlirRemarkPolicy policy, bool printAsEmitRemarks) {
+ MLIRContext *ctx = unwrap(context);
+ if (ctx->getRemarkEngine())
+ return mlirLogicalResultFailure();
+ return wrap(remark::enableOptimizationRemarks(
+ *ctx, /*streamer=*/nullptr, createPolicy(policy), unwrap(categories),
+ printAsEmitRemarks));
+}
+
+MlirLogicalResult mlirContextEnableOptimizationRemarksToFile(
+ MlirContext context, MlirRemarkCategories categories,
+ MlirRemarkPolicy policy, MlirRemarkFileFormat format, MlirStringRef path,
+ bool printAsEmitRemarks) {
+ MLIRContext *ctx = unwrap(context);
+ if (ctx->getRemarkEngine())
+ return mlirLogicalResultFailure();
+ return wrap(remark::enableOptimizationRemarksWithLLVMStreamer(
+ *ctx, unwrap(path), unwrap(format), createPolicy(policy),
+ unwrap(categories), printAsEmitRemarks));
+}
+
+MlirLogicalResult mlirContextEnableOptimizationRemarksWithCallback(
+ MlirContext context, MlirRemarkCategories categories,
+ MlirRemarkPolicy policy, MlirRemarkCallback callback, void *userData,
+ void (*deleteUserData)(void *), bool printAsEmitRemarks) {
+ assert(callback && "unexpected null remark callback");
+ MLIRContext *ctx = unwrap(context);
+ if (ctx->getRemarkEngine()) {
+ // The caller handed over `userData`; release it as on success.
+ if (deleteUserData)
+ deleteUserData(userData);
+ return mlirLogicalResultFailure();
+ }
+ return wrap(remark::enableOptimizationRemarks(
+ *ctx,
+ std::make_unique<CallbackRemarkStreamer>(callback, userData,
+ deleteUserData),
+ createPolicy(policy), unwrap(categories), printAsEmitRemarks));
+}
+
+void mlirContextFinalizeOptimizationRemarks(MlirContext context) {
+ // The engine destructor reports the postponed remarks, then destroys the
+ // streamer.
+ unwrap(context)->setRemarkEngine(nullptr);
+}
+
+bool mlirContextHasRemarkEngine(MlirContext context) {
+ return unwrap(context)->getRemarkEngine() != nullptr;
+}
+
+//===----------------------------------------------------------------------===//
+// Remark accessors
+//===----------------------------------------------------------------------===//
+
+MlirRemarkKind mlirRemarkGetKind(MlirRemark remark) {
+ return wrap(unwrap(remark)->getRemarkKind());
+}
+
+MlirStringRef mlirRemarkGetRemarkName(MlirRemark remark) {
+ return wrap(unwrap(remark)->getRemarkName());
+}
+
+MlirStringRef mlirRemarkGetCategoryName(MlirRemark remark) {
+ return wrap(unwrap(remark)->getCategoryName());
+}
+
+MlirStringRef mlirRemarkGetFullCategoryName(MlirRemark remark) {
+ return wrap(unwrap(remark)->getCombinedCategoryName());
+}
+
+MlirStringRef mlirRemarkGetFunctionName(MlirRemark remark) {
+ return wrap(unwrap(remark)->getFunction());
+}
+
+MlirLocation mlirRemarkGetLocation(MlirRemark remark) {
+ return wrap(unwrap(remark)->getLocation());
+}
+
+uint64_t mlirRemarkGetId(MlirRemark remark) {
+ return unwrap(remark)->getId().getValue();
+}
+
+intptr_t mlirRemarkGetNumArgs(MlirRemark remark) {
+ return static_cast<intptr_t>(unwrap(remark)->getArgs().size());
+}
+
+MlirStringRef mlirRemarkGetArgKey(MlirRemark remark, intptr_t pos) {
+ ArrayRef<Remark::Arg> args = unwrap(remark)->getArgs();
+ assert(pos >= 0 && static_cast<size_t>(pos) < args.size() &&
+ "remark argument index out of range");
+ return wrap(llvm::StringRef(args[pos].key));
+}
+
+MlirStringRef mlirRemarkGetArgValue(MlirRemark remark, intptr_t pos) {
+ ArrayRef<Remark::Arg> args = unwrap(remark)->getArgs();
+ assert(pos >= 0 && static_cast<size_t>(pos) < args.size() &&
+ "remark argument index out of range");
+ return wrap(llvm::StringRef(args[pos].val));
+}
+
+void mlirRemarkPrint(MlirRemark remark, MlirStringCallback callback,
+ void *userData) {
+ detail::CallbackOstream stream(callback, userData);
+ unwrap(remark)->print(stream);
+}
+
+//===----------------------------------------------------------------------===//
+// Emission
+//===----------------------------------------------------------------------===//
+
+bool mlirEmitOptimizationRemark(
+ MlirLocation location, MlirRemarkKind kind, MlirStringRef remarkName,
+ MlirStringRef categoryName, MlirStringRef subCategoryName,
+ MlirStringRef functionName, MlirStringRef message, intptr_t numArgs,
+ const MlirStringRef *argKeys, const MlirStringRef *argValues) {
+ assert((numArgs == 0 || (argKeys && argValues)) &&
+ "remark arguments require both keys and values");
+ Location loc = unwrap(location);
+ remark::RemarkOpts opts = remark::RemarkOpts::name(unwrap(remarkName))
+ .category(unwrap(categoryName))
+ .subCategory(unwrap(subCategoryName))
+ .function(unwrap(functionName));
+ remark::detail::InFlightRemark inFlight;
+ switch (kind) {
+ case MlirRemarkKindUnknown:
+ return false;
+ case MlirRemarkKindPassed:
+ inFlight = remark::passed(loc, opts);
+ break;
+ case MlirRemarkKindMissed:
+ inFlight = remark::missed(loc, opts);
+ break;
+ case MlirRemarkKindFailure:
+ inFlight = remark::failed(loc, opts);
+ break;
+ case MlirRemarkKindAnalysis:
+ inFlight = remark::analysis(loc, opts);
+ break;
+ }
+ if (!inFlight)
+ return false;
+ if (message.length != 0)
+ inFlight << unwrap(message);
+ for (intptr_t i = 0; i < numArgs; ++i)
+ inFlight << Remark::Arg(unwrap(argKeys[i]), unwrap(argValues[i]));
+ // The remark is reported when `inFlight` goes out of scope.
+ return true;
+}
diff --git a/mlir/python/CMakeLists.txt b/mlir/python/CMakeLists.txt
index aa0adea66d2fe..64554f0bac14a 100644
--- a/mlir/python/CMakeLists.txt
+++ b/mlir/python/CMakeLists.txt
@@ -838,6 +838,7 @@ declare_mlir_python_extension(MLIRPythonExtension.MLIRPythonSupport
Globals.cpp
IRAttributes.cpp
IRCore.cpp
+ IRRemarks.cpp
IRTypes.cpp
)
diff --git a/mlir/test/CAPI/CMakeLists.txt b/mlir/test/CAPI/CMakeLists.txt
index d29e7744527ef..bd8b58eb442bf 100644
--- a/mlir/test/CAPI/CMakeLists.txt
+++ b/mlir/test/CAPI/CMakeLists.txt
@@ -64,6 +64,12 @@ _add_capi_test_executable(mlir-capi-llvm-test
MLIRCAPIRegisterEverything
)
+_add_capi_test_executable(mlir-capi-remarks-test
+ remarks.c
+ LINK_LIBS PRIVATE
+ MLIRCAPIIR
+)
+
_add_capi_test_executable(mlir-capi-pass-test
pass.c
LINK_LIBS PRIVATE
diff --git a/mlir/test/CAPI/remarks.c b/mlir/test/CAPI/remarks.c
new file mode 100644
index 0000000000000..0e8d753c1d3b4
--- /dev/null
+++ b/mlir/test/CAPI/remarks.c
@@ -0,0 +1,343 @@
+//===- remarks.c - Test of the remark engine C API ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM
+// Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+/* RUN: mlir-capi-remarks-test 2>&1 | FileCheck %s
+ */
+
+#include "mlir-c/Remarks.h"
+#include "mlir-c/Diagnostics.h"
+#include "mlir-c/IR.h"
+#include "mlir-c/Support.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void printToStderr(MlirStringRef str, void *userData) {
+ (void)userData;
+ fwrite(str.data, 1, str.length, stderr);
+}
+
+static MlirStringRef str(const char *s) {
+ return mlirStringRefCreateFromCString(s);
+}
+
+static MlirRemarkCategories allCategories(const char *filter) {
+ MlirRemarkCategories cats;
+ memset(&cats, 0, sizeof(cats));
+ cats.all = str(filter);
+ return cats;
+}
+
+/// Prints every field of the remark and counts the deliveries in userData.
+static void remarkCallback(MlirRemark remark, void *userData) {
+ int *count = (int *)userData;
+ ++*count;
+ const char *kind = "unknown";
+ switch (mlirRemarkGetKind(remark)) {
+ case MlirRemarkKindPassed:
+ kind = "passed";
+ break;
+ case MlirRemarkKindMissed:
+ kind = "missed";
+ break;
+ case MlirRemarkKindFailure:
+ kind = "failure";
+ break;
+ case MlirRemarkKindAnalysis:
+ kind = "analysis";
+ break;
+ case MlirRemarkKindUnknown:
+ break;
+ }
+ MlirStringRef name = mlirRemarkGetRemarkName(remark);
+ MlirStringRef category = mlirRemarkGetCategoryName(remark);
+ MlirStringRef fullCategory = mlirRemarkGetFullCategoryName(remark);
+ MlirStringRef function = mlirRemarkGetFunctionName(remark);
+ fprintf(stderr,
+ "remark #%d kind=%s name=%.*s category=%.*s full=%.*s "
+ "function=%.*s id=%" PRIu64 "\n",
+ *count, kind, (int)name.length, name.data, (int)category.length,
+ category.data, (int)fullCategory.length, fullCategory.data,
+ (int)function.length, function.data, mlirRemarkGetId(remark));
+ intptr_t numArgs = mlirRemarkGetNumArgs(remark);
+ for (intptr_t i = 0; i < numArgs; ++i) {
+ MlirStringRef key = mlirRemarkGetArgKey(remark, i);
+ MlirStringRef value = mlirRemarkGetArgValue(remark, i);
+ fprintf(stderr, " arg %.*s=%.*s\n", (int)key.length, key.data,
+ (int)value.length, value.data);
+ }
+ fprintf(stderr, " print: ");
+ mlirRemarkPrint(remark, printToStderr, NULL);
+ fprintf(stderr, "\n location: ");
+ mlirLocationPrint(mlirRemarkGetLocation(remark), printToStderr, NULL);
+ fprintf(stderr, "\n");
+}
+
+static void deleteUserData(void *userData) {
+ fprintf(stderr, "deleteUserData called (count=%d)\n", *(int *)userData);
+}
+
+static void countRemark(MlirRemark remark, void *userData) {
+ (void)remark;
+ ++*(int *)userData;
+}
+
+static bool emit(MlirLocation loc, MlirRemarkKind kind, const char *name,
+ const char *category, const char *message) {
+ MlirStringRef keys[1] = {str("factor")};
+ MlirStringRef values[1] = {str("4")};
+ return mlirEmitOptimizationRemark(loc, kind, str(name), str(category),
+ str("inner"), str("main"), str(message), 1,
+ keys, values);
+}
+
+// CHECK-LABEL: @testCallbackStreamer
+static void testCallbackStreamer(void) {
+ fprintf(stderr, "@testCallbackStreamer\n");
+ MlirContext ctx = mlirContextCreate();
+ MlirLocation loc = mlirLocationFileLineColGet(ctx, str("remarks.c"), 7, 3);
+
+ // CHECK: no engine: enabled=0 emitted=0
+ bool emitted = emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "dropped");
+ fprintf(stderr, "no engine: enabled=%d emitted=%d\n",
+ mlirContextHasRemarkEngine(ctx), emitted);
+
+ int count = 0;
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+ ctx, allCategories("Loop"), MlirRemarkPolicyAll, remarkCallback, &count,
+ deleteUserData, /*printAsEmitRemarks=*/false);
+ // CHECK: enabled=1 ok=1
+ fprintf(stderr, "enabled=%d ok=%d\n", mlirContextHasRemarkEngine(ctx),
+ mlirLogicalResultIsSuccess(res));
+
+ // clang-format off
+ // CHECK: remark #1 kind=passed name=Unroll category=Loop full=Loop:inner function=main id=1
+ // CHECK: arg RemarkId=1
+ // CHECK: arg Remark=unrolled
+ // CHECK: arg factor=4
+ // CHECK: print: [Passed] Unroll | Category:Loop:inner | Function=main | Remark=unrolled, RemarkId=1, factor=4
+ // CHECK: location: loc("remarks.c":7:3)
+ // CHECK: emitted=1
+ // clang-format on
+ emitted = emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "unrolled");
+ fprintf(stderr, "emitted=%d\n", emitted);
+
+ // CHECK-NOT: name=Vectorize
+ // CHECK: count after filtered emit=1
+ emit(loc, MlirRemarkKindMissed, "Vectorize", "Vector", "not matched");
+ fprintf(stderr, "count after filtered emit=%d\n", count);
+
+ // CHECK: remark #2 kind=missed name=Interchange
+ // CHECK: remark #3 kind=failure name=Fuse
+ // CHECK: remark #4 kind=analysis name=TripCount
+ emit(loc, MlirRemarkKindMissed, "Interchange", "Loop", "no");
+ emit(loc, MlirRemarkKindFailure, "Fuse", "Loop", "no");
+ emit(loc, MlirRemarkKindAnalysis, "TripCount", "Loop", "4");
+
+ // CHECK: deleteUserData called (count=4)
+ // CHECK: after finalize: enabled=0 count=4
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ fprintf(stderr, "after finalize: enabled=%d count=%d\n",
+ mlirContextHasRemarkEngine(ctx), count);
+ // Finalizing without an engine is a no-op.
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testFinalPolicy
+static void testFinalPolicy(void) {
+ fprintf(stderr, "@testFinalPolicy\n");
+ MlirContext ctx = mlirContextCreate();
+ MlirLocation loc = mlirLocationUnknownGet(ctx);
+ int count = 0;
+ MlirRemarkCategories cats;
+ memset(&cats, 0, sizeof(cats));
+ cats.passed = str("Loop");
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+ ctx, cats, MlirRemarkPolicyFinal, remarkCallback, &count, NULL, false);
+ assert(mlirLogicalResultIsSuccess(res));
+ emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "postponed");
+ // Only the passed filter is set: a missed remark is not reported.
+ emit(loc, MlirRemarkKindMissed, "Interchange", "Loop", "dropped");
+ // CHECK: before finalize count=0
+ fprintf(stderr, "before finalize count=%d\n", count);
+ // CHECK: remark #1 kind=passed name=Unroll
+ // CHECK: after finalize count=1
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ fprintf(stderr, "after finalize count=%d\n", count);
+ mlirContextDestroy(ctx);
+}
+
+static MlirLogicalResult diagnosticHandler(MlirDiagnostic diagnostic,
+ void *userData) {
+ (void)userData;
+ const char *severity = "other";
+ if (mlirDiagnosticGetSeverity(diagnostic) == MlirDiagnosticRemark)
+ severity = "remark";
+ fprintf(stderr, "diagnostic %s: ", severity);
+ mlirDiagnosticPrint(diagnostic, printToStderr, NULL);
+ fprintf(stderr, "\n");
+ return mlirLogicalResultSuccess();
+}
+
+// CHECK-LABEL: @testEmitAsDiagnostics
+static void testEmitAsDiagnostics(void) {
+ fprintf(stderr, "@testEmitAsDiagnostics\n");
+ MlirContext ctx = mlirContextCreate();
+ MlirLocation loc = mlirLocationUnknownGet(ctx);
+ MlirDiagnosticHandlerID id =
+ mlirContextAttachDiagnosticHandler(ctx, diagnosticHandler, NULL, NULL);
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarks(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll,
+ /*printAsEmitRemarks=*/true);
+ assert(mlirLogicalResultIsSuccess(res));
+ // clang-format off
+ // CHECK: diagnostic remark: [Analysis] TripCount | Category:Loop:inner | Function=main | Remark=4, RemarkId=1, factor=4
+ // clang-format on
+ emit(loc, MlirRemarkKindAnalysis, "TripCount", "Loop", "4");
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ mlirContextDetachDiagnosticHandler(ctx, id);
+ mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testFileStreamer
+static void testFileStreamer(void) {
+ fprintf(stderr, "@testFileStreamer\n");
+ MlirContext ctx = mlirContextCreate();
+ MlirLocation loc = mlirLocationUnknownGet(ctx);
+
+ char yamlPath[] = "mlir-capi-remarks-test.yaml";
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarksToFile(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, MlirRemarkFileFormatYAML,
+ str(yamlPath), /*printAsEmitRemarks=*/false);
+ // CHECK: yaml enable ok=1
+ fprintf(stderr, "yaml enable ok=%d\n", mlirLogicalResultIsSuccess(res));
+ emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "to file");
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ FILE *f = fopen(yamlPath, "r");
+ bool found = false;
+ if (f) {
+ char line[256];
+ while (fgets(line, sizeof(line), f))
+ if (strstr(line, "Unroll"))
+ found = true;
+ fclose(f);
+ remove(yamlPath);
+ }
+ // CHECK: yaml has remark=1
+ fprintf(stderr, "yaml has remark=%d\n", found);
+
+ // The bitstream file starts with the LLVM remark bitstream magic.
+ char bitstreamPath[] = "mlir-capi-remarks-test.bitstream";
+ res = mlirContextEnableOptimizationRemarksToFile(
+ ctx, allCategories(".*"), MlirRemarkPolicyFinal,
+ MlirRemarkFileFormatBitstream, str(bitstreamPath),
+ /*printAsEmitRemarks=*/false);
+ // CHECK: bitstream enable ok=1
+ fprintf(stderr, "bitstream enable ok=%d\n", mlirLogicalResultIsSuccess(res));
+ emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "to file");
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ char magic[5] = {0};
+ f = fopen(bitstreamPath, "rb");
+ if (f) {
+ if (fread(magic, 1, 4, f) != 4)
+ magic[0] = '\0';
+ fclose(f);
+ remove(bitstreamPath);
+ }
+ // CHECK: bitstream magic=RMRK
+ fprintf(stderr, "bitstream magic=%s\n", magic);
+ mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testAccessorDefaults
+static void testAccessorDefaults(void) {
+ fprintf(stderr, "@testAccessorDefaults\n");
+ MlirContext ctx = mlirContextCreate();
+ MlirLocation loc = mlirLocationUnknownGet(ctx);
+ int count = 0;
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, remarkCallback, &count,
+ NULL, false);
+ assert(mlirLogicalResultIsSuccess(res));
+ // Unset names fall back to placeholders, the only argument is the id, and
+ // null argument arrays are accepted when numArgs is 0.
+ // clang-format off
+ // CHECK: remark #1 kind=passed name=<unknown remark name> category= full= function=<unknown function> id=1
+ // CHECK: arg RemarkId=1
+ // CHECK: print: [Passed] | RemarkId=1
+ // CHECK: location: loc(unknown)
+ // CHECK: emitted=1
+ // clang-format on
+ bool emitted =
+ mlirEmitOptimizationRemark(loc, MlirRemarkKindPassed, str(""), str(""),
+ str(""), str(""), str(""), 0, NULL, NULL);
+ fprintf(stderr, "emitted=%d\n", emitted);
+ // A sub-category alone is the full category name.
+ // clang-format off
+ // CHECK: remark #2 kind=missed name=Name category= full=Sub function=<unknown function> id=2
+ // CHECK: print: [Missed] Name | Category:Sub | RemarkId=2
+ // clang-format on
+ mlirEmitOptimizationRemark(loc, MlirRemarkKindMissed, str("Name"), str(""),
+ str("Sub"), str(""), str(""), 0, NULL, NULL);
+ // CHECK: unknown emitted=0
+ emitted = emit(loc, MlirRemarkKindUnknown, "Name", "Loop", "");
+ fprintf(stderr, "unknown emitted=%d\n", emitted);
+ mlirContextFinalizeOptimizationRemarks(ctx);
+ mlirContextDestroy(ctx);
+}
+
+// CHECK-LABEL: @testEnableWhileEnabled
+static void testEnableWhileEnabled(void) {
+ fprintf(stderr, "@testEnableWhileEnabled\n");
+ MlirContext ctx = mlirContextCreate();
+ MlirLocation loc = mlirLocationUnknownGet(ctx);
+ int first = 0, second = 0;
+ MlirLogicalResult res = mlirContextEnableOptimizationRemarksWithCallback(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, countRemark, &first,
+ deleteUserData, false);
+ // CHECK: first ok=1
+ fprintf(stderr, "first ok=%d\n", mlirLogicalResultIsSuccess(res));
+ // Every enable variant fails while an engine is active; the callback
+ // variant releases the user data it was handed right away.
+ // CHECK: deleteUserData called (count=0)
+ // CHECK: callback ok=0 file ok=0 plain ok=0
+ res = mlirContextEnableOptimizationRemarksWithCallback(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, countRemark, &second,
+ deleteUserData, false);
+ MlirLogicalResult fileRes = mlirContextEnableOptimizationRemarksToFile(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, MlirRemarkFileFormatYAML,
+ str("mlir-capi-remarks-test-unused.yaml"), false);
+ MlirLogicalResult plainRes = mlirContextEnableOptimizationRemarks(
+ ctx, allCategories(".*"), MlirRemarkPolicyAll, false);
+ fprintf(stderr, "callback ok=%d file ok=%d plain ok=%d\n",
+ mlirLogicalResultIsSuccess(res), mlirLogicalResultIsSuccess(fileRes),
+ mlirLogicalResultIsSuccess(plainRes));
+ emit(loc, MlirRemarkKindPassed, "Unroll", "Loop", "");
+ // CHECK: first=1 second=0
+ fprintf(stderr, "first=%d second=%d\n", first, second);
+ // Destroying the context with a live engine releases its user data.
+ // CHECK: deleteUserData called (count=1)
+ // CHECK: destroyed
+ mlirContextDestroy(ctx);
+ fprintf(stderr, "destroyed\n");
+}
+
+int main(void) {
+ testCallbackStreamer();
+ testFinalPolicy();
+ testEmitAsDiagnostics();
+ testFileStreamer();
+ testAccessorDefaults();
+ testEnableWhileEnabled();
+ return 0;
+}
diff --git a/mlir/test/CMakeLists.txt b/mlir/test/CMakeLists.txt
index c79949737d64b..747d0cf68116b 100644
--- a/mlir/test/CMakeLists.txt
+++ b/mlir/test/CMakeLists.txt
@@ -126,6 +126,7 @@ set(MLIR_TEST_DEPENDS
mlir-capi-llvm-test
mlir-capi-pass-test
mlir-capi-quant-test
+ mlir-capi-remarks-test
mlir-capi-rewrite-test
mlir-capi-smt-test
mlir-capi-sparse-tensor-test
diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py
index 3622fc0823fdf..dbd18f8e8b0f2 100644
--- a/mlir/test/lit.cfg.py
+++ b/mlir/test/lit.cfg.py
@@ -189,6 +189,7 @@ def find_real_python_interpreter():
"mlir-capi-pass-test",
"mlir-capi-pdl-test",
"mlir-capi-quant-test",
+ "mlir-capi-remarks-test",
"mlir-capi-rewrite-test",
"mlir-capi-sparse-tensor-test",
"mlir-capi-transform-test",
diff --git a/mlir/test/python/ir/remarks.py b/mlir/test/python/ir/remarks.py
new file mode 100644
index 0000000000000..49fbe4ebb6767
--- /dev/null
+++ b/mlir/test/python/ir/remarks.py
@@ -0,0 +1,419 @@
+# RUN: %PYTHON %s | FileCheck %s
+
+import gc
+import os
+import tempfile
+from mlir.ir import *
+
+
+def run(f):
+ print("\nTEST:", f.__name__)
+ f()
+ gc.collect()
+ assert Context._get_live_count() == 0
+ return f
+
+
+def emit(loc, kind=RemarkKind.PASSED, name="Unroll", category="Loop", **kwargs):
+ return loc.emit_remark(kind, name, category=category, **kwargs)
+
+
+def collect(into, attribute="remark_name"):
+ """Returns a remark callback appending `attribute` of each remark to `into`."""
+ return lambda remark: into.append(getattr(remark, attribute))
+
+
+# CHECK-LABEL: TEST: testNoEngine
+ at run
+def testNoEngine():
+ ctx = Context()
+ loc = Location.file("test.mlir", 1, 2, context=ctx)
+ # CHECK: enabled: False
+ print("enabled:", ctx.remarks_enabled)
+ # CHECK: emitted: False
+ print("emitted:", emit(loc))
+ # Finalizing without an engine is a no-op.
+ ctx.finalize_remarks()
+ # CHECK: still disabled: False
+ print("still disabled:", ctx.remarks_enabled)
+
+
+# CHECK-LABEL: TEST: testEnableFinalize
+ at run
+def testEnableFinalize():
+ ctx = Context()
+ states = [ctx.remarks_enabled]
+ ctx.enable_remarks(all_filter=".*")
+ states.append(ctx.remarks_enabled)
+ ctx.finalize_remarks()
+ states.append(ctx.remarks_enabled)
+ ctx.finalize_remarks()
+ states.append(ctx.remarks_enabled)
+ ctx.enable_remarks(policy=RemarkPolicy.FINAL, print_as_emit_remarks=False)
+ states.append(ctx.remarks_enabled)
+ ctx.finalize_remarks()
+ states.append(ctx.remarks_enabled)
+ # CHECK: states: [False, True, False, False, True, False]
+ print("states:", states)
+
+
+# CHECK-LABEL: TEST: testCallback
+ at run
+def testCallback():
+ ctx = Context()
+
+ def callback(remark):
+ print(
+ "kind:",
+ remark.kind,
+ "name:",
+ remark.remark_name,
+ "category:",
+ remark.category_name,
+ "full:",
+ remark.full_category_name,
+ "function:",
+ remark.function_name,
+ "id:",
+ remark.remark_id,
+ )
+ print("args:", remark.args)
+ print("message:", remark.message)
+ print("str:", str(remark))
+ print(
+ "location:",
+ remark.location,
+ type(remark.location).__name__,
+ remark.location.context is ctx,
+ )
+
+ ctx.enable_remarks(all_filter=".*", callback=callback)
+ # CHECK: enabled: True
+ print("enabled:", ctx.remarks_enabled)
+ loc = Location.file("test.mlir", 3, 4, context=ctx)
+ # The arguments keep their insertion order (the engine's id, the message,
+ # the explicit pairs); the message sorts them by key.
+ # CHECK: kind: RemarkKind.PASSED name: Unroll category: Loop full: Loop:Inner function: main id: 1
+ # CHECK: args: [('RemarkId', '1'), ('Remark', 'unrolled by 4'), ('factor', '4')]
+ # CHECK: message: [Passed] Unroll | Category:Loop:Inner | Function=main | Remark="unrolled by 4", RemarkId=1, factor=4
+ # CHECK: str: [Passed] Unroll | Category:Loop:Inner | Function=main | Remark="unrolled by 4", RemarkId=1, factor=4
+ # CHECK: location: loc("test.mlir":3:4) FileLineColLoc True
+ # CHECK: emitted: True
+ emitted = emit(
+ loc,
+ sub_category="Inner",
+ function_name="main",
+ message="unrolled by 4",
+ args=[("factor", "4")],
+ )
+ print("emitted:", emitted)
+ # Unset names fall back to placeholders; the only argument is the id.
+ # CHECK: kind: RemarkKind.MISSED name: <unknown remark name> category: full: function: <unknown function> id: 2
+ # CHECK: args: [('RemarkId', '2')]
+ # CHECK: message: [Missed] | RemarkId=2
+ Location.unknown(context=ctx).emit_remark(RemarkKind.MISSED, "")
+ ctx.finalize_remarks()
+ # CHECK: enabled after finalize: False
+ print("enabled after finalize:", ctx.remarks_enabled)
+
+
+# CHECK-LABEL: TEST: testRemarkInvalidatedAfterCallback
+ at run
+def testRemarkInvalidatedAfterCallback():
+ ctx = Context()
+ kept = []
+ ctx.enable_remarks(all_filter=".*", callback=kept.append)
+ emit(Location.unknown(context=ctx))
+ ctx.finalize_remarks()
+ remark = kept[0]
+ for attribute in (
+ "kind",
+ "remark_name",
+ "category_name",
+ "full_category_name",
+ "function_name",
+ "location",
+ "remark_id",
+ "args",
+ "message",
+ ):
+ try:
+ getattr(remark, attribute)
+ print(attribute, "-> no error (unexpected)")
+ except ValueError as e:
+ print(attribute, "->", e)
+ # CHECK: kind -> Remark is invalid (used outside of callback)
+ # CHECK: remark_name -> Remark is invalid (used outside of callback)
+ # CHECK: category_name -> Remark is invalid (used outside of callback)
+ # CHECK: full_category_name -> Remark is invalid (used outside of callback)
+ # CHECK: function_name -> Remark is invalid (used outside of callback)
+ # CHECK: location -> Remark is invalid (used outside of callback)
+ # CHECK: remark_id -> Remark is invalid (used outside of callback)
+ # CHECK: args -> Remark is invalid (used outside of callback)
+ # CHECK: message -> Remark is invalid (used outside of callback)
+ # CHECK: str: <Invalid Remark>
+ print("str:", str(remark))
+
+
+# CHECK-LABEL: TEST: testFilters
+ at run
+def testFilters():
+ ctx = Context()
+ names = []
+ ctx.enable_remarks(
+ all_filter="Loop", passed_filter="Vector", callback=collect(names)
+ )
+ loc = Location.unknown(context=ctx)
+ # CHECK: loop passed: True
+ print("loop passed:", emit(loc, RemarkKind.PASSED, "LoopPassed", "Loop"))
+ # CHECK: loop missed: True
+ print("loop missed:", emit(loc, RemarkKind.MISSED, "LoopMissed", "Loop"))
+ # CHECK: vector passed: True
+ print("vector passed:", emit(loc, RemarkKind.PASSED, "VectorPassed", "Vector"))
+ # CHECK: vector missed: False
+ print("vector missed:", emit(loc, RemarkKind.MISSED, "VectorMissed", "Vector"))
+ # CHECK: other: False
+ print("other:", emit(loc, RemarkKind.ANALYSIS, "Other", "Memory"))
+ # CHECK: unknown: False
+ print("unknown:", emit(loc, RemarkKind.UNKNOWN, "Unknown", "Loop"))
+ ctx.finalize_remarks()
+ # CHECK: names: ['LoopPassed', 'LoopMissed', 'VectorPassed']
+ print("names:", names)
+
+
+# CHECK-LABEL: TEST: testFinalPolicy
+ at run
+def testFinalPolicy():
+ ctx = Context()
+ names = []
+ ctx.enable_remarks(
+ policy=RemarkPolicy.FINAL, all_filter=".*", callback=collect(names)
+ )
+ loc = Location.unknown(context=ctx)
+ emit(loc, RemarkKind.PASSED, "First")
+ emit(loc, RemarkKind.MISSED, "Second")
+ emit(loc, RemarkKind.FAILURE, "Third")
+ emit(loc, RemarkKind.ANALYSIS, "Fourth")
+ # CHECK: before finalize: []
+ print("before finalize:", names)
+ ctx.finalize_remarks()
+ # CHECK: after finalize: ['First', 'Fourth', 'Second', 'Third']
+ print("after finalize:", sorted(names))
+
+
+# CHECK-LABEL: TEST: testYamlFile
+ at run
+def testYamlFile():
+ ctx = Context()
+ directory = tempfile.mkdtemp()
+ path = os.path.join(directory, "remarks.yaml")
+ ctx.enable_remarks(output_file=path, all_filter=".*")
+ loc = Location.file("test.mlir", 5, 6, context=ctx)
+ # CHECK: emitted: True
+ print(
+ "emitted:",
+ emit(
+ loc,
+ RemarkKind.MISSED,
+ "NotUnrolled",
+ sub_category="Inner",
+ function_name="f",
+ message="trip count too small",
+ args=[("tripCount", "4")],
+ ),
+ )
+ ctx.finalize_remarks()
+ with open(path) as f:
+ content = " ".join(f.read().split())
+ os.remove(path)
+ os.rmdir(directory)
+ for fragment in (
+ "--- !Missed",
+ "Pass: 'Loop:Inner'",
+ "Name: NotUnrolled",
+ "DebugLoc: { File: test.mlir, Line: 5, Column: 6 }",
+ "Function: f",
+ "- Remark: trip count too small",
+ "- tripCount: '4'",
+ ):
+ print(fragment, "->", fragment in content)
+ # CHECK: --- !Missed -> True
+ # CHECK: Pass: 'Loop:Inner' -> True
+ # CHECK: Name: NotUnrolled -> True
+ # CHECK: DebugLoc: { File: test.mlir, Line: 5, Column: 6 } -> True
+ # CHECK: Function: f -> True
+ # CHECK: - Remark: trip count too small -> True
+ # CHECK: - tripCount: '4' -> True
+
+
+# CHECK-LABEL: TEST: testBitstreamFile
+ at run
+def testBitstreamFile():
+ ctx = Context()
+ directory = tempfile.mkdtemp()
+ path = os.path.join(directory, "remarks.bitstream")
+ ctx.enable_remarks(
+ policy=RemarkPolicy.FINAL,
+ output_file=path,
+ format=RemarkFormat.BITSTREAM,
+ all_filter=".*",
+ )
+ # CHECK: emitted: True
+ print("emitted:", emit(Location.unknown(context=ctx)))
+ ctx.finalize_remarks()
+ # The file holds an LLVM remark bitstream once finalized.
+ with open(path, "rb") as f:
+ magic = f.read(4)
+ os.remove(path)
+ os.rmdir(directory)
+ # CHECK: magic: b'RMRK'
+ print("magic:", magic)
+
+
+# CHECK-LABEL: TEST: testEmitAsDiagnostics
+ at run
+def testEmitAsDiagnostics():
+ ctx = Context()
+ seen = []
+
+ def handler(d):
+ seen.append((d.severity, str(d.message)))
+ return True
+
+ handle = ctx.attach_diagnostic_handler(handler)
+ loc = Location.unknown(context=ctx)
+ # Without a sink of its own, the engine emits MLIR remark diagnostics.
+ ctx.enable_remarks(all_filter=".*")
+ emit(loc, RemarkKind.ANALYSIS, "TripCount", message="trip count is 4")
+ ctx.finalize_remarks()
+ # CHECK: diagnostics: [(DiagnosticSeverity.REMARK, '[Analysis] TripCount | Category:Loop | Remark="trip count is 4", RemarkId=1')]
+ print("diagnostics:", seen)
+ seen.clear()
+ ctx.enable_remarks(all_filter=".*", print_as_emit_remarks=False)
+ # CHECK: silent emitted: True []
+ print("silent emitted:", emit(loc), seen)
+ ctx.finalize_remarks()
+ # A callback is silent by default; combined with the diagnostics it runs first.
+ ctx.enable_remarks(
+ all_filter=".*",
+ callback=lambda r: seen.append("callback"),
+ print_as_emit_remarks=True,
+ )
+ emit(loc)
+ ctx.finalize_remarks()
+ handle.detach()
+ # CHECK: both: ['callback', (DiagnosticSeverity.REMARK, '[Passed] Unroll | Category:Loop | RemarkId=1')]
+ print("both:", seen)
+
+
+# CHECK-LABEL: TEST: testErrors
+ at run
+def testErrors():
+ ctx = Context()
+ try:
+ ctx.enable_remarks(output_file="remarks.yaml", callback=lambda r: None)
+ print("no error (unexpected)")
+ except ValueError as e:
+ # CHECK: ValueError: a remark callback cannot be combined with an output file
+ print("ValueError:", e)
+ try:
+ ctx.enable_remarks(output_file="/nonexistent-dir/remarks.yaml")
+ print("no error (unexpected)")
+ except ValueError as e:
+ # CHECK: ValueError: failed to enable remarks: cannot write '/nonexistent-dir/remarks.yaml'
+ print("ValueError:", e)
+ # CHECK: enabled after errors: False
+ print("enabled after errors:", ctx.remarks_enabled)
+ # The options are keyword-only and typed.
+ for kwargs in (dict(policy="final"), dict(format="yaml")):
+ try:
+ ctx.enable_remarks(**kwargs)
+ print("no error (unexpected)")
+ except TypeError as e:
+ print("TypeError:", str(e).splitlines()[0])
+ # CHECK-COUNT-2: TypeError: enable_remarks(): incompatible function arguments
+ ctx.enable_remarks(all_filter=".*")
+ try:
+ ctx.enable_remarks(all_filter=".*")
+ print("no error (unexpected)")
+ except ValueError as e:
+ # CHECK: ValueError: remarks are already enabled on this context; call finalize_remarks() first
+ print("ValueError:", e)
+ # CHECK: still enabled: True
+ print("still enabled:", ctx.remarks_enabled)
+ loc = Location.unknown(context=ctx)
+ try:
+ loc.emit_remark("passed", "Name")
+ print("no error (unexpected)")
+ except TypeError as e:
+ # CHECK: TypeError: emit_remark(): incompatible function arguments
+ print("TypeError:", str(e).splitlines()[0])
+ try:
+ Remark()
+ print("no error (unexpected)")
+ except TypeError as e:
+ # CHECK: TypeError: {{.*}}Remark: no constructor defined!
+ print("TypeError:", e)
+ ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testCallbackException
+ at run
+def testCallbackException():
+ ctx = Context()
+ names = []
+
+ def callback(remark):
+ names.append(remark.remark_name)
+ if remark.remark_name == "Boom":
+ raise RuntimeError("boom")
+
+ ctx.enable_remarks(all_filter=".*", callback=callback)
+ loc = Location.unknown(context=ctx)
+ # The exception is reported on stderr and dropped.
+ # CHECK: boom emitted: True enabled: True
+ print("boom emitted:", emit(loc, name="Boom"), "enabled:", ctx.remarks_enabled)
+ # CHECK: after emitted: True
+ print("after emitted:", emit(loc, name="After"))
+ # CHECK: names: ['Boom', 'After']
+ print("names:", names)
+ ctx.finalize_remarks()
+
+
+# CHECK-LABEL: TEST: testMultipleContexts
+ at run
+def testMultipleContexts():
+ ctx1, ctx2 = Context(), Context()
+ seen1, seen2 = [], []
+ ctx1.enable_remarks(all_filter=".*", callback=collect(seen1))
+ ctx2.enable_remarks(passed_filter="Loop", callback=collect(seen2))
+ loc1 = Location.unknown(context=ctx1)
+ loc2 = Location.unknown(context=ctx2)
+ emit(loc1, RemarkKind.PASSED, "A")
+ emit(loc2, RemarkKind.PASSED, "B")
+ emit(loc2, RemarkKind.MISSED, "C")
+ ctx1.finalize_remarks()
+ # CHECK: enabled: False True
+ print("enabled:", ctx1.remarks_enabled, ctx2.remarks_enabled)
+ emit(loc2, RemarkKind.PASSED, "D")
+ ctx2.finalize_remarks()
+ # CHECK: seen1: ['A']
+ # CHECK: seen2: ['B', 'D']
+ print("seen1:", seen1)
+ print("seen2:", seen2)
+
+
+# CHECK-LABEL: TEST: testContextDestroyedWithEngine
+ at run
+def testContextDestroyedWithEngine():
+ ctx = Context()
+ names = []
+ ctx.enable_remarks(
+ policy=RemarkPolicy.FINAL, all_filter=".*", callback=collect(names)
+ )
+ emit(Location.unknown(context=ctx), RemarkKind.PASSED, "Pending")
+ # A remark postponed until the context's destruction is dropped.
+ ctx = None
+ gc.collect()
+ # CHECK: dropped: []
+ print("dropped:", names)
More information about the Mlir-commits
mailing list