[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