[Mlir-commits] [mlir] [MLIR][EmitC] Mark CastOp as Pure (PR #202749)

Andrzej WarzyƄski llvmlistbot at llvm.org
Mon Jun 15 03:23:32 PDT 2026


https://github.com/banach-space updated https://github.com/llvm/llvm-project/pull/202749

>From 36c29b3fe2bfe6581cb4d0a3064056b9fcc89d81 Mon Sep 17 00:00:00 2001
From: Andrzej Warzynski <andrzej.warzynski at arm.com>
Date: Tue, 9 Jun 2026 19:39:51 +0000
Subject: [PATCH 1/5] [MLIR][EmitC] Mark CastOp as Pure

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 2 +-
 mlir/test/Dialect/EmitC/canonicalize.mlir   | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)
 create mode 100644 mlir/test/Dialect/EmitC/canonicalize.mlir

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 65361a987a08e..09fad02da39b2 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -394,7 +394,7 @@ def EmitC_MemberCallOpaqueOp : EmitC_Op<"member_call_opaque", [CExpressionInterf
 }
 
 def EmitC_CastOp : EmitC_Op<"cast",
-    [CExpressionInterface,
+    [Pure, CExpressionInterface,
      DeclareOpInterfaceMethods<CastOpInterface>]> {
   let summary = "Cast operation";
   let description = [{
diff --git a/mlir/test/Dialect/EmitC/canonicalize.mlir b/mlir/test/Dialect/EmitC/canonicalize.mlir
new file mode 100644
index 0000000000000..a2c41375da398
--- /dev/null
+++ b/mlir/test/Dialect/EmitC/canonicalize.mlir
@@ -0,0 +1,9 @@
+// RUN: mlir-opt %s -canonicalize="test-convergence" -split-input-file | FileCheck %s
+
+// While there is no dedicated folder for CastOp, it is Pure and hence should
+// be "folded away".
+func.func @cast(%arg0: i32) {
+  // CHECK-NOT: emitc.cast
+  %1 = emitc.cast %arg0: i32 to f32
+  return
+}

>From 055d86ac70f53316bae75f4874a1d2cbcac64f54 Mon Sep 17 00:00:00 2001
From: Andrzej Warzynski <andrzej.warzynski at arm.com>
Date: Wed, 10 Jun 2026 16:47:36 +0000
Subject: [PATCH 2/5] Swich from unconditional PURE to opt-in

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 14 +++++++++++---
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp         | 15 +++++++++++++++
 mlir/test/Dialect/EmitC/canonicalize.mlir   | 12 ++++++++----
 3 files changed, 34 insertions(+), 7 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 09fad02da39b2..4527afeedc346 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -394,8 +394,11 @@ def EmitC_MemberCallOpaqueOp : EmitC_Op<"member_call_opaque", [CExpressionInterf
 }
 
 def EmitC_CastOp : EmitC_Op<"cast",
-    [Pure, CExpressionInterface,
-     DeclareOpInterfaceMethods<CastOpInterface>]> {
+    [CExpressionInterface,
+     DeclareOpInterfaceMethods<CastOpInterface>,
+     DeclareOpInterfaceMethods<ConditionallySpeculatable>,
+     DeclareOpInterfaceMethods<MemoryEffectsOpInterface>
+    ]> {
   let summary = "Cast operation";
   let description = [{
     The `emitc.cast` operation performs an explicit type conversion and is emitted
@@ -414,7 +417,10 @@ def EmitC_CastOp : EmitC_Op<"cast",
     ```
   }];
 
-  let arguments = (ins EmitCType:$source);
+  let arguments = (ins EmitCType:$source,
+    UnitAttr:$pure
+  );
+
   let results = (outs EmitCType:$dest);
   let assemblyFormat = "$source attr-dict `:` type($source) `to` type($dest)";
 
@@ -422,6 +428,8 @@ def EmitC_CastOp : EmitC_Op<"cast",
     bool hasSideEffects() {
       return false;
     }
+
+    bool isPure();
   }];
 }
 
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index bab9cb4a91102..288ab0fe5215f 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -336,6 +336,21 @@ bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
        emitc::isSupportedFloatType(output) || isa<emitc::PointerType>(output)));
 }
 
+bool CastOp::isPure() { return static_cast<bool>(getPureAttr()); }
+
+Speculation::Speculatability emitc::CastOp::getSpeculatability() {
+  return getPure() ? Speculation::Speculatable : Speculation::NotSpeculatable;
+}
+
+void emitc::CastOp::getEffects(
+    SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+  if (getPure())
+    return;
+
+  effects.emplace_back(MemoryEffects::Read::get());
+  effects.emplace_back(MemoryEffects::Write::get());
+}
+
 //===----------------------------------------------------------------------===//
 // CallOpaqueOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/EmitC/canonicalize.mlir b/mlir/test/Dialect/EmitC/canonicalize.mlir
index a2c41375da398..29d2fd157229d 100644
--- a/mlir/test/Dialect/EmitC/canonicalize.mlir
+++ b/mlir/test/Dialect/EmitC/canonicalize.mlir
@@ -1,9 +1,13 @@
 // RUN: mlir-opt %s -canonicalize="test-convergence" -split-input-file | FileCheck %s
 
-// While there is no dedicated folder for CastOp, it is Pure and hence should
-// be "folded away".
-func.func @cast(%arg0: i32) {
-  // CHECK-NOT: emitc.cast
+func.func @no_fold_cast_to_f32(%arg0: i32) {
+  // CHECK: emitc.cast
   %1 = emitc.cast %arg0: i32 to f32
   return
 }
+
+func.func @fold_cast_to_i64(%arg0: i32) {
+  // CHECK-NOT: emitc.cast
+  %1 = emitc.cast %arg0 {pure} : i32 to i64
+  return
+}

>From 498106a26073f7c4b7d68b374fd1b04c09d619f6 Mon Sep 17 00:00:00 2001
From: Andrzej Warzynski <andrzej.warzynski at arm.com>
Date: Fri, 12 Jun 2026 14:49:43 +0000
Subject: [PATCH 3/5] Add comments

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 4527afeedc346..19971f2198193 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -418,6 +418,14 @@ def EmitC_CastOp : EmitC_Op<"cast",
   }];
 
   let arguments = (ins EmitCType:$source,
+    // In general, C++ cast expressions cannot always be assumed to be pure: they
+    // may invoke user-defined conversions or be affected by floating-point
+    // environment settings. However, in many practical cases, such as integer casts
+    // without operator overloading, the cast is pure and can be treated as
+    // speculatable and side-effect free.
+    //
+    // When this attribute is set, `getSpeculatability()` returns `Speculatable`
+    // and `getEffects()` reports no effects.
     UnitAttr:$pure
   );
 

>From a108de1cfe61e27b90590d9b2a6bcebe11b7ea31 Mon Sep 17 00:00:00 2001
From: Andrzej Warzynski <andrzej.warzynski at arm.com>
Date: Fri, 12 Jun 2026 15:07:05 +0000
Subject: [PATCH 4/5] Address comments from Jared

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 6 +++---
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp         | 2 --
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 19971f2198193..f0606db7af7f5 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -434,10 +434,10 @@ def EmitC_CastOp : EmitC_Op<"cast",
 
   let extraClassDeclaration = [{
     bool hasSideEffects() {
-      return false;
+      // Use the "pure" attribute to see whether this CastOp has side effects.
+      // Note that by default, `pure` is not set.
+      return getPure();
     }
-
-    bool isPure();
   }];
 }
 
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 288ab0fe5215f..c7bfb92fd6d40 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -336,8 +336,6 @@ bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
        emitc::isSupportedFloatType(output) || isa<emitc::PointerType>(output)));
 }
 
-bool CastOp::isPure() { return static_cast<bool>(getPureAttr()); }
-
 Speculation::Speculatability emitc::CastOp::getSpeculatability() {
   return getPure() ? Speculation::Speculatable : Speculation::NotSpeculatable;
 }

>From 79b6b049a0318644499da7c77f310c06a77713cb Mon Sep 17 00:00:00 2001
From: Andrzej Warzynski <andrzej.warzynski at arm.com>
Date: Mon, 15 Jun 2026 10:23:15 +0000
Subject: [PATCH 5/5] Move the comment

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index f0606db7af7f5..32a07e701b208 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -415,17 +415,21 @@ def EmitC_CastOp : EmitC_Op<"cast",
     %1 = emitc.cast %arg1 :
         !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<i32>
     ```
+
+    In general, C++ cast expressions cannot always be assumed to be pure:
+    they may invoke user-defined conversions or be affected by floating-point
+    environment settings. However, in many practical cases, such as integer
+    casts without operator overloading, the cast is pure and can be treated as
+    speculatable and side-effect-free. For such cases, the `pure` attribute
+    may be used.
+
+    When `pure` attribute is set, `getSpeculatability()` returns `Speculatable`
+    and `getEffects()` reports no effects. It is UB if the `pure` attribute is
+    set and the actual conversion is not pure, e.g. when the user-defined
+    conversion has memory effects.
   }];
 
   let arguments = (ins EmitCType:$source,
-    // In general, C++ cast expressions cannot always be assumed to be pure: they
-    // may invoke user-defined conversions or be affected by floating-point
-    // environment settings. However, in many practical cases, such as integer casts
-    // without operator overloading, the cast is pure and can be treated as
-    // speculatable and side-effect free.
-    //
-    // When this attribute is set, `getSpeculatability()` returns `Speculatable`
-    // and `getEffects()` reports no effects.
     UnitAttr:$pure
   );
 



More information about the Mlir-commits mailing list