https://github.com/prometheusfma-llvm created 
https://github.com/llvm/llvm-project/pull/218398

Fold scalar integer operands while honoring overflow flags, saturated 
arithmetic, and poison propagation. Add canonicalization coverage for the new 
folds.

>From aeaad545e849f029b8d9c138f0c45524dae29a2e Mon Sep 17 00:00:00 2001
From: Prometheus <[email protected]>
Date: Mon, 24 Aug 2026 05:56:35 -0700
Subject: [PATCH] [CIR] Fold constant add and sub operations

Fold scalar integer operands while honoring overflow flags, saturated
arithmetic, and poison propagation. Add canonicalization coverage for the
new folds.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  4 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 65 +++++++++++++++
 .../Dialect/Transforms/CIRCanonicalize.cpp    |  4 +-
 clang/test/CIR/Transforms/canonicalize.cir    | 82 +++++++++++++++++++
 4 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a52c37e4860ce..2065042d92326 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2764,6 +2764,8 @@ def CIR_AddOp
     %4 = cir.add %va, %vb : !cir.vector<4 x !s32i>
     ```
   }];
+
+  let hasFolder = 1;
 }
 
 
//===----------------------------------------------------------------------===//
@@ -2793,6 +2795,8 @@ def CIR_SubOp
     %3 = cir.sub %va, %vb : !cir.vector<4 x !s32i>
     ```
   }];
+
+  let hasFolder = 1;
 }
 
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 66d7feebc6372..a67c6fa717d43 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3185,6 +3185,71 @@ OpFoldResult cir::NotOp::fold(FoldAdaptor adaptor) {
   return {};
 }
 
+//===----------------------------------------------------------------------===//
+// AddOp & SubOp
+//===----------------------------------------------------------------------===//
+
+// Constant-fold integer add/sub. Honors nsw/nuw by folding to poison on the
+// corresponding overflow, and folds saturated arithmetic by clamping.
+static OpFoldResult foldAddSubConst(mlir::Type ty, const APInt &lhs,
+                                    const APInt &rhs, bool isSub, bool nsw,
+                                    bool nuw, bool sat) {
+  bool isSigned = mlir::cast<cir::IntType>(ty).isSigned();
+  if (sat) {
+    APInt res = isSub ? (isSigned ? lhs.ssub_sat(rhs) : lhs.usub_sat(rhs))
+                      : (isSigned ? lhs.sadd_sat(rhs) : lhs.uadd_sat(rhs));
+    return cir::IntAttr::get(ty, res);
+  }
+
+  bool poison = false;
+  if (nsw) {
+    bool overflow = false;
+    (void)(isSub ? lhs.ssub_ov(rhs, overflow) : lhs.sadd_ov(rhs, overflow));
+    poison |= overflow;
+  }
+  if (nuw) {
+    bool overflow = false;
+    (void)(isSub ? lhs.usub_ov(rhs, overflow) : lhs.uadd_ov(rhs, overflow));
+    poison |= overflow;
+  }
+  if (poison)
+    return cir::PoisonAttr::get(ty);
+
+  return cir::IntAttr::get(ty, isSub ? (lhs - rhs) : (lhs + rhs));
+}
+
+OpFoldResult cir::AddOp::fold(FoldAdaptor adaptor) {
+  if (mlir::isa_and_present<cir::PoisonAttr>(adaptor.getLhs()))
+    return adaptor.getLhs();
+  if (mlir::isa_and_present<cir::PoisonAttr>(adaptor.getRhs()))
+    return adaptor.getRhs();
+
+  auto lhs = mlir::dyn_cast_if_present<cir::IntAttr>(adaptor.getLhs());
+  auto rhs = mlir::dyn_cast_if_present<cir::IntAttr>(adaptor.getRhs());
+  if (!lhs || !rhs)
+    return {};
+
+  return foldAddSubConst(getType(), lhs.getValue(), rhs.getValue(),
+                         /*isSub=*/false, getNoSignedWrap(),
+                         getNoUnsignedWrap(), getSaturated());
+}
+
+OpFoldResult cir::SubOp::fold(FoldAdaptor adaptor) {
+  if (mlir::isa_and_present<cir::PoisonAttr>(adaptor.getLhs()))
+    return adaptor.getLhs();
+  if (mlir::isa_and_present<cir::PoisonAttr>(adaptor.getRhs()))
+    return adaptor.getRhs();
+
+  auto lhs = mlir::dyn_cast_if_present<cir::IntAttr>(adaptor.getLhs());
+  auto rhs = mlir::dyn_cast_if_present<cir::IntAttr>(adaptor.getRhs());
+  if (!lhs || !rhs)
+    return {};
+
+  return foldAddSubConst(getType(), lhs.getValue(), rhs.getValue(),
+                         /*isSub=*/true, getNoSignedWrap(), 
getNoUnsignedWrap(),
+                         getSaturated());
+}
+
 
//===----------------------------------------------------------------------===//
 // BaseDataMemberOp & DerivedDataMemberOp
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp 
b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
index b4890a1b5bf37..ed84ac1458314 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
@@ -71,8 +71,8 @@ void CIRCanonicalizePass::runOnOperation() {
     // Many operations are here to perform a manual `fold` in
     // applyOpPatternsGreedily.
     if (isa<BrOp, BrCondOp, BuiltinIntCastOp, CastOp, ScopeOp, SwitchOp,
-            SelectOp, IncOp, DecOp, MinusOp, FNegOp, NotOp, AddOp, MulOp, 
AndOp,
-            OrOp, XorOp, MaxOp, MinOp, ComplexCreateOp, ComplexImagOp,
+            SelectOp, IncOp, DecOp, MinusOp, FNegOp, NotOp, AddOp, SubOp, 
MulOp,
+            AndOp, OrOp, XorOp, MaxOp, MinOp, ComplexCreateOp, ComplexImagOp,
             ComplexRealOp, VecCmpOp, VecCreateOp, VecExtractOp, VecShuffleOp,
             VecShuffleDynamicOp, VecTernaryOp, BitClrsbOp, BitClzOp, BitCtzOp,
             BitFfsOp, BitParityOp, BitPopcountOp, BitReverseOp, ByteSwapOp,
diff --git a/clang/test/CIR/Transforms/canonicalize.cir 
b/clang/test/CIR/Transforms/canonicalize.cir
index 29b1d880a3748..6ff0da22ccee7 100644
--- a/clang/test/CIR/Transforms/canonicalize.cir
+++ b/clang/test/CIR/Transforms/canonicalize.cir
@@ -323,4 +323,86 @@ module {
   // CHECK-NEXT:   cir.return %[[P]] : !s32i
   // CHECK-NEXT: }
 
+  cir.func @add_int() -> !s32i {
+    %0 = cir.const #cir.int<3> : !s32i
+    %1 = cir.const #cir.int<4> : !s32i
+    %2 = cir.add %0, %1 : !s32i
+    cir.return %2 : !s32i
+  }
+  // CHECK:      @add_int()
+  // CHECK-NEXT:   %[[C:.+]] = cir.const #cir.int<7> : !s32i
+  // CHECK-NEXT:   cir.return %[[C]] : !s32i
+  // CHECK-NEXT: }
+
+  cir.func @sub_int() -> !s32i {
+    %0 = cir.const #cir.int<10> : !s32i
+    %1 = cir.const #cir.int<4> : !s32i
+    %2 = cir.sub %0, %1 : !s32i
+    cir.return %2 : !s32i
+  }
+  // CHECK:      @sub_int()
+  // CHECK-NEXT:   %[[C:.+]] = cir.const #cir.int<6> : !s32i
+  // CHECK-NEXT:   cir.return %[[C]] : !s32i
+  // CHECK-NEXT: }
+
+  // Without wrap flags the result wraps in two's complement.
+  cir.func @add_wrap() -> !s8i {
+    %0 = cir.const #cir.int<127> : !s8i
+    %1 = cir.const #cir.int<1> : !s8i
+    %2 = cir.add %0, %1 : !s8i
+    cir.return %2 : !s8i
+  }
+  // CHECK:      @add_wrap()
+  // CHECK-NEXT:   %[[C:.+]] = cir.const #cir.int<-128> : !s8i
+  // CHECK-NEXT:   cir.return %[[C]] : !s8i
+  // CHECK-NEXT: }
+
+  // nsw overflow folds to poison.
+  cir.func @add_nsw_overflow() -> !s8i {
+    %0 = cir.const #cir.int<127> : !s8i
+    %1 = cir.const #cir.int<1> : !s8i
+    %2 = cir.add nsw %0, %1 : !s8i
+    cir.return %2 : !s8i
+  }
+  // CHECK:      @add_nsw_overflow()
+  // CHECK-NEXT:   %[[P:.+]] = cir.const #cir.poison : !s8i
+  // CHECK-NEXT:   cir.return %[[P]] : !s8i
+  // CHECK-NEXT: }
+
+  // Saturated add clamps to the signed maximum.
+  cir.func @add_sat_signed() -> !s8i {
+    %0 = cir.const #cir.int<127> : !s8i
+    %1 = cir.const #cir.int<10> : !s8i
+    %2 = cir.add sat %0, %1 : !s8i
+    cir.return %2 : !s8i
+  }
+  // CHECK:      @add_sat_signed()
+  // CHECK-NEXT:   %[[C:.+]] = cir.const #cir.int<127> : !s8i
+  // CHECK-NEXT:   cir.return %[[C]] : !s8i
+  // CHECK-NEXT: }
+
+  // A poison operand propagates to the result.
+  cir.func @add_poison() -> !s32i {
+    %0 = cir.const #cir.poison : !s32i
+    %1 = cir.const #cir.int<1> : !s32i
+    %2 = cir.add %0, %1 : !s32i
+    cir.return %2 : !s32i
+  }
+  // CHECK:      @add_poison()
+  // CHECK-NEXT:   %[[P:.+]] = cir.const #cir.poison : !s32i
+  // CHECK-NEXT:   cir.return %[[P]] : !s32i
+  // CHECK-NEXT: }
+
+  // nuw overflow on subtraction folds to poison.
+  cir.func @sub_nuw_overflow() -> !u8i {
+    %0 = cir.const #cir.int<0> : !u8i
+    %1 = cir.const #cir.int<1> : !u8i
+    %2 = cir.sub nuw %0, %1 : !u8i
+    cir.return %2 : !u8i
+  }
+  // CHECK:      @sub_nuw_overflow()
+  // CHECK-NEXT:   %[[P:.+]] = cir.const #cir.poison : !u8i
+  // CHECK-NEXT:   cir.return %[[P]] : !u8i
+  // CHECK-NEXT: }
+
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to