Skip to content

[InstCombine] Add combines for unsigned comparison of absolute value to constant#176148

Merged
dtcxzyw merged 3 commits intollvm:mainfrom
cofibrant:users/cofibrant/abs-inst-combine
Jan 28, 2026
Merged

[InstCombine] Add combines for unsigned comparison of absolute value to constant#176148
dtcxzyw merged 3 commits intollvm:mainfrom
cofibrant:users/cofibrant/abs-inst-combine

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.

See the following Alive2 proofs: 1 and 2.

@cofibrant cofibrant requested a review from nikic as a code owner January 15, 2026 12:40
@cofibrant cofibrant requested a review from dtcxzyw January 15, 2026 12:40
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Jan 15, 2026
@cofibrant cofibrant requested a review from fhahn January 15, 2026 12:40
@llvmbot
Copy link
Member

llvmbot commented Jan 15, 2026

@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.

See the following Alive2 proofs: 1 and 2.


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+34)
  • (modified) llvm/test/Transforms/InstCombine/abs-intrinsic.ll (+208)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index d536cb752d562..e511099302aaa 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -4288,6 +4288,40 @@ Instruction *InstCombinerImpl::foldICmpIntrinsicWithConstant(ICmpInst &Cmp,
                             II->getArgOperand(1));
     }
     break;
+  case Intrinsic::abs: {
+    if (!Cmp.getOperand(0)->hasOneUse())
+      return nullptr;
+
+    Value *X = II->getArgOperand(0);
+    APInt IntMinIsPoison =
+        dyn_cast<ConstantInt>(II->getArgOperand(1))->getValue();
+
+    // abs(X) u> K --> K >= 0 ? `X + K u> 2 * K` : `false`
+    if (Pred == CmpInst::ICMP_UGT) {
+      if (C.isNegative()) {
+        Cmp.replaceAllUsesWith(ConstantInt::getFalse(Ty));
+        return nullptr;
+      }
+
+      return new ICmpInst(ICmpInst::ICMP_UGT,
+                          Builder.CreateAdd(X, ConstantInt::get(Ty, C)),
+                          ConstantInt::get(Ty, 2 * C));
+    }
+
+    // abs(X) u< K --> K >= 1 ? `X + (K - 1) u<= 2 * (K - 1)` : K != 0
+    if (Pred == CmpInst::ICMP_ULT) {
+      if (C.slt(1)) {
+        Cmp.replaceAllUsesWith(ConstantInt::getBool(Ty, !C.isZero()));
+        return nullptr;
+      }
+
+      return new ICmpInst(ICmpInst::ICMP_ULE,
+                          Builder.CreateAdd(X, ConstantInt::get(Ty, C - 1)),
+                          ConstantInt::get(Ty, 2 * (C - 1)));
+    }
+
+    break;
+  }
   default:
     break;
   }
diff --git a/llvm/test/Transforms/InstCombine/abs-intrinsic.ll b/llvm/test/Transforms/InstCombine/abs-intrinsic.ll
index 763d82652dd5d..aaac71d5a002a 100644
--- a/llvm/test/Transforms/InstCombine/abs-intrinsic.ll
+++ b/llvm/test/Transforms/InstCombine/abs-intrinsic.ll
@@ -997,3 +997,211 @@ 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:    [[TMP1:%.*]] = add i32 [[X:%.*]], 31
+; 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 ule i32 %x.abs, 31
+  ret i1 %cmp
+}
+
+define i1 @abs_cmp_ule_no_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ule_no_poison_multiuse(
+; CHECK-NEXT:    [[X_ABS:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS]])
+; 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)
+  call void @use(i32 %x.abs)
+  %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_ule_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ule_poison_multiuse(
+; CHECK-NEXT:    [[X_FR:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT:    [[X_ABS_FREEZE:%.*]] = call i32 @llvm.abs.i32(i32 [[X_FR]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS_FREEZE]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X_ABS_FREEZE]], 32
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+  %x.abs.freeze = freeze i32 %x.abs
+  call void @use(i32 %x.abs.freeze)
+  %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:    [[TMP1:%.*]] = add i32 [[X:%.*]], 31
+; 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 ult i32 %x.abs, 32
+  ret i1 %cmp
+}
+
+define i1 @abs_cmp_ult_no_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ult_no_poison_multiuse(
+; CHECK-NEXT:    [[X_ABS:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS]])
+; 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)
+  call void @use(i32 %x.abs)
+  %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_ult_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ult_poison_multiuse(
+; CHECK-NEXT:    [[X_FR:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT:    [[X_ABS_FREEZE:%.*]] = call i32 @llvm.abs.i32(i32 [[X_FR]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS_FREEZE]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X_ABS_FREEZE]], 32
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+  %x.abs.freeze = freeze i32 %x.abs
+  call void @use(i32 %x.abs.freeze)
+  %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_no_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_uge_no_poison_multiuse(
+; CHECK-NEXT:    [[X_ABS:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X_ABS]], 30
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 0)
+  call void @use(i32 %x.abs)
+  %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_uge_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_uge_poison_multiuse(
+; CHECK-NEXT:    [[X_FR:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT:    [[X_ABS_FREEZE:%.*]] = call i32 @llvm.abs.i32(i32 [[X_FR]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS_FREEZE]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X_ABS_FREEZE]], 30
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+  %x.abs.freeze = freeze i32 %x.abs
+  call void @use(i32 %x.abs.freeze)
+  %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_no_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ugt_no_poison_multiuse(
+; CHECK-NEXT:    [[X_ABS:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X_ABS]], 31
+; CHECK-NEXT:    call void @use(i32 [[X_ABS]])
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 0)
+  %cmp = icmp ugt i32 %x.abs, 31
+  call void @use(i32 %x.abs)
+  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
+}
+
+define i1 @abs_cmp_ugt_poison_multiuse(i32 %x) {
+; CHECK-LABEL: @abs_cmp_ugt_poison_multiuse(
+; CHECK-NEXT:    [[X_FR:%.*]] = freeze i32 [[X:%.*]]
+; CHECK-NEXT:    [[X_ABS_FREEZE:%.*]] = call i32 @llvm.abs.i32(i32 [[X_FR]], i1 false)
+; CHECK-NEXT:    call void @use(i32 [[X_ABS_FREEZE]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X_ABS_FREEZE]], 31
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %x.abs = call i32 @llvm.abs.i32(i32 %x, i1 1)
+  %x.abs.freeze = freeze i32 %x.abs
+  call void @use(i32 %x.abs.freeze)
+  %cmp = icmp ugt i32 %x.abs.freeze, 31
+  ret i1 %cmp
+}

@cofibrant cofibrant force-pushed the users/cofibrant/abs-inst-combine branch from 16ee280 to 23759cc Compare January 19, 2026 10:01
@github-actions
Copy link

github-actions bot commented Jan 19, 2026

🐧 Linux x64 Test Results

  • 188657 tests passed
  • 5005 tests skipped

✅ The build succeeded and all tests passed.

@cofibrant cofibrant force-pushed the users/cofibrant/abs-inst-combine branch from 23759cc to de7fbd2 Compare January 19, 2026 14:12
@cofibrant
Copy link
Contributor Author

Ping

Copy link
Contributor

@fhahn fhahn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG

@dtcxzyw dtcxzyw merged commit efe7562 into llvm:main Jan 28, 2026
11 checks passed
@cofibrant cofibrant deleted the users/cofibrant/abs-inst-combine branch January 29, 2026 07:51
honeygoyal pushed a commit to honeygoyal/llvm-project that referenced this pull request Jan 30, 2026
…to constant (llvm#176148)

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```.

See the following Alive2 proofs:
[1](https://alive2.llvm.org/ce/z/J2SRSv) and
[2](https://alive2.llvm.org/ce/z/tfxTrU).
SavchenkoValeriy pushed a commit to swiftlang/llvm-project that referenced this pull request Feb 2, 2026
…to constant (llvm#176148)

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```.

See the following Alive2 proofs:
[1](https://alive2.llvm.org/ce/z/J2SRSv) and
[2](https://alive2.llvm.org/ce/z/tfxTrU).
sshrestha-aa pushed a commit to sshrestha-aa/llvm-project that referenced this pull request Feb 4, 2026
…to constant (llvm#176148)

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```.

See the following Alive2 proofs:
[1](https://alive2.llvm.org/ce/z/J2SRSv) and
[2](https://alive2.llvm.org/ce/z/tfxTrU).
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.

5 participants