[Mlir-commits] [mlir] [MLIR][CAPI][Python] Add support for constructing memory effect instances (PR #210586)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Jul 23 08:48:41 PDT 2026


================
@@ -0,0 +1,289 @@
+# 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()
+
+    # 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
----------------
PragmaTwice wrote:

Done.

https://github.com/llvm/llvm-project/pull/210586


More information about the Mlir-commits mailing list