[Mlir-commits] [mlir] [mlir][arith] Add nneg to extui and uitofp. (PR #183165)

Erick Ochoa Lopez llvmlistbot at llvm.org
Wed Feb 25 08:31:41 PST 2026


https://github.com/amd-eochoalo updated https://github.com/llvm/llvm-project/pull/183165

>From 4d2a02318410daf498df1cd07d8c25fb05b82bfc Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Tue, 24 Feb 2026 13:54:11 -0500
Subject: [PATCH 1/8] [mlir][arith] Add ArithNonNegFlagInterface

---
 .../Dialect/Arith/IR/ArithOpsInterfaces.td    | 47 +++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/mlir/include/mlir/Dialect/Arith/IR/ArithOpsInterfaces.td b/mlir/include/mlir/Dialect/Arith/IR/ArithOpsInterfaces.td
index 82d6c9ad6b03d..d1b8e250cdb59 100644
--- a/mlir/include/mlir/Dialect/Arith/IR/ArithOpsInterfaces.td
+++ b/mlir/include/mlir/Dialect/Arith/IR/ArithOpsInterfaces.td
@@ -106,6 +106,53 @@ def ArithIntegerOverflowFlagsInterface : OpInterface<"ArithIntegerOverflowFlagsI
   ];
 }
 
+def ArithNonNegFlagInterface : OpInterface<"ArithNonNegFlagInterface"> {
+  let description = [{
+    Access to op non-negative (nneg) flag.
+  }];
+
+  let cppNamespace = "::mlir::arith";
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/        "Returns whether the operation has the nneg flag set",
+      /*returnType=*/  "bool",
+      /*methodName=*/  "getNonNeg",
+      /*args=*/        (ins),
+      /*methodBody=*/  [{}],
+      /*defaultImpl=*/ [{
+        auto op = cast<ConcreteOp>(this->getOperation());
+        return op.getNonNegAttr() != nullptr;
+      }]
+      >,
+    InterfaceMethod<
+      /*desc=*/        "Set the nneg flag for the operation",
+      /*returnType=*/  "void",
+      /*methodName=*/  "setNonNeg",
+      /*args=*/        (ins "bool":$nonNeg),
+      /*methodBody=*/  [{}],
+      /*defaultImpl=*/ [{
+        auto op = cast<ConcreteOp>(this->getOperation());
+        if (nonNeg)
+          op.setNonNegAttr(UnitAttr::get(op->getContext()));
+        else
+          op.removeNonNegAttr();
+      }]
+      >,
+    StaticInterfaceMethod<
+      /*desc=*/        [{Returns the name of the NonNeg flag attribute for
+                         the operation}],
+      /*returnType=*/  "StringRef",
+      /*methodName=*/  "getNonNegFlagAttrName",
+      /*args=*/        (ins),
+      /*methodBody=*/  [{}],
+      /*defaultImpl=*/ [{
+        return "nonNeg";
+      }]
+      >
+  ];
+}
+
 def ArithRoundingModeInterface : OpInterface<"ArithRoundingModeInterface"> {
   let description = [{
     Access to op rounding mode.

>From 9ffe6faaf14290d1357d62bf52a4326249b05fe7 Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Tue, 24 Feb 2026 14:10:32 -0500
Subject: [PATCH 2/8] [mlir][arith] Add ArithNonNegFlagInterface to extui and
 uitofp

---
 .../include/mlir/Dialect/Arith/IR/ArithOps.td | 39 ++++++++++++++++++-
 1 file changed, 37 insertions(+), 2 deletions(-)

diff --git a/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td b/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
index c8754954c181d..5308c133f9f29 100644
--- a/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
+++ b/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
@@ -1172,7 +1172,8 @@ def Arith_RemFOp : Arith_FloatBinaryOp<"remf"> {
 // ExtUIOp
 //===----------------------------------------------------------------------===//
 
-def Arith_ExtUIOp : Arith_IToICastOp<"extui"> {
+def Arith_ExtUIOp :
+    Arith_IToICastOp<"extui", [DeclareOpInterfaceMethods<ArithNonNegFlagInterface>]> {
   let summary = "integer zero extension operation";
   let description = [{
     The integer zero extension operation takes an integer input of
@@ -1180,6 +1181,10 @@ def Arith_ExtUIOp : Arith_IToICastOp<"extui"> {
     bit-width must be larger than the input bit-width (N > M).
     The top-most (N - M) bits of the output are filled with zeros.
 
+    This op supports an `nneg` flag. If the flag is present, the operand
+    is known to be non-negative, i.e. the sign bit is zero. This allows
+    the zero extension to be treated as a sign extension.
+
     Example:
 
     ```mlir
@@ -1189,9 +1194,17 @@ def Arith_ExtUIOp : Arith_IToICastOp<"extui"> {
       %4 = arith.extui %3 : i3 to i6  // %4 is 0b000010
 
       %5 = arith.extui %0 : vector<2 x i32> to vector<2 x i64>
+
+      // Zero extension with nneg flag.
+      %6 = arith.extui %3 nneg : i3 to i6
     ```
   }];
 
+  let arguments = (ins SignlessFixedWidthIntegerLike:$in, UnitAttr:$nonNeg);
+  let results = (outs SignlessFixedWidthIntegerLike:$out);
+  let assemblyFormat = [{
+    $in (`nneg` $nonNeg^)? attr-dict `:` type($in) `to` type($out)
+  }];
   let hasFolder = 1;
   let hasVerifier = 1;
 }
@@ -1477,13 +1490,35 @@ def Arith_ScalingTruncFOp
 // UIToFPOp
 //===----------------------------------------------------------------------===//
 
-def Arith_UIToFPOp : Arith_IToFCastOp<"uitofp"> {
+def Arith_UIToFPOp :
+    Arith_IToFCastOp<"uitofp", [DeclareOpInterfaceMethods<ArithNonNegFlagInterface>]> {
   let summary = "cast from unsigned integer type to floating-point";
   let description = [{
     Cast from a value interpreted as unsigned integer to the corresponding
     floating-point value. If the value cannot be exactly represented, it is
     rounded using the default rounding mode. When operating on vectors, casts
     elementwise.
+
+    This op supports an `nneg` flag. If the flag is present, the operand
+    is known to be non-negative, i.e. the sign bit is zero. This allows
+    the unsigned-to-float conversion to be treated as a signed-to-float
+    conversion.
+
+    Example:
+
+    ```mlir
+      // Without nneg flag.
+      %0 = arith.uitofp %a : i32 to f64
+
+      // With nneg flag.
+      %1 = arith.uitofp %a nneg : i32 to f64
+    ```
+  }];
+
+  let arguments = (ins SignlessFixedWidthIntegerLike:$in, UnitAttr:$nonNeg);
+  let results = (outs FloatLike:$out);
+  let assemblyFormat = [{
+    $in (`nneg` $nonNeg^)? attr-dict `:` type($in) `to` type($out)
   }];
   let hasFolder = 1;
 }

>From 4384b7930aec80f000b1e888200a56b259b0e52d Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Tue, 24 Feb 2026 14:12:30 -0500
Subject: [PATCH 3/8] [mlir][arith] Add AttrConvertNonNegToLLVM

---
 .../ArithCommon/AttrToLLVMConverter.h         | 23 +++++++++++++++++++
 .../Conversion/ArithToLLVM/ArithToLLVM.cpp    |  5 ++--
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h b/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
index 7020e24517d09..09e3c7b454764 100644
--- a/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
+++ b/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
@@ -111,6 +111,29 @@ class AttrConvertOverflowToLLVM {
   DictionaryAttr propertiesAttr;
 };
 
+// Attribute converter that populates a NamedAttrList by removing the nonNeg
+// attribute from the source operation attributes, and setting it as a property
+// on the target LLVM operation.
+template <typename SourceOp, typename TargetOp>
+class AttrConvertNonNegToLLVM {
+public:
+  AttrConvertNonNegToLLVM(SourceOp srcOp) {
+    convertedAttr = NamedAttrList{srcOp->getAttrs()};
+    if (convertedAttr.erase("nonNeg")) {
+      MLIRContext *ctx = srcOp.getOperation()->getContext();
+      Builder b(ctx);
+      NamedAttribute attr{"nonNeg", b.getUnitAttr()};
+      propertiesAttr = b.getDictionaryAttr(ArrayRef(attr));
+    }
+  }
+  ArrayRef<NamedAttribute> getAttrs() const { return convertedAttr.getAttrs(); }
+  Attribute getPropAttr() const { return propertiesAttr; }
+
+private:
+  NamedAttrList convertedAttr;
+  DictionaryAttr propertiesAttr;
+};
+
 template <typename SourceOp, typename TargetOp>
 class AttrConverterConstrainedFPToLLVM {
   static_assert(TargetOp::template hasTrait<
diff --git a/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp b/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
index 220826dc5f3ac..178dcd419264d 100644
--- a/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
+++ b/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
@@ -104,7 +104,8 @@ using ExtFOpLowering = VectorConvertToLLVMPattern<arith::ExtFOp, LLVM::FPExtOp,
 using ExtSIOpLowering =
     VectorConvertToLLVMPattern<arith::ExtSIOp, LLVM::SExtOp>;
 using ExtUIOpLowering =
-    VectorConvertToLLVMPattern<arith::ExtUIOp, LLVM::ZExtOp>;
+    VectorConvertToLLVMPattern<arith::ExtUIOp, LLVM::ZExtOp,
+                               arith::AttrConvertNonNegToLLVM>;
 using FPToSIOpLowering =
     VectorConvertToLLVMPattern<arith::FPToSIOp, LLVM::FPToSIOp,
                                AttrConvertPassThrough,
@@ -187,7 +188,7 @@ using TruncIOpLowering =
                                arith::AttrConvertOverflowToLLVM>;
 using UIToFPOpLowering =
     VectorConvertToLLVMPattern<arith::UIToFPOp, LLVM::UIToFPOp,
-                               AttrConvertPassThrough,
+                               arith::AttrConvertNonNegToLLVM,
                                /*FailOnUnsupportedFP=*/true>;
 using XOrIOpLowering = VectorConvertToLLVMPattern<arith::XOrIOp, LLVM::XOrOp>;
 

>From ee196a7d6fd4246f0e123d87e2ccfc002759c261 Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Tue, 24 Feb 2026 14:46:15 -0500
Subject: [PATCH 4/8] [mlir][arith] Roundtrip tests for nneg

---
 mlir/test/Dialect/Arith/ops.mlir | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/mlir/test/Dialect/Arith/ops.mlir b/mlir/test/Dialect/Arith/ops.mlir
index 58eadfda17060..9541cecb489f6 100644
--- a/mlir/test/Dialect/Arith/ops.mlir
+++ b/mlir/test/Dialect/Arith/ops.mlir
@@ -625,6 +625,20 @@ func.func @test_extui_scalable_vector(%arg0 : vector<[8]xi32>) -> vector<[8]xi64
   return %0 : vector<[8]xi64>
 }
 
+// CHECK-LABEL: test_extui_nneg
+// CHECK: arith.extui %{{.*}} nneg : i32 to i64
+func.func @test_extui_nneg(%arg0 : i32) -> i64 {
+  %0 = arith.extui %arg0 nneg : i32 to i64
+  return %0 : i64
+}
+
+// CHECK-LABEL: test_extui_nneg_vector
+// CHECK: arith.extui %{{.*}} nneg : vector<8xi32> to vector<8xi64>
+func.func @test_extui_nneg_vector(%arg0 : vector<8xi32>) -> vector<8xi64> {
+  %0 = arith.extui %arg0 nneg : vector<8xi32> to vector<8xi64>
+  return %0 : vector<8xi64>
+}
+
 // CHECK-LABEL: test_extsi
 func.func @test_extsi(%arg0 : i32) -> i64 {
   %0 = arith.extsi %arg0 : i32 to i64
@@ -761,6 +775,20 @@ func.func @test_uitofp_scalable_vector(%arg0 : vector<[8]xi32>) -> vector<[8]xf3
   return %0 : vector<[8]xf32>
 }
 
+// CHECK-LABEL: test_uitofp_nneg
+// CHECK: arith.uitofp %{{.*}} nneg : i32 to f32
+func.func @test_uitofp_nneg(%arg0 : i32) -> f32 {
+  %0 = arith.uitofp %arg0 nneg : i32 to f32
+  return %0 : f32
+}
+
+// CHECK-LABEL: test_uitofp_nneg_vector
+// CHECK: arith.uitofp %{{.*}} nneg : vector<8xi32> to vector<8xf32>
+func.func @test_uitofp_nneg_vector(%arg0 : vector<8xi32>) -> vector<8xf32> {
+  %0 = arith.uitofp %arg0 nneg : vector<8xi32> to vector<8xf32>
+  return %0 : vector<8xf32>
+}
+
 // CHECK-LABEL: test_sitofp
 func.func @test_sitofp(%arg0 : i16) -> f64 {
   %0 = arith.sitofp %arg0 : i16 to f64

>From 43719c8a77a7f0307edb9fded45ff7c81ae94433 Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Tue, 24 Feb 2026 14:47:38 -0500
Subject: [PATCH 5/8] [mlir] Add tests for nneg in arith-to-llvm

---
 .../Conversion/ArithToLLVM/arith-to-llvm.mlir | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/mlir/test/Conversion/ArithToLLVM/arith-to-llvm.mlir b/mlir/test/Conversion/ArithToLLVM/arith-to-llvm.mlir
index b53c52d75c0aa..7ae27a884d5d3 100644
--- a/mlir/test/Conversion/ArithToLLVM/arith-to-llvm.mlir
+++ b/mlir/test/Conversion/ArithToLLVM/arith-to-llvm.mlir
@@ -754,6 +754,30 @@ func.func @ops_supporting_exact(i32, i32) {
 
 // -----
 
+// CHECK-LABEL: @ops_supporting_nneg
+func.func @ops_supporting_nneg(%arg0: i32) {
+  // CHECK: llvm.zext nneg %{{.*}} : i32 to i64
+  %0 = arith.extui %arg0 nneg : i32 to i64
+  // CHECK: llvm.uitofp nneg %{{.*}} : i32 to f32
+  %1 = arith.uitofp %arg0 nneg : i32 to f32
+  return
+}
+
+// -----
+
+// CHECK-LABEL: @ops_nneg_not_set
+func.func @ops_nneg_not_set(%arg0: i32) {
+  // CHECK: llvm.zext %{{.*}} : i32 to i64
+  // CHECK-NOT: nneg
+  %0 = arith.extui %arg0 : i32 to i64
+  // CHECK: llvm.uitofp %{{.*}} : i32 to f32
+  // CHECK-NOT: nneg
+  %1 = arith.uitofp %arg0 : i32 to f32
+  return
+}
+
+// -----
+
 // CHECK-LABEL: func @memref_bitcast
 //  CHECK-SAME:   (%[[ARG:.*]]: memref<?xi16>)
 //       CHECK:   %[[V1:.*]] = builtin.unrealized_conversion_cast %[[ARG]] : memref<?xi16> to !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>

>From 7b9f4fe075e8ad4238d1327622d8de3735161d1c Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Tue, 24 Feb 2026 15:33:26 -0500
Subject: [PATCH 6/8] [mlir][arith] Canonicalization with nneg

---
 .../Dialect/Arith/IR/ArithCanonicalization.td |  38 ++++--
 mlir/test/Dialect/Arith/canonicalize.mlir     | 127 ++++++++++++++++++
 2 files changed, 152 insertions(+), 13 deletions(-)

diff --git a/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td b/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
index e256915933a71..8be2af5cb3cfc 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
+++ b/mlir/lib/Dialect/Arith/IR/ArithCanonicalization.td
@@ -218,8 +218,12 @@ def XOrINotCmpI :
         (Arith_CmpIOp (InvertPredicate $pred), $a, $b)>;
 
 // xor extui(x), extui(y) -> extui(xor(x,y))
+// XOR preserves nneg only if both operands are non-negative: xor of two values
+// with sign bit 0 has sign bit 0.
 def XOrIOfExtUI :
-    Pat<(Arith_XOrIOp (Arith_ExtUIOp $x), (Arith_ExtUIOp $y)), (Arith_ExtUIOp (Arith_XOrIOp $x, $y)),
+    Pat<(Arith_XOrIOp (Arith_ExtUIOp $x, $nneg1), (Arith_ExtUIOp $y, $nneg2)),
+        (Arith_ExtUIOp (Arith_XOrIOp $x, $y),
+                       (NativeCodeCall<"($0 && $1) ? $0 : UnitAttr()"> $nneg1, $nneg2)),
       [(Constraint<CPred<"$0.getType() == $1.getType()">> $x, $y)]>;
 
 // xor extsi(x), extsi(y) -> extsi(xor(x,y))
@@ -245,8 +249,8 @@ def CmpIExtSI :
 // cmpi(== or !=, a ext iNN, b ext iNN) == cmpi(== or !=, a, b)
 def CmpIExtUI :
     Pat<(Arith_CmpIOp $pred,
-          (Arith_ExtUIOp $a),
-          (Arith_ExtUIOp $b)),
+          (Arith_ExtUIOp $a, $nneg1),
+          (Arith_ExtUIOp $b, $nneg2)),
         (Arith_CmpIOp $pred, $a, $b),
         [(Constraint<CPred<"$0.getType() == $1.getType()">> $a, $b),
          (Constraint<
@@ -306,7 +310,8 @@ def IndexCastUIOfIndexCastUI :
 
 // index_castui(extui(x)) -> index_castui(x)
 def IndexCastUIOfExtUI :
-    Pat<(Arith_IndexCastUIOp (Arith_ExtUIOp $x)), (Arith_IndexCastUIOp $x)>;
+    Pat<(Arith_IndexCastUIOp (Arith_ExtUIOp $x, $nneg)),
+        (Arith_IndexCastUIOp $x)>;
 
 
 //===----------------------------------------------------------------------===//
@@ -323,16 +328,20 @@ def BitcastOfBitcast :
 
 // extsi(extui(x iN : iM) : iL) -> extui(x : iL)
 def ExtSIOfExtUI :
-    Pat<(Arith_ExtSIOp (Arith_ExtUIOp $x)), (Arith_ExtUIOp $x)>;
+    Pat<(Arith_ExtSIOp (Arith_ExtUIOp $x, $nneg)),
+        (Arith_ExtUIOp $x, $nneg)>;
 
 //===----------------------------------------------------------------------===//
 // AndIOp
 //===----------------------------------------------------------------------===//
 
 // and extui(x), extui(y) -> extui(and(x,y))
+// AND can only clear bits, so if either operand is non-negative (sign bit 0),
+// the result also has sign bit 0. Preserves nneg if either operand has it.
 def AndOfExtUI :
-    Pat<(Arith_AndIOp (Arith_ExtUIOp $x), (Arith_ExtUIOp $y)),
-        (Arith_ExtUIOp (Arith_AndIOp $x, $y)),
+    Pat<(Arith_AndIOp (Arith_ExtUIOp $x, $nneg1), (Arith_ExtUIOp $y, $nneg2)),
+        (Arith_ExtUIOp (Arith_AndIOp $x, $y),
+                       (NativeCodeCall<"$0 ? $0 : $1"> $nneg1, $nneg2)),
         [(Constraint<CPred<"$0.getType() == $1.getType()">> $x, $y)]>;
 
 // and extsi(x), extsi(y) -> extsi(and(x,y))
@@ -346,9 +355,12 @@ def AndOfExtSI :
 //===----------------------------------------------------------------------===//
 
 // or extui(x), extui(y) -> extui(or(x,y))
+// OR preserves nneg only if both operands are non-negative: or of two values
+// with sign bit 0 has sign bit 0.
 def OrOfExtUI :
-    Pat<(Arith_OrIOp (Arith_ExtUIOp $x), (Arith_ExtUIOp $y)),
-        (Arith_ExtUIOp (Arith_OrIOp $x, $y)),
+    Pat<(Arith_OrIOp (Arith_ExtUIOp $x, $nneg1), (Arith_ExtUIOp $y, $nneg2)),
+        (Arith_ExtUIOp (Arith_OrIOp $x, $y),
+                       (NativeCodeCall<"($0 && $1) ? $0 : UnitAttr()"> $nneg1, $nneg2)),
         [(Constraint<CPred<"$0.getType() == $1.getType()">> $x, $y)]>;
 
 // or extsi(x), extsi(y) -> extsi(or(x,y))
@@ -381,8 +393,8 @@ def TruncIExtSIToExtSI :
 
 // trunci(extui(x)) -> extui(x), when only the zero-extension bits are truncated
 def TruncIExtUIToExtUI :
-    Pat<(Arith_TruncIOp:$tr (Arith_ExtUIOp:$ext $x), $overflow),
-        (Arith_ExtUIOp $x),
+    Pat<(Arith_TruncIOp:$tr (Arith_ExtUIOp:$ext $x, $nneg), $overflow),
+        (Arith_ExtUIOp $x, $nneg),
         [(ValueWiderThan $ext, $tr),
          (ValueWiderThan $tr, $x)]>;
 
@@ -405,8 +417,8 @@ def TruncFSIToFPToSIToFP :
 
 // truncf(uitofp(x)) -> uitofp(x) if default rounding mode.
 def TruncFUIToFPToUIToFP :
-    Pat<(Arith_TruncFOp:$tr (Arith_UIToFPOp:$fp $x), $rmf, $fmf),
-        (Arith_UIToFPOp $x),
+    Pat<(Arith_TruncFOp:$tr (Arith_UIToFPOp:$fp $x, $nneg), $rmf, $fmf),
+        (Arith_UIToFPOp $x, $nneg),
         [(Constraint<CPred<"$0 == nullptr">, "default rounding mode"> $rmf)]>;
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/Arith/canonicalize.mlir b/mlir/test/Dialect/Arith/canonicalize.mlir
index 18e0d2d2ea3c4..52f4a54feebab 100644
--- a/mlir/test/Dialect/Arith/canonicalize.mlir
+++ b/mlir/test/Dialect/Arith/canonicalize.mlir
@@ -366,6 +366,15 @@ func.func @extSIOfExtUI(%arg0: i1) -> i64 {
   return %ext2 : i64
 }
 
+// CHECK-LABEL: @extSIOfExtUI_nneg
+//       CHECK:   %[[res:.+]] = arith.extui %arg0 nneg : i1 to i64
+//       CHECK:   return %[[res]]
+func.func @extSIOfExtUI_nneg(%arg0: i1) -> i64 {
+  %ext1 = arith.extui %arg0 nneg : i1 to i8
+  %ext2 = arith.extsi %ext1 : i8 to i64
+  return %ext2 : i64
+}
+
 // CHECK-LABEL: @extUIOfExtUI
 //       CHECK:   %[[res:.+]] = arith.extui %arg0 : i1 to i64
 //       CHECK:   return %[[res]]
@@ -490,6 +499,39 @@ func.func @andOfExtUI(%arg0: i8, %arg1: i8) -> i64 {
   return %res : i64
 }
 
+// CHECK-LABEL: @andOfExtUI_nneg_lhs
+//       CHECK:  %[[comb:.+]] = arith.andi %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] nneg : i8 to i64
+//       CHECK:   return %[[ext]]
+func.func @andOfExtUI_nneg_lhs(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 nneg : i8 to i64
+  %ext1 = arith.extui %arg1 : i8 to i64
+  %res = arith.andi %ext0, %ext1 : i64
+  return %res : i64
+}
+
+// CHECK-LABEL: @andOfExtUI_nneg_rhs
+//       CHECK:  %[[comb:.+]] = arith.andi %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] nneg : i8 to i64
+//       CHECK:   return %[[ext]]
+func.func @andOfExtUI_nneg_rhs(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 : i8 to i64
+  %ext1 = arith.extui %arg1 nneg : i8 to i64
+  %res = arith.andi %ext0, %ext1 : i64
+  return %res : i64
+}
+
+// CHECK-LABEL: @andOfExtUI_nneg_both
+//       CHECK:  %[[comb:.+]] = arith.andi %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] nneg : i8 to i64
+//       CHECK:   return %[[ext]]
+func.func @andOfExtUI_nneg_both(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 nneg : i8 to i64
+  %ext1 = arith.extui %arg1 nneg : i8 to i64
+  %res = arith.andi %ext0, %ext1 : i64
+  return %res : i64
+}
+
 // CHECK-LABEL: @orOfExtSI
 //       CHECK:  %[[comb:.+]] = arith.ori %arg0, %arg1 : i8
 //       CHECK:  %[[ext:.+]] = arith.extsi %[[comb]] : i8 to i64
@@ -512,6 +554,29 @@ func.func @orOfExtUI(%arg0: i8, %arg1: i8) -> i64 {
   return %res : i64
 }
 
+// CHECK-LABEL: @orOfExtUI_nneg_both
+//       CHECK:  %[[comb:.+]] = arith.ori %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] nneg : i8 to i64
+//       CHECK:   return %[[ext]]
+func.func @orOfExtUI_nneg_both(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 nneg : i8 to i64
+  %ext1 = arith.extui %arg1 nneg : i8 to i64
+  %res = arith.ori %ext0, %ext1 : i64
+  return %res : i64
+}
+
+// CHECK-LABEL: @orOfExtUI_nneg_mixed
+//       CHECK:  %[[comb:.+]] = arith.ori %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] : i8 to i64
+//   CHECK-NOT:  nneg
+//       CHECK:   return %[[ext]]
+func.func @orOfExtUI_nneg_mixed(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 nneg : i8 to i64
+  %ext1 = arith.extui %arg1 : i8 to i64
+  %res = arith.ori %ext0, %ext1 : i64
+  return %res : i64
+}
+
 // -----
 
 // CHECK-LABEL: @indexCastOfSignExtend
@@ -532,6 +597,15 @@ func.func @indexCastUIOfUnsignedExtend(%arg0: i8) -> index {
   return %idx : index
 }
 
+// CHECK-LABEL: @indexCastUIOfUnsignedExtend_nneg
+//       CHECK:   %[[res:.+]] = arith.index_castui %arg0 : i8 to index
+//       CHECK:   return %[[res]]
+func.func @indexCastUIOfUnsignedExtend_nneg(%arg0: i8) -> index {
+  %ext = arith.extui %arg0 nneg : i8 to i16
+  %idx = arith.index_castui %ext : i16 to index
+  return %idx : index
+}
+
 // CHECK-LABEL: @indexCastFold
 //       CHECK:   %[[res:.*]] = arith.constant -2 : index
 //       CHECK:   return %[[res]]
@@ -771,6 +845,26 @@ func.func @truncSitofpConstrained(%arg0: i32) -> f32 {
   return %trunc : f32
 }
 
+// CHECK-LABEL: @truncUitofp
+//       CHECK:     %[[UITOFP:.*]] = arith.uitofp %[[ARG0:.*]] : i32 to f32
+//       CHECK-NOT: truncf
+//       CHECK:     return %[[UITOFP]]
+func.func @truncUitofp(%arg0: i32) -> f32 {
+  %uitofp = arith.uitofp %arg0 : i32 to f64
+  %trunc = arith.truncf %uitofp : f64 to f32
+  return %trunc : f32
+}
+
+// CHECK-LABEL: @truncUitofp_nneg
+//       CHECK:     %[[UITOFP:.*]] = arith.uitofp %[[ARG0:.*]] nneg : i32 to f32
+//       CHECK-NOT: truncf
+//       CHECK:     return %[[UITOFP]]
+func.func @truncUitofp_nneg(%arg0: i32) -> f32 {
+  %uitofp = arith.uitofp %arg0 nneg : i32 to f64
+  %trunc = arith.truncf %uitofp : f64 to f32
+  return %trunc : f32
+}
+
 // TODO: We should also add a test for not folding arith.extf on information loss.
 // This may happen when extending f8E5M2FNUZ to f16.
 
@@ -812,6 +906,16 @@ func.func @truncExtui3(%arg0: i8) -> i16 {
   return %trunci : i16
 }
 
+// CHECK-LABEL: @truncExtui3_nneg
+//       CHECK:  %[[ARG0:.+]]: i8
+//       CHECK:  %[[CST:.*]] = arith.extui %[[ARG0:.+]] nneg : i8 to i16
+//       CHECK:   return  %[[CST:.*]] : i16
+func.func @truncExtui3_nneg(%arg0: i8) -> i16 {
+  %extui = arith.extui %arg0 nneg : i8 to i32
+  %trunci = arith.trunci %extui : i32 to i16
+  return %trunci : i16
+}
+
 // CHECK-LABEL: @truncExtuiVector
 //       CHECK:  %[[ARG0:.+]]: vector<2xi32>
 //       CHECK:  %[[CST:.*]] = arith.trunci %[[ARG0:.+]] : vector<2xi32> to vector<2xi16>
@@ -1817,6 +1921,29 @@ func.func @xorOfExtUI(%arg0: i8, %arg1: i8) -> i64 {
   return %res : i64
 }
 
+// CHECK-LABEL: @xorOfExtUI_nneg_both
+//       CHECK:  %[[comb:.+]] = arith.xori %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] nneg : i8 to i64
+//       CHECK:   return %[[ext]]
+func.func @xorOfExtUI_nneg_both(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 nneg : i8 to i64
+  %ext1 = arith.extui %arg1 nneg : i8 to i64
+  %res = arith.xori %ext0, %ext1 : i64
+  return %res : i64
+}
+
+// CHECK-LABEL: @xorOfExtUI_nneg_mixed
+//       CHECK:  %[[comb:.+]] = arith.xori %arg0, %arg1 : i8
+//       CHECK:  %[[ext:.+]] = arith.extui %[[comb]] : i8 to i64
+//   CHECK-NOT:  nneg
+//       CHECK:   return %[[ext]]
+func.func @xorOfExtUI_nneg_mixed(%arg0: i8, %arg1: i8) -> i64 {
+  %ext0 = arith.extui %arg0 nneg : i8 to i64
+  %ext1 = arith.extui %arg1 : i8 to i64
+  %res = arith.xori %ext0, %ext1 : i64
+  return %res : i64
+}
+
 // -----
 
 // CHECK-LABEL: @bitcastSameType(

>From 5aaa1b42584c19322600edc245a564fe88f07292 Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Wed, 25 Feb 2026 11:28:36 -0500
Subject: [PATCH 7/8] Exit early

---
 .../Conversion/ArithCommon/AttrToLLVMConverter.h     | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h b/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
index 09e3c7b454764..fccfe4897114e 100644
--- a/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
+++ b/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
@@ -119,12 +119,12 @@ class AttrConvertNonNegToLLVM {
 public:
   AttrConvertNonNegToLLVM(SourceOp srcOp) {
     convertedAttr = NamedAttrList{srcOp->getAttrs()};
-    if (convertedAttr.erase("nonNeg")) {
-      MLIRContext *ctx = srcOp.getOperation()->getContext();
-      Builder b(ctx);
-      NamedAttribute attr{"nonNeg", b.getUnitAttr()};
-      propertiesAttr = b.getDictionaryAttr(ArrayRef(attr));
-    }
+    if (!convertedAttr.erase("nonNeg"))
+      return;
+    MLIRContext *ctx = srcOp.getOperation()->getContext();
+    Builder b(ctx);
+    NamedAttribute attr{"nonNeg", b.getUnitAttr()};
+    propertiesAttr = b.getDictionaryAttr(ArrayRef(attr));
   }
   ArrayRef<NamedAttribute> getAttrs() const { return convertedAttr.getAttrs(); }
   Attribute getPropAttr() const { return propertiesAttr; }

>From d5f10d9be357e252954797ccf6b94822f0f4ecdc Mon Sep 17 00:00:00 2001
From: Erick Ochoa <erick.ochoalopez at amd.com>
Date: Wed, 25 Feb 2026 11:31:18 -0500
Subject: [PATCH 8/8] Update descriptions

---
 mlir/include/mlir/Dialect/Arith/IR/ArithOps.td | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td b/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
index 5308c133f9f29..c372038a8d43e 100644
--- a/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
+++ b/mlir/include/mlir/Dialect/Arith/IR/ArithOps.td
@@ -1181,9 +1181,10 @@ def Arith_ExtUIOp :
     bit-width must be larger than the input bit-width (N > M).
     The top-most (N - M) bits of the output are filled with zeros.
 
-    This op supports an `nneg` flag. If the flag is present, the operand
-    is known to be non-negative, i.e. the sign bit is zero. This allows
-    the zero extension to be treated as a sign extension.
+    When the `nneg` flag is present, the operand is assumed to have
+    the most significant bit set to 0. In this case, zero extension is
+    equivalent to sign extension. When this assumption is violated, the
+    result is poison.
 
     Example:
 
@@ -1499,10 +1500,10 @@ def Arith_UIToFPOp :
     rounded using the default rounding mode. When operating on vectors, casts
     elementwise.
 
-    This op supports an `nneg` flag. If the flag is present, the operand
-    is known to be non-negative, i.e. the sign bit is zero. This allows
-    the unsigned-to-float conversion to be treated as a signed-to-float
-    conversion.
+    When the `nneg` flag is present, the operand is assumed to have
+    the most significant bit set to 0. In this case, zero extension is
+    equivalent to sign extension. When this assumption is violated, the
+    result is poison.
 
     Example:
 



More information about the Mlir-commits mailing list