Skip to content

Commit fcd51de

Browse files
authored
[InstCombine] Factorise Add and Min/Max using distributivity (#101717)
This PR fixes part of #92433. It specifically adds the 4 cases mentioned in #92433 (comment). I've added 8 positive tests, 4 of which are mentioned in the comment above and 4 which are their commutative equivalents. Alive proof: https://alive2.llvm.org/ce/z/z6eFTb I've also added 8 negative tests, because we want to make sure we do not optimise if the relevant flags are not relevant because the optimisation wouldn't be sound. Alive proof that the optimisation is invalid: https://alive2.llvm.org/ce/z/NvNjTD I did have to make the integer types `i4` to make Alive not timeout and to fit them all on one page.
1 parent 79178ca commit fcd51de

File tree

3 files changed

+615
-0
lines changed

3 files changed

+615
-0
lines changed

llvm/include/llvm/IR/Operator.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ class OverflowingBinaryOperator : public Operator {
123123
return NoWrapKind;
124124
}
125125

126+
/// Return true if the instruction is commutative
127+
bool isCommutative() const { return Instruction::isCommutative(getOpcode()); }
128+
126129
static bool classof(const Instruction *I) {
127130
return I->getOpcode() == Instruction::Add ||
128131
I->getOpcode() == Instruction::Sub ||

llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,76 @@ foldMinimumOverTrailingOrLeadingZeroCount(Value *I0, Value *I1,
15031503
ConstantInt::getTrue(ZeroUndef->getType()));
15041504
}
15051505

1506+
/// Return whether "X LOp (Y ROp Z)" is always equal to
1507+
/// "(X LOp Y) ROp (X LOp Z)".
1508+
static bool leftDistributesOverRight(Instruction::BinaryOps LOp, bool HasNUW,
1509+
bool HasNSW, Intrinsic::ID ROp) {
1510+
switch (ROp) {
1511+
case Intrinsic::umax:
1512+
case Intrinsic::umin:
1513+
return HasNUW && LOp == Instruction::Add;
1514+
case Intrinsic::smax:
1515+
case Intrinsic::smin:
1516+
return HasNSW && LOp == Instruction::Add;
1517+
default:
1518+
return false;
1519+
}
1520+
}
1521+
1522+
// Attempts to factorise a common term
1523+
// in an instruction that has the form "(A op' B) op (C op' D)
1524+
// where op is an intrinsic and op' is a binop
1525+
static Value *
1526+
foldIntrinsicUsingDistributiveLaws(IntrinsicInst *II,
1527+
InstCombiner::BuilderTy &Builder) {
1528+
Value *LHS = II->getOperand(0), *RHS = II->getOperand(1);
1529+
Intrinsic::ID TopLevelOpcode = II->getIntrinsicID();
1530+
1531+
OverflowingBinaryOperator *Op0 = dyn_cast<OverflowingBinaryOperator>(LHS);
1532+
OverflowingBinaryOperator *Op1 = dyn_cast<OverflowingBinaryOperator>(RHS);
1533+
1534+
if (!Op0 || !Op1)
1535+
return nullptr;
1536+
1537+
if (Op0->getOpcode() != Op1->getOpcode())
1538+
return nullptr;
1539+
1540+
if (!Op0->hasOneUse() || !Op1->hasOneUse())
1541+
return nullptr;
1542+
1543+
Instruction::BinaryOps InnerOpcode =
1544+
static_cast<Instruction::BinaryOps>(Op0->getOpcode());
1545+
bool HasNUW = Op0->hasNoUnsignedWrap() && Op1->hasNoUnsignedWrap();
1546+
bool HasNSW = Op0->hasNoSignedWrap() && Op1->hasNoSignedWrap();
1547+
1548+
if (!leftDistributesOverRight(InnerOpcode, HasNUW, HasNSW, TopLevelOpcode))
1549+
return nullptr;
1550+
1551+
assert(II->isCommutative() && Op0->isCommutative() &&
1552+
"Only inner and outer commutative op codes are supported.");
1553+
1554+
Value *A = Op0->getOperand(0);
1555+
Value *B = Op0->getOperand(1);
1556+
Value *C = Op1->getOperand(0);
1557+
Value *D = Op1->getOperand(1);
1558+
1559+
// Attempts to swap variables such that A always equals C
1560+
if (A != C && A != D)
1561+
std::swap(A, B);
1562+
if (A == C || A == D) {
1563+
if (A != C)
1564+
std::swap(C, D);
1565+
Value *NewIntrinsic = Builder.CreateBinaryIntrinsic(TopLevelOpcode, B, D);
1566+
BinaryOperator *NewBinop =
1567+
cast<BinaryOperator>(Builder.CreateBinOp(InnerOpcode, NewIntrinsic, A));
1568+
NewBinop->setHasNoSignedWrap(HasNSW);
1569+
NewBinop->setHasNoUnsignedWrap(HasNUW);
1570+
return NewBinop;
1571+
}
1572+
1573+
return nullptr;
1574+
}
1575+
15061576
/// CallInst simplification. This mostly only handles folding of intrinsic
15071577
/// instructions. For normal calls, it allows visitCallBase to do the heavy
15081578
/// lifting.
@@ -1928,6 +1998,9 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
19281998
}
19291999
}
19302000

2001+
if (Value *V = foldIntrinsicUsingDistributiveLaws(II, Builder))
2002+
return replaceInstUsesWith(*II, V);
2003+
19312004
break;
19322005
}
19332006
case Intrinsic::scmp: {

0 commit comments

Comments
 (0)