[Mlir-commits] [mlir] [mlir][arith] Fold addi(x, not(x)) -> -1 (PR #212272)

Victor Perez llvmlistbot at llvm.org
Mon Jul 27 09:48:25 PDT 2026


https://github.com/victor-eds updated https://github.com/llvm/llvm-project/pull/212272

>From 5d7000ae69667cd0c58ddf6902182d0fa6a8fbf6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20P=C3=A9rez=20Carrasco?=
 <victor.pc.upm at gmail.com>
Date: Sat, 25 Jul 2026 16:06:44 -0700
Subject: [PATCH] [mlir][arith] Fold addi(x, not(x)) -> -1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a value-preserving fold mirroring LLVM's InstructionSimplify:

  addi(x, not(x)) -> -1
  addi(not(x), x) -> -1

where not(x) is xori(x, -1). For all signless integers x + ~x == -1,
so the fold is unconditional and needs no overflow flags. Both operand
orders are matched because the canonicalizer does not order two
non-constant operands.

LLVM performs the same simplification: https://godbolt.org/z/a6h1a1Kn5

Verified with Alive2:
- addi(x, not(x)) -> -1: https://alive2.llvm.org/ce/z/z5mgsi
- addi(not(x), x) -> -1: https://alive2.llvm.org/ce/z/bWHZUZ

Signed-off-by: Víctor Pérez Carrasco <victor.pc.upm at gmail.com>
---
 mlir/lib/Dialect/Arith/IR/ArithOps.cpp    | 23 +++++++
 mlir/test/Dialect/Arith/canonicalize.mlir | 75 +++++++++++++++++++++++
 2 files changed, 98 insertions(+)

diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index b7fdb97aba335..563e79aa76af8 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -191,6 +191,21 @@ static Attribute getIntegerAttrOfType(Type type, int64_t value) {
   return DenseElementsAttr::get(shapedType, scalarAttr);
 }
 
+/// If `value` is a bitwise NOT (an xori with an all-ones constant operand),
+/// return the negated operand; otherwise return a null Value. The xori is
+/// commutative, so either operand may hold the constant.
+static Value matchBitwiseNot(Value value) {
+  auto xorOp = value.getDefiningOp<XOrIOp>();
+  if (!xorOp)
+    return {};
+  APInt cst;
+  if (matchPattern(xorOp.getRhs(), m_ConstantInt(&cst)) && cst.isAllOnes())
+    return xorOp.getLhs();
+  if (matchPattern(xorOp.getLhs(), m_ConstantInt(&cst)) && cst.isAllOnes())
+    return xorOp.getRhs();
+  return {};
+}
+
 //===----------------------------------------------------------------------===//
 // TableGen'd canonicalization patterns
 //===----------------------------------------------------------------------===//
@@ -444,6 +459,14 @@ OpFoldResult arith::AddIOp::fold(FoldAdaptor adaptor) {
     if (getLhs() == sub.getRhs())
       return sub.getLhs();
 
+  // addi(x, not(x)) -> -1 and addi(not(x), x) -> -1, where not(x) is
+  // xori(x, -1). For any N-bit integer, ~x == (2^N - 1) - x, so
+  // x + ~x == 2^N - 1: the all-ones bit pattern, which is -1. This is
+  // independent of bit width and sign interpretation and needs no flags.
+  if (matchBitwiseNot(getRhs()) == getLhs() ||
+      matchBitwiseNot(getLhs()) == getRhs())
+    return getIntegerAttrOfType(getType(), -1);
+
   return constFoldBinaryOp<IntegerAttr>(
       adaptor.getOperands(),
       [](APInt a, const APInt &b) { return std::move(a) + b; });
diff --git a/mlir/test/Dialect/Arith/canonicalize.mlir b/mlir/test/Dialect/Arith/canonicalize.mlir
index d6ea02e5508dd..4ce26fffde41f 100644
--- a/mlir/test/Dialect/Arith/canonicalize.mlir
+++ b/mlir/test/Dialect/Arith/canonicalize.mlir
@@ -4019,6 +4019,81 @@ func.func @andand3(%a : i32, %b : i32) -> i32 {
 
 // -----
 
+// CHECK-LABEL: @addi_of_not
+//       CHECK:   %[[CM1:.+]] = arith.constant -1 : i32
+//       CHECK:   return %[[CM1]]
+func.func @addi_of_not(%arg0 : i32) -> i32 {
+  %cm1 = arith.constant -1 : i32
+  %not = arith.xori %arg0, %cm1 : i32
+  %res = arith.addi %arg0, %not : i32
+  return %res : i32
+}
+
+// CHECK-LABEL: @addi_of_not_swapped
+//       CHECK:   %[[CM1:.+]] = arith.constant -1 : i32
+//       CHECK:   return %[[CM1]]
+func.func @addi_of_not_swapped(%arg0 : i32) -> i32 {
+  %cm1 = arith.constant -1 : i32
+  %not = arith.xori %arg0, %cm1 : i32
+  %res = arith.addi %not, %arg0 : i32
+  return %res : i32
+}
+
+// CHECK-LABEL: @addi_of_not_vector
+//       CHECK:   %[[CM1:.+]] = arith.constant dense<-1> : vector<4xi32>
+//       CHECK:   return %[[CM1]]
+func.func @addi_of_not_vector(%arg0 : vector<4xi32>) -> vector<4xi32> {
+  %cm1 = arith.constant dense<-1> : vector<4xi32>
+  %not = arith.xori %arg0, %cm1 : vector<4xi32>
+  %res = arith.addi %arg0, %not : vector<4xi32>
+  return %res : vector<4xi32>
+}
+
+// The negated addend may be a compound expression.
+// CHECK-LABEL: @addi_of_not_compound
+//       CHECK:   %[[CM1:.+]] = arith.constant -1 : i32
+//       CHECK:   return %[[CM1]]
+func.func @addi_of_not_compound(%arg0 : i32, %arg1 : i32) -> i32 {
+  %a = arith.xori %arg0, %arg1 : i32
+  %cm1 = arith.constant -1 : i32
+  %not = arith.xori %a, %cm1 : i32
+  %res = arith.addi %a, %not : i32
+  return %res : i32
+}
+
+// The all-ones constant may be either xori operand (xori is commutative).
+// CHECK-LABEL: @addi_of_not_const_first
+//       CHECK:   %[[CM1:.+]] = arith.constant -1 : i32
+//       CHECK:   return %[[CM1]]
+func.func @addi_of_not_const_first(%arg0 : i32) -> i32 {
+  %cm1 = arith.constant -1 : i32
+  %not = arith.xori %cm1, %arg0 : i32
+  %res = arith.addi %arg0, %not : i32
+  return %res : i32
+}
+
+// The xori must be a NOT of the other addend, not of an unrelated value.
+// CHECK-LABEL: @addi_of_not_distinct_no_fold
+//       CHECK:   arith.addi
+func.func @addi_of_not_distinct_no_fold(%arg0 : i32, %arg1 : i32) -> i32 {
+  %cm1 = arith.constant -1 : i32
+  %not = arith.xori %arg1, %cm1 : i32
+  %res = arith.addi %arg0, %not : i32
+  return %res : i32
+}
+
+// A non-all-ones xori constant is not a NOT, so it must not fold.
+// CHECK-LABEL: @addi_of_xori_non_all_ones_no_fold
+//       CHECK:   arith.addi
+func.func @addi_of_xori_non_all_ones_no_fold(%arg0 : i32) -> i32 {
+  %c5 = arith.constant 5 : i32
+  %xor = arith.xori %arg0, %c5 : i32
+  %res = arith.addi %arg0, %xor : i32
+  return %res : i32
+}
+
+// -----
+
 // CHECK-LABEL: @truncIShrSIToTrunciShrUI
 //  CHECK-SAME:   (%[[A:.+]]: i64)
 //  CHECK-NEXT:   %[[C32:.+]] = arith.constant 32 : i64



More information about the Mlir-commits mailing list