[llvm] [InstCombine] Fold zext into de-interleaving (factor=2) instructions (PR #195330)
Min-Yih Hsu via llvm-commits
llvm-commits at lists.llvm.org
Tue May 26 11:55:07 PDT 2026
================
@@ -3296,3 +3296,168 @@ Instruction *InstCombinerImpl::visitShuffleVectorInst(ShuffleVectorInst &SVI) {
return MadeChange ? &SVI : nullptr;
}
+
+/// Given the following de-interleaving shufflevectors and the consuming zexts:
+/// ```
+/// %f0 = shufflevector <8 x i32> %v, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
+/// %f1 = shufflevector <8 x i32> %v, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+/// %z0 = zext <4 x i32> %f0 to <4 x i64>
+/// %z1 = zext <4 x i32> %f1 to <4 x i64>
+/// ```
+/// We can actually bitcast the input value, `%v` first before replacing zexts
+/// with simple arithmetics on this new bitcast:
+/// ```
+/// %bc = bitcast <8 x i32> %v to <4 x i64>
+// %z0 = and <4 x i64> %bc, splat (i64 4294967295)
+// %z1 = lshr <4 x i64> %bc, splat (i64 32)
+/// ```
+/// This transformation is almost always benefitial as shufflevector is more
+/// expensive than normal arithmetics.
+Instruction *
+InstCombinerImpl::foldExtractionOfVectorDeinterleave(ZExtInst &RootZExt) {
+ // This pattern involves bitcast that is not compatible with big endian.
+ if (DL.isBigEndian())
+ return nullptr;
+
+ // The actual value that got de-interleaved.
+ Value *DIV;
+
+ using namespace PatternMatch;
+ Value *SVI = nullptr, *DI = nullptr;
+ if (!match(&RootZExt,
+ m_ZExt(m_CombineOr(
+ m_ExtractValue(m_Value(DI, m_Deinterleave2(m_Value(DIV)))),
+ m_Value(SVI, m_Shuffle(m_Value(), m_Value()))))))
+ return nullptr;
+
+ auto isDeinterleaveShuffle =
+ [](Instruction *I) -> std::pair<Value *, unsigned> {
+ Value *V;
+ ArrayRef<int> ShuffleMask;
+ unsigned Index;
+ if (match(I, m_Shuffle(m_Value(V), m_Undef(), m_Mask(ShuffleMask))) &&
+ isa<FixedVectorType>(V->getType())) {
+ unsigned NumInputElements =
+ cast<VectorType>(V->getType())->getElementCount().getFixedValue();
+ if (ShuffleVectorInst::isDeInterleaveMaskOfFactor(ShuffleMask, 2,
+ Index) &&
+ Index < 2 &&
+ ShuffleVectorInst::isSingleSourceMask(ShuffleMask,
+ NumInputElements) &&
+ ShuffleMask.size() * 2 == NumInputElements)
+ return {V, Index};
+ }
+ return {nullptr, UINT_MAX};
+ };
+
+ // Validate either the shufflevector or the vector.deinterleave2 and obtain
+ // the value they're de-interleaving.
+ if (SVI) {
+ // We will find other shufflevectors later.
+ DIV = isDeinterleaveShuffle(cast<Instruction>(SVI)).first;
+ if (!DIV)
+ return nullptr;
+ } else {
+ // We should already capture the value that got de-interleaved (i.e. DIV).
+ assert(DI && DIV);
+ if (!all_of(DI->users(), [](User *Usr) -> bool {
+ auto *EV = dyn_cast<ExtractValueInst>(Usr);
+ return EV && EV->getNumIndices() == 1;
+ }))
+ return nullptr;
+ }
+
+ auto *InputVecTy = dyn_cast<VectorType>(DIV->getType());
+ if (!InputVecTy)
+ return nullptr;
+ auto *InElementTy = dyn_cast<IntegerType>(InputVecTy->getElementType());
+ if (!InElementTy)
+ return nullptr;
+ if (!InputVecTy->getElementCount().isKnownEven())
+ return nullptr;
+
+ // {Field instruction, Field index}
+ SmallVector<std::pair<Instruction *, unsigned>, 4> Fields;
+ if (SVI) {
+ for (auto *Usr : DIV->users()) {
+ auto *FieldI = dyn_cast<Instruction>(Usr);
+ if (!FieldI)
+ continue;
+ auto [V, Index] = isDeinterleaveShuffle(FieldI);
+ if (V != DIV)
+ continue;
+ assert(Index < 2);
+ Fields.push_back({FieldI, Index});
+ }
+ } else {
+ // llvm.vector.deinterleave2.
+ for (User *Field : DI->users()) {
+ auto *FieldI = cast<ExtractValueInst>(Field);
+ unsigned FieldIdx = *FieldI->idx_begin();
+ assert(FieldIdx < 2);
+ Fields.push_back({FieldI, FieldIdx});
+ }
+ }
+
+ // We commit the transformation only if all the field users can be replaced,
+ // otherwise the primary de-interleaving construction, regardless of
+ // llvm.vector.deinterleave2 or shufflevectors, will still be there.
+ SmallVector<ZExtInst *, 4> Field0Replacements;
+ SmallVector<ZExtInst *, 4> Field1Replacements;
+ for (auto [Field, FieldIdx] : Fields) {
+ for (User *FieldUsr : Field->users()) {
+ auto *ZExt = dyn_cast<ZExtInst>(FieldUsr);
+ if (!ZExt)
+ return nullptr;
+ // Only if it's doubling the element size.
+ if (ZExt->getDestTy() != ZExt->getSrcTy()->getExtendedType())
+ return nullptr;
+ if (FieldIdx)
+ Field1Replacements.push_back(ZExt);
+ else
+ Field0Replacements.push_back(ZExt);
+ }
+ }
+
+ // Double the element size but half the vector length.
+ auto *BitcastedTy = VectorType::getExtendedElementVectorType(InputVecTy);
+ BitcastedTy = VectorType::getHalfElementsVectorType(BitcastedTy);
+ // Since we're going to "merge" lanes via bitcast, we need to freeze any
+ // potential poison lanes first.
+ Value *Freeze = Builder.CreateFreeze(DIV);
+ Value *Bitcast = Builder.CreateBitCast(Freeze, BitcastedTy);
+ unsigned InElementBitWidth = InElementTy->getBitWidth();
+ auto Mask = APInt::getLowBitsSet(InElementBitWidth * 2, InElementBitWidth);
+ Value *NewField0 = Builder.CreateAnd(Bitcast, Mask);
+ Value *NewField1 = Builder.CreateLShr(Bitcast, InElementBitWidth);
+
+ Value *RootNewField = nullptr;
+ for (ZExtInst *I : Field0Replacements) {
+ // The root ZExt will be replaced by the InstCombiner upon
+ // returning from this function.
+ if (I == &RootZExt) {
+ assert(!RootNewField);
+ RootNewField = NewField0;
+ continue;
+ }
+ replaceInstUsesWith(*I, NewField0);
+ // Make sure non-root ZExt are in the worklist so that they
+ // can be removed in the following iterations.
+ addToWorklist(I);
+ }
+ for (ZExtInst *I : Field1Replacements) {
+ if (I == &RootZExt) {
+ assert(!RootNewField);
+ RootNewField = NewField1;
+ continue;
+ }
+ replaceInstUsesWith(*I, NewField1);
+ addToWorklist(I);
+ }
+
+ assert(RootNewField && "cannot find replacement for the root zext");
+ auto *NewRootInst = cast<Instruction>(RootNewField);
+ // The InstCombiner expects a parent-less instruction.
----------------
mshockwave wrote:
Yes that works. The code is now updated.
https://github.com/llvm/llvm-project/pull/195330
More information about the llvm-commits
mailing list