[llvm] cff226a - [ConstraintElim] Fold sadd.with.overflow with a constant op. (#223239)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 13 08:41:42 PDT 2026
Author: Florian Hahn
Date: 2026-09-13T16:41:38+01:00
New Revision: cff226a5716e4bc3589d4869066ea6687ed142ce
URL: https://github.com/llvm/llvm-project/commit/cff226a5716e4bc3589d4869066ea6687ed142ce
DIFF: https://github.com/llvm/llvm-project/commit/cff226a5716e4bc3589d4869066ea6687ed142ce.diff
LOG: [ConstraintElim] Fold sadd.with.overflow with a constant op. (#223239)
Generalize the overflow-math simplification to also allow simplifying
sadd.with.overflow if the second operand is constant, using
makeGuaranteedNoWrapRegion.
Note that this partially overlaps with the flag strengthening logic.
I'll share a patch soon to try to unify the code more.
No improvements in llvm-opt-benchmarks-nightly. It improves end-to-end
optimizations for Swift (which emits a lot of checked arithmetic) and
also Clang + UBsan.
An end-to-end improvement with overflow intrinsics is
https://llvm.godbolt.org/z/6nrj7aodd.
PR: https://github.com/llvm/llvm-project/pull/223239
Added:
Modified:
llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index ed95686e9e5d9..da2d61e1c6e27 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1544,7 +1544,8 @@ void State::addInfoFor(BasicBlock &BB) {
}
break;
}
- // Enqueue ssub_with_overflow for simplification.
+ // Enqueue intrinsics for simplification.
+ case Intrinsic::sadd_with_overflow:
case Intrinsic::ssub_with_overflow:
case Intrinsic::ucmp:
case Intrinsic::scmp:
@@ -2217,16 +2218,21 @@ void ConstraintInfo::addFactImpl(CmpInst::Predicate Pred, Value *A, Value *B,
}
}
-static bool replaceSubOverflowUses(IntrinsicInst *II, Value *A, Value *B,
- SmallVectorImpl<Instruction *> &ToRemove) {
+/// Replace the uses of the overflow intrinsic \p II, which has been proven not
+/// to signed-overflow, by (Opcode A, B).
+static bool replaceOverflowUses(IntrinsicInst *II,
+ Instruction::BinaryOps Opcode, Value *A,
+ Value *B,
+ SmallVectorImpl<Instruction *> &ToRemove) {
bool Changed = false;
IRBuilder<> Builder(II->getParent(), II->getIterator());
- Value *Sub = nullptr;
+ Value *Res = nullptr;
for (User *U : make_early_inc_range(II->users())) {
if (match(U, m_ExtractValue<0>(m_Value()))) {
- if (!Sub)
- Sub = Builder.CreateNSWSub(A, B);
- U->replaceAllUsesWith(Sub);
+ if (!Res)
+ Res = Builder.CreateNoWrapBinOp(Opcode, A, B, /*IsNUW=*/false,
+ /*IsNSW=*/true);
+ U->replaceAllUsesWith(Res);
Changed = true;
} else if (match(U, m_ExtractValue<1>(m_Value()))) {
U->replaceAllUsesWith(Builder.getFalse());
@@ -2267,8 +2273,8 @@ tryToSimplifyOverflowMath(IntrinsicInst *II, ConstraintInfo &Info,
return CSToUse.isConditionImpliedInSubSystem(R.Coefficients);
};
- bool Changed = false;
- if (II->getIntrinsicID() == Intrinsic::ssub_with_overflow) {
+ switch (II->getIntrinsicID()) {
+ case Intrinsic::ssub_with_overflow: {
// If A s>= B && B s>= 0, ssub.with.overflow(a, b) should not overflow and
// can be simplified to a regular sub.
Value *A = II->getArgOperand(0);
@@ -2277,9 +2283,24 @@ tryToSimplifyOverflowMath(IntrinsicInst *II, ConstraintInfo &Info,
!DoesConditionHold(CmpInst::ICMP_SGE, B,
ConstantInt::get(A->getType(), 0), Info))
return false;
- Changed = replaceSubOverflowUses(II, A, B, ToRemove);
+ return replaceOverflowUses(II, Instruction::Sub, A, B, ToRemove);
+ }
+ case Intrinsic::sadd_with_overflow: {
+ Value *A = II->getArgOperand(0);
+ Value *B = II->getArgOperand(1);
+ auto *C = dyn_cast<ConstantInt>(B);
+ if (!C ||
+ !doesHoldInRange(Info, A,
+ ConstantRange::makeGuaranteedNoWrapRegion(
+ Instruction::Add, ConstantRange(C->getValue()),
+ OverflowingBinaryOperator::NoSignedWrap),
+ /*Signed=*/true))
+ return false;
+ return replaceOverflowUses(II, Instruction::Add, A, B, ToRemove);
+ }
+ default:
+ return false;
}
- return Changed;
}
static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
diff --git a/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll b/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
index 7dbbf29b5a9d3..4194797fa5ea0 100644
--- a/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
+++ b/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
@@ -2,6 +2,7 @@
; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s
declare void @use(i1)
+declare void @use.agg({ i8, i1 })
; A is in [0, 100), so A + 1 does not signed-overflow (A s<= SMAX - 1).
define i8 @sadd_no_overflow_pos_const(i8 %a) {
@@ -13,10 +14,8 @@ define i8 @sadd_no_overflow_pos_const(i8 %a) {
; CHECK-NEXT: [[OK:%.*]] = and i1 [[LO]], [[HI]]
; CHECK-NEXT: br i1 [[OK]], label %[[THEN:.*]], label %[[ELSE:.*]]
; CHECK: [[THEN]]:
-; CHECK-NEXT: [[S:%.*]] = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 [[A]], i8 1)
-; CHECK-NEXT: [[V:%.*]] = extractvalue { i8, i1 } [[S]], 0
-; CHECK-NEXT: [[O:%.*]] = extractvalue { i8, i1 } [[S]], 1
-; CHECK-NEXT: call void @use(i1 [[O]])
+; CHECK-NEXT: [[V:%.*]] = add nsw i8 [[A]], 1
+; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: ret i8 [[V]]
; CHECK: [[ELSE]]:
; CHECK-NEXT: ret i8 0
@@ -48,10 +47,8 @@ define i8 @sadd_no_overflow_neg_const(i8 %a) {
; CHECK-NEXT: [[OK:%.*]] = and i1 [[LO]], [[HI]]
; CHECK-NEXT: br i1 [[OK]], label %[[THEN:.*]], label %[[ELSE:.*]]
; CHECK: [[THEN]]:
-; CHECK-NEXT: [[S:%.*]] = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 [[A]], i8 -1)
-; CHECK-NEXT: [[V:%.*]] = extractvalue { i8, i1 } [[S]], 0
-; CHECK-NEXT: [[O:%.*]] = extractvalue { i8, i1 } [[S]], 1
-; CHECK-NEXT: call void @use(i1 [[O]])
+; CHECK-NEXT: [[V:%.*]] = add nsw i8 [[A]], -1
+; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: ret i8 [[V]]
; CHECK: [[ELSE]]:
; CHECK-NEXT: ret i8 0
@@ -103,3 +100,221 @@ then:
else:
ret i8 0
}
+
+; C == SMIN, so A + C does not signed-overflow iff A s>= 0.
+define i8 @sadd_c_smin(i8 %a) {
+; CHECK-LABEL: define i8 @sadd_c_smin(
+; CHECK-SAME: i8 [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[LO:%.*]] = icmp sge i8 [[A]], 0
+; CHECK-NEXT: br i1 [[LO]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[V:%.*]] = add nsw i8 [[A]], -128
+; CHECK-NEXT: call void @use(i1 false)
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %lo = icmp sge i8 %a, 0
+ br i1 %lo, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 -128)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
+
+; C == SMAX, so A + C does not signed-overflow iff A s<= 0.
+define i8 @sadd_c_smax(i8 %a) {
+; CHECK-LABEL: define i8 @sadd_c_smax(
+; CHECK-SAME: i8 [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[HI:%.*]] = icmp sle i8 [[A]], 0
+; CHECK-NEXT: br i1 [[HI]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[V:%.*]] = add nsw i8 [[A]], 127
+; CHECK-NEXT: call void @use(i1 false)
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %hi = icmp sle i8 %a, 0
+ br i1 %hi, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 127)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
+
+; The upper bound is one too large.
+define i8 @sadd_pos_const_bound_too_large(i8 %a) {
+; CHECK-LABEL: define i8 @sadd_pos_const_bound_too_large(
+; CHECK-SAME: i8 [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[HI:%.*]] = icmp sle i8 [[A]], 127
+; CHECK-NEXT: br i1 [[HI]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[S:%.*]] = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 [[A]], i8 1)
+; CHECK-NEXT: [[V:%.*]] = extractvalue { i8, i1 } [[S]], 0
+; CHECK-NEXT: [[O:%.*]] = extractvalue { i8, i1 } [[S]], 1
+; CHECK-NEXT: call void @use(i1 [[O]])
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %hi = icmp sle i8 %a, 127
+ br i1 %hi, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 1)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
+
+; The lower bound is one too small.
+define i8 @sadd_neg_const_bound_too_small(i8 %a) {
+; CHECK-LABEL: define i8 @sadd_neg_const_bound_too_small(
+; CHECK-SAME: i8 [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[LO:%.*]] = icmp sge i8 [[A]], -128
+; CHECK-NEXT: br i1 [[LO]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[S:%.*]] = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 [[A]], i8 -1)
+; CHECK-NEXT: [[V:%.*]] = extractvalue { i8, i1 } [[S]], 0
+; CHECK-NEXT: [[O:%.*]] = extractvalue { i8, i1 } [[S]], 1
+; CHECK-NEXT: call void @use(i1 [[O]])
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %lo = icmp sge i8 %a, -128
+ br i1 %lo, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 -1)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
+
+define i8 @sadd_unsigned_bound(i8 %a) {
+; CHECK-LABEL: define i8 @sadd_unsigned_bound(
+; CHECK-SAME: i8 [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[HI:%.*]] = icmp ult i8 [[A]], 100
+; CHECK-NEXT: br i1 [[HI]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[V:%.*]] = add nsw i8 [[A]], 1
+; CHECK-NEXT: call void @use(i1 false)
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %hi = icmp ult i8 %a, 100
+ br i1 %hi, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 1)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
+
+; The second operand is not a constant.
+define i8 @sadd_variable_ops(i8 %a, i8 %b) {
+; CHECK-LABEL: define i8 @sadd_variable_ops(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[LO:%.*]] = icmp sge i8 [[A]], 0
+; CHECK-NEXT: [[HI:%.*]] = icmp slt i8 [[A]], 100
+; CHECK-NEXT: [[OK:%.*]] = and i1 [[LO]], [[HI]]
+; CHECK-NEXT: br i1 [[OK]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[S:%.*]] = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 [[A]], i8 [[B]])
+; CHECK-NEXT: [[V:%.*]] = extractvalue { i8, i1 } [[S]], 0
+; CHECK-NEXT: [[O:%.*]] = extractvalue { i8, i1 } [[S]], 1
+; CHECK-NEXT: call void @use(i1 [[O]])
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %lo = icmp sge i8 %a, 0
+ %hi = icmp slt i8 %a, 100
+ %ok = and i1 %lo, %hi
+ br i1 %ok, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 %b)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
+
+define i8 @sadd_aggregate_escapes(i8 %a) {
+; CHECK-LABEL: define i8 @sadd_aggregate_escapes(
+; CHECK-SAME: i8 [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[LO:%.*]] = icmp sge i8 [[A]], 0
+; CHECK-NEXT: [[HI:%.*]] = icmp slt i8 [[A]], 100
+; CHECK-NEXT: [[OK:%.*]] = and i1 [[LO]], [[HI]]
+; CHECK-NEXT: br i1 [[OK]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: [[V:%.*]] = add nsw i8 [[A]], 1
+; CHECK-NEXT: [[S:%.*]] = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 [[A]], i8 1)
+; CHECK-NEXT: call void @use.agg({ i8, i1 } [[S]])
+; CHECK-NEXT: call void @use(i1 false)
+; CHECK-NEXT: ret i8 [[V]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i8 0
+;
+entry:
+ %lo = icmp sge i8 %a, 0
+ %hi = icmp slt i8 %a, 100
+ %ok = and i1 %lo, %hi
+ br i1 %ok, label %then, label %else
+
+then:
+ %s = call { i8, i1 } @llvm.sadd.with.overflow.i8(i8 %a, i8 1)
+ %v = extractvalue { i8, i1 } %s, 0
+ %o = extractvalue { i8, i1 } %s, 1
+ call void @use.agg({ i8, i1 } %s)
+ call void @use(i1 %o)
+ ret i8 %v
+
+else:
+ ret i8 0
+}
More information about the llvm-commits
mailing list