#include "X86.h"
#include "X86TargetMachine.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/IntrinsicsX86.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/Pass.h"
#include "llvm/Support/KnownBits.h"
using namespace llvm;
#define DEBUG_TYPE "x86-partial-reduction"
namespace {
class X86PartialReduction : public FunctionPass {
const DataLayout *DL;
const X86Subtarget *ST;
public:
static char ID;
X86PartialReduction() : FunctionPass(ID) { }
bool runOnFunction(Function &Fn) override;
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
}
StringRef getPassName() const override {
return "X86 Partial Reduction";
}
private:
bool tryMAddReplacement(Instruction *Op, bool ReduceInOneBB);
bool trySADReplacement(Instruction *Op);
};
}
FunctionPass *llvm::createX86PartialReductionPass() {
return new X86PartialReduction();
}
char X86PartialReduction::ID = 0;
INITIALIZE_PASS(X86PartialReduction, DEBUG_TYPE,
"X86 Partial Reduction", false, false)
static bool matchVPDPBUSDPattern(const X86Subtarget *ST, BinaryOperator *Mul,
const DataLayout *DL) {
if (!ST->hasVNNI() && !ST->hasAVXVNNI())
return false;
Value *LHS = Mul->getOperand(0);
Value *RHS = Mul->getOperand(1);
if (isa<SExtInst>(LHS))
std::swap(LHS, RHS);
auto IsFreeTruncation = [&](Value *Op) {
if (auto *Cast = dyn_cast<CastInst>(Op)) {
if (Cast->getParent() == Mul->getParent() &&
(Cast->getOpcode() == Instruction::SExt ||
Cast->getOpcode() == Instruction::ZExt) &&
Cast->getOperand(0)->getType()->getScalarSizeInBits() <= 8)
return true;
}
return isa<Constant>(Op);
};
if ((IsFreeTruncation(LHS) &&
computeKnownBits(LHS, *DL).countMaxActiveBits() <= 8) &&
(IsFreeTruncation(RHS) && ComputeMaxSignificantBits(RHS, *DL) <= 8))
return true;
return false;
}
bool X86PartialReduction::tryMAddReplacement(Instruction *Op,
bool ReduceInOneBB) {
if (!ST->hasSSE2())
return false;
if (cast<FixedVectorType>(Op->getType())->getNumElements() < 8)
return false;
if (!cast<VectorType>(Op->getType())->getElementType()->isIntegerTy(32))
return false;
auto *Mul = dyn_cast<BinaryOperator>(Op);
if (!Mul || Mul->getOpcode() != Instruction::Mul)
return false;
Value *LHS = Mul->getOperand(0);
Value *RHS = Mul->getOperand(1);
if (ReduceInOneBB && matchVPDPBUSDPattern(ST, Mul, DL))
return false;
if (ST->hasSSE41()) {
if (LHS == RHS) {
if (!isa<Constant>(LHS) && !LHS->hasNUses(2))
return false;
} else {
if (!isa<Constant>(LHS) && !LHS->hasOneUse())
return false;
if (!isa<Constant>(RHS) && !RHS->hasOneUse())
return false;
}
}
auto CanShrinkOp = [&](Value *Op) {
auto IsFreeTruncation = [&](Value *Op) {
if (auto *Cast = dyn_cast<CastInst>(Op)) {
if (Cast->getParent() == Mul->getParent() &&
(Cast->getOpcode() == Instruction::SExt ||
Cast->getOpcode() == Instruction::ZExt) &&
Cast->getOperand(0)->getType()->getScalarSizeInBits() <= 16)
return true;
}
return isa<Constant>(Op);
};
if (IsFreeTruncation(Op) &&
ComputeNumSignBits(Op, *DL, 0, nullptr, Mul) > 16)
return true;
if (auto *BO = dyn_cast<BinaryOperator>(Op)) {
if (BO->getParent() == Mul->getParent() &&
IsFreeTruncation(BO->getOperand(0)) &&
IsFreeTruncation(BO->getOperand(1)) &&
ComputeNumSignBits(Op, *DL, 0, nullptr, Mul) > 16)
return true;
}
return false;
};
if (!CanShrinkOp(LHS) && !CanShrinkOp(RHS))
return false;
IRBuilder<> Builder(Mul);
auto *MulTy = cast<FixedVectorType>(Op->getType());
unsigned NumElts = MulTy->getNumElements();
SmallVector<int, 16> EvenMask(NumElts / 2);
SmallVector<int, 16> OddMask(NumElts / 2);
for (int i = 0, e = NumElts / 2; i != e; ++i) {
EvenMask[i] = i * 2;
OddMask[i] = i * 2 + 1;
}
Value *NewMul = Builder.CreateMul(Mul->getOperand(0), Mul->getOperand(1));
Value *EvenElts = Builder.CreateShuffleVector(NewMul, NewMul, EvenMask);
Value *OddElts = Builder.CreateShuffleVector(NewMul, NewMul, OddMask);
Value *MAdd = Builder.CreateAdd(EvenElts, OddElts);
SmallVector<int, 32> ConcatMask(NumElts);
std::iota(ConcatMask.begin(), ConcatMask.end(), 0);
Value *Zero = Constant::getNullValue(MAdd->getType());
Value *Concat = Builder.CreateShuffleVector(MAdd, Zero, ConcatMask);
Mul->replaceAllUsesWith(Concat);
Mul->eraseFromParent();
return true;
}
bool X86PartialReduction::trySADReplacement(Instruction *Op) {
if (!ST->hasSSE2())
return false;
if (!cast<VectorType>(Op->getType())->getElementType()->isIntegerTy(32))
return false;
Value *LHS;
if (match(Op, PatternMatch::m_Intrinsic<Intrinsic::abs>())) {
LHS = Op->getOperand(0);
} else {
auto *SI = dyn_cast<SelectInst>(Op);
if (!SI)
return false;
Value *RHS;
auto SPR = matchSelectPattern(SI, LHS, RHS);
if (SPR.Flavor != SPF_ABS)
return false;
}
auto *Sub = dyn_cast<BinaryOperator>(LHS);
if (!Sub || Sub->getOpcode() != Instruction::Sub)
return false;
auto getZeroExtendedVal = [](Value *Op) -> Value * {
if (auto *ZExt = dyn_cast<ZExtInst>(Op))
if (cast<VectorType>(ZExt->getOperand(0)->getType())
->getElementType()
->isIntegerTy(8))
return ZExt->getOperand(0);
return nullptr;
};
Value *Op0 = getZeroExtendedVal(Sub->getOperand(0));
Value *Op1 = getZeroExtendedVal(Sub->getOperand(1));
if (!Op0 || !Op1)
return false;
IRBuilder<> Builder(Op);
auto *OpTy = cast<FixedVectorType>(Op->getType());
unsigned NumElts = OpTy->getNumElements();
unsigned IntrinsicNumElts;
Intrinsic::ID IID;
if (ST->hasBWI() && NumElts >= 64) {
IID = Intrinsic::x86_avx512_psad_bw_512;
IntrinsicNumElts = 64;
} else if (ST->hasAVX2() && NumElts >= 32) {
IID = Intrinsic::x86_avx2_psad_bw;
IntrinsicNumElts = 32;
} else {
IID = Intrinsic::x86_sse2_psad_bw;
IntrinsicNumElts = 16;
}
Function *PSADBWFn = Intrinsic::getDeclaration(Op->getModule(), IID);
if (NumElts < 16) {
SmallVector<int, 32> ConcatMask(16);
for (unsigned i = 0; i != NumElts; ++i)
ConcatMask[i] = i;
for (unsigned i = NumElts; i != 16; ++i)
ConcatMask[i] = (i % NumElts) + NumElts;
Value *Zero = Constant::getNullValue(Op0->getType());
Op0 = Builder.CreateShuffleVector(Op0, Zero, ConcatMask);
Op1 = Builder.CreateShuffleVector(Op1, Zero, ConcatMask);
NumElts = 16;
}
auto *I32Ty =
FixedVectorType::get(Builder.getInt32Ty(), IntrinsicNumElts / 4);
assert(NumElts % IntrinsicNumElts == 0 && "Unexpected number of elements!");
unsigned NumSplits = NumElts / IntrinsicNumElts;
SmallVector<Value *, 4> Ops(NumSplits);
for (unsigned i = 0; i != NumSplits; ++i) {
SmallVector<int, 64> ExtractMask(IntrinsicNumElts);
std::iota(ExtractMask.begin(), ExtractMask.end(), i * IntrinsicNumElts);
Value *ExtractOp0 = Builder.CreateShuffleVector(Op0, Op0, ExtractMask);
Value *ExtractOp1 = Builder.CreateShuffleVector(Op1, Op0, ExtractMask);
Ops[i] = Builder.CreateCall(PSADBWFn, {ExtractOp0, ExtractOp1});
Ops[i] = Builder.CreateBitCast(Ops[i], I32Ty);
}
assert(isPowerOf2_32(NumSplits) && "Expected power of 2 splits");
unsigned Stages = Log2_32(NumSplits);
for (unsigned s = Stages; s > 0; --s) {
unsigned NumConcatElts =
cast<FixedVectorType>(Ops[0]->getType())->getNumElements() * 2;
for (unsigned i = 0; i != 1U << (s - 1); ++i) {
SmallVector<int, 64> ConcatMask(NumConcatElts);
std::iota(ConcatMask.begin(), ConcatMask.end(), 0);
Ops[i] = Builder.CreateShuffleVector(Ops[i*2], Ops[i*2+1], ConcatMask);
}
}
NumElts = cast<FixedVectorType>(OpTy)->getNumElements();
if (NumElts == 2) {
Ops[0] = Builder.CreateShuffleVector(Ops[0], Ops[0], ArrayRef<int>{0, 1});
} else if (NumElts >= 8) {
SmallVector<int, 32> ConcatMask(NumElts);
unsigned SubElts =
cast<FixedVectorType>(Ops[0]->getType())->getNumElements();
for (unsigned i = 0; i != SubElts; ++i)
ConcatMask[i] = i;
for (unsigned i = SubElts; i != NumElts; ++i)
ConcatMask[i] = (i % SubElts) + SubElts;
Value *Zero = Constant::getNullValue(Ops[0]->getType());
Ops[0] = Builder.CreateShuffleVector(Ops[0], Zero, ConcatMask);
}
Op->replaceAllUsesWith(Ops[0]);
Op->eraseFromParent();
return true;
}
static Value *matchAddReduction(const ExtractElementInst &EE,
bool &ReduceInOneBB) {
ReduceInOneBB = true;
auto *Index = dyn_cast<ConstantInt>(EE.getIndexOperand());
if (!Index || !Index->isNullValue())
return nullptr;
const auto *BO = dyn_cast<BinaryOperator>(EE.getVectorOperand());
if (!BO || BO->getOpcode() != Instruction::Add || !BO->hasOneUse())
return nullptr;
if (EE.getParent() != BO->getParent())
ReduceInOneBB = false;
unsigned NumElems = cast<FixedVectorType>(BO->getType())->getNumElements();
if (!isPowerOf2_32(NumElems))
return nullptr;
const Value *Op = BO;
unsigned Stages = Log2_32(NumElems);
for (unsigned i = 0; i != Stages; ++i) {
const auto *BO = dyn_cast<BinaryOperator>(Op);
if (!BO || BO->getOpcode() != Instruction::Add)
return nullptr;
if (EE.getParent() != BO->getParent())
ReduceInOneBB = false;
if (i != 0 && !BO->hasNUses(2))
return nullptr;
Value *LHS = BO->getOperand(0);
Value *RHS = BO->getOperand(1);
auto *Shuffle = dyn_cast<ShuffleVectorInst>(LHS);
if (Shuffle) {
Op = RHS;
} else {
Shuffle = dyn_cast<ShuffleVectorInst>(RHS);
Op = LHS;
}
if (!Shuffle || Shuffle->getOperand(0) != Op)
return nullptr;
unsigned MaskEnd = 1 << i;
for (unsigned Index = 0; Index < MaskEnd; ++Index)
if (Shuffle->getMaskValue(Index) != (int)(MaskEnd + Index))
return nullptr;
}
return const_cast<Value *>(Op);
}
static bool isReachableFromPHI(PHINode *Phi, BinaryOperator *BO) {
if (!Phi->hasOneUse())
return false;
Instruction *U = cast<Instruction>(*Phi->user_begin());
if (U == BO)
return true;
while (U->hasOneUse() && U->getOpcode() == BO->getOpcode())
U = cast<Instruction>(*U->user_begin());
return U == BO;
}
static void collectLeaves(Value *Root, SmallVectorImpl<Instruction *> &Leaves) {
SmallPtrSet<Value *, 8> Visited;
SmallVector<Value *, 8> Worklist;
Worklist.push_back(Root);
while (!Worklist.empty()) {
Value *V = Worklist.pop_back_val();
if (!Visited.insert(V).second)
continue;
if (auto *PN = dyn_cast<PHINode>(V)) {
if (!PN->hasNUses(PN == Root ? 2 : 1))
break;
append_range(Worklist, PN->incoming_values());
continue;
}
if (auto *BO = dyn_cast<BinaryOperator>(V)) {
if (BO->getOpcode() == Instruction::Add) {
if (BO->hasNUses(BO == Root ? 2 : 1)) {
append_range(Worklist, BO->operands());
continue;
}
if (BO->hasNUses(BO == Root ? 3 : 2)) {
PHINode *PN = nullptr;
for (auto *U : BO->users())
if (auto *P = dyn_cast<PHINode>(U))
if (!Visited.count(P))
PN = P;
if (!PN || PN->getNumIncomingValues() != 2)
continue;
if (!isReachableFromPHI(PN, BO))
continue;
append_range(Worklist, BO->operands());
}
}
}
if (auto *I = dyn_cast<Instruction>(V)) {
if (!V->hasNUses(I == Root ? 2 : 1))
continue;
Leaves.push_back(I);
}
}
}
bool X86PartialReduction::runOnFunction(Function &F) {
if (skipFunction(F))
return false;
auto *TPC = getAnalysisIfAvailable<TargetPassConfig>();
if (!TPC)
return false;
auto &TM = TPC->getTM<X86TargetMachine>();
ST = TM.getSubtargetImpl(F);
DL = &F.getParent()->getDataLayout();
bool MadeChange = false;
for (auto &BB : F) {
for (auto &I : BB) {
auto *EE = dyn_cast<ExtractElementInst>(&I);
if (!EE)
continue;
bool ReduceInOneBB;
Value *Root = matchAddReduction(*EE, ReduceInOneBB);
if (!Root)
continue;
SmallVector<Instruction *, 8> Leaves;
collectLeaves(Root, Leaves);
for (Instruction *I : Leaves) {
if (tryMAddReplacement(I, ReduceInOneBB)) {
MadeChange = true;
continue;
}
if (I != Root && trySADReplacement(I))
MadeChange = true;
}
}
}
return MadeChange;
}