[InstCombine] icmp eq/ne (gep inbounds P, Idx..), null -> icmp eq/ne P, null

This generalizes the isGEPKnownNonNull rule from ValueTracking to apply when we do not know if the base is non-null, and thus need to replace one condition with another.

The core notion is that since an inbounds GEP can only form null if the base pointer is null and the offset is zero. However, if the offset is non-zero, the the "inbounds" marker makes the result poison. Thus, we're free to ignore the case where the offset is non-zero. Similarly, there's no case under which a non-null base can result in a null result without generating poison.

Differential Revision: https://reviews.llvm.org/D66608

llvm-svn: 369789
This commit is contained in:
Philip Reames 2019-08-23 17:58:58 +00:00
parent e7823a5316
commit 5b02cfa0b3
2 changed files with 205 additions and 0 deletions

View File

@ -894,6 +894,27 @@ Instruction *InstCombiner::foldGEPICmp(GEPOperator *GEPLHS, Value *RHS,
Offset = EmitGEPOffset(GEPLHS);
return new ICmpInst(ICmpInst::getSignedPredicate(Cond), Offset,
Constant::getNullValue(Offset->getType()));
} else if (GEPLHS->isInBounds() && ICmpInst::isEquality(Cond) &&
GEPLHS->getType()->isPointerTy() && // TODO: extend to vector geps
isa<Constant>(RHS) && cast<Constant>(RHS)->isNullValue() &&
!NullPointerIsDefined(I.getFunction(),
RHS->getType()->getPointerAddressSpace())) {
// For most address spaces, an allocation can't be placed at null, but null
// itself is treated as a 0 size allocation in the in bounds rules. Thus,
// the only valid inbounds address derived from null, is null itself.
// Thus, we have four cases to consider:
// 1) Base == nullptr, Offset == 0 -> inbounds, null
// 2) Base == nullptr, Offset != 0 -> poison as the result is out of bounds
// 3) Base != nullptr, Offset == (-base) -> poison (crossing allocations)
// 4) Base != nullptr, Offset != (-base) -> nonnull (and possibly poison)
//
// (Note if we're indexing a type of size 0, that simply collapses into one
// of the buckets above.)
//
// In general, we're allowed to make values less poison (i.e. remove
// sources of full UB), so in this case, we just select between the two
// non-poison cases (1 and 4 above).
return new ICmpInst(Cond, GEPLHS->getPointerOperand(), RHS);
} else if (GEPOperator *GEPRHS = dyn_cast<GEPOperator>(RHS)) {
// If the base pointers are different, but the indices are the same, just
// compare the base pointer.

View File

@ -0,0 +1,184 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -S < %s -instcombine | FileCheck %s
;; Start by showing the results of constant folding (which doesn't use
;; the poison implied by gep for the nonnull cases).
define i1 @test_ne_constants_null() {
; CHECK-LABEL: @test_ne_constants_null(
; CHECK-NEXT: entry:
; CHECK-NEXT: ret i1 false
;
entry:
%gep = getelementptr inbounds i8, i8* null, i64 0
%cnd = icmp ne i8* %gep, null
ret i1 %cnd
}
define i1 @test_ne_constants_nonnull() {
; CHECK-LABEL: @test_ne_constants_nonnull(
; CHECK-NEXT: entry:
; CHECK-NEXT: ret i1 true
;
entry:
%gep = getelementptr inbounds i8, i8* null, i64 1
%cnd = icmp ne i8* %gep, null
ret i1 %cnd
}
define i1 @test_eq_constants_null() {
; CHECK-LABEL: @test_eq_constants_null(
; CHECK-NEXT: entry:
; CHECK-NEXT: ret i1 true
;
entry:
%gep = getelementptr inbounds i8, i8* null, i64 0
%cnd = icmp eq i8* %gep, null
ret i1 %cnd
}
define i1 @test_eq_constants_nonnull() {
; CHECK-LABEL: @test_eq_constants_nonnull(
; CHECK-NEXT: entry:
; CHECK-NEXT: ret i1 false
;
entry:
%gep = getelementptr inbounds i8, i8* null, i64 1
%cnd = icmp eq i8* %gep, null
ret i1 %cnd
}
;; Then show the results for non-constants. These use the inbounds provided
;; UB fact to ignore the possible overflow cases.
define i1 @test_ne(i8* %base, i64 %idx) {
; CHECK-LABEL: @test_ne(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[CND:%.*]] = icmp ne i8* [[BASE:%.*]], null
; CHECK-NEXT: ret i1 [[CND]]
;
entry:
%gep = getelementptr inbounds i8, i8* %base, i64 %idx
%cnd = icmp ne i8* %gep, null
ret i1 %cnd
}
define i1 @test_eq(i8* %base, i64 %idx) {
; CHECK-LABEL: @test_eq(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[CND:%.*]] = icmp eq i8* [[BASE:%.*]], null
; CHECK-NEXT: ret i1 [[CND]]
;
entry:
%gep = getelementptr inbounds i8, i8* %base, i64 %idx
%cnd = icmp eq i8* %gep, null
ret i1 %cnd
}
;; TODO: vectors not yet handled
define <2 x i1> @test_vector_base(<2 x i8*> %base, i64 %idx) {
; CHECK-LABEL: @test_vector_base(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, <2 x i8*> [[BASE:%.*]], i64 [[IDX:%.*]]
; CHECK-NEXT: [[CND:%.*]] = icmp eq <2 x i8*> [[GEP]], zeroinitializer
; CHECK-NEXT: ret <2 x i1> [[CND]]
;
entry:
%gep = getelementptr inbounds i8, <2 x i8*> %base, i64 %idx
%cnd = icmp eq <2 x i8*> %gep, zeroinitializer
ret <2 x i1> %cnd
}
define <2 x i1> @test_vector_index(i8* %base, <2 x i64> %idx) {
; CHECK-LABEL: @test_vector_index(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[BASE:%.*]], <2 x i64> [[IDX:%.*]]
; CHECK-NEXT: [[CND:%.*]] = icmp eq <2 x i8*> [[GEP]], zeroinitializer
; CHECK-NEXT: ret <2 x i1> [[CND]]
;
entry:
%gep = getelementptr inbounds i8, i8* %base, <2 x i64> %idx
%cnd = icmp eq <2 x i8*> %gep, zeroinitializer
ret <2 x i1> %cnd
}
;; These two show instsimplify's reasoning getting to the non-zero offsets
;; before instcombine does.
define i1 @test_eq_pos_idx(i8* %base) {
; CHECK-LABEL: @test_eq_pos_idx(
; CHECK-NEXT: entry:
; CHECK-NEXT: ret i1 false
;
entry:
%gep = getelementptr inbounds i8, i8* %base, i64 1
%cnd = icmp eq i8* %gep, null
ret i1 %cnd
}
define i1 @test_eq_neg_idx(i8* %base) {
; CHECK-LABEL: @test_eq_neg_idx(
; CHECK-NEXT: entry:
; CHECK-NEXT: ret i1 false
;
entry:
%gep = getelementptr inbounds i8, i8* %base, i64 -1
%cnd = icmp eq i8* %gep, null
ret i1 %cnd
}
;; Show an example with a zero sized type since that's
;; a cornercase which keeps getting mentioned. The GEP
;; produces %base regardless of the value of the index
;; expression.
define i1 @test_size0({}* %base, i64 %idx) {
; CHECK-LABEL: @test_size0(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[CND:%.*]] = icmp ne {}* [[BASE:%.*]], null
; CHECK-NEXT: ret i1 [[CND]]
;
entry:
%gep = getelementptr inbounds {}, {}* %base, i64 %idx
%cnd = icmp ne {}* %gep, null
ret i1 %cnd
}
define i1 @test_size0_nonzero_offset({}* %base) {
; CHECK-LABEL: @test_size0_nonzero_offset(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[CND:%.*]] = icmp ne {}* [[BASE:%.*]], null
; CHECK-NEXT: ret i1 [[CND]]
;
entry:
%gep = getelementptr inbounds {}, {}* %base, i64 15
%cnd = icmp ne {}* %gep, null
ret i1 %cnd
}
;; Finally, some negative tests for sanity checking.
define i1 @neq_noinbounds(i8* %base, i64 %idx) {
; CHECK-LABEL: @neq_noinbounds(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr i8, i8* [[BASE:%.*]], i64 [[IDX:%.*]]
; CHECK-NEXT: [[CND:%.*]] = icmp ne i8* [[GEP]], null
; CHECK-NEXT: ret i1 [[CND]]
;
entry:
%gep = getelementptr i8, i8* %base, i64 %idx
%cnd = icmp ne i8* %gep, null
ret i1 %cnd
}
define i1 @neg_objectatnull(i8 addrspace(2)* %base, i64 %idx) {
; CHECK-LABEL: @neg_objectatnull(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8 addrspace(2)* [[BASE:%.*]], i64 [[IDX:%.*]]
; CHECK-NEXT: [[CND:%.*]] = icmp eq i8 addrspace(2)* [[GEP]], null
; CHECK-NEXT: ret i1 [[CND]]
;
entry:
%gep = getelementptr inbounds i8, i8 addrspace(2)* %base, i64 %idx
%cnd = icmp eq i8 addrspace(2)* %gep, null
ret i1 %cnd
}