From f400d442a261e8a781f5cfc51cb5fae1a8b36de3 Mon Sep 17 00:00:00 2001 From: bababuck 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 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(A) && + isa(B) && isa(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(A); + PtrToIntOperator *COp = dyn_cast(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]] ;