Skip to content

Conversation

@cofibrant
Copy link
Contributor

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;
  2. 1 with abs(X) unfrozen and abs(INT_MIN) == poison;
  3. 1 with abs(X) frozen abs(INT_MIN) == INT_MIN;
  4. 1 with abs(X) frozen abs(INT_MIN) == poison;
  5. 2 with abs(X) unfrozen;
  6. 2 with abs(X) frozen.

A similar transformation to 2 is possible even in cases when abs(INT_MIN) is INT_MIN, but not profitable in practice (see Godbolt). We deliberately exclude this case.

@cofibrant cofibrant requested a review from fhahn December 12, 2025 15:32
@cofibrant cofibrant requested a review from nikic as a code owner December 12, 2025 15:32
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Dec 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 12, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Nathan Corbyn (cofibrant)

Changes

This patch implements the following two peephole optimisations:

  1. abs(X) u&gt; K --&gt; K &gt;= 0 ? `X + K u&gt; 2 * K` : `false`;
  2. If abs(INT_MIN) is poison, abs(X) u&lt; K --&gt; K &gt;= 1 ? `X + (K - 1) u&lt;= 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;
  2. 1 with abs(X) unfrozen and abs(INT_MIN) == poison;
  3. 1 with abs(X) frozen abs(INT_MIN) == INT_MIN;
  4. 1 with abs(X) frozen abs(INT_MIN) == poison;
  5. 2 with abs(X) unfrozen;
  6. 2 with abs(X) frozen.

A similar transformation to 2 is possible even in cases when abs(INT_MIN) is INT_MIN, but not profitable in practice (see Godbolt). We deliberately exclude this case.


Full diff: https://github.com/llvm/llvm-project/pull/172021.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+45)
  • (modified) llvm/test/Transforms/InstCombine/abs-intrinsic.ll (+96)
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
+}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants