Skip to content

[VPlan] Add VPPhiAccessors to provide interface for phi recipes (NFC) #129388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 4, 2025

Conversation

fhahn
Copy link
Contributor

@fhahn fhahn commented Mar 1, 2025

Add a VPPhiAccessors class to provide interfaces to access incoming values and blocks, with corresponding iterators.

The first user is VPWidenPhiRecipe, with the other phi-like recipes following soon.

This will also be used to verify def-use chains where users are phi-like recipes, simplifying #124838.

Add a VPPhiAccessors class to provide interfaces to access incoming
values and blocks, with corresponding iterators.

The first user is VPWidenPhiRecipe, with the other phi-like recipes
following soon.

This will also be used to verify def-use chains where users are phi-like
recipes, simplifying llvm#124838.
@llvmbot
Copy link
Member

llvmbot commented Mar 1, 2025

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-vectorizers

Author: Florian Hahn (fhahn)

Changes

Add a VPPhiAccessors class to provide interfaces to access incoming values and blocks, with corresponding iterators.

The first user is VPWidenPhiRecipe, with the other phi-like recipes following soon.

This will also be used to verify def-use chains where users are phi-like recipes, simplifying #124838.


Full diff: https://github.com/llvm/llvm-project/pull/129388.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/Vectorize/LoopVectorize.cpp (+2-4)
  • (modified) llvm/lib/Transforms/Vectorize/VPlan.h (+58-7)
  • (modified) llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp (+27-19)
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index e2612698b6b0f..98ff1a4cdc449 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -3069,11 +3069,9 @@ void InnerLoopVectorizer::fixNonInductionPHIs(VPTransformState &State) {
       PHINode *NewPhi = cast<PHINode>(State.get(VPPhi));
       // Make sure the builder has a valid insert point.
       Builder.SetInsertPoint(NewPhi);
-      for (unsigned Idx = 0; Idx < VPPhi->getNumOperands(); ++Idx) {
-        VPValue *Inc = VPPhi->getIncomingValue(Idx);
-        VPBasicBlock *VPBB = VPPhi->getIncomingBlock(Idx);
+
+      for (const auto &[Inc, VPBB] : VPPhi->incoming_values_and_blocks())
         NewPhi->addIncoming(State.get(Inc), State.CFG.VPBB2IRBB[VPBB]);
-      }
     }
   }
 }
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.h b/llvm/lib/Transforms/Vectorize/VPlan.h
index 70e684826ed2d..4d5f2014a40dc 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.h
+++ b/llvm/lib/Transforms/Vectorize/VPlan.h
@@ -1085,6 +1085,62 @@ class VPIRInstruction : public VPRecipeBase {
   void extractLastLaneOfOperand(VPBuilder &Builder);
 };
 
+/// Helper type to provide functions to access incoming values and blocks for
+/// phi-like recipes. RecipeTy must be a sub-class of VPRecipeBase.
+template <typename RecipeTy> class VPPhiAccessors {
+  /// Return a VPRecipeBase* to the current object.
+  const VPRecipeBase *getAsRecipe() const {
+    return static_cast<const RecipeTy *>(this);
+  }
+
+public:
+  /// Returns the \p I th incoming VPValue.
+  VPValue *getIncomingValue(unsigned I) const {
+    return getAsRecipe()->getOperand(I);
+  }
+
+  /// Returns an interator range over the incoming values
+  VPUser::const_operand_range incoming_values() const {
+    return getAsRecipe()->operands();
+  }
+
+  /// Returns the \p I th incoming block.
+  const VPBasicBlock *getIncomingBlock(unsigned Idx) const;
+
+  using const_incoming_block_iterator =
+      mapped_iterator<detail::index_iterator,
+                      std::function<const VPBasicBlock *(size_t)>>;
+  using const_incoming_blocks_range =
+      iterator_range<const_incoming_block_iterator>;
+
+  const_incoming_block_iterator incoming_block_begin() const {
+    return const_incoming_block_iterator(
+        detail::index_iterator(0),
+        [this](size_t Idx) { return getIncomingBlock(Idx); });
+  }
+  const_incoming_block_iterator incoming_block_end() const {
+    return const_incoming_block_iterator(
+        detail::index_iterator(getAsRecipe()->getVPDefID() ==
+                                       VPDef::VPWidenIntOrFpInductionSC
+                                   ? 2
+                                   : getAsRecipe()->getNumOperands()),
+        [this](size_t Idx) { return getIncomingBlock(Idx); });
+  }
+
+  /// Returns an iterator range over the incoming blocks.
+  const_incoming_blocks_range incoming_blocks() const {
+    return make_range(incoming_block_begin(), incoming_block_end());
+  }
+
+  /// Returns an iterator range over pairs of incoming values and corrsponding
+  /// incoming blocks.
+  detail::zippy<llvm::detail::zip_shortest, VPUser::const_operand_range,
+                const_incoming_blocks_range>
+  incoming_values_and_blocks() const {
+    return zip(incoming_values(), incoming_blocks());
+  }
+};
+
 /// VPWidenRecipe is a recipe for producing a widened instruction using the
 /// opcode and operands of the recipe. This recipe covers most of the
 /// traditional vectorization cases where each recipe transforms into a
@@ -1944,7 +2000,8 @@ class VPScalarPHIRecipe : public VPHeaderPHIRecipe {
 /// recipe is placed in an entry block to a (non-replicate) region, it must have
 /// exactly 2 incoming values, the first from the predecessor of the region and
 /// the second from the exiting block of the region.
-class VPWidenPHIRecipe : public VPSingleDefRecipe {
+class VPWidenPHIRecipe : public VPSingleDefRecipe,
+                         public VPPhiAccessors<VPWidenPHIRecipe> {
 public:
   /// Create a new VPWidenPHIRecipe for \p Phi with start value \p Start and
   /// debug location \p DL.
@@ -1970,12 +2027,6 @@ class VPWidenPHIRecipe : public VPSingleDefRecipe {
   void print(raw_ostream &O, const Twine &Indent,
              VPSlotTracker &SlotTracker) const override;
 #endif
-
-  /// Returns the \p I th incoming VPBasicBlock.
-  VPBasicBlock *getIncomingBlock(unsigned I);
-
-  /// Returns the \p I th incoming VPValue.
-  VPValue *getIncomingValue(unsigned I) { return getOperand(I); }
 };
 
 /// A recipe for handling first-order recurrence phis. The start value is the
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index e9f50e88867b2..efac05785203c 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -1031,6 +1031,29 @@ void VPIRInstruction::print(raw_ostream &O, const Twine &Indent,
 }
 #endif
 
+static const VPBasicBlock *getIncomingBlockForRecipe(const VPRecipeBase *R,
+                                                     unsigned I) {
+  const VPBasicBlock *Parent = R->getParent();
+  const VPBlockBase *Pred = nullptr;
+  if (Parent->getNumPredecessors() > 0) {
+    Pred = Parent->getPredecessors()[I];
+  } else {
+    auto *Region = Parent->getParent();
+    assert(Region && !Region->isReplicator() && Region->getEntry() == Parent &&
+           "must be in the entry block of a non-replicate region");
+    assert(
+        I < 2 &&
+        (R->getNumOperands() == 2 || isa<VPWidenIntOrFpInductionRecipe>(R)) &&
+        "when placed in an entry block, only 2 incoming blocks are available");
+
+    // I ==  0 selects the predecessor of the region, I == 1 selects the region
+    // itself whose exiting block feeds the phi across the backedge.
+    Pred = I == 0 ? Region->getSinglePredecessor() : Region;
+  }
+
+  return Pred->getExitingBasicBlock();
+}
+
 void VPWidenCallRecipe::execute(VPTransformState &State) {
   assert(State.VF.isVector() && "not widening");
   State.setDebugLocFrom(getDebugLoc());
@@ -3580,25 +3603,10 @@ void VPReductionPHIRecipe::print(raw_ostream &O, const Twine &Indent,
 }
 #endif
 
-VPBasicBlock *VPWidenPHIRecipe::getIncomingBlock(unsigned I) {
-  VPBasicBlock *Parent = getParent();
-  VPBlockBase *Pred = nullptr;
-  if (Parent->getNumPredecessors() > 0) {
-    Pred = Parent->getPredecessors()[I];
-  } else {
-    auto *Region = Parent->getParent();
-    assert(Region && !Region->isReplicator() && Region->getEntry() == Parent &&
-           "must be in the entry block of a non-replicate region");
-    assert(
-        I < 2 && getNumOperands() == 2 &&
-        "when placed in an entry block, only 2 incoming blocks are available");
-
-    // I ==  0 selects the predecessor of the region, I == 1 selects the region
-    // itself whose exiting block feeds the phi across the backedge.
-    Pred = I == 0 ? Region->getSinglePredecessor() : Region;
-  }
-
-  return Pred->getExitingBasicBlock();
+template <>
+const VPBasicBlock *
+VPPhiAccessors<VPWidenPHIRecipe>::getIncomingBlock(unsigned Idx) const {
+  return getIncomingBlockForRecipe(getAsRecipe(), Idx);
 }
 
 void VPWidenPHIRecipe::execute(VPTransformState &State) {

Copy link
Contributor

@lukel97 lukel97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can VPHeaderPHIRecipe and its subclasses inherit from this too? Where the incoming values are { getStartValue(), getBackedgeValue() } and the incoming blocks are { loopPreheader, exitingBlock } (or {region->getSinglePredecessor, region }?)

Copy link
Contributor

@lukel97 lukel97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, makes sense. I just left some small questions

VPValue *Inc = VPPhi->getIncomingValue(Idx);
VPBasicBlock *VPBB = VPPhi->getIncomingBlock(Idx);

for (const auto &[Inc, VPBB] : VPPhi->incoming_values_and_blocks())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Perhaps better to rename Inc to Idx? It sounds like a short form of increment or incoming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a short form of incoming value, while Idx would refer to an Index?


public:
/// Returns the \p I th incoming VPValue.
VPValue *getIncomingValue(unsigned I) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps rename to Idx to be consistent with getIncomingBlock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, thanks

return getAsRecipe()->operands();
}

/// Returns the \p I th incoming block.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I this should be Returns the incoming block for \p Idx. since the variable name is Idx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, thanks

}
const_incoming_block_iterator incoming_block_end() const {
return const_incoming_block_iterator(
detail::index_iterator(getAsRecipe()->getVPDefID() ==
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not use some sort of isa<VPWiden...> class check instead of looking at the VPDefID?

Just out of curiosity why doesn't getNumOperands return the correct answer for classes of type VPWidenIntOrFpInductionSC? Wouldn't it be better to fix getNumOperands to return the right answer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity why doesn't getNumOperands return the correct answer for classes of type VPWidenIntOrFpInductionSC?

The operands for VPWidenIntOrFpInductionRecipe aren't the incoming values but are used for constructing them, and they don't match 1-to-1. So I'm not actually sure if VPWidenIntOrFpInductionRecipe should inherit from this as a result?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change isn't needed yet, I removed it for now, thanks

@@ -1031,6 +1031,29 @@ void VPIRInstruction::print(raw_ostream &O, const Twine &Indent,
}
#endif

static const VPBasicBlock *getIncomingBlockForRecipe(const VPRecipeBase *R,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth using the same Idx variable name as getIncomingBlock for consistency? Or vice-versa - rename all instances of Idx to I?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use Idx consistently.

fhahn added a commit to fhahn/llvm-project that referenced this pull request Mar 4, 2025
Now that all phi nodes manage their incoming blocks through the
VPlan-predecessors, there should be no need for having a dedicate
recipe, it should be sufficient to allow PHI opcodes in VPInstruction.

Follow-ups will also migrate VPWidenPHIRecipe and possibly others,
building on top of llvm#129388.
fhahn added a commit that referenced this pull request Mar 13, 2025
Now that all phi nodes manage their incoming blocks through the
VPlan-predecessors, there should be no need for having a dedicate
recipe, it should be sufficient to allow PHI opcodes in VPInstruction.

Follow-ups will also migrate VPWidenPHIRecipe and possibly others,
building on top of #129388.

PR: #129767
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Mar 13, 2025
…129767)

Now that all phi nodes manage their incoming blocks through the
VPlan-predecessors, there should be no need for having a dedicate
recipe, it should be sufficient to allow PHI opcodes in VPInstruction.

Follow-ups will also migrate VPWidenPHIRecipe and possibly others,
building on top of llvm/llvm-project#129388.

PR: llvm/llvm-project#129767
frederik-h pushed a commit to frederik-h/llvm-project that referenced this pull request Mar 18, 2025
Now that all phi nodes manage their incoming blocks through the
VPlan-predecessors, there should be no need for having a dedicate
recipe, it should be sufficient to allow PHI opcodes in VPInstruction.

Follow-ups will also migrate VPWidenPHIRecipe and possibly others,
building on top of llvm#129388.

PR: llvm#129767
Copy link
Contributor Author

@fhahn fhahn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping :)

Comment on lines 2893 to 2890
for (unsigned Idx = 0; Idx < VPPhi->getNumOperands(); ++Idx) {
VPValue *Inc = VPPhi->getIncomingValue(Idx);
VPBasicBlock *VPBB = VPPhi->getIncomingBlock(Idx);
for (const auto &[Inc, VPBB] : VPPhi->incoming_values_and_blocks())
NewPhi->addIncoming(State.get(Inc), State.CFG.VPBB2IRBB[VPBB]);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this return (in simplification?) worth the investment. Would an API of getIncomingValue(Idx), getIncomingBlock(Idx), and possibly getNumIncomings() common to all phi recipes suffice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be additional users when updating more recipes to use the accessors; they are more conveiient, but getIncomingValue and getIncomingBlock would also suffice, at the cost of being a bit less convenient.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about first wrapping getIncomingValue and getIncomingBlock within VPPhiAccessors, and introduce the additional zipped iterator API as a separate follow-up. That would help clarify the convenience versus investment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, done for now, thanks

Comment on lines +1184 to +1186
VPValue *getIncomingValue(unsigned Idx) const {
return getAsRecipe()->getOperand(Idx);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about VPPhiAccessors having both getIncomingBlock(Idx) and getIncomingValue(Idx) be pure virtual, and have another interim VPSingleDefPhiRecipe inherit from both VPSingleDefRecipe and VPPhiAccessors take care of implementing getIncomingValue(Idx) for all (singleDef) phi recipes, instead of getAsRecipe() subclass casting. Are all recipes candidate of inheritance singleDef?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All recipes are single defs, but we now unfortunately have some recipes (e.g. VPIRPhi) where the base class VPIRInstruction inherits from VPSingleDefRecipe, but inheriting from VPSinglePhiDefRecipe would not be approriate, hence the trait/mix-in. Down the road, we could also support casting any recipe that supports it to VPPhiAccessors, e.g. for verifying all phi-like nodes that implement the trait.

Alternatively we could manually add definitions of getIncomingBlock and getIncomingValue to all relevant classes?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, who are all the relevant classes - expected to inherit directly from VPPhiAccessors in addition to VPWidenPHIRecipe (who could use VPSingleDefPhiRecipe with other potential partners) and VPIRPhi?

If VPIRPhi inherits directly from VPPhiAccessors, could it implement getIncomingBlock based on the direct predecessors of its VPBasicBlock, as it is not used to represent header phi's of HCFG regions? I.e., assert it has direct predecessors.

In any case, good to implement both getIncomingBlock and getIncomingValue by the mix-in, as done here, or neither (and have both defined by all derived classes instead).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need it for VPWidenPHIRecipe, VPHeaderPHIRecipe, VPIRPhi, VPEVLBasedPhi and VPPhi (scalar phis via VPInstruction, probably via a new specialization).

Define both getIncomingBlock and getIncomingValue there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one is VPPredInstPhi

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VPWidenPHIRecipe, VPHeaderPHIRecipe (base class of VPEVLBasedPhi), and VPPredInstPhi all inherit from VPSingleDefRecipe. So could inherit from VPSingleDefPhiRecipe instead, which could take care of implementing these pure virtual methods for them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the getAsRecipe pure-virtual to avoid the template argument + static cast. WDYT?

@@ -3694,25 +3720,10 @@ void VPReductionPHIRecipe::print(raw_ostream &O, const Twine &Indent,
}
#endif

VPBasicBlock *VPWidenPHIRecipe::getIncomingBlock(unsigned I) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better keep delegating the implementation of getIncomingBlock(Idx) to recipes that inherit from VPPhiAccessors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now providing a specialized definition of VPPhiAccessors<VPWidenPHIRecipe>::getIncomingBlock, which is needed to instantiate the template for the type and use the generic getIncomingBlockForRecipe

/// recipes placed in entry blocks of loop regions (incoming blocks are the
/// region's predecessor and the region's exit) and other locations (incoming
/// blocks are the direct predecessors).
static const VPBasicBlock *getIncomingBlockForRecipe(const VPRecipeBase *R,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be getCFGPredecessors(VPBasicBlock) or VPBasicBlock:: getCFGPredecessors(), as iterators and/or single element by index? Complementing getHierarchicalPredecessors() and getPredecessors().

OTOH, if used only by VPPhiAccessors::getIncomingBlock(Idx), better inline it there. But by templating VPPhiAccessors, this would need to be replicated per instance, as in VPPhiAccessors<VPWidenPHIRecipe>::getIncomingBlock(Idx)?

This provides the CFG predecessor basic-blocks of a given block (rather than recipe), which could be in CFG mode (in which case they are held explicitly, can cast them from block to basic-block) or HCFG mode (in which case region header blocks need to collect their region's predecessor('s exiting) basic-block and exiting basic-block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep had a similar thought, but wasn't sure what a good name would be. Added as getCFGPredecessor(Idx) to start with. Could add an iterator version separately when other iterators are added

Comment on lines 2893 to 2890
for (unsigned Idx = 0; Idx < VPPhi->getNumOperands(); ++Idx) {
VPValue *Inc = VPPhi->getIncomingValue(Idx);
VPBasicBlock *VPBB = VPPhi->getIncomingBlock(Idx);
for (const auto &[Inc, VPBB] : VPPhi->incoming_values_and_blocks())
NewPhi->addIncoming(State.get(Inc), State.CFG.VPBB2IRBB[VPBB]);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about first wrapping getIncomingValue and getIncomingBlock within VPPhiAccessors, and introduce the additional zipped iterator API as a separate follow-up. That would help clarify the convenience versus investment.

Comment on lines +1184 to +1186
VPValue *getIncomingValue(unsigned Idx) const {
return getAsRecipe()->getOperand(Idx);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, who are all the relevant classes - expected to inherit directly from VPPhiAccessors in addition to VPWidenPHIRecipe (who could use VPSingleDefPhiRecipe with other potential partners) and VPIRPhi?

If VPIRPhi inherits directly from VPPhiAccessors, could it implement getIncomingBlock based on the direct predecessors of its VPBasicBlock, as it is not used to represent header phi's of HCFG regions? I.e., assert it has direct predecessors.

In any case, good to implement both getIncomingBlock and getIncomingValue by the mix-in, as done here, or neither (and have both defined by all derived classes instead).

/// incoming blocks.
detail::zippy<llvm::detail::zip_shortest, VPUser::const_operand_range,
const_incoming_blocks_range>
incoming_values_and_blocks() const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suffice to keep this method public, as getIncomingValuesAndBlocks(), complementing getIncomingValue(Idx) and getIncomingBlock(Idx)? Trying to reduce non_/camelCase inconsistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now lets just add getIncomingValue and getIncomingBlock

Comment on lines 1202 to 1216
const_incoming_block_iterator incoming_block_begin() const {
return const_incoming_block_iterator(
detail::index_iterator(0),
[this](size_t Idx) { return getIncomingBlock(Idx); });
}
const_incoming_block_iterator incoming_block_end() const {
return const_incoming_block_iterator(
detail::index_iterator(getAsRecipe()->getNumOperands()),
[this](size_t Idx) { return getIncomingBlock(Idx); });
}

/// Returns an iterator range over the incoming blocks.
const_incoming_blocks_range incoming_blocks() const {
return make_range(incoming_block_begin(), incoming_block_end());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If/when predecessors already have a natural iterator, would building an index_iterator from retrieving each block get folded into the former?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can check once we bring those iterators back

Copy link
Collaborator

@ayalz ayalz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

"must be in the entry block of a non-replicate region");
assert(
Idx < 2 && Region->getNumPredecessors() == 1 &&
"when placed in an entry block, only 2 incoming blocks are available");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"when placed in an entry block, only 2 incoming blocks are available");
"loop region has a single predecessor (preheader), its entry block has 2 incoming blocks");

Perhaps better have the verifier assert loop regions have a single predecessor, than here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment, will adjust the verifier separately, thanks!

// region itself whose exiting block feeds the phi across the backedge.
Pred = Idx == 0 ? Region->getSinglePredecessor() : Region;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: redundant?

Suggested change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped, thanks

Comment on lines +1184 to +1186
VPValue *getIncomingValue(unsigned Idx) const {
return getAsRecipe()->getOperand(Idx);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VPWidenPHIRecipe, VPHeaderPHIRecipe (base class of VPEVLBasedPhi), and VPPredInstPhi all inherit from VPSingleDefRecipe. So could inherit from VPSingleDefPhiRecipe instead, which could take care of implementing these pure virtual methods for them.

template <typename RecipeTy> class VPPhiAccessors {
/// Return a VPRecipeBase* to the current object.
const VPRecipeBase *getAsRecipe() const {
return static_cast<const RecipeTy *>(this);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the templating really needed, can getAsRecipe() simply down cast this to VPRecipeBase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep it seems so, problem is that w/o the template there's no inheritance relation here, and static_casts are rejected (Curiously Recurring Template Pattern). For some reason, we need to cast exactly to the type.

But the template parameter may cause problems in the future, so I updated getAsRecipe to be pure virtual to be implemented by the derived classes. The single implementation could also be used for other trait classes in the future. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case enabled by getting rid of the template argument is supporting dyn_cast from VPRecipeBase -> VPPhiAccessors, as used in #124838 ( https://github.com/llvm/llvm-project/pull/124838/files#diff-a69094b5fcfce6b2bf9e957e2ac7011e5492e81c885129506c90874375e621fbR210) by implementing CastInfo<VPPhiAccessors, const VPRecipeBase *>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, would having getAsRecipe() do a dyn_cast to VPRecipeBase be ok? Possibly followed by an assert...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we could dyn_cast from VPPhiAccessors to VPRecipeBase because there's no directy relationship between them (and the this pointer for the VPPhiAccessors (sub-)object may not be the same as the VPRecipeBase (base-) object. The other way works, because we can down-cast to the concrete types inheriting from VPPhiAccessors).

}

/// Returns the incoming block with index \p Idx.
const VPBasicBlock *getIncomingBlock(unsigned Idx) const;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline as well? Good to see its implementation next to that of getIncomingValue() and getNumIncoming().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs access to VPBasicBlock's definition, which isn't available here; could be resolved by moving VPBlock* definitions to a separate header possibly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, probably better done separately, if at all.

/// Returns the incoming block with index \p Idx.
const VPBasicBlock *getIncomingBlock(unsigned Idx) const;

unsigned getNumIncomingValues() const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unsigned getNumIncomingValues() const {
/// Returns the number of incoming values, also number of incoming blocks.
unsigned getNumIncoming() const {

its both Values and Blocks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done thanks

@fhahn fhahn merged commit 6e20519 into llvm:main May 4, 2025
9 of 11 checks passed
@fhahn fhahn deleted the vplan-phi-accessors branch May 4, 2025 12:47
@llvm-ci
Copy link
Collaborator

llvm-ci commented May 4, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-gcc-ubuntu running on sie-linux-worker3 while building llvm at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/174/builds/17209

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'AddressSanitizer-x86_64-linux-dynamic :: TestCases/asan_lsan_deadlock.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/build/./bin/clang  --driver-mode=g++ -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only  -m64  -shared-libasan -O0 /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/llvm-project/compiler-rt/test/asan/TestCases/asan_lsan_deadlock.cpp -o /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/build/runtimes/runtimes-bins/compiler-rt/test/asan/X86_64LinuxDynamicConfig/TestCases/Output/asan_lsan_deadlock.cpp.tmp # RUN: at line 4
+ /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/build/./bin/clang --driver-mode=g++ -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -m64 -shared-libasan -O0 /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/llvm-project/compiler-rt/test/asan/TestCases/asan_lsan_deadlock.cpp -o /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/build/runtimes/runtimes-bins/compiler-rt/test/asan/X86_64LinuxDynamicConfig/TestCases/Output/asan_lsan_deadlock.cpp.tmp
env ASAN_OPTIONS=detect_leaks=1 not  /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/build/runtimes/runtimes-bins/compiler-rt/test/asan/X86_64LinuxDynamicConfig/TestCases/Output/asan_lsan_deadlock.cpp.tmp 2>&1 | FileCheck /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/llvm-project/compiler-rt/test/asan/TestCases/asan_lsan_deadlock.cpp # RUN: at line 5
+ env ASAN_OPTIONS=detect_leaks=1 not /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/build/runtimes/runtimes-bins/compiler-rt/test/asan/X86_64LinuxDynamicConfig/TestCases/Output/asan_lsan_deadlock.cpp.tmp
+ FileCheck /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/llvm-project/compiler-rt/test/asan/TestCases/asan_lsan_deadlock.cpp
�[1m/home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/llvm-project/compiler-rt/test/asan/TestCases/asan_lsan_deadlock.cpp:58:12: �[0m�[0;1;31merror: �[0m�[1mCHECK: expected string not found in input
�[0m // CHECK: SUMMARY: AddressSanitizer: stack-buffer-overflow
�[0;1;32m           ^
�[0m�[1m<stdin>:1:1: �[0m�[0;1;30mnote: �[0m�[1mscanning from here
�[0m=================================================================
�[0;1;32m^
�[0m�[1m<stdin>:2:10: �[0m�[0;1;30mnote: �[0m�[1mpossible intended match here
�[0m==2699692==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b57260de034 at pc 0x5563c852d220 bp 0x7b57242fdce0 sp 0x7b57242fdcd8
�[0;1;32m         ^
�[0m
Input file: <stdin>
Check file: /home/buildbot/buildbot-root/llvm-clang-x86_64-gcc-ubuntu/llvm-project/compiler-rt/test/asan/TestCases/asan_lsan_deadlock.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
�[1m�[0m�[0;1;30m            1: �[0m�[1m�[0;1;46m================================================================= �[0m
�[0;1;31mcheck:58'0     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
�[0m�[0;1;30m            2: �[0m�[1m�[0;1;46m==2699692==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b57260de034 at pc 0x5563c852d220 bp 0x7b57242fdce0 sp 0x7b57242fdcd8 �[0m
�[0;1;31mcheck:58'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
�[0m�[0;1;35mcheck:58'1              ?                                                                                                                                    possible intended match
�[0m�[0;1;30m            3: �[0m�[1m�[0;1;46mWRITE of size 4 at 0x7b57260de034 thread T2 �[0m
�[0;1;31mcheck:58'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
�[0m>>>>>>

--

********************


IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…llvm#129388)

Add a VPPhiAccessors class to provide interfaces to access incoming
values and blocks.

The first user is VPWidenPhiRecipe, with the other phi-like recipes
following soon.

This will also be used to verify def-use chains where users are phi-like
recipes, simplifying llvm#124838.

PR: llvm#129388
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…llvm#129388)

Add a VPPhiAccessors class to provide interfaces to access incoming
values and blocks.

The first user is VPWidenPhiRecipe, with the other phi-like recipes
following soon.

This will also be used to verify def-use chains where users are phi-like
recipes, simplifying llvm#124838.

PR: llvm#129388
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…llvm#129388)

Add a VPPhiAccessors class to provide interfaces to access incoming
values and blocks.

The first user is VPWidenPhiRecipe, with the other phi-like recipes
following soon.

This will also be used to verify def-use chains where users are phi-like
recipes, simplifying llvm#124838.

PR: llvm#129388
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 6, 2025
…cipes (NFC) (#129388)

Add a VPPhiAccessors class to provide interfaces to access incoming
values and blocks.

The first user is VPWidenPhiRecipe, with the other phi-like recipes
following soon.

This will also be used to verify def-use chains where users are phi-like
recipes, simplifying llvm/llvm-project#124838.

PR: llvm/llvm-project#129388
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
…llvm#129388)

Add a VPPhiAccessors class to provide interfaces to access incoming
values and blocks.

The first user is VPWidenPhiRecipe, with the other phi-like recipes
following soon.

This will also be used to verify def-use chains where users are phi-like
recipes, simplifying llvm#124838.

PR: llvm#129388
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants