[llvm] [LV] Implement SWAR loop vectorization (PR #69306)

via llvm-commits llvm-commits at lists.llvm.org
Wed Oct 18 02:01:21 PDT 2023


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Sergey Kachkov (skachkov-sc)

<details>
<summary>Changes</summary>

Implement "SIMD within a register" (SWAR) loop vectorization. This technique can vectorize some loops on targets without vector registers. Currently supported instructions are:
1. Consecutive loads/stores
2. Bitwise operations (add/sub)
3. Shifts (shl, lshr) with constant 2nd operand
4. Addition/Subtraction


---

Patch is 95.41 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/69306.diff


6 Files Affected:

- (modified) llvm/lib/Transforms/Vectorize/LoopVectorize.cpp (+223-17) 
- (modified) llvm/lib/Transforms/Vectorize/VPRecipeBuilder.h (+4) 
- (modified) llvm/lib/Transforms/Vectorize/VPlan.h (+72) 
- (modified) llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp (+122) 
- (modified) llvm/lib/Transforms/Vectorize/VPlanValue.h (+3) 
- (added) llvm/test/Transforms/LoopVectorize/swar-vectorization.ll (+1150) 


``````````diff
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 2ca7e75f97f0f02..df2bbb509ae091c 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -172,6 +172,10 @@ STATISTIC(LoopsVectorized, "Number of loops vectorized");
 STATISTIC(LoopsAnalyzed, "Number of loops analyzed for vectorization");
 STATISTIC(LoopsEpilogueVectorized, "Number of epilogues vectorized");
 
+static cl::opt<bool> EnableSWARVectorization(
+    "enable-swar-vectorization", cl::init(false), cl::Hidden,
+    cl::desc("Enable SWAR (SIMD within a register) vectorization"));
+
 static cl::opt<bool> EnableEpilogueVectorization(
     "enable-epilogue-vectorization", cl::init(true), cl::Hidden,
     cl::desc("Enable vectorization of epilogue loops."));
@@ -1203,10 +1207,10 @@ class LoopVectorizationCostModel {
                              AssumptionCache *AC,
                              OptimizationRemarkEmitter *ORE, const Function *F,
                              const LoopVectorizeHints *Hints,
-                             InterleavedAccessInfo &IAI)
+                             InterleavedAccessInfo &IAI, bool UseSWAR)
       : ScalarEpilogueStatus(SEL), TheLoop(L), PSE(PSE), LI(LI), Legal(Legal),
         TTI(TTI), TLI(TLI), DB(DB), AC(AC), ORE(ORE), TheFunction(F),
-        Hints(Hints), InterleaveInfo(IAI) {}
+        Hints(Hints), InterleaveInfo(IAI), UseSWAR(UseSWAR) {}
 
   /// \return An upper bound for the vectorization factors (both fixed and
   /// scalable). If the factors are 0, vectorization and interleaving should be
@@ -1712,6 +1716,9 @@ class LoopVectorizationCostModel {
   /// of elements.
   ElementCount getMaxLegalScalableVF(unsigned MaxSafeElements);
 
+  /// Calculate cost of SWAR instruction.
+  InstructionCost getSWARInstructionCost(Instruction *I, unsigned VF);
+
   /// Returns the execution time cost of an instruction for a given vector
   /// width. Vector width of one means scalar.
   VectorizationCostTy getInstructionCost(Instruction *I, ElementCount VF);
@@ -1921,6 +1928,9 @@ class LoopVectorizationCostModel {
 
   /// All element types found in the loop.
   SmallPtrSet<Type *, 16> ElementTypesInLoop;
+
+  /// Use SWAR vectorization mode.
+  const bool UseSWAR;
 };
 } // end namespace llvm
 
@@ -5071,9 +5081,11 @@ ElementCount LoopVectorizationCostModel::getMaximizedVFForTarget(
     unsigned MaxTripCount, unsigned SmallestType, unsigned WidestType,
     ElementCount MaxSafeVF, bool FoldTailByMasking) {
   bool ComputeScalableMaxVF = MaxSafeVF.isScalable();
-  const TypeSize WidestRegister = TTI.getRegisterBitWidth(
+  const TargetTransformInfo::RegisterKind RegKind =
       ComputeScalableMaxVF ? TargetTransformInfo::RGK_ScalableVector
-                           : TargetTransformInfo::RGK_FixedWidthVector);
+      : UseSWAR            ? TargetTransformInfo::RGK_Scalar
+                           : TargetTransformInfo::RGK_FixedWidthVector;
+  const TypeSize WidestRegister = TTI.getRegisterBitWidth(RegKind);
 
   // Convenience function to return the minimum of two ElementCounts.
   auto MinVF = [](const ElementCount &LHS, const ElementCount &RHS) {
@@ -5128,9 +5140,6 @@ ElementCount LoopVectorizationCostModel::getMaximizedVFForTarget(
         FoldTailByMasking ? MaxVectorElementCount.isScalable() : false);
   }
 
-  TargetTransformInfo::RegisterKind RegKind =
-      ComputeScalableMaxVF ? TargetTransformInfo::RGK_ScalableVector
-                           : TargetTransformInfo::RGK_FixedWidthVector;
   ElementCount MaxVF = MaxVectorElementCount;
   if (MaximizeBandwidth || (MaximizeBandwidth.getNumOccurrences() == 0 &&
                             TTI.shouldMaximizeVectorBandwidth(RegKind))) {
@@ -6684,6 +6693,65 @@ LoopVectorizationCostModel::getMemoryInstructionCost(Instruction *I,
   return getWideningCost(I, VF);
 }
 
+InstructionCost
+LoopVectorizationCostModel::getSWARInstructionCost(Instruction *I,
+                                                   unsigned VF) {
+  uint64_t RegSize =
+      TTI.getRegisterBitWidth(TargetTransformInfo::RGK_Scalar).getFixedValue();
+  auto *RegType = IntegerType::get(I->getModule()->getContext(), RegSize);
+  auto GetMultiplier = [&](IntegerType *Ty) -> uint64_t {
+    return divideCeil(Ty->getBitWidth() * VF, RegSize);
+  };
+  if (isa<LoadInst, StoreInst>(I)) {
+    if (getWideningDecision(I, ElementCount::getFixed(VF)) !=
+        LoopVectorizationCostModel::CM_Widen)
+      return InstructionCost::getInvalid();
+    auto *ValTy = dyn_cast<IntegerType>(getLoadStoreType(I));
+    if (!ValTy)
+      return InstructionCost::getInvalid();
+    const auto &DL = I->getModule()->getDataLayout();
+    const Align Alignment = DL.getPrefTypeAlign(RegType);
+    unsigned AddressSpace =
+        getLoadStorePointerOperand(I)->getType()->getPointerAddressSpace();
+    return GetMultiplier(ValTy) * TTI.getMemoryOpCost(I->getOpcode(), RegType,
+                                                      Alignment, AddressSpace);
+  }
+  auto *ValTy = dyn_cast<IntegerType>(I->getType());
+  if (!ValTy)
+    return InstructionCost::getInvalid();
+  if (auto *PN = dyn_cast<PHINode>(I))
+    if (Legal->isReductionVariable(PN))
+      return TTI.getCFInstrCost(Instruction::PHI, TTI::TCK_RecipThroughput);
+  auto Multiplier = GetMultiplier(ValTy);
+  if (I->isBitwiseLogicOp())
+    return Multiplier * TTI.getArithmeticInstrCost(I->getOpcode(), RegType);
+  switch (I->getOpcode()) {
+  case Instruction::Shl:
+  case Instruction::LShr:
+    // Shl: (LHS << ShiftAmnt) & Mask
+    // LShr: (LHS >> ShiftAmnt) & Mask
+    if (!isa<ConstantInt>(I->getOperand(1)))
+      return InstructionCost::getInvalid();
+    return Multiplier * (TTI.getArithmeticInstrCost(I->getOpcode(), RegType) +
+                         TTI.getArithmeticInstrCost(Instruction::And, RegType));
+  case Instruction::Add:
+    // Add: ((LHS & ~Mask) + (RHS & ~Mask)) ^ ((LHS ^  RHS) & Mask)
+    return Multiplier *
+           (TTI.getArithmeticInstrCost(Instruction::Add, RegType) +
+            2 * TTI.getArithmeticInstrCost(Instruction::Xor, RegType) +
+            3 * TTI.getArithmeticInstrCost(Instruction::And, RegType));
+  case Instruction::Sub:
+    // Sub: ((LHS |  Mask) - (RHS & ~Mask)) ^ ((LHS ^ ~RHS) & Mask)
+    return Multiplier *
+           (TTI.getArithmeticInstrCost(Instruction::Sub, RegType) +
+            TTI.getArithmeticInstrCost(Instruction::Or, RegType) +
+            2 * TTI.getArithmeticInstrCost(Instruction::And, RegType) +
+            3 * TTI.getArithmeticInstrCost(Instruction::Xor, RegType));
+  default:
+    return InstructionCost::getInvalid();
+  }
+}
+
 LoopVectorizationCostModel::VectorizationCostTy
 LoopVectorizationCostModel::getInstructionCost(Instruction *I,
                                                ElementCount VF) {
@@ -6706,6 +6774,13 @@ LoopVectorizationCostModel::getInstructionCost(Instruction *I,
           false);
   }
 
+  if (UseSWAR && VF.isVector()) {
+    assert(!VF.isScalable() && "Scalable VF not supported");
+    if (!I->isTerminator())
+      return VectorizationCostTy(getSWARInstructionCost(I, VF.getFixedValue()),
+                                 true);
+  }
+
   Type *VectorTy;
   InstructionCost C = getInstructionCost(I, VF, VectorTy);
 
@@ -8208,6 +8283,23 @@ VPValue *VPRecipeBuilder::createBlockInMask(BasicBlock *BB, VPlan &Plan) {
   return BlockMaskCache[BB] = BlockMask;
 }
 
+VPRecipeBase *VPRecipeBuilder::tryToSWARMemory(Instruction *I,
+                                               ArrayRef<VPValue *> Operands,
+                                               VFRange &Range) {
+  if (Legal->isMaskRequired(I))
+    return nullptr;
+  if (CM.getWideningDecision(I, Range.Start) !=
+      LoopVectorizationCostModel::CM_Widen)
+    return nullptr;
+  if (!isa<IntegerType>(getLoadStoreType(I)))
+    return nullptr;
+  if (auto *LI = dyn_cast<LoadInst>(I))
+    return new VPSWARMemoryInstructionRecipe(*LI, Operands[0]);
+  if (auto *SI = dyn_cast<StoreInst>(I))
+    return new VPSWARMemoryInstructionRecipe(*SI, Operands[1], Operands[0]);
+  llvm_unreachable("Unhandled instruction!");
+}
+
 VPRecipeBase *VPRecipeBuilder::tryToWidenMemory(Instruction *I,
                                                 ArrayRef<VPValue *> Operands,
                                                 VFRange &Range,
@@ -8474,6 +8566,25 @@ bool VPRecipeBuilder::shouldWiden(Instruction *I, VFRange &Range) const {
                                                              Range);
 }
 
+VPRecipeBase *VPRecipeBuilder::tryToSWAR(Instruction *I,
+                                         ArrayRef<VPValue *> Operands) {
+  switch (I->getOpcode()) {
+  case Instruction::Shl:
+  case Instruction::LShr:
+    if (!isa<ConstantInt>(I->getOperand(1)))
+      return nullptr;
+    [[fallthrough]];
+  case Instruction::And:
+  case Instruction::Or:
+  case Instruction::Xor:
+  case Instruction::Add:
+  case Instruction::Sub:
+    return new VPSWARRecipe(*I, make_range(Operands.begin(), Operands.end()));
+  default:
+    return nullptr;
+  }
+}
+
 VPRecipeBase *VPRecipeBuilder::tryToWiden(Instruction *I,
                                           ArrayRef<VPValue *> Operands,
                                           VPBasicBlock *VPBB, VPlanPtr &Plan) {
@@ -8656,7 +8767,9 @@ VPRecipeBuilder::tryToCreateWidenRecipe(Instruction *Instr,
     return toVPRecipeResult(tryToWidenCall(CI, Operands, Range, Plan));
 
   if (isa<LoadInst>(Instr) || isa<StoreInst>(Instr))
-    return toVPRecipeResult(tryToWidenMemory(Instr, Operands, Range, Plan));
+    return toVPRecipeResult(
+        CM.UseSWAR ? tryToSWARMemory(Instr, Operands, Range)
+                   : tryToWidenMemory(Instr, Operands, Range, Plan));
 
   if (!shouldWiden(Instr, Range))
     return nullptr;
@@ -8675,7 +8788,8 @@ VPRecipeBuilder::tryToCreateWidenRecipe(Instruction *Instr,
         new VPWidenCastRecipe(CI->getOpcode(), Operands[0], CI->getType(), CI));
   }
 
-  return toVPRecipeResult(tryToWiden(Instr, Operands, VPBB, Plan));
+  return toVPRecipeResult(CM.UseSWAR ? tryToSWAR(Instr, Operands)
+                                     : tryToWiden(Instr, Operands, VPBB, Plan));
 }
 
 void LoopVectorizationPlanner::buildVPlansWithVPRecipes(ElementCount MinVF,
@@ -9117,7 +9231,8 @@ void LoopVectorizationPlanner::adjustRecipesForReductions(
                  "must be a select recipe");
           IndexOfFirstOperand = 1;
         } else {
-          assert((MinVF.isScalar() || isa<VPWidenRecipe>(CurrentLink)) &&
+          assert((MinVF.isScalar() || CM.UseSWAR ||
+                  isa<VPWidenRecipe>(CurrentLink)) &&
                  "Expected to replace a VPWidenSC");
           IndexOfFirstOperand = 0;
         }
@@ -9454,6 +9569,49 @@ void VPReplicateRecipe::execute(VPTransformState &State) {
       State.ILV->scalarizeInstruction(UI, this, VPIteration(Part, Lane), State);
 }
 
+static Type *getSWARType(Type *ScalarTy, ElementCount VF) {
+  assert(isa<IntegerType>(ScalarTy));
+  unsigned ScalarBitWidth = cast<IntegerType>(ScalarTy)->getBitWidth();
+  assert(!VF.isScalable() && "Scalable VF not supported");
+  return IntegerType::get(ScalarTy->getContext(),
+                          ScalarBitWidth * VF.getFixedValue());
+}
+
+void VPSWARMemoryInstructionRecipe::execute(VPTransformState &State) {
+  auto VF = State.VF;
+  Value *Ptr = State.get(getAddr(), VPIteration(0, 0));
+  bool InBounds = false;
+  if (auto *GEP = dyn_cast<GetElementPtrInst>(Ptr->stripPointerCasts()))
+    InBounds = GEP->isInBounds();
+  Type *ScalarTy = getLoadStoreType(&Ingredient);
+  Type *SWARTy = getSWARType(ScalarTy, VF);
+  Type *VecTy = VectorType::get(ScalarTy, VF);
+  const auto &DL = Ingredient.getModule()->getDataLayout();
+  const Align Alignment = DL.getPrefTypeAlign(SWARTy);
+
+  auto &Builder = State.Builder;
+  State.setDebugLocFrom(Ingredient.getDebugLoc());
+  for (unsigned Part = 0; Part < State.UF; ++Part) {
+    Value *GEP = Builder.CreateGEP(ScalarTy, Ptr,
+                                   Builder.getInt32(VF.getFixedValue() * Part),
+                                   Ptr->getName() + ".swar", InBounds);
+    Value *SWARPtr = Builder.CreateBitCast(
+        GEP, SWARTy->getPointerTo(Ptr->getType()->getPointerAddressSpace()));
+    Instruction *Res = nullptr;
+    if (isa<LoadInst>(Ingredient)) {
+      Res = Builder.CreateAlignedLoad(SWARTy, SWARPtr, Alignment,
+                                      Ingredient.getName() + ".swar");
+      State.set(getVPSingleValue(), Builder.CreateBitCast(Res, VecTy), Part);
+    } else if (isa<StoreInst>(Ingredient))
+      Res = Builder.CreateAlignedStore(
+          Builder.CreateBitCast(State.get(getStoredValue(), Part), SWARTy),
+          SWARPtr, Alignment);
+    else
+      llvm_unreachable("Unhandled instruction!");
+    State.addMetadata(Res, &Ingredient);
+  }
+}
+
 void VPWidenMemoryInstructionRecipe::execute(VPTransformState &State) {
   VPValue *StoredValue = isStore() ? getStoredValue() : nullptr;
 
@@ -9643,7 +9801,7 @@ static bool processLoopInVPlanNativePath(
     TargetLibraryInfo *TLI, DemandedBits *DB, AssumptionCache *AC,
     OptimizationRemarkEmitter *ORE, BlockFrequencyInfo *BFI,
     ProfileSummaryInfo *PSI, LoopVectorizeHints &Hints,
-    LoopVectorizationRequirements &Requirements) {
+    LoopVectorizationRequirements &Requirements, bool UseSWAR) {
 
   if (isa<SCEVCouldNotCompute>(PSE.getBackedgeTakenCount())) {
     LLVM_DEBUG(dbgs() << "LV: cannot compute the outer-loop trip count\n");
@@ -9657,7 +9815,7 @@ static bool processLoopInVPlanNativePath(
       getScalarEpilogueLowering(F, L, Hints, PSI, BFI, TTI, TLI, *LVL, &IAI);
 
   LoopVectorizationCostModel CM(SEL, L, PSE, LI, LVL, *TTI, TLI, DB, AC, ORE, F,
-                                &Hints, IAI);
+                                &Hints, IAI, UseSWAR);
   // Use the planner for outer loop vectorization.
   // TODO: CM is not used at this point inside the planner. Turn CM into an
   // optional argument if we don't need it in the future.
@@ -9841,6 +9999,45 @@ static bool areRuntimeChecksProfitable(GeneratedRTChecks &Checks,
   return true;
 }
 
+static const SCEVPredicate *getAlignPredicate(ScalarEvolution *SE,
+                                              const DataLayout &DL,
+                                              const SCEV *Start,
+                                              Align Alignment) {
+  Type *IntTy = DL.getIntPtrType(Start->getType());
+  const SCEV *Rem = SE->getURemExpr(SE->getPtrToIntExpr(Start, IntTy),
+                                    SE->getConstant(IntTy, Alignment.value()));
+  if (Rem->isZero())
+    return nullptr;
+  return SE->getEqualPredicate(Rem, SE->getZero(IntTy));
+}
+
+static void generateAlignChecks(PredicatedScalarEvolution &PSE,
+                                const VPlan &Plan, ElementCount VF) {
+  ScalarEvolution *SE = PSE.getSE();
+  const DataLayout &DL = SE->getDataLayout();
+  MapVector<const SCEV *, Align> Checks;
+  for (const auto *VPBlock : vp_depth_first_shallow(Plan.getEntry()))
+    for (const auto &Recipe : *VPBlock->getEntryBasicBlock()) {
+      auto *SWARRecipe = dyn_cast<VPSWARMemoryInstructionRecipe>(&Recipe);
+      if (!SWARRecipe)
+        continue;
+      auto &MemInst = SWARRecipe->getIngredient();
+      const SCEVAddRecExpr *PtrSCEV =
+          PSE.getAsAddRec(getLoadStorePointerOperand(&MemInst));
+      assert(PtrSCEV && "Consecutive Ptr expected");
+      const SCEV *Start = PtrSCEV->getStart();
+      Type *SWARTy = getSWARType(getLoadStoreType(&MemInst), VF);
+      Align Alignment = DL.getPrefTypeAlign(SWARTy);
+      if (auto It = Checks.find(Start); It != Checks.end())
+        It->second = std::max(It->second, Alignment);
+      else
+        Checks.insert({Start, Alignment});
+    }
+  for (auto [Start, Alignment] : Checks)
+    if (auto *Predicate = getAlignPredicate(SE, DL, Start, Alignment))
+      PSE.addPredicate(*Predicate);
+}
+
 LoopVectorizePass::LoopVectorizePass(LoopVectorizeOptions Opts)
     : InterleaveOnlyWhenForced(Opts.InterleaveOnlyWhenForced ||
                                !EnableLoopInterleaving),
@@ -9905,9 +10102,13 @@ bool LoopVectorizePass::processLoop(Loop *L) {
   // even evaluating whether vectorization is profitable. Since we cannot modify
   // the incoming IR, we need to build VPlan upfront in the vectorization
   // pipeline.
+  bool UseSWAR =
+      !TTI->getNumberOfRegisters(TTI->getRegisterClassForType(true)) &&
+      EnableSWARVectorization;
   if (!L->isInnermost())
     return processLoopInVPlanNativePath(L, PSE, LI, DT, &LVL, TTI, TLI, DB, AC,
-                                        ORE, BFI, PSI, Hints, Requirements);
+                                        ORE, BFI, PSI, Hints, Requirements,
+                                        UseSWAR);
 
   assert(L->isInnermost() && "Inner loop expected.");
 
@@ -10001,7 +10202,7 @@ bool LoopVectorizePass::processLoop(Loop *L) {
 
   // Use the cost model.
   LoopVectorizationCostModel CM(SEL, L, PSE, LI, &LVL, *TTI, TLI, DB, AC, ORE,
-                                F, &Hints, IAI);
+                                F, &Hints, IAI, UseSWAR);
   // Use the planner for vectorization.
   LoopVectorizationPlanner LVP(L, LI, TLI, *TTI, &LVL, CM, IAI, PSE, Hints,
                                ORE);
@@ -10026,8 +10227,11 @@ bool LoopVectorizePass::processLoop(Loop *L) {
     unsigned SelectedIC = std::max(IC, UserIC);
     //  Optimistically generate runtime checks if they are needed. Drop them if
     //  they turn out to not be profitable.
-    if (VF.Width.isVector() || SelectedIC > 1)
+    if (VF.Width.isVector() || SelectedIC > 1) {
+      if (UseSWAR)
+        generateAlignChecks(PSE, LVP.getBestPlanFor(VF.Width), VF.Width);
       Checks.Create(L, *LVL.getLAI(), PSE.getPredicate(), VF.Width, SelectedIC);
+    }
 
     // Check if it is profitable to vectorize with runtime checks.
     bool ForceVectorization =
@@ -10299,12 +10503,14 @@ LoopVectorizeResult LoopVectorizePass::runImpl(
 
   // Don't attempt if
   // 1. the target claims to have no vector registers, and
-  // 2. interleaving won't help ILP.
+  // 2. SWAR vectorization is disabled, and
+  // 3. interleaving won't help ILP.
   //
-  // The second condition is necessary because, even if the target has no
+  // The last condition is necessary because, even if the target has no
   // vector registers, loop vectorization may still enable scalar
   // interleaving.
   if (!TTI->getNumberOfRegisters(TTI->getRegisterClassForType(true)) &&
+      !EnableSWARVectorization &&
       TTI->getMaxInterleaveFactor(ElementCount::getFixed(1)) < 2)
     return LoopVectorizeResult(false, false);
 
diff --git a/llvm/lib/Transforms/Vectorize/VPRecipeBuilder.h b/llvm/lib/Transforms/Vectorize/VPRecipeBuilder.h
index 7ff6749a09089e9..7369de4320cddd8 100644
--- a/llvm/lib/Transforms/Vectorize/VPRecipeBuilder.h
+++ b/llvm/lib/Transforms/Vectorize/VPRecipeBuilder.h
@@ -103,6 +103,10 @@ class VPRecipeBuilder {
   VPRecipeBase *tryToWiden(Instruction *I, ArrayRef<VPValue *> Operands,
                            VPBasicBlock *VPBB, VPlanPtr &Plan);
 
+  VPRecipeBase *tryToSWARMemory(Instruction *I, ArrayRef<VPValue *> Operands,
+                                VFRange &Range);
+  VPRecipeBase *tryToSWAR(Instruction *I, ArrayRef<VPValue *> Operands);
+
   /// Return a VPRecipeOrValueTy with VPRecipeBase * being set. This can be used to force the use as VPRecipeBase* for recipe sub-types that also inherit from VPValue.
   VPRecipeOrVPValueTy toVPRecipeResult(VPRecipeBase *R) const { return R; }
 
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.h b/llvm/lib/Transforms/Vectorize/VPlan.h
index e65a7ab2cd028ee..4c29e843401e82c 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.h
+++ b/llvm/lib/Transforms/Vectorize/VPlan.h
@@ -1148,6 +1148,32 @@ class VPInstruction : public VPRecipeWithIRFlags, public VPValue {
   }
 };
 
+// VPSWARRecipe is a recipe for producing a SIMD-within-a-register (SWAR)
+// operation for its ingredient. The operation works on values that are packed
+// into scalar registers. This recipe covers the following cases:
+// 1. Bitwise operations (and, or, xor)
+// 2. Shifts (shl, lshr) with constant second operand
+// 3. Add/Sub operations.
+class VPSWARRecipe : public VPRecipeBase, public VPValue {
+public:
+  template <typename IterT>
+  VPSWARRecipe(Inst...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/69306


More information about the llvm-commits mailing list