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

>From aeaad545e849f029b8d9c138f0c45524dae29a2e Mon Sep 17 00:00:00 2001
From: Prometheus <[email protected]>
Date: Mon, 24 Aug 2026 05:56:35 -0700
Subject: [PATCH 1/2] [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: }
+
 }

>From cadca305dbbd3d11f2fc09764c1713f8f816abf0 Mon Sep 17 00:00:00 2001
From: Prometheus <[email protected]>
Date: Mon, 24 Aug 2026 23:05:30 -0700
Subject: [PATCH 2/2] fixup! address review comments

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 14 ++++----
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 27 +++++++++------
 clang/test/CIR/IR/invalid-binop.cir          | 36 ++++++++++++++++++++
 3 files changed, 60 insertions(+), 17 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-binop.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2065042d92326..b2d7ebed58a81 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2750,9 +2750,10 @@ def CIR_AddOp
 
     The optional `nsw` (no signed wrap) and `nuw` (no unsigned wrap) unit
     attributes indicate that the result is poison if signed or unsigned
-    overflow occurs, respectively. The optional `sat` (saturated) attribute
-    clamps the result to the type's representable range instead of wrapping.
-    The `nsw`/`nuw` flags and `sat` are mutually exclusive.
+    overflow occurs, respectively. `nsw` requires a signed type, while `nuw`
+    requires an unsigned type. The optional `sat` (saturated) attribute clamps
+    the result to the type's representable range instead of wrapping. The
+    `nsw`, `nuw`, and `sat` flags are mutually exclusive.
 
     Example:
 
@@ -2782,9 +2783,10 @@ def CIR_SubOp
 
     The optional `nsw` (no signed wrap) and `nuw` (no unsigned wrap) unit
     attributes indicate that the result is poison if signed or unsigned
-    overflow occurs, respectively. The optional `sat` (saturated) attribute
-    clamps the result to the type's representable range. The `nsw`/`nuw`
-    flags and `sat` are mutually exclusive.
+    overflow occurs, respectively. `nsw` requires a signed type, while `nuw`
+    requires an unsigned type. The optional `sat` (saturated) attribute clamps
+    the result to the type's representable range. The `nsw`, `nuw`, and `sat`
+    flags are mutually exclusive.
 
     Example:
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index a67c6fa717d43..6dfc44a144119 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2941,22 +2941,27 @@ mlir::LogicalResult cir::FuncOp::verify() {
 // AddOp / SubOp
 
//===----------------------------------------------------------------------===//
 
-// The integer-only type constraint on these ops makes the nsw/nuw/sat flag
-// type checks unnecessary. Only the mutual-exclusivity between nsw/nuw and
-// sat needs to be verified.
+static LogicalResult verifyAddSubFlags(Operation *op, Type type, bool nsw,
+                                       bool nuw, bool sat) {
+  if ((nsw && nuw) || (sat && (nsw || nuw)))
+    return op->emitOpError()
+           << "the nsw, nuw, and saturated flags are mutually exclusive";
+  if (nsw && !cir::isSIntOrVectorOfSIntType(type))
+    return op->emitOpError() << "the nsw flag requires a signed integer type";
+  if (nuw && !cir::isUIntOrVectorOfUIntType(type))
+    return op->emitOpError()
+           << "the nuw flag requires an unsigned integer type";
+  return mlir::success();
+}
 
 LogicalResult cir::AddOp::verify() {
-  if (getSaturated() && (getNoSignedWrap() || getNoUnsignedWrap()))
-    return emitOpError()
-           << "the nsw/nuw flags and the saturated flag are mutually 
exclusive";
-  return mlir::success();
+  return verifyAddSubFlags(getOperation(), getType(), getNoSignedWrap(),
+                           getNoUnsignedWrap(), getSaturated());
 }
 
 LogicalResult cir::SubOp::verify() {
-  if (getSaturated() && (getNoSignedWrap() || getNoUnsignedWrap()))
-    return emitOpError()
-           << "the nsw/nuw flags and the saturated flag are mutually 
exclusive";
-  return mlir::success();
+  return verifyAddSubFlags(getOperation(), getType(), getNoSignedWrap(),
+                           getNoUnsignedWrap(), getSaturated());
 }
 
 
//===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/IR/invalid-binop.cir 
b/clang/test/CIR/IR/invalid-binop.cir
new file mode 100644
index 0000000000000..06e66f9d07a4d
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-binop.cir
@@ -0,0 +1,36 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!s32i = !cir.int<s, 32>
+!vs32i = !cir.vector<4 x !s32i>
+
+module {
+  cir.func @nuw_on_signed(%lhs: !vs32i, %rhs: !vs32i) {
+    // expected-error@below {{the nuw flag requires an unsigned integer type}}
+    %0 = cir.add nuw %lhs, %rhs : !vs32i
+    cir.return
+  }
+}
+
+// -----
+
+!u32i = !cir.int<u, 32>
+
+module {
+  cir.func @nsw_on_unsigned(%lhs: !u32i, %rhs: !u32i) {
+    // expected-error@below {{the nsw flag requires a signed integer type}}
+    %0 = cir.sub nsw %lhs, %rhs : !u32i
+    cir.return
+  }
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+
+module {
+  cir.func @conflicting_wrap_flags(%lhs: !s32i, %rhs: !s32i) {
+    // expected-error@below {{flags are mutually exclusive}}
+    %0 = cir.add nsw nuw %lhs, %rhs : !s32i
+    cir.return
+  }
+}

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

Reply via email to