[clang] [CIR] Fold constant add and sub operations (PR #218398)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 24 05:59:10 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: prometheusfma-llvm
<details>
<summary>Changes</summary>
Fold scalar integer operands while honoring overflow flags, saturated arithmetic, and poison propagation. Add canonicalization coverage for the new folds.
---
Full diff: https://github.com/llvm/llvm-project/pull/218398.diff
4 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+4)
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+65)
- (modified) clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp (+2-2)
- (modified) clang/test/CIR/Transforms/canonicalize.cir (+82)
``````````diff
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: }
+
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/218398
More information about the cfe-commits
mailing list