Skip to content

Commit

Permalink
[InstCombine] Reuse icmp of and/or folds for logical and/or
Browse files Browse the repository at this point in the history
Similarly to a change recently done for fcmps, add a flag that
indicates whether the and/or is logical to foldAndOrOfICmps, and
reuse the function when folding logical and/or.

We were already calling some parts of it, but this gives us a
clearer indication of which parts may need poison-safe variants,
and would also allow to fold combinations of bitwise and logical
and/or.

This change should be close to NFC, because all folds this enables
were either already called previously, or can make use of implied
poison reasoning.
  • Loading branch information
nikic committed May 23, 2022
1 parent 72832ef commit 45226d0
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 53 deletions.
94 changes: 58 additions & 36 deletions llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@ static Value *foldSignedTruncationCheck(ICmpInst *ICmp0, ICmpInst *ICmp1,

/// Fold (icmp eq ctpop(X) 1) | (icmp eq X 0) into (icmp ult ctpop(X) 2) and
/// fold (icmp ne ctpop(X) 1) & (icmp ne X 0) into (icmp ugt ctpop(X) 1).
/// Also used for logical and/or, must be poison safe.
static Value *foldIsPowerOf2OrZero(ICmpInst *Cmp0, ICmpInst *Cmp1, bool IsAnd,
InstCombiner::BuilderTy &Builder) {
CmpInst::Predicate Pred0, Pred1;
Expand All @@ -891,6 +892,7 @@ static Value *foldIsPowerOf2OrZero(ICmpInst *Cmp0, ICmpInst *Cmp1, bool IsAnd,
}

/// Reduce a pair of compares that check if a value has exactly 1 bit set.
/// Also used for logical and/or, must be poison safe.
static Value *foldIsPowerOf2(ICmpInst *Cmp0, ICmpInst *Cmp1, bool JoinedByAnd,
InstCombiner::BuilderTy &Builder) {
// Handle 'and' / 'or' commutation: make the equality check the first operand.
Expand Down Expand Up @@ -1083,12 +1085,9 @@ Value *InstCombinerImpl::foldEqOfParts(ICmpInst *Cmp0, ICmpInst *Cmp1,
/// common operand with the constant. Callers are expected to call this with
/// Cmp0/Cmp1 switched to handle logic op commutativity.
static Value *foldAndOrOfICmpsWithConstEq(ICmpInst *Cmp0, ICmpInst *Cmp1,
BinaryOperator &Logic,
bool IsAnd,
InstCombiner::BuilderTy &Builder,
const SimplifyQuery &Q) {
bool IsAnd = Logic.getOpcode() == Instruction::And;
assert((IsAnd || Logic.getOpcode() == Instruction::Or) && "Wrong logic op");

// Match an equality compare with a non-poison constant as Cmp0.
// Also, give up if the compare can be constant-folded to avoid looping.
ICmpInst::Predicate Pred0;
Expand Down Expand Up @@ -1122,7 +1121,8 @@ static Value *foldAndOrOfICmpsWithConstEq(ICmpInst *Cmp0, ICmpInst *Cmp1,
return nullptr;
SubstituteCmp = Builder.CreateICmp(Pred1, Y, C);
}
return Builder.CreateBinOp(Logic.getOpcode(), Cmp0, SubstituteCmp);
return Builder.CreateBinOp(IsAnd ? Instruction::And : Instruction::Or, Cmp0,
SubstituteCmp);
}

/// Fold (icmp Pred1 V1, C1) & (icmp Pred2 V2, C2)
Expand Down Expand Up @@ -2389,14 +2389,17 @@ Value *foldAndOrOfICmpEqZeroAndICmp(ICmpInst *LHS, ICmpInst *RHS, bool IsAnd,
}

/// Fold (icmp)&(icmp) or (icmp)|(icmp) if possible.
/// If IsLogical is true, then the and/or is in select form and the transform
/// must be poison-safe.
Value *InstCombinerImpl::foldAndOrOfICmps(ICmpInst *LHS, ICmpInst *RHS,
BinaryOperator &BO, bool IsAnd) {
const SimplifyQuery Q = SQ.getWithInstruction(&BO);
Instruction &I, bool IsAnd,
bool IsLogical) {
const SimplifyQuery Q = SQ.getWithInstruction(&I);

// Fold (iszero(A & K1) | iszero(A & K2)) -> (A & (K1 | K2)) != (K1 | K2)
// Fold (!iszero(A & K1) & !iszero(A & K2)) -> (A & (K1 | K2)) == (K1 | K2)
// if K1 and K2 are a one-bit mask.
if (Value *V = foldAndOrOfICmpsOfAndWithPow2(LHS, RHS, &BO, IsAnd))
if (Value *V = foldAndOrOfICmpsOfAndWithPow2(LHS, RHS, &I, IsAnd, IsLogical))
return V;

ICmpInst::Predicate PredL = LHS->getPredicate(), PredR = RHS->getPredicate();
Expand All @@ -2421,56 +2424,75 @@ Value *InstCombinerImpl::foldAndOrOfICmps(ICmpInst *LHS, ICmpInst *RHS,
}
}

// handle (roughly):
// (icmp ne (A & B), C) | (icmp ne (A & D), E)
// (icmp eq (A & B), C) & (icmp eq (A & D), E)
if (Value *V = foldLogOpOfMaskedICmps(LHS, RHS, IsAnd, Builder))
return V;
// TODO: Some (but not all) of the patterns handled by this function are
// safe with logical and/or.
if (!IsLogical) {
// handle (roughly):
// (icmp ne (A & B), C) | (icmp ne (A & D), E)
// (icmp eq (A & B), C) & (icmp eq (A & D), E)
if (Value *V = foldLogOpOfMaskedICmps(LHS, RHS, IsAnd, Builder))
return V;
}

if (Value *V = foldAndOrOfICmpEqZeroAndICmp(LHS, RHS, IsAnd, Builder))
return V;
if (Value *V = foldAndOrOfICmpEqZeroAndICmp(RHS, LHS, IsAnd, Builder))
return V;
// TODO: One of these directions is fine with logical and/or, the other could
// be supported by inserting freeze.
if (!IsLogical) {
if (Value *V = foldAndOrOfICmpEqZeroAndICmp(LHS, RHS, IsAnd, Builder))
return V;
if (Value *V = foldAndOrOfICmpEqZeroAndICmp(RHS, LHS, IsAnd, Builder))
return V;
}

if (Value *V = foldAndOrOfICmpsWithConstEq(LHS, RHS, BO, Builder, Q))
return V;
if (Value *V = foldAndOrOfICmpsWithConstEq(RHS, LHS, BO, Builder, Q))
return V;
// TODO: Verify whether this is safe for logical and/or.
if (!IsLogical) {
if (Value *V = foldAndOrOfICmpsWithConstEq(LHS, RHS, IsAnd, Builder, Q))
return V;
if (Value *V = foldAndOrOfICmpsWithConstEq(RHS, LHS, IsAnd, Builder, Q))
return V;
}

if (Value *V = foldIsPowerOf2OrZero(LHS, RHS, IsAnd, Builder))
return V;
if (Value *V = foldIsPowerOf2OrZero(RHS, LHS, IsAnd, Builder))
return V;

// E.g. (icmp slt x, 0) | (icmp sgt x, n) --> icmp ugt x, n
// E.g. (icmp sge x, 0) & (icmp slt x, n) --> icmp ult x, n
if (Value *V = simplifyRangeCheck(LHS, RHS, /*Inverted=*/!IsAnd))
return V;
// TODO: One of these directions is fine with logical and/or, the other could
// be supported by inserting freeze.
if (!IsLogical) {
// E.g. (icmp slt x, 0) | (icmp sgt x, n) --> icmp ugt x, n
// E.g. (icmp sge x, 0) & (icmp slt x, n) --> icmp ult x, n
if (Value *V = simplifyRangeCheck(LHS, RHS, /*Inverted=*/!IsAnd))
return V;

// E.g. (icmp sgt x, n) | (icmp slt x, 0) --> icmp ugt x, n
// E.g. (icmp slt x, n) & (icmp sge x, 0) --> icmp ult x, n
if (Value *V = simplifyRangeCheck(RHS, LHS, /*Inverted=*/!IsAnd))
return V;
// E.g. (icmp sgt x, n) | (icmp slt x, 0) --> icmp ugt x, n
// E.g. (icmp slt x, n) & (icmp sge x, 0) --> icmp ult x, n
if (Value *V = simplifyRangeCheck(RHS, LHS, /*Inverted=*/!IsAnd))
return V;
}

if (IsAnd)
if (Value *V = foldSignedTruncationCheck(LHS, RHS, BO, Builder))
// TODO: Add conjugated or fold, check whether it is safe for logical and/or.
if (IsAnd && !IsLogical)
if (Value *V = foldSignedTruncationCheck(LHS, RHS, I, Builder))
return V;

if (Value *V = foldIsPowerOf2(LHS, RHS, IsAnd, Builder))
return V;

if (Value *X = foldUnsignedUnderflowCheck(LHS, RHS, IsAnd, Q, Builder))
return X;
if (Value *X = foldUnsignedUnderflowCheck(RHS, LHS, IsAnd, Q, Builder))
return X;
// TODO: Verify whether this is safe for logical and/or.
if (!IsLogical) {
if (Value *X = foldUnsignedUnderflowCheck(LHS, RHS, IsAnd, Q, Builder))
return X;
if (Value *X = foldUnsignedUnderflowCheck(RHS, LHS, IsAnd, Q, Builder))
return X;
}

if (Value *X = foldEqOfParts(LHS, RHS, IsAnd))
return X;

// (icmp ne A, 0) | (icmp ne B, 0) --> (icmp ne (A|B), 0)
// (icmp eq A, 0) & (icmp eq B, 0) --> (icmp eq (A|B), 0)
// TODO: Remove this when foldLogOpOfMaskedICmps can handle undefs.
if (PredL == (IsAnd ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_NE) &&
if (!IsLogical && PredL == (IsAnd ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_NE) &&
PredL == PredR && match(LHS1, m_ZeroInt()) && match(RHS1, m_ZeroInt()) &&
LHS0->getType() == RHS0->getType()) {
Value *NewOr = Builder.CreateOr(LHS0, RHS0);
Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/Transforms/InstCombine/InstCombineInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,8 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
const CastInst *CI2);
Value *simplifyIntToPtrRoundTripCast(Value *Val);

Value *foldAndOrOfICmps(ICmpInst *LHS, ICmpInst *RHS, BinaryOperator &BO,
bool IsAnd);
Value *foldAndOrOfICmps(ICmpInst *LHS, ICmpInst *RHS, Instruction &I,
bool IsAnd, bool IsLogical = false);
Value *foldXorOfICmps(ICmpInst *LHS, ICmpInst *RHS, BinaryOperator &Xor);

Value *foldEqOfParts(ICmpInst *Cmp0, ICmpInst *Cmp1, bool IsAnd);
Expand Down
19 changes: 4 additions & 15 deletions llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2781,22 +2781,11 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
/* IsAnd */ IsAnd))
return I;

if (auto *ICmp0 = dyn_cast<ICmpInst>(CondVal)) {
if (auto *ICmp1 = dyn_cast<ICmpInst>(Op1)) {
if (auto *V = foldAndOrOfICmpsOfAndWithPow2(ICmp0, ICmp1, &SI, IsAnd,
/* IsLogical */ true))
if (auto *ICmp0 = dyn_cast<ICmpInst>(CondVal))
if (auto *ICmp1 = dyn_cast<ICmpInst>(Op1))
if (auto *V = foldAndOrOfICmps(ICmp0, ICmp1, SI, IsAnd,
/* IsLogical */ true))
return replaceInstUsesWith(SI, V);

if (auto *V = foldEqOfParts(ICmp0, ICmp1, IsAnd))
return replaceInstUsesWith(SI, V);

// This pattern would usually be converted into a bitwise and/or based
// on "implies poison" reasoning. However, this may fail if adds with
// nowrap flags are involved.
if (auto *V = foldAndOrOfICmpsUsingRanges(ICmp0, ICmp1, IsAnd))
return replaceInstUsesWith(SI, V);
}
}
}

// select (select a, true, b), c, false -> select a, c, false
Expand Down

0 comments on commit 45226d0

Please sign in to comment.