[llvm] [VectorCombine] Fold Deinterleave/Interleave Pairs (PR #211022)
Graham Hunter via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 7 09:50:54 PDT 2026
================
@@ -5882,6 +5883,265 @@ bool VectorCombine::foldInsExtVectorToShuffle(Instruction &I) {
return true;
}
+namespace {
+
+class InterleavedElementwiseChain {
+ struct ElementwiseStep {
+ SmallVector<Instruction *, 8> Insts;
+ unsigned ChainOperand;
+
+ ElementwiseStep(ArrayRef<Instruction *> Insts, unsigned ChainOperand)
+ : Insts(Insts.begin(), Insts.end()), ChainOperand(ChainOperand) {}
+ };
+
+ IRBuilderBase &Builder;
+ Value *Root;
+ SmallVector<ElementwiseStep, 4> Steps;
+
+ static unsigned getNumDataOperands(Instruction *Inst) {
+ if (auto *II = dyn_cast<IntrinsicInst>(Inst))
+ return II->arg_size();
+ return Inst->getNumOperands();
+ }
+
+ static Value *getSplatOrScalar(Value *V) {
+ return isa<VectorType>(V->getType()) ? getSplatValue(V) : V;
+ }
+
+ static bool isSupportedElementwise(Instruction *Inst) {
+ auto *ResultTy = dyn_cast<VectorType>(Inst->getType());
+ if (!ResultTy)
+ return false;
+
+ if (auto *II = dyn_cast<IntrinsicInst>(Inst)) {
+ if (II->hasOperandBundles() ||
+ !isTriviallyVectorizable(II->getIntrinsicID()))
+ return false;
+ } else if (!isa<BinaryOperator, UnaryOperator, CastInst, CmpInst,
+ SelectInst, FreezeInst>(Inst)) {
+ return false;
+ }
+
+ // Reject operations that change the element-count.
+ for (unsigned Op = 0, E = getNumDataOperands(Inst); Op != E; ++Op) {
+ auto *OperandTy = dyn_cast<VectorType>(Inst->getOperand(Op)->getType());
+ if (OperandTy &&
+ OperandTy->getElementCount() != ResultTy->getElementCount())
+ return false;
+ }
+
+ return true;
+ }
+
+ Value *createWideInstruction(Instruction *NarrowInst,
+ ArrayRef<Value *> NewOperands,
+ VectorType *WideResultTy) {
+ if (isa<BinaryOperator, UnaryOperator>(NarrowInst))
+ return Builder.CreateNAryOp(NarrowInst->getOpcode(), NewOperands);
+ if (auto *Cast = dyn_cast<CastInst>(NarrowInst))
+ return Builder.CreateCast(Cast->getOpcode(), NewOperands[0],
+ WideResultTy);
+ if (auto *Cmp = dyn_cast<CmpInst>(NarrowInst))
+ return Builder.CreateCmp(Cmp->getPredicate(), NewOperands[0],
+ NewOperands[1]);
+ if (isa<SelectInst>(NarrowInst))
+ return Builder.CreateSelect(NewOperands[0], NewOperands[1],
+ NewOperands[2]);
+ if (isa<FreezeInst>(NarrowInst))
+ return Builder.CreateFreeze(NewOperands[0]);
+ if (auto *II = dyn_cast<IntrinsicInst>(NarrowInst))
+ return Builder.CreateIntrinsic(WideResultTy, II->getIntrinsicID(),
+ NewOperands);
+ llvm_unreachable("Unsupported instruction");
+ }
+
+public:
+ InterleavedElementwiseChain(IRBuilderBase &Builder, Value *Root)
+ : Builder(Builder), Root(Root) {}
+
+ /// Visit a list of instructions and check if they can be rewritten as a
+ /// single wider instruction. If so, the list is appened to \p Steps.
+ bool visitInstLevel(ArrayRef<Instruction *> Insts,
+ ArrayRef<unsigned> OperandNumbers) {
+ if (Insts.empty() || Insts.size() != OperandNumbers.size())
+ return false;
+
+ Instruction *FirstInst = Insts.front();
+ unsigned ChainOperand = OperandNumbers.front();
+
+ if (!isSupportedElementwise(FirstInst))
+ return false;
+
+ for (unsigned Index = 1; Index != Insts.size(); ++Index)
+ if (OperandNumbers[Index] != ChainOperand ||
+ !FirstInst->isSameOperationAs(Insts[Index]))
+ return false;
+
+ // Non-chain operands must be either the same scalar or splats of that
+ // scalar. This intentionally rejects differing poison/undef or non-splat
+ // vector operands between chains.
+ for (unsigned Op = 0, E = getNumDataOperands(FirstInst); Op != E; ++Op) {
+ if (Op == ChainOperand)
+ continue;
+
+ Value *CommonValue = getSplatOrScalar(FirstInst->getOperand(Op));
+ if (!CommonValue || any_of(drop_begin(Insts), [&](Instruction *Inst) {
+ return getSplatOrScalar(Inst->getOperand(Op)) != CommonValue;
+ }))
+ return false;
+ }
+
+ Steps.emplace_back(Insts, ChainOperand);
+ return true;
+ }
+
+ /// Go through all the collected \p Steps and rewrite each of them as a wide
+ /// instruction.
+ Value *widenInstructions() {
+ Value *WideValue = Root;
+ ElementCount WideEC =
+ cast<VectorType>(WideValue->getType())->getElementCount();
+
+ for (const ElementwiseStep &Step : Steps) {
+ Instruction *NarrowInst = Step.Insts.front();
+
+ Builder.SetInsertPoint(NarrowInst);
+ Builder.SetCurrentDebugLocation(NarrowInst->getDebugLoc());
+
+ unsigned NumOperands = getNumDataOperands(NarrowInst);
+ SmallVector<Value *, 4> NewOperands;
+ NewOperands.reserve(NumOperands);
+
+ for (unsigned Op = 0; Op != NumOperands; ++Op) {
+ Value *Operand = NarrowInst->getOperand(Op);
+
+ if (Op == Step.ChainOperand)
+ Operand = WideValue;
+ else if (isa<VectorType>(Operand->getType()))
+ Operand = Builder.CreateVectorSplat(WideEC, getSplatValue(Operand));
+ NewOperands.push_back(Operand);
+ }
+
+ auto *WideResultTy =
+ VectorType::get(NarrowInst->getType()->getScalarType(), WideEC);
+ Value *NewValue =
+ createWideInstruction(NarrowInst, NewOperands, WideResultTy);
+
+ SmallVector<Value *, 8> NarrowInsts(Step.Insts.begin(), Step.Insts.end());
+ propagateIRFlags(NewValue, NarrowInsts);
+
+ if (auto *NewInst = dyn_cast<Instruction>(NewValue))
+ propagateMetadata(NewInst, NarrowInsts);
+
+ WideValue = NewValue;
+ }
+
+ return WideValue;
+ }
+};
+
+} // namespace
+
+/// Fold away a matched pair of vector.deinterleave/interleave intrinsics
+/// with a chain of elementwise operations on each between the
+/// deinterleave and interleave.
+///
+/// For example:
+/// ```
+/// %d = call { <2 x i16>, <2 x i16> } @deinterleave2.v4i16(<4 x i16> %v)
+/// %f0 = extractvalue { <2 x i16>, <2 x i16> } %d, 0
+/// %f1 = extractvalue { <2 x i16>, <2 x i16> } %d, 1
+///
+/// %u0 = add <2 x i16> %f0, splat (i16 3)
+/// %u1 = add <2 x i16> %f1, splat (i16 3)
+///
+/// %r = call <4 x i16> @interleave2.v4i16(<2 x i16> %u0, <2 x i16> %u1)
+/// ```
+/// Folds to:
+/// ```
+/// %r = add <4 x i16> %v, splat (i16 3)
+/// ```
+bool VectorCombine::foldDeinterleaveInterleavePair(Instruction &I) {
+ auto *Deinterleave = dyn_cast<IntrinsicInst>(&I);
+ if (!Deinterleave)
+ return false;
+
+ unsigned Factor =
+ getDeinterleaveIntrinsicFactor(Deinterleave->getIntrinsicID());
+ if (!Factor || Deinterleave->hasOperandBundles() ||
+ !Deinterleave->hasNUndroppableUses(Factor))
+ return false;
+
+ const Intrinsic::ID InterleaveIID =
+ Intrinsic::getInterleaveIntrinsicID(Factor);
+
+ // Collect one extract for each deinterleaved field.
+ SmallVector<Instruction *, 8> CurrentInsts(Factor, nullptr);
+ for (Use &U : Deinterleave->uses()) {
+ if (U.getUser()->isDroppable())
+ continue;
+
+ auto *Extract = dyn_cast<ExtractValueInst>(U.getUser());
+ if (!Extract || Extract->getNumIndices() != 1)
+ return false;
+
+ unsigned Index = *Extract->idx_begin();
+ if (Index >= Factor || CurrentInsts[Index])
+ return false;
+
+ CurrentInsts[Index] = Extract;
+ }
+
+ InterleavedElementwiseChain Chain(Builder, Deinterleave->getArgOperand(0));
+ IntrinsicInst *Interleave = nullptr;
+ unsigned NumVisited = 0;
+
+ // Traverse the Factor use chains with a breadth-first search.
+ // At each level, expect every chain to perform the same operation with the
+ // preceding chain value at the same operand position, until they all reach
+ // the matching interleave.
+ SmallVector<unsigned, 8> OperandNumbers(CurrentInsts.size());
+ while (NumVisited + Factor <= MaxInstrsToScan) {
+ NumVisited += Factor;
+
+ for (auto [Current, OpNumber] : zip_equal(CurrentInsts, OperandNumbers)) {
+ Use *U = Current->getSingleUndroppableUse();
+ auto *Next = U ? dyn_cast<Instruction>(U->getUser()) : nullptr;
----------------
huntergr-arm wrote:
```suggestion
auto *Next = dyn_cast_if_present<Instruction>(U->getUser());
```
https://github.com/llvm/llvm-project/pull/211022
More information about the llvm-commits
mailing list