[Mlir-commits] [mlir] [MLIR][CAPI][Python] Add support for constructing memory effect instances (PR #210586)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sun Jul 19 03:15:32 PDT 2026
https://github.com/PragmaTwice created https://github.com/llvm/llvm-project/pull/210586
Python implementations of `MemoryEffectsOpInterface` receive a `MemoryEffectInstancesList` (added in #176920), but currently have no generic way to populate it.
This adds the C API for constructing and appending memory effect instances and exposes it in Python through `MemoryEffect`, `SideEffectResource`, and `MemoryEffectInstancesList.append`. The tests use CSE and DCE to verify that the declared effects are observed by MLIR passes.
Assisted-by: GPT 5.6 Sol
>From ee4b1d4aa4c3c0afaf896ace40531d702551cb77 Mon Sep 17 00:00:00 2001
From: PragmaTwice <twice at apache.org>
Date: Sun, 19 Jul 2026 18:07:37 +0800
Subject: [PATCH] [MLIR][CAPI][Python] Add support for constructing memory
effect instances
---
mlir/include/mlir-c/Interfaces.h | 78 +++++
.../mlir/Bindings/Python/IRInterfaces.h | 53 +++-
mlir/include/mlir/CAPI/Interfaces.h | 4 +
mlir/lib/Bindings/Python/DialectTransform.cpp | 20 +-
mlir/lib/Bindings/Python/IRInterfaces.cpp | 107 ++++++-
mlir/lib/CAPI/Interfaces/Interfaces.cpp | 72 +++++
mlir/test/CAPI/ir.c | 43 +++
.../dialects/memory_effects_op_interface.py | 291 ++++++++++++++++++
8 files changed, 654 insertions(+), 14 deletions(-)
create mode 100644 mlir/test/python/dialects/memory_effects_op_interface.py
diff --git a/mlir/include/mlir-c/Interfaces.h b/mlir/include/mlir-c/Interfaces.h
index a5251dc78471f..a416a6ab76f87 100644
--- a/mlir/include/mlir-c/Interfaces.h
+++ b/mlir/include/mlir-c/Interfaces.h
@@ -28,7 +28,10 @@ extern "C" {
}; \
typedef struct name name
+DEFINE_C_API_STRUCT(MlirMemoryEffect, void);
+DEFINE_C_API_STRUCT(MlirMemoryEffectInstance, void);
DEFINE_C_API_STRUCT(MlirMemoryEffectInstancesList, void);
+DEFINE_C_API_STRUCT(MlirSideEffectResource, void);
#undef DEFINE_C_API_STRUCT
@@ -144,6 +147,81 @@ mlirConditionallySpeculatableOpInterfaceGetSpeculatability(
// MemoryEffectsOpInterface
//===---------------------------------------------------------------------===//
+/// Returns the borrowed singleton instance of the allocate memory effect.
+MLIR_CAPI_EXPORTED MlirMemoryEffect mlirMemoryEffectsAllocateGet(void);
+
+/// Returns the borrowed singleton instance of the free memory effect.
+MLIR_CAPI_EXPORTED MlirMemoryEffect mlirMemoryEffectsFreeGet(void);
+
+/// Returns the borrowed singleton instance of the read memory effect.
+MLIR_CAPI_EXPORTED MlirMemoryEffect mlirMemoryEffectsReadGet(void);
+
+/// Returns the borrowed singleton instance of the write memory effect.
+MLIR_CAPI_EXPORTED MlirMemoryEffect mlirMemoryEffectsWriteGet(void);
+
+/// Returns the borrowed singleton instance of the default side effect
+/// resource.
+MLIR_CAPI_EXPORTED MlirSideEffectResource
+mlirSideEffectsDefaultResourceGet(void);
+
+/// Creates a memory effect instance without an associated IR entity.
+/// `parameters` may be a null attribute. The caller owns the returned instance
+/// and must destroy it with `mlirMemoryEffectInstanceDestroy`.
+MLIR_CAPI_EXPORTED MlirMemoryEffectInstance mlirMemoryEffectInstanceCreate(
+ MlirMemoryEffect effect, MlirAttribute parameters, int stage,
+ bool effectOnFullRegion, MlirSideEffectResource resource);
+
+/// Creates a memory effect instance associated with an operation operand.
+/// `parameters` may be a null attribute. The caller owns the returned instance
+/// and must destroy it with `mlirMemoryEffectInstanceDestroy`.
+MLIR_CAPI_EXPORTED MlirMemoryEffectInstance
+mlirMemoryEffectInstanceCreateForOpOperand(MlirMemoryEffect effect,
+ MlirOpOperand opOperand,
+ MlirAttribute parameters, int stage,
+ bool effectOnFullRegion,
+ MlirSideEffectResource resource);
+
+/// Creates a memory effect instance associated with an operation result.
+/// `result` must wrap an OpResult. `parameters` may be a null attribute. The
+/// caller owns the returned instance and must destroy it with
+/// `mlirMemoryEffectInstanceDestroy`.
+MLIR_CAPI_EXPORTED MlirMemoryEffectInstance
+mlirMemoryEffectInstanceCreateForOpResult(MlirMemoryEffect effect,
+ MlirValue result,
+ MlirAttribute parameters, int stage,
+ bool effectOnFullRegion,
+ MlirSideEffectResource resource);
+
+/// Creates a memory effect instance associated with a block argument.
+/// `blockArgument` must wrap a BlockArgument. `parameters` may be a null
+/// attribute. The caller owns the returned instance and must destroy it with
+/// `mlirMemoryEffectInstanceDestroy`.
+MLIR_CAPI_EXPORTED MlirMemoryEffectInstance
+mlirMemoryEffectInstanceCreateForBlockArgument(
+ MlirMemoryEffect effect, MlirValue blockArgument, MlirAttribute parameters,
+ int stage, bool effectOnFullRegion, MlirSideEffectResource resource);
+
+/// Creates a memory effect instance associated with a symbol. `symbol` must be
+/// a SymbolRefAttr. `parameters` may be a null attribute. The caller owns the
+/// returned instance and must destroy it with
+/// `mlirMemoryEffectInstanceDestroy`.
+MLIR_CAPI_EXPORTED MlirMemoryEffectInstance
+mlirMemoryEffectInstanceCreateForSymbol(MlirMemoryEffect effect,
+ MlirAttribute symbol,
+ MlirAttribute parameters, int stage,
+ bool effectOnFullRegion,
+ MlirSideEffectResource resource);
+
+/// Destroys a memory effect instance created by one of the functions above.
+MLIR_CAPI_EXPORTED void
+mlirMemoryEffectInstanceDestroy(MlirMemoryEffectInstance instance);
+
+/// Appends a copy of `instance` to the given list. This does not take ownership
+/// of `instance`; the caller remains responsible for destroying it.
+MLIR_CAPI_EXPORTED void
+mlirMemoryEffectInstancesListAppend(MlirMemoryEffectInstancesList list,
+ MlirMemoryEffectInstance instance);
+
/// Returns the interface TypeID of the MemoryEffectsOpInterface.
MLIR_CAPI_EXPORTED MlirTypeID mlirMemoryEffectsOpInterfaceTypeID(void);
diff --git a/mlir/include/mlir/Bindings/Python/IRInterfaces.h b/mlir/include/mlir/Bindings/Python/IRInterfaces.h
index fb30e030b6c32..1ba995aa82c5e 100644
--- a/mlir/include/mlir/Bindings/Python/IRInterfaces.h
+++ b/mlir/include/mlir/Bindings/Python/IRInterfaces.h
@@ -135,7 +135,58 @@ class PyConcreteOpInterface {
nanobind::object obj;
};
-struct PyMemoryEffectsInstanceList {
+/// A borrowed memory effect.
+class PyMemoryEffect {
+public:
+ explicit PyMemoryEffect(MlirMemoryEffect effect) : effect(effect) {}
+
+ MlirMemoryEffect get() const { return effect; }
+
+private:
+ MlirMemoryEffect effect;
+};
+
+/// A borrowed side effect resource.
+class PySideEffectResource {
+public:
+ explicit PySideEffectResource(MlirSideEffectResource resource)
+ : resource(resource) {}
+
+ MlirSideEffectResource get() const { return resource; }
+
+private:
+ MlirSideEffectResource resource;
+};
+
+/// An owning memory effect instance.
+class PyMemoryEffectInstance {
+public:
+ explicit PyMemoryEffectInstance(MlirMemoryEffectInstance instance)
+ : instance(instance) {}
+ PyMemoryEffectInstance(PyMemoryEffectInstance &&other) noexcept
+ : instance(other.instance) {
+ other.instance.ptr = nullptr;
+ }
+ ~PyMemoryEffectInstance() {
+ if (instance.ptr)
+ mlirMemoryEffectInstanceDestroy(instance);
+ }
+
+ MlirMemoryEffectInstance get() const { return instance; }
+
+private:
+ MlirMemoryEffectInstance instance;
+};
+
+/// A callback-scoped view of a list of memory effect instances.
+class PyMemoryEffectsInstanceList {
+public:
+ explicit PyMemoryEffectsInstanceList(MlirMemoryEffectInstancesList effects)
+ : effects(effects) {}
+
+ MlirMemoryEffectInstancesList get() const { return effects; }
+
+private:
MlirMemoryEffectInstancesList effects;
};
diff --git a/mlir/include/mlir/CAPI/Interfaces.h b/mlir/include/mlir/CAPI/Interfaces.h
index 15afc9fb0f18e..55d850ccd01eb 100644
--- a/mlir/include/mlir/CAPI/Interfaces.h
+++ b/mlir/include/mlir/CAPI/Interfaces.h
@@ -22,5 +22,9 @@
DEFINE_C_API_PTR_METHODS(
MlirMemoryEffectInstancesList,
llvm::SmallVectorImpl<mlir::MemoryEffects::EffectInstance>)
+DEFINE_C_API_PTR_METHODS(MlirMemoryEffect, mlir::MemoryEffects::Effect)
+DEFINE_C_API_PTR_METHODS(MlirMemoryEffectInstance,
+ mlir::MemoryEffects::EffectInstance)
+DEFINE_C_API_PTR_METHODS(MlirSideEffectResource, mlir::SideEffects::Resource)
#endif // MLIR_CAPI_INTERFACES_H
diff --git a/mlir/lib/Bindings/Python/DialectTransform.cpp b/mlir/lib/Bindings/Python/DialectTransform.cpp
index bd72082cea7e8..f84189e3a22ff 100644
--- a/mlir/lib/Bindings/Python/DialectTransform.cpp
+++ b/mlir/lib/Bindings/Python/DialectTransform.cpp
@@ -494,38 +494,38 @@ struct ParamType : PyConcreteType<ParamType> {
namespace {
void onlyReadsHandle(nb::iterable &operands,
- PyMemoryEffectsInstanceList effects) {
+ const PyMemoryEffectsInstanceList &effects) {
std::vector<MlirOpOperand> operandsVec;
for (auto operand : operands)
operandsVec.push_back(nb::cast<PyOpOperand>(operand));
mlirTransformOnlyReadsHandle(operandsVec.data(), operandsVec.size(),
- effects.effects);
+ effects.get());
};
void consumesHandle(nb::iterable &operands,
- PyMemoryEffectsInstanceList effects) {
+ const PyMemoryEffectsInstanceList &effects) {
std::vector<MlirOpOperand> operandsVec;
for (auto operand : operands)
operandsVec.push_back(nb::cast<PyOpOperand>(operand));
mlirTransformConsumesHandle(operandsVec.data(), operandsVec.size(),
- effects.effects);
+ effects.get());
};
void producesHandle(nb::iterable &results,
- PyMemoryEffectsInstanceList effects) {
+ const PyMemoryEffectsInstanceList &effects) {
std::vector<MlirValue> resultsVec;
for (auto result : results)
resultsVec.push_back(nb::cast<PyOpResult>(result).get());
mlirTransformProducesHandle(resultsVec.data(), resultsVec.size(),
- effects.effects);
+ effects.get());
};
-void modifiesPayload(PyMemoryEffectsInstanceList effects) {
- mlirTransformModifiesPayload(effects.effects);
+void modifiesPayload(const PyMemoryEffectsInstanceList &effects) {
+ mlirTransformModifiesPayload(effects.get());
}
-void onlyReadsPayload(PyMemoryEffectsInstanceList effects) {
- mlirTransformOnlyReadsPayload(effects.effects);
+void onlyReadsPayload(const PyMemoryEffectsInstanceList &effects) {
+ mlirTransformOnlyReadsPayload(effects.get());
}
} // namespace
diff --git a/mlir/lib/Bindings/Python/IRInterfaces.cpp b/mlir/lib/Bindings/Python/IRInterfaces.cpp
index c4e87246bd68c..4495cb6e8397d 100644
--- a/mlir/lib/Bindings/Python/IRInterfaces.cpp
+++ b/mlir/lib/Bindings/Python/IRInterfaces.cpp
@@ -34,6 +34,70 @@ its return shaped type components. Raises ValueError on failure.)";
namespace {
+MlirAttribute unwrapOptionalAttribute(const nb::object &attribute) {
+ if (attribute.is_none())
+ return mlirAttributeGetNull();
+
+ PyAttribute *pyAttribute = nullptr;
+ if (!nb::try_cast<PyAttribute *>(attribute, pyAttribute) || !pyAttribute)
+ throw nb::type_error("parameters must be an Attribute or None");
+ return pyAttribute->get();
+}
+
+void appendMemoryEffectInstance(PyMemoryEffectsInstanceList &effects,
+ const PyMemoryEffect &effect,
+ const nb::object &target,
+ const nb::object ¶meters, int stage,
+ bool effectOnFullRegion,
+ const PySideEffectResource &resource) {
+ MlirMemoryEffectInstancesList list = effects.get();
+ MlirAttribute unwrappedParameters = unwrapOptionalAttribute(parameters);
+
+ MlirMemoryEffectInstance rawInstance{nullptr};
+ if (target.is_none()) {
+ rawInstance =
+ mlirMemoryEffectInstanceCreate(effect.get(), unwrappedParameters, stage,
+ effectOnFullRegion, resource.get());
+ } else {
+ PyOpOperand *opOperand = nullptr;
+ PyValue *value = nullptr;
+ PyAttribute *attribute = nullptr;
+ if (nb::try_cast<PyOpOperand *>(target, opOperand) && opOperand) {
+ rawInstance = mlirMemoryEffectInstanceCreateForOpOperand(
+ effect.get(), *opOperand, unwrappedParameters, stage,
+ effectOnFullRegion, resource.get());
+ } else if (nb::try_cast<PyValue *>(target, value) && value) {
+ MlirValue mlirValue = value->get();
+ if (mlirValueIsAOpResult(mlirValue)) {
+ rawInstance = mlirMemoryEffectInstanceCreateForOpResult(
+ effect.get(), mlirValue, unwrappedParameters, stage,
+ effectOnFullRegion, resource.get());
+ } else if (mlirValueIsABlockArgument(mlirValue)) {
+ rawInstance = mlirMemoryEffectInstanceCreateForBlockArgument(
+ effect.get(), mlirValue, unwrappedParameters, stage,
+ effectOnFullRegion, resource.get());
+ } else {
+ throw nb::type_error(
+ "target Value must be an OpResult or BlockArgument");
+ }
+ } else if (nb::try_cast<PyAttribute *>(target, attribute) && attribute) {
+ MlirAttribute symbol = attribute->get();
+ if (!mlirAttributeIsASymbolRef(symbol))
+ throw nb::type_error("target Attribute must be a SymbolRefAttr");
+ rawInstance = mlirMemoryEffectInstanceCreateForSymbol(
+ effect.get(), symbol, unwrappedParameters, stage, effectOnFullRegion,
+ resource.get());
+ } else {
+ throw nb::type_error(
+ "target must be an OpOperand, OpResult, BlockArgument, "
+ "SymbolRefAttr, or None");
+ }
+ }
+
+ PyMemoryEffectInstance instance(rawInstance);
+ mlirMemoryEffectInstancesListAppend(list, instance.get());
+}
+
/// Takes in an optional ist of operands and converts them into a std::vector
/// of MlirVlaues. Returns an empty std::vector if the list is empty.
std::vector<MlirValue> wrapOperands(std::optional<nb::sequence> operandList) {
@@ -474,9 +538,46 @@ void populateIRInterfaces(nb::module_ &m) {
.value("Speculatable", MlirSpeculatabilitySpeculatable)
.value("RecursivelySpeculatable",
MlirSpeculatabilityRecursivelySpeculatable);
- auto memoryEffectsInstanceListClass =
- nb::class_<PyMemoryEffectsInstanceList>(m, "MemoryEffectInstancesList");
- (void)memoryEffectsInstanceListClass;
+ nb::class_<PyMemoryEffect>(m, "MemoryEffect", "A memory effect.")
+ .def_prop_ro_static("allocate",
+ [](nb::object & /*class*/) {
+ return PyMemoryEffect(
+ mlirMemoryEffectsAllocateGet());
+ })
+ .def_prop_ro_static("free",
+ [](nb::object & /*class*/) {
+ return PyMemoryEffect(mlirMemoryEffectsFreeGet());
+ })
+ .def_prop_ro_static("read",
+ [](nb::object & /*class*/) {
+ return PyMemoryEffect(mlirMemoryEffectsReadGet());
+ })
+ .def_prop_ro_static("write", [](nb::object & /*class*/) {
+ return PyMemoryEffect(mlirMemoryEffectsWriteGet());
+ });
+
+ nb::class_<PySideEffectResource>(m, "SideEffectResource",
+ "A side effect resource.")
+ .def_prop_ro_static("default", [](nb::object & /*class*/) {
+ return PySideEffectResource(mlirSideEffectsDefaultResourceGet());
+ });
+
+ nb::class_<PyMemoryEffectsInstanceList>(
+ m, "MemoryEffectInstancesList",
+ "A memory effect list that is valid only during get_effects.")
+ .def("append", &appendMemoryEffectInstance, nb::arg("effect"),
+ nb::arg("target").none() = nb::none(), nb::kw_only(),
+ nb::arg("parameters").none() = nb::none(), nb::arg("stage") = 0,
+ nb::arg("effect_on_full_region") = false,
+ nb::arg("resource") =
+ PySideEffectResource(mlirSideEffectsDefaultResourceGet()),
+ nb::sig("def append(self, effect: MemoryEffect, target: OpOperand | "
+ "OpResult | BlockArgument | SymbolRefAttr | None = None, *, "
+ "parameters: Attribute | None = None, stage: int = 0, "
+ "effect_on_full_region: bool = False, resource: "
+ "SideEffectResource = ...) -> None"),
+ "Append a memory effect instance. The target may be an OpOperand, "
+ "OpResult, BlockArgument, SymbolRefAttr, or None.");
PyConditionallySpeculatableOpInterface::bind(m);
PyInferShapedTypeOpInterface::bind(m);
diff --git a/mlir/lib/CAPI/Interfaces/Interfaces.cpp b/mlir/lib/CAPI/Interfaces/Interfaces.cpp
index 35a2bd562a8a1..1bf8dffa9c431 100644
--- a/mlir/lib/CAPI/Interfaces/Interfaces.cpp
+++ b/mlir/lib/CAPI/Interfaces/Interfaces.cpp
@@ -280,6 +280,78 @@ MlirSpeculatability mlirConditionallySpeculatableOpInterfaceGetSpeculatability(
// MemoryEffectOpInterface
//===---------------------------------------------------------------------===//
+MlirMemoryEffect mlirMemoryEffectsAllocateGet() {
+ return wrap(
+ static_cast<MemoryEffects::Effect *>(MemoryEffects::Allocate::get()));
+}
+
+MlirMemoryEffect mlirMemoryEffectsFreeGet() {
+ return wrap(static_cast<MemoryEffects::Effect *>(MemoryEffects::Free::get()));
+}
+
+MlirMemoryEffect mlirMemoryEffectsReadGet() {
+ return wrap(static_cast<MemoryEffects::Effect *>(MemoryEffects::Read::get()));
+}
+
+MlirMemoryEffect mlirMemoryEffectsWriteGet() {
+ return wrap(
+ static_cast<MemoryEffects::Effect *>(MemoryEffects::Write::get()));
+}
+
+MlirSideEffectResource mlirSideEffectsDefaultResourceGet() {
+ return wrap(static_cast<SideEffects::Resource *>(
+ SideEffects::DefaultResource::get()));
+}
+
+MlirMemoryEffectInstance mlirMemoryEffectInstanceCreate(
+ MlirMemoryEffect effect, MlirAttribute parameters, int stage,
+ bool effectOnFullRegion, MlirSideEffectResource resource) {
+ return wrap(new MemoryEffects::EffectInstance(
+ unwrap(effect), unwrap(parameters), stage, effectOnFullRegion,
+ unwrap(resource)));
+}
+
+MlirMemoryEffectInstance mlirMemoryEffectInstanceCreateForOpOperand(
+ MlirMemoryEffect effect, MlirOpOperand opOperand, MlirAttribute parameters,
+ int stage, bool effectOnFullRegion, MlirSideEffectResource resource) {
+ return wrap(new MemoryEffects::EffectInstance(
+ unwrap(effect), unwrap(opOperand), unwrap(parameters), stage,
+ effectOnFullRegion, unwrap(resource)));
+}
+
+MlirMemoryEffectInstance mlirMemoryEffectInstanceCreateForOpResult(
+ MlirMemoryEffect effect, MlirValue result, MlirAttribute parameters,
+ int stage, bool effectOnFullRegion, MlirSideEffectResource resource) {
+ return wrap(new MemoryEffects::EffectInstance(
+ unwrap(effect), cast<OpResult>(unwrap(result)), unwrap(parameters), stage,
+ effectOnFullRegion, unwrap(resource)));
+}
+
+MlirMemoryEffectInstance mlirMemoryEffectInstanceCreateForBlockArgument(
+ MlirMemoryEffect effect, MlirValue blockArgument, MlirAttribute parameters,
+ int stage, bool effectOnFullRegion, MlirSideEffectResource resource) {
+ return wrap(new MemoryEffects::EffectInstance(
+ unwrap(effect), cast<BlockArgument>(unwrap(blockArgument)),
+ unwrap(parameters), stage, effectOnFullRegion, unwrap(resource)));
+}
+
+MlirMemoryEffectInstance mlirMemoryEffectInstanceCreateForSymbol(
+ MlirMemoryEffect effect, MlirAttribute symbol, MlirAttribute parameters,
+ int stage, bool effectOnFullRegion, MlirSideEffectResource resource) {
+ return wrap(new MemoryEffects::EffectInstance(
+ unwrap(effect), cast<SymbolRefAttr>(unwrap(symbol)), unwrap(parameters),
+ stage, effectOnFullRegion, unwrap(resource)));
+}
+
+void mlirMemoryEffectInstanceDestroy(MlirMemoryEffectInstance instance) {
+ delete unwrap(instance);
+}
+
+void mlirMemoryEffectInstancesListAppend(MlirMemoryEffectInstancesList list,
+ MlirMemoryEffectInstance instance) {
+ unwrap(list)->push_back(*unwrap(instance));
+}
+
MlirTypeID mlirMemoryEffectsOpInterfaceTypeID() {
return wrap(MemoryEffectOpInterface::getInterfaceID());
}
diff --git a/mlir/test/CAPI/ir.c b/mlir/test/CAPI/ir.c
index 57ae8b9a2819b..3c72209ad95c3 100644
--- a/mlir/test/CAPI/ir.c
+++ b/mlir/test/CAPI/ir.c
@@ -2589,6 +2589,8 @@ int testInterfaces(MlirContext ctx) {
// CHECK: arith.constant speculatability: 1
MlirOperationState storeState = mlirOperationStateGet(storeName, loc);
+ MlirValue constantResult = mlirOperationGetResult(constantOp, 0);
+ mlirOperationStateAddOperands(&storeState, 1, &constantResult);
MlirOperation storeOp = mlirOperationCreate(&storeState);
if (mlirOperationImplementsInterface(storeOp, condSpecTypeID)) {
fprintf(stderr, "ERROR: Expected memref.store instance to not implement "
@@ -2619,6 +2621,47 @@ int testInterfaces(MlirContext ctx) {
// CHECK: memref.store speculatability: 2
// CHECK: callback count: 1
+ MlirMemoryEffect allocate = mlirMemoryEffectsAllocateGet();
+ MlirMemoryEffect free = mlirMemoryEffectsFreeGet();
+ MlirMemoryEffect read = mlirMemoryEffectsReadGet();
+ MlirMemoryEffect write = mlirMemoryEffectsWriteGet();
+ MlirSideEffectResource defaultResource = mlirSideEffectsDefaultResourceGet();
+ if (!allocate.ptr || !free.ptr || !read.ptr || !write.ptr ||
+ !defaultResource.ptr) {
+ fprintf(stderr, "ERROR: Expected memory effect components\n");
+ return 6;
+ }
+
+ MlirAttribute nullParameters = {NULL};
+ MlirOpOperand opOperand = mlirOperationGetOpOperand(storeOp, 0);
+ MlirBlock block = mlirBlockCreate(1, &i32, &loc);
+ MlirValue blockArgument = mlirBlockGetArgument(block, 0);
+ MlirAttribute symbol = mlirFlatSymbolRefAttrGet(
+ ctx, mlirStringRefCreateFromCString("effect_target"));
+
+ MlirMemoryEffectInstance instances[] = {
+ mlirMemoryEffectInstanceCreate(allocate, nullParameters, 0, false,
+ defaultResource),
+ mlirMemoryEffectInstanceCreateForOpOperand(read, opOperand, zero, 1,
+ false, defaultResource),
+ mlirMemoryEffectInstanceCreateForOpResult(
+ write, constantResult, nullParameters, 2, false, defaultResource),
+ mlirMemoryEffectInstanceCreateForBlockArgument(free, blockArgument, zero,
+ 3, false, defaultResource),
+ mlirMemoryEffectInstanceCreateForSymbol(read, symbol, zero, 4, true,
+ defaultResource),
+ };
+ for (intptr_t i = 0; i < 5; ++i) {
+ if (!instances[i].ptr) {
+ fprintf(stderr, "ERROR: Expected memory effect instance\n");
+ return 7;
+ }
+ mlirMemoryEffectInstanceDestroy(instances[i]);
+ }
+ mlirBlockDestroy(block);
+ fprintf(stderr, "memory effect instances constructed\n");
+ // CHECK: memory effect instances constructed
+
mlirOperationDestroy(storeOp);
mlirOperationDestroy(constantOp);
return 0;
diff --git a/mlir/test/python/dialects/memory_effects_op_interface.py b/mlir/test/python/dialects/memory_effects_op_interface.py
new file mode 100644
index 0000000000000..5689130c584b5
--- /dev/null
+++ b/mlir/test/python/dialects/memory_effects_op_interface.py
@@ -0,0 +1,291 @@
+# RUN: env PYTHONUNBUFFERED=1 %PYTHON %s 2>&1 | FileCheck %s
+
+from typing import Any
+
+from mlir import ir
+from mlir.dialects import ext, func
+from mlir.passmanager import PassManager
+
+
+class MemoryEffectsTest(ext.Dialect, name="memory_effects_test"):
+ pass
+
+
+class NoEffectModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ pass
+
+
+class ReadModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(
+ ir.MemoryEffect.read,
+ op.op_operands[0],
+ parameters=ir.StringAttr.get("read parameter"),
+ stage=1,
+ effect_on_full_region=True,
+ resource=ir.SideEffectResource.default,
+ )
+
+
+class ReadDeadModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(ir.MemoryEffect.read)
+
+
+class WriteModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(ir.MemoryEffect.write)
+
+
+class FreeModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(ir.MemoryEffect.free)
+
+
+class AllocateModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(ir.MemoryEffect.allocate)
+
+
+class AllocateResultModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(ir.MemoryEffect.allocate, op.results[0])
+
+
+class BlockArgumentTargetModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ effects.append(ir.MemoryEffect.read, op.regions[0].blocks[0].arguments[0])
+
+
+class SymbolTargetModel(ir.MemoryEffectsOpInterface):
+ @staticmethod
+ def get_effects(op, effects):
+ try:
+ effects.append(ir.MemoryEffect.read, ir.StringAttr.get("not a symbol"))
+ except TypeError as error:
+ print("invalid symbol target:", error)
+ try:
+ effects.append(ir.MemoryEffect.read, parameters=42)
+ except TypeError as error:
+ print("invalid parameters:", error)
+ effects.append(
+ ir.MemoryEffect.read,
+ ir.FlatSymbolRefAttr.get("global"),
+ parameters=ir.StringAttr.get("symbol parameter"),
+ stage=2,
+ effect_on_full_region=True,
+ )
+
+
+class ReadOp(MemoryEffectsTest.Operation, name="read", traits=[ReadModel]):
+ operand: ext.Operand[Any]
+ result: ext.Result[Any]
+
+
+class WriteOp(MemoryEffectsTest.Operation, name="write", traits=[WriteModel]):
+ operand: ext.Operand[Any]
+ result: ext.Result[Any]
+
+
+class WriteBarrierOp(
+ MemoryEffectsTest.Operation, name="write_barrier", traits=[WriteModel]
+):
+ operand: ext.Operand[Any]
+
+
+class NoEffectOp(MemoryEffectsTest.Operation, name="no_effect", traits=[NoEffectModel]):
+ pass
+
+
+class ReadDeadOp(MemoryEffectsTest.Operation, name="read_dead", traits=[ReadDeadModel]):
+ pass
+
+
+class WriteDeadOp(MemoryEffectsTest.Operation, name="write_dead", traits=[WriteModel]):
+ pass
+
+
+class FreeDeadOp(MemoryEffectsTest.Operation, name="free_dead", traits=[FreeModel]):
+ pass
+
+
+class AllocateDeadOp(
+ MemoryEffectsTest.Operation, name="allocate_dead", traits=[AllocateModel]
+):
+ pass
+
+
+class AllocateResultOp(
+ MemoryEffectsTest.Operation,
+ name="allocate_result",
+ traits=[AllocateResultModel],
+):
+ result: ext.Result[Any]
+
+
+class BlockArgumentTargetOp(
+ MemoryEffectsTest.Operation,
+ name="block_argument_target",
+ traits=[ir.NoTerminatorTrait, BlockArgumentTargetModel],
+):
+ body: ext.Region
+
+
+class SymbolTargetOp(
+ MemoryEffectsTest.Operation, name="symbol_target", traits=[SymbolTargetModel]
+):
+ pass
+
+
+def run_pass(source, pipeline):
+ module = ir.Module.parse(source)
+ PassManager.parse(pipeline).run(module.operation)
+ return str(module)
+
+
+with ir.Context(), ir.Location.unknown():
+ MemoryEffectsTest.load()
+
+ # The static properties return wrappers around borrowed, statically-owned
+ # C++ singletons.
+ # CHECK: memory effect properties: True True True True
+ print(
+ "memory effect properties:",
+ isinstance(ir.MemoryEffect.allocate, ir.MemoryEffect),
+ isinstance(ir.MemoryEffect.free, ir.MemoryEffect),
+ isinstance(ir.MemoryEffect.read, ir.MemoryEffect),
+ isinstance(ir.MemoryEffect.write, ir.MemoryEffect),
+ )
+ # CHECK: default resource property: True
+ print(
+ "default resource property:",
+ isinstance(ir.SideEffectResource.default, ir.SideEffectResource),
+ )
+
+ read_cse = run_pass(
+ """
+ module {
+ func.func @test(%arg0: i32) -> (i32, i32) {
+ %0 = "memory_effects_test.read"(%arg0) : (i32) -> i32
+ %1 = "memory_effects_test.read"(%arg0) : (i32) -> i32
+ return %0, %1 : i32, i32
+ }
+ }
+ """,
+ "builtin.module(func.func(cse))",
+ )
+ # A single Read effect remains CSE-eligible.
+ # CHECK: CSE read count: 1
+ print("CSE read count:", read_cse.count('"memory_effects_test.read"'))
+
+ write_cse = run_pass(
+ """
+ module {
+ func.func @test(%arg0: i32) -> (i32, i32) {
+ %0 = "memory_effects_test.write"(%arg0) : (i32) -> i32
+ %1 = "memory_effects_test.write"(%arg0) : (i32) -> i32
+ return %0, %1 : i32, i32
+ }
+ }
+ """,
+ "builtin.module(func.func(cse))",
+ )
+ # Writes cannot be CSE'd.
+ # CHECK: CSE write count: 2
+ print("CSE write count:", write_cse.count('"memory_effects_test.write"'))
+
+ read_across_write = run_pass(
+ """
+ module {
+ func.func @test(%arg0: i32) -> (i32, i32) {
+ %0 = "memory_effects_test.read"(%arg0) : (i32) -> i32
+ "memory_effects_test.write_barrier"(%arg0) : (i32) -> ()
+ %1 = "memory_effects_test.read"(%arg0) : (i32) -> i32
+ return %0, %1 : i32, i32
+ }
+ }
+ """,
+ "builtin.module(func.func(cse))",
+ )
+ # A potentially-aliasing Write on the default resource blocks Read CSE.
+ # CHECK: CSE read across write count: 2
+ print(
+ "CSE read across write count:",
+ read_across_write.count('"memory_effects_test.read"'),
+ )
+
+ dead_code = run_pass(
+ """
+ module {
+ func.func @test() {
+ "memory_effects_test.no_effect"() : () -> ()
+ "memory_effects_test.read_dead"() : () -> ()
+ "memory_effects_test.write_dead"() : () -> ()
+ "memory_effects_test.free_dead"() : () -> ()
+ "memory_effects_test.allocate_dead"() : () -> ()
+ %0 = "memory_effects_test.allocate_result"() : () -> i32
+ return
+ }
+ }
+ """,
+ "builtin.module(func.func(trivial-dce))",
+ )
+ # Empty and Read-only effect lists are dead. Write, Free and untargeted
+ # Allocate effects are observable. An Allocate targeting its own unused
+ # result is dead.
+ # CHECK: DCE no effect count: 0
+ # CHECK: DCE read count: 0
+ # CHECK: DCE write count: 1
+ # CHECK: DCE free count: 1
+ # CHECK: DCE untargeted allocate count: 1
+ # CHECK: DCE result allocate count: 0
+ print("DCE no effect count:", dead_code.count('"memory_effects_test.no_effect"'))
+ print("DCE read count:", dead_code.count('"memory_effects_test.read_dead"'))
+ print("DCE write count:", dead_code.count('"memory_effects_test.write_dead"'))
+ print("DCE free count:", dead_code.count('"memory_effects_test.free_dead"'))
+ print(
+ "DCE untargeted allocate count:",
+ dead_code.count('"memory_effects_test.allocate_dead"'),
+ )
+ print(
+ "DCE result allocate count:",
+ dead_code.count('"memory_effects_test.allocate_result"'),
+ )
+
+ target_variants = run_pass(
+ """
+ module {
+ func.func @test() {
+ "memory_effects_test.block_argument_target"() ({
+ ^bb0(%arg0: i32):
+ }) : () -> ()
+ "memory_effects_test.symbol_target"() : () -> ()
+ return
+ }
+ }
+ """,
+ "builtin.module(func.func(trivial-dce))",
+ )
+ # These Read effects exercise BlockArgument and SymbolRefAttr targets and
+ # remain removable by trivial-dce.
+ # CHECK: invalid symbol target: target Attribute must be a SymbolRefAttr
+ # CHECK: invalid parameters: parameters must be an Attribute or None
+ # CHECK: DCE block argument target count: 0
+ # CHECK: DCE symbol target count: 0
+ print(
+ "DCE block argument target count:",
+ target_variants.count('"memory_effects_test.block_argument_target"'),
+ )
+ print(
+ "DCE symbol target count:",
+ target_variants.count('"memory_effects_test.symbol_target"'),
+ )
More information about the Mlir-commits
mailing list