[llvm] [InstCombine] Optimize icmp(sub(a, c), sub(b, c)) to icmp(a, b) if a, b, and c are pointers (PR #161698)

Ryan Buchner via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 2 09:53:41 PDT 2025


https://github.com/bababuck created https://github.com/llvm/llvm-project/pull/161698

 icmp (A-B), (C-B) -> icmp A, C for comparisons of pointer subtraction, that is, if A, B and C are all ptrs converted to integers.

A couple of things I need a little help understanding:
- A little unclear to me about the implications of `ptrtoint` vs `ptrtoaddr` on this optimization. See #159419.
- Alive proof has some issues with `undef` that I don't quite understand, https://alive2.llvm.org/ce/z/AdZoWJ
    - Particularly `%p2 = ptrtoint ptr %2 to i2` is solved different, in `Src`, `i2 %p2 = #x3 (3, -1)`, while in `Tgt`: `i2 %p2 = #x0 (0)`

This case has to be specially handled due to C language semantics for pointers.
```
If an array is so large (greater than PTRDIFF_MAX elements, but less than SIZE_MAX bytes),
that the difference between two pointers may not be representable as std::ptrdiff_t, the
result of subtracting two such pointers is undefined.
```
Due to this definition, pointer subtraction can be treated as NSW in this scenario. However, the comparison must be made to be unsigned because pointer comparisons are unsigned, even though the result of pointer subtraction is signed.

This change allows for the following optimization (Seen in  `502.GCC` from Spec2017. GCC makes this optimization, but LLVM previously failed to.)
```c
int8_t* foo(int8_t* a, int8_t* b, int8_t* c) {
  a = a + 1;
  if ((a - b) > (c - b))
    a = c;
  return a;
}
```

Origionally lowers to:
```
  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
  %5 = ptrtoint ptr %4 to i64
  %6 = ptrtoint ptr %1 to i64
  %7 = sub i64 %5, %6
  %8 = ptrtoint ptr %2 to i64
  %9 = sub i64 %8, %6
  %10 = icmp sgt i64 %7, %9
  %11 = select i1 %10, ptr %2, ptr %4
  ret ptr %11
```

Now lowers to:
```
  %add.ptr = getelementptr inbounds nuw i8, ptr %a, i64 1
  %cmp = icmp ugt ptr %add.ptr, %c
  %spec.select = select i1 %cmp, ptr %c, ptr %add.ptr
  ret ptr %spec.select
```



>From f400d442a261e8a781f5cfc51cb5fae1a8b36de3 Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Mon, 25 Aug 2025 16:42:39 -0700
Subject: [PATCH 1/2] [InstCombine] Add tests for checking optimization of icmp
 of ptrdiff_t

---
 .../Transforms/InstCombine/icmp-ptrdiff.ll    | 129 ++++++++++++++++++
 1 file changed, 129 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll

diff --git a/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll b/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll
new file mode 100644
index 0000000000000..5155c9ec956b5
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll
@@ -0,0 +1,129 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define ptr @icmp_ptrdiff_gt(ptr %0, ptr %1, ptr %2) {
+; CHECK-LABEL: @icmp_ptrdiff_gt(
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
+; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
+; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
+; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
+; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
+; CHECK-NEXT:    [[TMP10:%.*]] = icmp sgt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[TMP10]], ptr [[TMP2]], ptr [[TMP4]]
+; CHECK-NEXT:    ret ptr [[TMP11]]
+;
+  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
+  %5 = ptrtoint ptr %4 to i64
+  %6 = ptrtoint ptr %1 to i64
+  %7 = sub i64 %5, %6
+  %8 = ptrtoint ptr %2 to i64
+  %9 = sub i64 %8, %6
+  %10 = icmp sgt i64 %7, %9
+  %11 = select i1 %10, ptr %2, ptr %4
+  ret ptr %11
+}
+
+define ptr @icmp_ptrdiff_lt(ptr %0, ptr %1, ptr %2) {
+; CHECK-LABEL: @icmp_ptrdiff_lt(
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
+; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
+; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
+; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
+; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
+; CHECK-NEXT:    [[TMP10:%.*]] = icmp slt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[TMP10]], ptr [[TMP2]], ptr [[TMP4]]
+; CHECK-NEXT:    ret ptr [[TMP11]]
+;
+  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
+  %5 = ptrtoint ptr %4 to i64
+  %6 = ptrtoint ptr %1 to i64
+  %7 = sub i64 %5, %6
+  %8 = ptrtoint ptr %2 to i64
+  %9 = sub i64 %8, %6
+  %10 = icmp slt i64 %7, %9
+  %11 = select i1 %10, ptr %2, ptr %4
+  ret ptr %11
+}
+
+define ptr @icmp_ptrdiff_ge(ptr %0, ptr %1, ptr %2) {
+; CHECK-LABEL: @icmp_ptrdiff_ge(
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
+; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
+; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
+; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
+; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
+; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp slt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[TMP10:%.*]] = select i1 [[DOTNOT]], ptr [[TMP4]], ptr [[TMP2]]
+; CHECK-NEXT:    ret ptr [[TMP10]]
+;
+  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
+  %5 = ptrtoint ptr %4 to i64
+  %6 = ptrtoint ptr %1 to i64
+  %7 = sub i64 %5, %6
+  %8 = ptrtoint ptr %2 to i64
+  %9 = sub i64 %8, %6
+  %10 = icmp sge i64 %7, %9
+  %11 = select i1 %10, ptr %2, ptr %4
+  ret ptr %11
+}
+
+define ptr @icmp_ptrdiff_le(ptr %0, ptr %1, ptr %2) {
+; CHECK-LABEL: @icmp_ptrdiff_le(
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
+; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
+; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
+; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
+; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
+; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp sgt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[TMP10:%.*]] = select i1 [[DOTNOT]], ptr [[TMP4]], ptr [[TMP2]]
+; CHECK-NEXT:    ret ptr [[TMP10]]
+;
+  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
+  %5 = ptrtoint ptr %4 to i64
+  %6 = ptrtoint ptr %1 to i64
+  %7 = sub i64 %5, %6
+  %8 = ptrtoint ptr %2 to i64
+  %9 = sub i64 %8, %6
+  %10 = icmp sle i64 %7, %9
+  %11 = select i1 %10, ptr %2, ptr %4
+  ret ptr %11
+}
+
+define ptr @icmp_ptrdiff_eq(ptr %0, ptr %1, ptr %2) {
+; CHECK-LABEL: @icmp_ptrdiff_eq(
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
+; CHECK-NEXT:    ret ptr [[TMP4]]
+;
+  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
+  %5 = ptrtoint ptr %4 to i64
+  %6 = ptrtoint ptr %1 to i64
+  %7 = sub i64 %5, %6
+  %8 = ptrtoint ptr %2 to i64
+  %9 = sub i64 %8, %6
+  %10 = icmp eq i64 %7, %9
+  %11 = select i1 %10, ptr %2, ptr %4
+  ret ptr %11
+}
+
+define ptr @icmp_ptrdiff_ne(ptr %0, ptr %1, ptr %2) {
+; CHECK-LABEL: @icmp_ptrdiff_ne(
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
+; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp eq ptr [[TMP4]], [[TMP2:%.*]]
+; CHECK-NEXT:    [[TMP5:%.*]] = select i1 [[DOTNOT]], ptr [[TMP4]], ptr [[TMP2]]
+; CHECK-NEXT:    ret ptr [[TMP5]]
+;
+  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
+  %5 = ptrtoint ptr %4 to i64
+  %6 = ptrtoint ptr %1 to i64
+  %7 = sub i64 %5, %6
+  %8 = ptrtoint ptr %2 to i64
+  %9 = sub i64 %8, %6
+  %10 = icmp ne i64 %7, %9
+  %11 = select i1 %10, ptr %2, ptr %4
+  ret ptr %11
+}
+

>From b88ef305f01fd2d5ac6bbf073a97ad89cc194abf Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Mon, 25 Aug 2025 15:49:06 -0700
Subject: [PATCH 2/2] [InstCombine] Optimize icmp(sub(a, c), sub(b, c)) to
 icmp(a, b) if a, b, and c are pointers

Has to be specially handled due to C langauage semantics for pointers.
```
If an array is so large (greater than PTRDIFF_MAX elements, but less than
SIZE_MAX bytes), that the difference between two pointers may not be
representable as std::ptrdiff_t, the result of subtracting two such pointers
is undefined.
```
Due to this definition, pointer substraction can be treated as NSW in this
scenario. However, the comparison must be made to be unsigned because
pointer comparisons are unsigned, even though the result of pointer
substraction is signed.

This change allows for the following optimization:
```c
int8_t* foo(int8_t* a, int8_t* b, int8_t* c) {
  a = a + 1;
  if ((a - b) > (c - b))
    a = c;
  return a;
}
```

Origionally lowers to:
```
  %4 = getelementptr inbounds nuw i8, ptr %0, i64 1
  %5 = ptrtoint ptr %4 to i64
  %6 = ptrtoint ptr %1 to i64
  %7 = sub i64 %5, %6
  %8 = ptrtoint ptr %2 to i64
  %9 = sub i64 %8, %6
  %10 = icmp sgt i64 %7, %9
  %11 = select i1 %10, ptr %2, ptr %4
  ret ptr %11
```

Now lowers to:
```
  %add.ptr = getelementptr inbounds nuw i8, ptr %a, i64 1
  %cmp = icmp ugt ptr %add.ptr, %c
  %spec.select = select i1 %cmp, ptr %c, ptr %add.ptr
  ret ptr %spec.select
```
---
 .../InstCombine/InstCombineCompares.cpp       | 39 +++++++++++++++++++
 .../Transforms/InstCombine/icmp-ptrdiff.ll    | 28 ++-----------
 2 files changed, 43 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index e4cb457499ef5..9d53acbe6d46a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5416,6 +5416,45 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
   if (B && D && B == D && NoOp0WrapProblem && NoOp1WrapProblem)
     return new ICmpInst(Pred, A, C);
 
+  // icmp (A-B), (C-B) -> icmp A, C for comparisons of pointer subtraction
+  // that is, if A, B and C are all ptrs converted to integers.
+  //
+  // Tricky case because pointers are effectively unsigned integers, but the
+  // result of their subtraction is signed. Also, these subtractions ought to
+  // have NSW semantics, except we cannot give that to them because the results
+  // are considered signed, but if we optimize away the subtraction, the
+  // underlying pointers need to be treated as unsigned, thus special handling
+  // is required. In this scenario, we must ensure that the comparison is
+  // unsigned after removing the subtraction operations.
+  if (B && D && B == D && isa<PtrToIntOperator>(A) &&
+      isa<PtrToIntOperator>(B) && isa<PtrToIntOperator>(C)) {
+    CmpInst::Predicate UnsignedPred;
+    switch (Pred) {
+    default:
+      // If already unsigned, explicit cast from ptr to unsigned,
+      // so cannot optimize
+      UnsignedPred = CmpInst::BAD_ICMP_PREDICATE;
+      break;
+    case ICmpInst::ICMP_SGT:
+      UnsignedPred = ICmpInst::ICMP_UGT;
+      break;
+    case ICmpInst::ICMP_SLT:
+      UnsignedPred = ICmpInst::ICMP_ULT;
+      break;
+    case ICmpInst::ICMP_SGE:
+      UnsignedPred = ICmpInst::ICMP_UGE;
+      break;
+    case ICmpInst::ICMP_SLE:
+      UnsignedPred = ICmpInst::ICMP_ULE;
+      break;
+    }
+    if (UnsignedPred != CmpInst::BAD_ICMP_PREDICATE) {
+      PtrToIntOperator *AOp = dyn_cast<PtrToIntOperator>(A);
+      PtrToIntOperator *COp = dyn_cast<PtrToIntOperator>(C);
+      return new ICmpInst(UnsignedPred, AOp->getOperand(0), COp->getOperand(0));
+    }
+  }
+
   // icmp (A-B), (A-D) -> icmp D, B for equalities or if there is no overflow.
   if (A && C && A == C && NoOp0WrapProblem && NoOp1WrapProblem)
     return new ICmpInst(Pred, D, B);
diff --git a/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll b/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll
index 5155c9ec956b5..2512135172196 100644
--- a/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-ptrdiff.ll
@@ -4,12 +4,7 @@
 define ptr @icmp_ptrdiff_gt(ptr %0, ptr %1, ptr %2) {
 ; CHECK-LABEL: @icmp_ptrdiff_gt(
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
-; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
-; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
-; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
-; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
-; CHECK-NEXT:    [[TMP10:%.*]] = icmp sgt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[TMP10:%.*]] = icmp ugt ptr [[TMP4]], [[TMP2:%.*]]
 ; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[TMP10]], ptr [[TMP2]], ptr [[TMP4]]
 ; CHECK-NEXT:    ret ptr [[TMP11]]
 ;
@@ -27,12 +22,7 @@ define ptr @icmp_ptrdiff_gt(ptr %0, ptr %1, ptr %2) {
 define ptr @icmp_ptrdiff_lt(ptr %0, ptr %1, ptr %2) {
 ; CHECK-LABEL: @icmp_ptrdiff_lt(
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
-; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
-; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
-; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
-; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
-; CHECK-NEXT:    [[TMP10:%.*]] = icmp slt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[TMP10:%.*]] = icmp ult ptr [[TMP4]], [[TMP2:%.*]]
 ; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[TMP10]], ptr [[TMP2]], ptr [[TMP4]]
 ; CHECK-NEXT:    ret ptr [[TMP11]]
 ;
@@ -50,12 +40,7 @@ define ptr @icmp_ptrdiff_lt(ptr %0, ptr %1, ptr %2) {
 define ptr @icmp_ptrdiff_ge(ptr %0, ptr %1, ptr %2) {
 ; CHECK-LABEL: @icmp_ptrdiff_ge(
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
-; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
-; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
-; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
-; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
-; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp slt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp ult ptr [[TMP4]], [[TMP2:%.*]]
 ; CHECK-NEXT:    [[TMP10:%.*]] = select i1 [[DOTNOT]], ptr [[TMP4]], ptr [[TMP2]]
 ; CHECK-NEXT:    ret ptr [[TMP10]]
 ;
@@ -73,12 +58,7 @@ define ptr @icmp_ptrdiff_ge(ptr %0, ptr %1, ptr %2) {
 define ptr @icmp_ptrdiff_le(ptr %0, ptr %1, ptr %2) {
 ; CHECK-LABEL: @icmp_ptrdiff_le(
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0:%.*]], i64 1
-; CHECK-NEXT:    [[TMP5:%.*]] = ptrtoint ptr [[TMP4]] to i64
-; CHECK-NEXT:    [[TMP6:%.*]] = ptrtoint ptr [[TMP1:%.*]] to i64
-; CHECK-NEXT:    [[TMP7:%.*]] = sub i64 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    [[TMP8:%.*]] = ptrtoint ptr [[TMP2:%.*]] to i64
-; CHECK-NEXT:    [[TMP9:%.*]] = sub i64 [[TMP8]], [[TMP6]]
-; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp sgt i64 [[TMP7]], [[TMP9]]
+; CHECK-NEXT:    [[DOTNOT:%.*]] = icmp ugt ptr [[TMP4]], [[TMP2:%.*]]
 ; CHECK-NEXT:    [[TMP10:%.*]] = select i1 [[DOTNOT]], ptr [[TMP4]], ptr [[TMP2]]
 ; CHECK-NEXT:    ret ptr [[TMP10]]
 ;



More information about the llvm-commits mailing list