[clang] [CIR] Fold constant add and sub operations (PR #218398)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 24 23:12:59 PDT 2026
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 <prometheus.f.ma at gmail.com>
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 <prometheus.f.ma at gmail.com>
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 at 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 at 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 at below {{flags are mutually exclusive}}
+ %0 = cir.add nsw nuw %lhs, %rhs : !s32i
+ cir.return
+ }
+}
More information about the cfe-commits
mailing list