Skip to content

[ValueTracking] Recognize X op (X != 0) as non-zero #88579

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
29 changes: 29 additions & 0 deletions llvm/lib/Analysis/ValueTracking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2449,9 +2449,20 @@ static bool isNonZeroRecurrence(const PHINode *PN) {
}
}

static bool matchOpWithOpEqZero(Value *Op0, Value *Op1) {
ICmpInst::Predicate Pred;
return (match(Op0, m_ZExtOrSExt(m_ICmp(Pred, m_Specific(Op1), m_Zero()))) ||
match(Op1, m_ZExtOrSExt(m_ICmp(Pred, m_Specific(Op0), m_Zero())))) &&
Pred == ICmpInst::ICMP_EQ;
}

static bool isNonZeroAdd(const APInt &DemandedElts, unsigned Depth,
const SimplifyQuery &Q, unsigned BitWidth, Value *X,
Value *Y, bool NSW, bool NUW) {
// (X + (X != 0)) is non zero
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it is a common pattern. But I would like to canonicalize it into umax(X, 1) (or vice versa).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think umax would make sense, although imo this patch still makes sense b.c this will be a single-user only transform

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would umax canonicalization cover the original SPEC case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are two transform here.

  1. fold (X == C) Op X -> X == C ? (X Op 0) : (C Op 1). assuming that X Op 0 simplifies.
  2. recognize something like X == 0 ? X : 1 as umax(X, 1).
    I'm happy to write patches for both.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already handle the X == 0 ? 1 : X -> umax(X, 1). Ill post patch for the select case soon. That being said, still prefer to get this in as I don't think we should rely on single-use constrained canonicalization in ValueTracking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made PR for the folding #89020
Would still like to get this in for the reason of the fold requiring single-use.

if (matchOpWithOpEqZero(X, Y))
return true;

if (NUW)
return isKnownNonZero(Y, DemandedElts, Q, Depth) ||
isKnownNonZero(X, DemandedElts, Q, Depth);
Expand Down Expand Up @@ -2495,6 +2506,11 @@ static bool isNonZeroAdd(const APInt &DemandedElts, unsigned Depth,
static bool isNonZeroSub(const APInt &DemandedElts, unsigned Depth,
const SimplifyQuery &Q, unsigned BitWidth, Value *X,
Value *Y) {
// (X - (X != 0)) is non zero
// ((X != 0) - X) is non zero
if (matchOpWithOpEqZero(X, Y))
return true;

// TODO: Move this case into isKnownNonEqual().
if (auto *C = dyn_cast<Constant>(X))
if (C->isNullValue() && isKnownNonZero(Y, DemandedElts, Q, Depth))
Expand Down Expand Up @@ -2654,7 +2670,15 @@ static bool isKnownNonZeroFromOperator(const Operator *I,
case Instruction::Sub:
return isNonZeroSub(DemandedElts, Depth, Q, BitWidth, I->getOperand(0),
I->getOperand(1));
case Instruction::Xor:
// (X ^ (X != 0)) is non zero
if (matchOpWithOpEqZero(I->getOperand(0), I->getOperand(1)))
return true;
break;
case Instruction::Or:
// (X | (X != 0)) is non zero
if (matchOpWithOpEqZero(I->getOperand(0), I->getOperand(1)))
return true;
// X | Y != 0 if X != 0 or Y != 0.
return isKnownNonZero(I->getOperand(1), DemandedElts, Q, Depth) ||
isKnownNonZero(I->getOperand(0), DemandedElts, Q, Depth);
Expand Down Expand Up @@ -2945,6 +2969,11 @@ static bool isKnownNonZeroFromOperator(const Operator *I,
return isKnownNonZero(II->getArgOperand(0), Q, Depth);
case Intrinsic::umax:
case Intrinsic::uadd_sat:
// umax(X, (X != 0)) is non zero
// X +usat (X != 0) is non zero
if (matchOpWithOpEqZero(II->getArgOperand(0), II->getArgOperand(1)))
return true;

return isKnownNonZero(II->getArgOperand(1), DemandedElts, Q, Depth) ||
isKnownNonZero(II->getArgOperand(0), DemandedElts, Q, Depth);
case Intrinsic::smax: {
Expand Down
183 changes: 183 additions & 0 deletions llvm/test/Transforms/InstSimplify/known-non-zero.ll
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,186 @@ define i1 @nonzero_reduce_or_fail(<2 x i8> %xx) {
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_add_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_add_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = zext i1 %x_eq_0 to i8
%v = add i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_add_x_eq_1_fail(i8 %x) {
; CHECK-LABEL: @src_x_add_x_eq_1_fail(
; CHECK-NEXT: [[X_EQ_1:%.*]] = icmp eq i8 [[X:%.*]], 1
; CHECK-NEXT: [[Y:%.*]] = zext i1 [[X_EQ_1]] to i8
; CHECK-NEXT: [[V:%.*]] = add i8 [[X]], [[Y]]
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%x_eq_1 = icmp eq i8 %x, 1
%y = zext i1 %x_eq_1 to i8
%v = add i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_or_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_or_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = sext i1 %x_eq_0 to i8
%v = or i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_or_x_sle_0_fail(i8 %x) {
; CHECK-LABEL: @src_x_or_x_sle_0_fail(
; CHECK-NEXT: [[X_EQ_0:%.*]] = icmp sle i8 [[X:%.*]], 0
; CHECK-NEXT: [[Y:%.*]] = sext i1 [[X_EQ_0]] to i8
; CHECK-NEXT: [[V:%.*]] = or i8 [[X]], [[Y]]
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%x_eq_0 = icmp sle i8 %x, 0
%y = sext i1 %x_eq_0 to i8
%v = or i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_xor_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_xor_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = zext i1 %x_eq_0 to i8
%v = xor i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_xor_x_ne_0_fail(i8 %x) {
; CHECK-LABEL: @src_x_xor_x_ne_0_fail(
; CHECK-NEXT: [[X_NE_0:%.*]] = icmp ne i8 [[X:%.*]], 0
; CHECK-NEXT: [[Y:%.*]] = zext i1 [[X_NE_0]] to i8
; CHECK-NEXT: [[V:%.*]] = xor i8 [[X]], [[Y]]
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%x_ne_0 = icmp ne i8 %x, 0
%y = zext i1 %x_ne_0 to i8
%v = xor i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_sub0_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_sub0_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = sext i1 %x_eq_0 to i8
%v = sub i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_sub0_z_eq_0_fail(i8 %x, i8 %z) {
; CHECK-LABEL: @src_x_sub0_z_eq_0_fail(
; CHECK-NEXT: [[Z_EQ_0:%.*]] = icmp eq i8 [[Z:%.*]], 0
; CHECK-NEXT: [[Y:%.*]] = sext i1 [[Z_EQ_0]] to i8
; CHECK-NEXT: [[V:%.*]] = sub i8 [[X:%.*]], [[Y]]
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%z_eq_0 = icmp eq i8 %z, 0
%y = sext i1 %z_eq_0 to i8
%v = sub i8 %x, %y
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_sub1_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_sub1_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = zext i1 %x_eq_0 to i8
%v = sub i8 %y, %x
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_sub1_x_eq_0_or_fail(i8 %x, i1 %c1) {
; CHECK-LABEL: @src_x_sub1_x_eq_0_or_fail(
; CHECK-NEXT: [[X_EQ_0:%.*]] = icmp eq i8 [[X:%.*]], 0
; CHECK-NEXT: [[X_EQ_0_OR:%.*]] = or i1 [[X_EQ_0]], [[C1:%.*]]
; CHECK-NEXT: [[Y:%.*]] = zext i1 [[X_EQ_0_OR]] to i8
; CHECK-NEXT: [[V:%.*]] = sub i8 [[Y]], [[X]]
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%x_eq_0 = icmp eq i8 %x, 0
%x_eq_0_or = or i1 %x_eq_0, %c1
%y = zext i1 %x_eq_0_or to i8
%v = sub i8 %y, %x
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_umax_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_umax_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = sext i1 %x_eq_0 to i8
%v = call i8 @llvm.umax.i8(i8 %y, i8 %x)
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_umax_x_ugt_10_fail(i8 %x) {
; CHECK-LABEL: @src_x_umax_x_ugt_10_fail(
; CHECK-NEXT: [[X_UGT_10:%.*]] = icmp ugt i8 [[X:%.*]], 10
; CHECK-NEXT: [[Y:%.*]] = sext i1 [[X_UGT_10]] to i8
; CHECK-NEXT: [[V:%.*]] = call i8 @llvm.umax.i8(i8 [[Y]], i8 [[X]])
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%x_ugt_10 = icmp ugt i8 %x, 10
%y = sext i1 %x_ugt_10 to i8
%v = call i8 @llvm.umax.i8(i8 %y, i8 %x)
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_uadd.sat_x_eq_0(i8 %x) {
; CHECK-LABEL: @src_x_uadd.sat_x_eq_0(
; CHECK-NEXT: ret i1 false
;
%x_eq_0 = icmp eq i8 %x, 0
%y = zext i1 %x_eq_0 to i8
%v = call i8 @llvm.uadd.sat.i8(i8 %y, i8 %x)
%r = icmp eq i8 %v, 0
ret i1 %r
}

define i1 @src_x_uadd.sat_c1_fail(i8 %x, i1 %c1) {
; CHECK-LABEL: @src_x_uadd.sat_c1_fail(
; CHECK-NEXT: [[Y:%.*]] = zext i1 [[C1:%.*]] to i8
; CHECK-NEXT: [[V:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[Y]], i8 [[X:%.*]])
; CHECK-NEXT: [[R:%.*]] = icmp eq i8 [[V]], 0
; CHECK-NEXT: ret i1 [[R]]
;
%y = zext i1 %c1 to i8
%v = call i8 @llvm.uadd.sat.i8(i8 %y, i8 %x)
%r = icmp eq i8 %v, 0
ret i1 %r
}

Loading