Skip to content

[llvm] [InstCombine] fold "icmp eq (X + (V - 1)) & -V, X" to "icmp eq (and X, V - 1), 0" #152851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
53 changes: 53 additions & 0 deletions llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,56 @@ Instruction *InstCombinerImpl::foldICmpWithZero(ICmpInst &Cmp) {
return nullptr;
}

// Fold icmp eq (num + (val - 1)) & -val, num
// to
// icmp eq 0, (and num, val - 1)
// For value being power of two
Instruction *InstCombinerImpl::foldIsMultipleOfAPowerOfTwo(ICmpInst &Cmp) {
Value *Neg, *Num, *Mask, *Value;
CmpPredicate Pred;
const APInt *NegConst, *MaskConst;

if (!match(&Cmp, m_c_ICmp(Pred, m_Value(Num),
m_OneUse(m_c_And(m_OneUse(m_c_Add(m_Deferred(Num),
m_Value(Mask))),
m_Value(Neg))))))
return nullptr;

if (!ICmpInst::isEquality(Pred))
return nullptr;

// Check the constant case
if (match(Neg, m_APInt(NegConst)) && match(Mask, m_LowBitMask(MaskConst))) {
// Neg = -(Mask + 1)
if (*NegConst != -(*MaskConst + 1))
return nullptr;
} else {
// Match neg = sub 0, val
if (!match(Neg, m_Sub(m_Zero(), m_Value(Value))))
return nullptr;

// mask = add %val, -1. No commutative here, since it's canonical
// representation for sub %val, -1
if (!match(Mask, m_Add(m_Value(Value), m_AllOnes()))) {
if (match(Num, m_Add(m_Value(Value), m_AllOnes()))) {
std::swap(Mask, Num);
} else {
return nullptr;
}
}

// Value should be a known power-of-two.
if (!isKnownToBeAPowerOfTwo(Value, false, &Cmp))
return nullptr;
}

// Create new icmp eq (num & (val - 1)), 0
auto NewAnd = Builder.CreateAnd(Num, Mask);
auto Zero = llvm::Constant::getNullValue(Num->getType());

return new ICmpInst(Pred, NewAnd, Zero);
}

/// Fold icmp Pred X, C.
/// TODO: This code structure does not make sense. The saturating add fold
/// should be moved to some other helper and extended as noted below (it is also
Expand Down Expand Up @@ -7644,6 +7694,9 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
if (Instruction *Res = foldICmpUsingKnownBits(I))
return Res;

if (Instruction *Res = foldIsMultipleOfAPowerOfTwo(I))
return Res;

// Test if the ICmpInst instruction is used exclusively by a select as
// part of a minimum or maximum operation. If so, refrain from doing
// any other folding. This helps out other analyses which understand
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Transforms/InstCombine/InstCombineInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
Instruction *foldICmpUsingKnownBits(ICmpInst &Cmp);
Instruction *foldICmpWithDominatingICmp(ICmpInst &Cmp);
Instruction *foldICmpWithConstant(ICmpInst &Cmp);
Instruction *foldIsMultipleOfAPowerOfTwo(ICmpInst &Cmp);
Instruction *foldICmpUsingBoolRange(ICmpInst &I);
Instruction *foldICmpInstWithConstant(ICmpInst &Cmp);
Instruction *foldICmpInstWithConstantNotInt(ICmpInst &Cmp);
Expand Down
198 changes: 198 additions & 0 deletions llvm/test/Transforms/InstCombine/icmp-add.ll
Original file line number Diff line number Diff line change
Expand Up @@ -3300,3 +3300,201 @@ entry:
%cmp = icmp ult i32 %add, 253
ret i1 %cmp
}

; PR 152851

define i1 @val_is_aligend_sub(i32 %num, i32 %val) {
; CHECK-LABEL: @val_is_aligend_sub(
; CHECK-NEXT: [[TMP1:%.*]] = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 [[NUM:%.*]])
; CHECK-NEXT: [[POW:%.*]] = icmp eq i32 [[TMP1]], 1
; CHECK-NEXT: call void @llvm.assume(i1 [[POW]])
; CHECK-NEXT: [[NEG:%.*]] = add i32 [[NUM]], -1
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[NUM1:%.*]], [[NEG]]
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[TMP2]], 0
; CHECK-NEXT: ret i1 [[_0]]
;
%1 = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %val)
%pow = icmp eq i32 %1, 1
call void @llvm.assume(i1 %pow)

%mask = sub i32 %val, 1
%neg = sub nsw i32 0, %val

%num.biased = add i32 %num, %mask
%_2.sroa.0.0 = and i32 %num.biased, %neg
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_add(i32 %num, i32 %val) {
; CHECK-LABEL: @val_is_aligend_add(
; CHECK-NEXT: [[TMP1:%.*]] = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 [[NUM:%.*]])
; CHECK-NEXT: [[POW:%.*]] = icmp eq i32 [[TMP1]], 1
; CHECK-NEXT: call void @llvm.assume(i1 [[POW]])
; CHECK-NEXT: [[NEG:%.*]] = add i32 [[NUM]], -1
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[NUM1:%.*]], [[NEG]]
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[TMP2]], 0
; CHECK-NEXT: ret i1 [[_0]]
;
%1 = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %val)
%pow = icmp eq i32 %1, 1
call void @llvm.assume(i1 %pow)

%mask = add i32 %val, -1
%neg = sub nsw i32 0, %val

%num.biased = add i32 %num, %mask
%_2.sroa.0.0 = and i32 %num.biased, %neg
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_add_commute_add(i32 %num, i32 %val) {
; CHECK-LABEL: @val_is_aligend_add_commute_add(
; CHECK-NEXT: [[TMP1:%.*]] = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 [[VAL:%.*]])
; CHECK-NEXT: [[POW:%.*]] = icmp eq i32 [[TMP1]], 1
; CHECK-NEXT: call void @llvm.assume(i1 [[POW]])
; CHECK-NEXT: [[MASK:%.*]] = add i32 [[VAL]], -1
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[NUM:%.*]], [[MASK]]
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[TMP2]], 0
; CHECK-NEXT: ret i1 [[_0]]
;
%1 = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %val)
%pow = icmp eq i32 %1, 1
call void @llvm.assume(i1 %pow)

%mask = add i32 %val, -1
%neg = sub nsw i32 0, %val

%num.biased = add i32 %mask, %num
%_2.sroa.0.0 = and i32 %num.biased, %neg
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_add_commute_and(i32 %num, i32 %val) {
; CHECK-LABEL: @val_is_aligend_add_commute_and(
; CHECK-NEXT: [[TMP1:%.*]] = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 [[VAL:%.*]])
; CHECK-NEXT: [[POW:%.*]] = icmp eq i32 [[TMP1]], 1
; CHECK-NEXT: call void @llvm.assume(i1 [[POW]])
; CHECK-NEXT: [[MASK:%.*]] = add i32 [[VAL]], -1
; CHECK-NEXT: [[TMP2:%.*]] = and i32 [[NUM:%.*]], [[MASK]]
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[TMP2]], 0
; CHECK-NEXT: ret i1 [[_0]]
;
%1 = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %val)
%pow = icmp eq i32 %1, 1
call void @llvm.assume(i1 %pow)

%mask = add i32 %val, -1
%neg = sub nsw i32 0, %val

%num.biased = add i32 %mask, %num
%_2.sroa.0.0 = and i32 %neg, %num.biased
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_const_pow2(i32 %num) {
; CHECK-LABEL: @val_is_aligend_const_pow2(
; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[NUM:%.*]], 4095
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[TMP1]], 0
; CHECK-NEXT: ret i1 [[_0]]
;
%num.biased = add i32 %num, 4095
%_2.sroa.0.0 = and i32 %num.biased, -4096
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

; Should not work for non-power-of-two cases
define i1 @val_is_aligend_const_non_pow2(i32 %num) {
; CHECK-LABEL: @val_is_aligend_const_non_pow2(
; CHECK-NEXT: [[NUM_BIASED:%.*]] = add i32 [[NUM:%.*]], 6
; CHECK-NEXT: [[_2_SROA_0_0:%.*]] = and i32 [[NUM_BIASED]], -7
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[_2_SROA_0_0]], [[NUM]]
; CHECK-NEXT: ret i1 [[_0]]
;
%num.biased = add i32 %num, 6
%_2.sroa.0.0 = and i32 %num.biased, -7
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_non_pow(i32 %num, i32 %val) {
; CHECK-LABEL: @val_is_aligend_non_pow(
; CHECK-NEXT: [[MASK:%.*]] = add i32 [[VAL:%.*]], -1
; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[VAL]]
; CHECK-NEXT: [[NUM_BIASED:%.*]] = add i32 [[NUM:%.*]], [[MASK]]
; CHECK-NEXT: [[_2_SROA_0_0:%.*]] = and i32 [[NUM_BIASED]], [[NEG]]
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[_2_SROA_0_0]], [[NUM]]
; CHECK-NEXT: ret i1 [[_0]]
;
%mask = add i32 %val, -1
%neg = sub nsw i32 0, %val

%num.biased = add i32 %num, %mask
%_2.sroa.0.0 = and i32 %num.biased, %neg
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_const_pow2_multiuse(i32 %num) {
; CHECK-LABEL: @val_is_aligend_const_pow2_multiuse(
; CHECK-NEXT: [[NUM_BIASED:%.*]] = add i32 [[NUM:%.*]], 4095
; CHECK-NEXT: [[_2_SROA_0_0:%.*]] = and i32 [[NUM_BIASED]], -4096
; CHECK-NEXT: call void @use(i32 [[_2_SROA_0_0]])
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[_2_SROA_0_0]], [[NUM]]
; CHECK-NEXT: ret i1 [[_0]]
;
%num.biased = add i32 %num, 4095
%_2.sroa.0.0 = and i32 %num.biased, -4096
call void @use(i32 %_2.sroa.0.0)
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

; Applies since number of instructions do not change
define i1 @val_is_aligend_const_pow2_multiuse1(i32 %num) {
; CHECK-LABEL: @val_is_aligend_const_pow2_multiuse1(
; CHECK-NEXT: [[NUM_BIASED:%.*]] = add i32 [[NUM:%.*]], 4095
; CHECK-NEXT: call void @use(i32 [[NUM_BIASED]])
; CHECK-NEXT: [[_2_SROA_0_0:%.*]] = and i32 [[NUM_BIASED]], -4096
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[_2_SROA_0_0]], [[NUM]]
; CHECK-NEXT: ret i1 [[_0]]
;
%num.biased = add i32 %num, 4095
call void @use(i32 %num.biased)
%_2.sroa.0.0 = and i32 %num.biased, -4096
%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}

define i1 @val_is_aligend_add_multiuse(i32 %num, i32 %val) {
; CHECK-LABEL: @val_is_aligend_add_multiuse(
; CHECK-NEXT: [[TMP1:%.*]] = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 [[VAL:%.*]])
; CHECK-NEXT: [[POW:%.*]] = icmp eq i32 [[TMP1]], 1
; CHECK-NEXT: call void @llvm.assume(i1 [[POW]])
; CHECK-NEXT: [[MASK:%.*]] = add i32 [[VAL]], -1
; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[VAL]]
; CHECK-NEXT: [[NUM_BIASED:%.*]] = add i32 [[NUM:%.*]], [[MASK]]
; CHECK-NEXT: [[_2_SROA_0_0:%.*]] = and i32 [[NUM_BIASED]], [[NEG]]
; CHECK-NEXT: call void @use(i32 [[_2_SROA_0_0]])
; CHECK-NEXT: [[_0:%.*]] = icmp eq i32 [[_2_SROA_0_0]], [[NUM]]
; CHECK-NEXT: ret i1 [[_0]]
;
%1 = tail call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %val)
%pow = icmp eq i32 %1, 1
call void @llvm.assume(i1 %pow)

%mask = add i32 %val, -1
%neg = sub nsw i32 0, %val

%num.biased = add i32 %num, %mask
%_2.sroa.0.0 = and i32 %num.biased, %neg

call void @use(i32 %_2.sroa.0.0)

%_0 = icmp eq i32 %_2.sroa.0.0, %num
ret i1 %_0
}