blob: 61b32366153a94ffa53b918b32991f71d83067d0 [file] [edit]
//===------- SVEShuffleOpts - SVE Shuffle Optimization --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Tries to pattern match and combine scalable vector shuffles that could
// be more efficiently performed by tbl instructions.
//
// An example would be a loop with 4 multiply-accumulate reductions, where the
// new data in each vector iterations comes from a 4-way deinterleaving of
// smaller datatypes loaded from memory which are then zero extended.
//
// Something like the following:
// %bgra = call ... @llvm.masked.load
// %deinterleave = call ... @llvm.vector.deinterleave4(%bgra)
// If the load was of a <vscale x 8 x i16>, we now have 4 deinterleaved
// <vscale x 2 x i16> values.
// %b.i16 = extractvalue %deinterleave, 0
// %b.i64 = zext <vscale x 2 x i16> %b.i16 to <vscale x 2 x i64>
// %acc.b.next = add <vscale x 2 x i64> %acc.b, %b.i64
// <repeat for the other 3 subvectors>
//
// If the initial load is a legal vector rather than 4x the size (generating a
// structured ld4 instead), we would see multiple uunpkhi/lo instructions for
// the extensions, followed by uzp1/2 instructions for the deinterleave.
// Instead, we can replace all of those with 4 tbl instructions. The tradeoff,
// of course, is that we now have 4 mask values to maintain which may increase
// register pressure.
//
// This basic transform could be performed in CodeGenPrepare (as the equivalent
// for NEON is), or in a DAG Combine. However, we hope to extend it to detect
// other shuffles that we can fold into the tbl. Extending the above example,
// if instead of directly adding to the accumulator we multiplied it by a
// common term for all 4 components that had been reversed:
// %common.load = call @llvm.masked.load
// %common.reverse = call @llvm.vector.reverse
// These would be loaded at the extended size, <vscale x 2 x i64> in our
// example.
// %b.mul = mul <vscale x 2 x i64> %b.i64, %common.reverse
// %acc.b.next = add <vscale x 2 x i64> %acc.b, %b.mul
// <repeat for the other 3 subvectors, using %common.reverse for each)
//
// In this case, the reverse isn't applied to the deinterleaved data in the
// original IR, but to the common term multiplied by the individual bgra
// elements. If the order of the elements in the accumulator is important, we
// cannot change that. If, however, we know that the accumulator is reduced to
// a single scalar after the loop and the data is either integers or floating
// point with reassociation allowed, we could instead choose a different mask
// for the tbls to reverse the individual bgra elements instead, removing an
// additional instruction from the loop. This does require looking beyond the
// blocks in the loop, so DAGCombine won't help.
//
// We should also be able to introduce new shuffles in order to balance out
// SVE's bottom/top instruction pairs, which act on even/odd lanes instead of
// the high or low half of a register.
//
// This pass may end up being a temporary solution that is removed if we can
// create a generic vector shuffle intrinsic and move this feature to
// LoopVectorize itself, as that would allow for better cost modelling.
//
//===----------------------------------------------------------------------===//
#include "AArch64.h"
#include "AArch64Subtarget.h"
#include "AArch64TargetMachine.h"
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/LoopPass.h"
#include "llvm/Analysis/MemorySSA.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/CodeGen/TargetLowering.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/IntrinsicsAArch64.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/InitializePasses.h"
#include <array>
using namespace llvm;
using namespace llvm::PatternMatch;
#define DEBUG_TYPE "aarch64-sve-shuffle-opts"
/// A mapping between a vector_deinterleaveN intrinsic and extending cast
/// instructions used on the resulting subvectors.
using DeinterleaveMap = SmallDenseMap<CallInst *, std::array<CastInst *, 4>>;
/// Evaluate a deinterleave and see what the uses are. If we find other
/// operations that we can combine into a tbl shuffle, add the deinterleave and
/// the operations (currently only zext or uitofp) to the candidates map.
static void evaluateDeinterleave(IntrinsicInst *I, DeinterleaveMap &Candidates,
Loop &L, const AArch64TargetLowering &TL,
const DataLayout DL) {
assert(I->getIntrinsicID() == Intrinsic::vector_deinterleave4 &&
"Only deinterleave4 supported currently");
ConstantRange VScaleRange = getVScaleRange(I->getFunction(), 64);
// TBL zeroes elements with an out-of-bounds index, but for the largest
// possible SVE vector (2048b) the maximum value for i8 elements (255) is not
// large enough to encode an 'out of bounds' value. So we can only perform
// this optimization for i8 elements if we know vscale is < 16.
EVT InputVT = TL.getValueType(DL, I->getOperand(0)->getType());
if (!InputVT.isScalableVector() ||
(InputVT.getScalarSizeInBits() < 16 &&
(!VScaleRange.getUpper().ult(16) || VScaleRange.isUpperWrapped())) ||
TL.getTypeConversion(I->getContext(), InputVT).first !=
TargetLoweringBase::TypeLegal)
return;
std::array<CastInst *, 4> Extends = {};
unsigned Opcode = 0;
Type *DestTy = nullptr;
for (User *U : I->users()) {
auto *Extract = dyn_cast<ExtractValueInst>(U);
if (!Extract || !Extract->hasOneUse())
return;
// We expect only a single cast instruction as a user for the extract.
auto *Extend = dyn_cast_if_present<CastInst>(*Extract->users().begin());
if (!Extend || (!isa<ZExtInst>(Extend) && !isa<UIToFPInst>(Extend)))
return;
// We're only interested if the uses are in the loop. This is almost
// certainly the case.
if (!L.contains(Extend))
return;
Opcode = Extend->getOpcode();
DestTy = Extend->getDestTy();
// Make sure DestTy matches the input size.
if (DestTy->getPrimitiveSizeInBits() != InputVT.getSizeInBits())
return;
Extends[Extract->getIndices().front()] = Extend;
}
// Check that all extracted values are being extended the same way, and that
// we have the expected number of extensions.
if (!all_of(Extends, [DestTy, Opcode](CastInst *CI) {
return !CI || (CI->getDestTy() == DestTy && CI->getOpcode() == Opcode);
}))
return;
Candidates.try_emplace(I, Extends);
}
/// Given a map of deinterleaves to zext or uitofp casts, remove the operations
/// and replace them with tbl shuffles.
static void optimizeSVEDeinterleavedExtends(DeinterleaveMap Deinterleaves) {
for (auto &[Deinterleave, Extends] : Deinterleaves) {
VectorType *DestTy = cast<VectorType>(Extends[0]->getDestTy());
VectorType *SrcTy = cast<VectorType>(Extends[0]->getSrcTy());
unsigned DstBits = DestTy->getScalarSizeInBits();
unsigned SrcBits = SrcTy->getScalarSizeInBits();
bool IsUIToFP = isa<UIToFPInst>(Extends[0]);
VectorType *StepVecTy = VectorType::getInteger(DestTy);
Value *Input = Deinterleave->getOperand(0);
Type *InputTy = Input->getType();
APInt Invalid = APInt::getAllOnes(DstBits);
for (auto [Idx, Extend] : enumerate(Extends)) {
// If not all lanes were extracted, we can have gaps. Skip over them.
if (!Extend)
continue;
// Build the mask using stepvectors and casting.
// We want to select the Idx'th element, and every 4 elements after that.
// Each element needs to be zero extended; we can do that by providing
// tbl index values that are out of range. We can't do that nicely with
// a stepvector of the same element type as the input type, but we can
// do it with elements the size of the output type.
// E.g. for element 0 of a 16b -> 64b zext, we would start with a mask of
// 0xFFFF_FFFF_FFFF_0000 + Idx for the start of the stepvector, and use a
// step of 4. We then cast that back to an element size of 16b, yielding
// <0x0000 + Idx, 0xFFFF, 0xFFFF, 0xFFFF, 0x0004 + Idx, 0xFFFF...>.
APInt StartIdx = Invalid << SrcBits;
StartIdx += Idx;
IRBuilder<> Builder(Extend);
Value *StepVector = Builder.CreateStepVector(StepVecTy);
Value *ScaledSteps =
Builder.CreateNUWMul(StepVector, ConstantInt::get(StepVecTy, 4));
Value *ZextTbl = Builder.CreateNUWAdd(
ScaledSteps, ConstantInt::get(StepVecTy, StartIdx));
Value *FinalMask = Builder.CreateBitCast(ZextTbl, InputTy);
// Replace the deinterleave, extractvalue, and extension chain with
// a tbl directly on the input value.
Value *Tbl = Builder.CreateIntrinsic(Intrinsic::aarch64_sve_tbl,
{InputTy}, {Input, FinalMask});
Value *Widen = Builder.CreateBitCast(Tbl, StepVecTy);
if (IsUIToFP)
Widen = Builder.CreateUIToFP(Widen, DestTy);
LLVM_DEBUG(dbgs() << "SVETBLOPT: Replaced " << *Extend << " with "
<< *Widen << "\n");
Extend->replaceAllUsesWith(Widen);
Extend->eraseFromParent();
}
// Delete the unused extracts and deinterleave.
for (User *U : make_early_inc_range(Deinterleave->users()))
cast<Instruction>(U)->eraseFromParent();
Deinterleave->eraseFromParent();
}
}
static bool processLoop(Loop &L, const AArch64Subtarget &ST, DataLayout DL) {
// At present, we only want to do this for innermost loops when SVE
// is available.
if (!L.isInnermost() || !ST.isSVEorStreamingSVEAvailable())
return false;
// TODO: Pull other shuffles into the tbl where possible.
// TODO: Add more advanced cases, such as introducing shuffles so that
// the SVE odd/even BT narrowing instructions can be used.
// TODO: Support other deinterleaves.
const AArch64TargetLowering &TL = *ST.getTargetLowering();
assert(DL.isLittleEndian() &&
"Shuffle optimizations unsupported for big endian targets.");
DeinterleaveMap Candidates;
for (auto *BB : L.blocks())
for (auto &I : *BB)
if (match(&I, m_Intrinsic<Intrinsic::vector_deinterleave4>(m_Value())))
evaluateDeinterleave(cast<IntrinsicInst>(&I), Candidates, L, TL, DL);
if (Candidates.empty())
return false;
optimizeSVEDeinterleavedExtends(Candidates);
return true;
}
namespace {
struct SVEShuffleOpts : public LoopPass {
static char ID; // Pass identification, replacement for typeid
SVEShuffleOpts() : LoopPass(ID) {}
bool runOnLoop(Loop *L, LPPassManager &PM) override {
if (skipLoop(L))
return false;
TargetPassConfig &TPC = getAnalysis<TargetPassConfig>();
const AArch64TargetMachine &TM = TPC.getTM<AArch64TargetMachine>();
const AArch64Subtarget &ST =
*TM.getSubtargetImpl(*L->getHeader()->getParent());
return processLoop(*L, ST, TM.createDataLayout());
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<TargetPassConfig>();
AU.setPreservesCFG();
}
StringRef getPassName() const override { return "SVE Shuffle Optimizations"; }
};
} // end anonymous namespace
char SVEShuffleOpts::ID = 0;
static const char *name = "SVE Shuffle Optimizations";
INITIALIZE_PASS_BEGIN(SVEShuffleOpts, DEBUG_TYPE, name, false, false)
INITIALIZE_PASS_DEPENDENCY(TargetPassConfig)
INITIALIZE_PASS_END(SVEShuffleOpts, DEBUG_TYPE, name, false, false)
Pass *llvm::createSVEShuffleOptsPass() { return new SVEShuffleOpts(); }
PreservedAnalyses SVEShuffleOptsPass::run(Loop &L, LoopAnalysisManager &AM,
LoopStandardAnalysisResults &AR,
LPMUpdater &U) {
const AArch64Subtarget &ST =
*TM.getSubtargetImpl(*L.getHeader()->getParent());
if (processLoop(L, ST, TM.createDataLayout())) {
PreservedAnalyses PA;
PA.preserveSet<CFGAnalyses>();
PA.preserve<TargetIRAnalysis>();
PA.preserve<AssumptionAnalysis>();
PA.preserve<MemorySSAAnalysis>();
return PA;
}
return PreservedAnalyses::all();
}