[llvm] [InstCombine] Add combines for unsigned comparison of absolute value to constant (PR #172021)
Nathan Corbyn via llvm-commits
llvm-commits at lists.llvm.org
Fri Dec 12 07:32:24 PST 2025
https://github.com/cofibrant created https://github.com/llvm/llvm-project/pull/172021
This patch implements the following two peephole optimisations:
1. ``` abs(X) u> K --> K >= 0 ? `X + K u> 2 * K` : `false` ```;
2. If `abs(INT_MIN)` is `poison`, ```abs(X) u< K --> K >= 1 ? `X + (K - 1) u<= 2 * (K - 1)` : K != 0```.
In practice, the result of `abs(X)` is typically frozen, in which case, for the transformation to be correct, the results of the adds must be frozen accordingly.
See the the following Alive2 proofs:
1. [1 with `abs(X)` unfrozen and `abs(INT_MIN) == INT_MIN`](https://alive2.llvm.org/ce/z/J2SRSv);
2. [1 with `abs(X)` unfrozen and `abs(INT_MIN) == poison`](https://alive2.llvm.org/ce/z/K5qS59);
3. [1 with `abs(X)` frozen `abs(INT_MIN) == INT_MIN`](https://alive2.llvm.org/ce/z/-44XsX);
4. [1 with `abs(X)` frozen `abs(INT_MIN) == poison`](https://alive2.llvm.org/ce/z/GqPcAH);
5. [2 with `abs(X)` unfrozen](https://alive2.llvm.org/ce/z/tfxTrU);
6. [2 with `abs(X)` frozen](https://alive2.llvm.org/ce/z/S69UaS).
A similar transformation to 2 is possible even in cases when `abs(INT_MIN)` is `INT_MIN`, but not profitable in practice (see [Godbolt](https://alive2.llvm.org/ce/z/hN4-Vk)). We deliberately exclude this case.
>From b2ebc43a7b7f06bc39a281d46d21fe0bd50066a6 Mon Sep 17 00:00:00 2001
From: Nathan Corbyn <n_corbyn at apple.com>
Date: Thu, 11 Dec 2025 14:20:54 +0000
Subject: [PATCH] [InstCombine] Add combines for unsigned comparison of
absolute value to constant
---
.../InstCombine/InstCombineCompares.cpp | 45 +++++++++
.../Transforms/InstCombine/abs-intrinsic.ll | 96 +++++++++++++++++++
2 files changed, 141 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 1859dad4ec00b..1a3355cb42edb 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -7602,6 +7602,51 @@ Instruction *InstCombinerImpl::foldICmpCommutative(CmpPredicate Pred,
}
}
+ // abs(X) u> K --> K >= 0 ? `X + K u> 2 * K` : `false`
+ // If abs(INT_MIN) is poison:
+ // abs(X) u< K --> K >= 1 ? `X + (K - 1) u<= 2 * (K - 1)` : K != 0
+ {
+ Value *X;
+ ConstantInt *C, *K;
+ bool Frozen = false;
+ if ((match(Op0,
+ m_Intrinsic<Intrinsic::abs>(m_Value(X), m_ConstantInt(C))) ||
+ (Frozen = match(Op0, m_Freeze(m_Intrinsic<Intrinsic::abs>(
+ m_Value(X), m_ConstantInt(C)))))) &&
+ match(Op1, m_ConstantInt(K))) {
+ const APInt KValue = K->getValue();
+
+ if (Pred == CmpInst::ICMP_UGT) {
+ if (KValue.isNegative())
+ return replaceInstUsesWith(CxtI,
+ ConstantInt::getFalse(CxtI.getType()));
+
+ Value *XPlusK = Builder.CreateAdd(X, K);
+ if (Frozen)
+ XPlusK = Builder.CreateFreeze(XPlusK);
+ return replaceInstUsesWith(
+ CxtI, Builder.CreateICmpUGT(
+ XPlusK, ConstantInt::get(K->getType(), 2 * KValue)));
+ }
+
+ if (Pred == CmpInst::ICMP_ULT && !C->getValue().isZero()) {
+ if (KValue.slt(1))
+ return replaceInstUsesWith(
+ CxtI, KValue.isZero() ? ConstantInt::getFalse(CxtI.getType())
+ : ConstantInt::getTrue(CxtI.getType()));
+
+ Value *XPlusKDec =
+ Builder.CreateAdd(X, ConstantInt::get(K->getType(), KValue - 1));
+ if (Frozen)
+ XPlusKDec = Builder.CreateFreeze(XPlusKDec);
+ return replaceInstUsesWith(
+ CxtI,
+ Builder.CreateICmpULE(
+ XPlusKDec, ConstantInt::get(K->getType(), 2 * (KValue - 1))));
+ }
+ }
+ }
+
const SimplifyQuery Q = SQ.getWithInstruction(&CxtI);
if (Value *V = foldICmpWithLowBitMaskedVal(Pred, Op0, Op1, Q, *this))
return replaceInstUsesWith(CxtI, V);
diff --git a/llvm/test/Transforms/InstCombine/abs-intrinsic.ll b/llvm/test/Transforms/InstCombine/abs-intrinsic.ll
index 763d82652dd5d..7355a7a71dada 100644
--- a/llvm/test/Transforms/InstCombine/abs-intrinsic.ll
+++ b/llvm/test/Transforms/InstCombine/abs-intrinsic.ll
@@ -997,3 +997,99 @@ define i8 @abs_diff_sle_y_x(i8 %x, i8 %y) {
%cond = select i1 %cmp, i8 %sub, i8 %sub1
ret i8 %cond
}
+
+define i1 @abs_cmp_ule_no_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ule_no_poison(
+; CHECK-NEXT: [[X_ABS:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[X_ABS]], 32
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 0)
+ %cmp = icmp ule i32 %x.abs, 31
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_ule_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ule_poison(
+; CHECK-NEXT: [[X_ABS_FREEZE:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X_ABS_FREEZE]], 31
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[TMP1]], 63
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+ %x.abs.freeze = freeze i32 %x.abs
+ %cmp = icmp ule i32 %x.abs.freeze, 31
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_ult_no_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ult_no_poison(
+; CHECK-NEXT: [[X_ABS:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[X_ABS]], 32
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 0)
+ %cmp = icmp ult i32 %x.abs, 32
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_ult_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ult_poison(
+; CHECK-NEXT: [[X_FR:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X_FR]], 31
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[TMP1]], 63
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+ %x.abs.freeze = freeze i32 %x.abs
+ %cmp = icmp ult i32 %x.abs.freeze, 32
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_uge_no_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_uge_no_poison(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -31
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[TMP1]], -61
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 0)
+ %cmp = icmp ugt i32 %x.abs, 30
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_uge_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_uge_poison(
+; CHECK-NEXT: [[X_FR:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X_FR]], -31
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[TMP1]], -61
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+ %x.abs.freeze = freeze i32 %x.abs
+ %cmp = icmp ugt i32 %x.abs.freeze, 30
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_ugt_no_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ugt_no_poison(
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X:%.*]], -32
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[TMP1]], -63
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 0)
+ %cmp = icmp ugt i32 %x.abs, 31
+ ret i1 %cmp
+}
+
+define i1 @abs_cmp_ugt_poison(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ugt_poison(
+; CHECK-NEXT: [[X:%.*]] = freeze i32 [[X1:%.*]]
+; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[X]], -32
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[TMP1]], -63
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+ %x.abs.freeze = freeze i32 %x.abs
+ %cmp = icmp ugt i32 %x.abs.freeze, 31
+ ret i1 %cmp
+}
More information about the llvm-commits
mailing list