Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions llvm/include/llvm/Analysis/ValueTracking.h
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,11 @@ LLVM_ABI void
findValuesAffectedByCondition(Value *Cond, bool IsAssume,
function_ref<void(Value *)> InsertAffected);

/// Returns the inner value X if the expression has the form f(X)
/// where f(X) == 0 if and only if X == 0, otherwise returns nullptr.
LLVM_ABI Value *stripNullTest(Value *V);
LLVM_ABI const Value *stripNullTest(const Value *V);

} // end namespace llvm

#endif // LLVM_ANALYSIS_VALUETRACKING_H
26 changes: 26 additions & 0 deletions llvm/lib/Analysis/ValueTracking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3521,6 +3521,9 @@ bool isKnownNonZero(const Value *V, const APInt &DemandedElts,
isKnownNonNullFromDominatingCondition(V, Q.CxtI, Q.DT))
return true;

if (const Value *Stripped = stripNullTest(V))
return isKnownNonZero(Stripped, DemandedElts, Q, Depth);

return false;
}

Expand Down Expand Up @@ -10170,3 +10173,26 @@ void llvm::findValuesAffectedByCondition(
}
}
}

const Value *llvm::stripNullTest(const Value *V) {
// (X >> C) or/add (X & mask(C) != 0)
if (const auto *BO = dyn_cast<BinaryOperator>(V)) {
if (BO->getOpcode() == Instruction::Add ||
BO->getOpcode() == Instruction::Or) {
const Value *X;
const APInt *C1, *C2;
if (match(BO, m_c_BinOp(m_LShr(m_Value(X), m_APInt(C1)),
m_ZExt(m_SpecificICmp(
ICmpInst::ICMP_NE,
m_And(m_Deferred(X), m_LowBitMask(C2)),
m_Zero())))) &&
C2->popcount() == C1->getZExtValue())
return X;
}
}
return nullptr;
}

Value *llvm::stripNullTest(Value *V) {
return const_cast<Value *>(stripNullTest(const_cast<const Value *>(V)));
}
7 changes: 7 additions & 0 deletions llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,13 @@ Instruction *InstCombinerImpl::foldICmpWithZero(ICmpInst &Cmp) {
// eq/ne (mul X, Y)) with (icmp eq/ne X/Y) and if X/Y is known non-zero that
// will fold to a constant elsewhere.
}

// (icmp eq/ne f(X), 0) -> (icmp eq/ne X, 0)
// where f(X) == 0 if and only if X == 0
if (ICmpInst::isEquality(Pred))
if (Value *Stripped = stripNullTest(Cmp.getOperand(0)))
return new ICmpInst(Pred, Stripped, Cmp.getOperand(1));

return nullptr;
}

Expand Down
308 changes: 308 additions & 0 deletions llvm/test/Transforms/InstCombine/ceil-shift.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; RUN: opt < %s -passes=instcombine -S | FileCheck %s

define i1 @ceil_shift4(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift4(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP1]]
;
%quot = lshr i32 %arg0, 4
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%is_zero = icmp eq i32 %quot_or_rem, 0
ret i1 %is_zero
}

define i1 @ceil_shift4_add(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift4_add(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP6:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP6]]
;
%quot = lshr i32 %arg0, 4
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%ceil = add i32 %quot, %zext_has_rem
%res = icmp eq i32 %ceil, 0
ret i1 %res
}

define i1 @ceil_shift6(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift6(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP1]]
;
%quot = lshr i32 %arg0, 6
%rem = and i32 %arg0, 63
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift6_ne(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift6_ne(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[RES:%.*]] = icmp ne i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[RES]]
;
%quot = lshr i32 %arg0, 6
%rem = and i32 %arg0, 63
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp ne i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift11(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift11(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP1]]
;
%quot = lshr i32 %arg0, 11
%rem = and i32 %arg0, 2047
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift11_ne(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift11_ne(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[RES:%.*]] = icmp ne i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[RES]]
;
%quot = lshr i32 %arg0, 6
%rem = and i32 %arg0, 63
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp ne i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift0(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift0(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP1]]
;
%quot = lshr i32 %arg0, 0
%rem = and i32 %arg0, 0
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift4_comm(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift4_comm(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP6:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP6]]
;
%quot = lshr i32 %arg0, 4
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %zext_has_rem, %quot
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

declare void @use(i32)

define i1 @ceil_shift4_used_1(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift4_used_1(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[ARG0]], 4
; CHECK-NEXT: call void @use(i32 [[TMP1]])
; CHECK-NEXT: [[TMP6:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP6]]
;
%quot = lshr i32 %arg0, 4
call void @use(i32 %quot)
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift4_used_5(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift4_used_5(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[ARG0]], 4
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[ARG0]], 15
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i32 [[TMP2]], 0
; CHECK-NEXT: [[TMP4:%.*]] = zext i1 [[TMP3]] to i32
; CHECK-NEXT: [[TMP5:%.*]] = or i32 [[TMP1]], [[TMP4]]
; CHECK-NEXT: call void @use(i32 [[TMP5]])
; CHECK-NEXT: [[TMP6:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[TMP6]]
;
%quot = lshr i32 %arg0, 4
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
call void @use(i32 %quot_or_rem)
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift4_used_add_nuw_nsw(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift4_used_add_nuw_nsw(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[QUOT:%.*]] = lshr i32 [[ARG0]], 4
; CHECK-NEXT: [[REM:%.*]] = and i32 [[ARG0]], 15
; CHECK-NEXT: [[HAS_REM:%.*]] = icmp ne i32 [[REM]], 0
; CHECK-NEXT: [[ZEXT_HAS_REM:%.*]] = zext i1 [[HAS_REM]] to i32
; CHECK-NEXT: [[CEIL:%.*]] = add nuw nsw i32 [[QUOT]], [[ZEXT_HAS_REM]]
; CHECK-NEXT: call void @use(i32 [[CEIL]])
; CHECK-NEXT: [[RES:%.*]] = icmp eq i32 [[ARG0]], 0
; CHECK-NEXT: ret i1 [[RES]]
;
%quot = lshr i32 %arg0, 4
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%ceil = add nuw nsw i32 %quot, %zext_has_rem
call void @use(i32 %ceil)
%res = icmp eq i32 %ceil, 0
ret i1 %res
}

define <4 x i1> @ceil_shift4_v4i32(<4 x i32> %arg0) {
; CHECK-LABEL: define <4 x i1> @ceil_shift4_v4i32(
; CHECK-SAME: <4 x i32> [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq <4 x i32> [[ARG0]], zeroinitializer
; CHECK-NEXT: ret <4 x i1> [[TMP1]]
;
%quot = lshr <4 x i32> %arg0, splat (i32 16)
%rem = and <4 x i32> %arg0, splat (i32 65535)
%has_rem = icmp ne <4 x i32> %rem, zeroinitializer
%zext_has_rem = zext <4 x i1> %has_rem to <4 x i32>
%quot_or_rem = or <4 x i32> %quot, %zext_has_rem
%res = icmp eq <4 x i32> %quot_or_rem, zeroinitializer
ret <4 x i1> %res
}

define <8 x i1> @ceil_shift4_v8i16(<8 x i16> %arg0) {
; CHECK-LABEL: define <8 x i1> @ceil_shift4_v8i16(
; CHECK-SAME: <8 x i16> [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq <8 x i16> [[ARG0]], zeroinitializer
; CHECK-NEXT: ret <8 x i1> [[TMP1]]
;
%quot = lshr <8 x i16> %arg0, splat (i16 4)
%rem = and <8 x i16> %arg0, splat (i16 15)
%has_rem = icmp ne <8 x i16> %rem, zeroinitializer
%zext_has_rem = zext <8 x i1> %has_rem to <8 x i16>
%quot_or_rem = or <8 x i16> %quot, %zext_has_rem
%res = icmp eq <8 x i16> %quot_or_rem, zeroinitializer
ret <8 x i1> %res
}

; negative tests

define i1 @ceil_shift_not_mask_1(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift_not_mask_1(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[ARG0]], 4
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[ARG0]], 31
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i32 [[TMP2]], 0
; CHECK-NEXT: [[TMP4:%.*]] = zext i1 [[TMP3]] to i32
; CHECK-NEXT: [[TMP5:%.*]] = or i32 [[TMP1]], [[TMP4]]
; CHECK-NEXT: [[TMP6:%.*]] = icmp eq i32 [[TMP5]], 0
; CHECK-NEXT: ret i1 [[TMP6]]
;
%quot = lshr i32 %arg0, 4
%rem = and i32 %arg0, 31
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift_not_mask_2(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift_not_mask_2(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[ARG0]], 5
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[ARG0]], 15
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i32 [[TMP2]], 0
; CHECK-NEXT: [[TMP4:%.*]] = zext i1 [[TMP3]] to i32
; CHECK-NEXT: [[TMP5:%.*]] = or i32 [[TMP1]], [[TMP4]]
; CHECK-NEXT: [[TMP6:%.*]] = icmp eq i32 [[TMP5]], 0
; CHECK-NEXT: ret i1 [[TMP6]]
;
%quot = lshr i32 %arg0, 5
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_or_rem = or i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_or_rem, 0
ret i1 %res
}

define i1 @ceil_shift_not_add_or(i32 %arg0) {
; CHECK-LABEL: define i1 @ceil_shift_not_add_or(
; CHECK-SAME: i32 [[ARG0:%.*]]) {
; CHECK-NEXT: [[REM:%.*]] = and i32 [[ARG0]], 15
; CHECK-NEXT: [[HAS_REM_NOT:%.*]] = icmp eq i32 [[REM]], 0
; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[ARG0]], 32
; CHECK-NEXT: [[RES1:%.*]] = icmp eq i32 [[TMP1]], 0
; CHECK-NEXT: [[RES:%.*]] = or i1 [[HAS_REM_NOT]], [[RES1]]
; CHECK-NEXT: ret i1 [[RES]]
;
%quot = lshr i32 %arg0, 5
%rem = and i32 %arg0, 15
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%quot_and_rem = and i32 %quot, %zext_has_rem
%res = icmp eq i32 %quot_and_rem, 0
ret i1 %res
}

define i32 @ceil_shift_should_infer_ge_zero(i32 %x) {
; CHECK-LABEL: define i32 @ceil_shift_should_infer_ge_zero(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[COND_NOT:%.*]] = icmp eq i32 [[X]], 0
; CHECK-NEXT: br i1 [[COND_NOT]], label %[[IF_ELSE:.*]], label %[[IF_THEN:.*]]
; CHECK: [[IF_THEN]]:
; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[X]], 20
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[X]], 1048575
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i32 [[TMP2]], 0
; CHECK-NEXT: [[TMP4:%.*]] = zext i1 [[TMP3]] to i32
; CHECK-NEXT: [[TMP5:%.*]] = add nuw nsw i32 [[TMP1]], [[TMP4]]
; CHECK-NEXT: ret i32 [[TMP5]]
; CHECK: [[IF_ELSE]]:
; CHECK-NEXT: ret i32 0
;
%cond = icmp ne i32 %x, 0
br i1 %cond, label %if.then, label %if.else

if.then:
%quot = lshr i32 %x, 20
%rem = and i32 %x, 1048575
%has_rem = icmp ne i32 %rem, 0
%zext_has_rem = zext i1 %has_rem to i32
%ceil = add nuw nsw i32 %quot, %zext_has_rem
%max = call i32 @llvm.umax.i32(i32 %ceil, i32 1)
ret i32 %max

if.else:
ret i32 0
}