[llvm] [VPlan] Make VPRegionValue a subclass of VPSymbolicValue. (NFC) (PR #196753)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Sun Jun 21 13:26:25 PDT 2026
https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/196753
>From 6be467d5b3afb40a15f8fcc11859711fa526d82c Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sun, 24 May 2026 14:35:54 +0100
Subject: [PATCH] [VPlan] Make VPRegionValue a subclass of VPSymbolicValue
(NFC).
---
llvm/lib/Transforms/Vectorize/VPlan.cpp | 8 +-
.../Transforms/Vectorize/VPlanPatternMatch.h | 12 ++-
.../Transforms/Vectorize/VPlanTransforms.cpp | 6 +-
llvm/lib/Transforms/Vectorize/VPlanUnroll.cpp | 2 +-
llvm/lib/Transforms/Vectorize/VPlanUtils.cpp | 18 ++---
llvm/lib/Transforms/Vectorize/VPlanValue.h | 74 +++++++++----------
.../Transforms/Vectorize/VPlanTest.cpp | 26 +++++++
7 files changed, 93 insertions(+), 53 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.cpp b/llvm/lib/Transforms/Vectorize/VPlan.cpp
index 8565bb85c4314..2c9ecacf1fecf 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlan.cpp
@@ -1242,17 +1242,19 @@ VPlan *VPlan::duplicate() {
// else NewTripCount will be created and inserted into Old2NewVPValues when
// TripCount is cloned. In any case NewPlan->TripCount is updated below.
+ assert(none_of(Old2NewVPValues.keys(), IsaPred<VPSymbolicValue>) &&
+ "All VPSymbolicValues must be handled below");
+
if (auto *LoopRegion = getVectorLoopRegion()) {
auto *OldCanIV = LoopRegion->getCanonicalIV();
auto *NewCanIV = NewPlan->getVectorLoopRegion()->getCanonicalIV();
assert(OldCanIV && NewCanIV &&
"Loop regions of both plans must have canonical IVs.");
Old2NewVPValues[OldCanIV] = NewCanIV;
+ if (OldCanIV->isMaterialized())
+ NewCanIV->markMaterialized();
}
- assert(none_of(Old2NewVPValues.keys(), IsaPred<VPSymbolicValue>) &&
- "All VPSymbolicValues must be handled below");
-
if (BackedgeTakenCount)
NewPlan->BackedgeTakenCount =
new VPSymbolicValue(BackedgeTakenCount->getType());
diff --git a/llvm/lib/Transforms/Vectorize/VPlanPatternMatch.h b/llvm/lib/Transforms/Vectorize/VPlanPatternMatch.h
index 0fab9cfe48dbe..ac22abd494673 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanPatternMatch.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanPatternMatch.h
@@ -1068,7 +1068,17 @@ inline auto m_WidenIntrinsic(const T &...Ops) {
return m_Isa<VPWidenIntrinsicRecipe>(m_Intrinsic<IntrID>(Ops...));
}
-inline auto m_LiveIn() { return m_Isa<VPIRValue, VPSymbolicValue>(); }
+/// Match VPValues that represent live-ins: VPIRValues and (plain)
+/// VPSymbolicValues. VPRegionValues (which inherit from VPSymbolicValue) are
+/// not live-ins and are excluded.
+struct LiveIn_match {
+ template <typename ITy> bool match(ITy *V) const {
+ return isa<VPIRValue>(V) ||
+ (isa<VPSymbolicValue>(V) && !isa<VPRegionValue>(V));
+ }
+};
+
+inline LiveIn_match m_LiveIn() { return {}; }
/// Match a GEP recipe (VPWidenGEPRecipe, VPInstruction, or VPReplicateRecipe)
/// and bind the source element type and operands.
diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
index d1899db20b20e..9b480a81f2158 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
@@ -3451,8 +3451,10 @@ void VPlanTransforms::addExplicitVectorLength(
// Replace all uses of the canonical IV with VPCurrentIterationPHIRecipe
// except for the canonical IV increment.
- CanonicalIV->replaceAllUsesWith(CurrentIteration);
- CanonicalIVIncrement->setOperand(0, CanonicalIV);
+ CanonicalIV->replaceUsesWithIf(CurrentIteration,
+ [CanonicalIVIncrement](VPUser &U, unsigned) {
+ return &U != CanonicalIVIncrement;
+ });
// TODO: support unroll factor > 1.
Plan.setUF(1);
}
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUnroll.cpp b/llvm/lib/Transforms/Vectorize/VPlanUnroll.cpp
index 3c4dc12950823..f21d34dbd5c00 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUnroll.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUnroll.cpp
@@ -78,7 +78,7 @@ class UnrollState {
void unrollBlock(VPBlockBase *VPB);
VPValue *getValueForPart(VPValue *V, unsigned Part) {
- if (Part == 0 || isa<VPIRValue, VPSymbolicValue, VPRegionValue>(V))
+ if (Part == 0 || isa<VPIRValue, VPSymbolicValue>(V))
return V;
assert((VPV2Parts.contains(V) && VPV2Parts[V].size() >= Part) &&
"accessed value does not exist");
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index fc35332708218..baf90300139b4 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -172,13 +172,6 @@ const SCEV *vputils::getSCEVExprForVPValue(const VPValue *V,
PredicatedScalarEvolution &PSE,
const Loop *L) {
ScalarEvolution &SE = *PSE.getSE();
- if (isa<VPIRValue, VPSymbolicValue>(V)) {
- Value *LiveIn = V->getUnderlyingValue();
- if (LiveIn && SE.isSCEVable(LiveIn->getType()))
- return SE.getSCEV(LiveIn);
- return SE.getCouldNotCompute();
- }
-
if (auto *RV = dyn_cast<VPRegionValue>(V)) {
assert(RV == RV->getDefiningRegion()->getCanonicalIV() &&
"RegionValue must be canonical IV");
@@ -188,6 +181,13 @@ const SCEV *vputils::getSCEVExprForVPValue(const VPValue *V,
L, SCEV::FlagAnyWrap);
}
+ if (isa<VPIRValue, VPSymbolicValue>(V)) {
+ Value *LiveIn = V->getUnderlyingValue();
+ if (LiveIn && SE.isSCEVable(LiveIn->getType()))
+ return SE.getSCEV(LiveIn);
+ return SE.getCouldNotCompute();
+ }
+
// Helper to create SCEVs for binary and unary operations.
auto CreateSCEV = [&](ArrayRef<VPValue *> Ops,
function_ref<const SCEV *(ArrayRef<SCEVUse>)> CreateFn)
@@ -414,7 +414,7 @@ static bool preservesUniformity(unsigned Opcode) {
bool vputils::isSingleScalar(const VPValue *VPV) {
// Live-in, symbolic and region-values represent single-scalar values.
- if (isa<VPIRValue, VPSymbolicValue, VPRegionValue>(VPV))
+ if (isa<VPIRValue, VPSymbolicValue>(VPV))
return true;
if (auto *Rep = dyn_cast<VPReplicateRecipe>(VPV)) {
@@ -451,7 +451,7 @@ bool vputils::isSingleScalar(const VPValue *VPV) {
bool vputils::isUniformAcrossVFsAndUFs(const VPValue *V) {
// Live-ins and region values are uniform.
- if (isa<VPIRValue, VPSymbolicValue, VPRegionValue>(V))
+ if (isa<VPIRValue, VPSymbolicValue>(V))
return true;
const VPRecipeBase *R = V->getDefiningRecipe();
diff --git a/llvm/lib/Transforms/Vectorize/VPlanValue.h b/llvm/lib/Transforms/Vectorize/VPlanValue.h
index 8356bcb08634f..97e0f9313cb10 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanValue.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanValue.h
@@ -49,7 +49,7 @@ class VPSingleDefRecipe;
/// coming from the input IR, symbolic values and values defined by recipes.
class LLVM_ABI_FOR_TEST VPValue {
friend struct VPIRValue;
- friend struct VPSymbolicValue;
+ friend class VPSymbolicValue;
friend class VPRecipeValue;
friend class VPRegionValue;
@@ -211,15 +211,48 @@ class LLVM_ABI_FOR_TEST VPValue {
}
};
+/// A symbolic live-in VPValue, used for values like vector trip count, VF, and
+/// VFxUF.
+class VPSymbolicValue : public VPValue {
+ /// The scalar type of this symbolic value.
+ Type *Ty;
+
+ /// Track whether this value has been materialized (replaced). After
+ /// materialization, accessing users should trigger an assertion.
+ bool Materialized = false;
+
+protected:
+ VPSymbolicValue(unsigned char SC, Type *Ty) : VPValue(SC, nullptr), Ty(Ty) {}
+
+public:
+ VPSymbolicValue(Type *Ty) : VPSymbolicValue(VPVSymbolicSC, Ty) {}
+
+ /// Returns the scalar type of this symbolic value.
+ Type *getType() const { return Ty; }
+
+ /// Returns true if this value has been materialized.
+ bool isMaterialized() const { return Materialized; }
+
+ /// Mark this value as materialized.
+ void markMaterialized() {
+ assert(!Materialized && "VPSymbolicValue already materialized");
+ Materialized = true;
+ }
+
+ static bool classof(const VPValue *V) {
+ return V->getVPValueID() == VPVSymbolicSC ||
+ V->getVPValueID() == VPRegionValueSC;
+ }
+};
+
/// VPValues defined by a VPRegionBlock, like the canonical IV.
-class VPRegionValue : public VPValue {
+class VPRegionValue : public VPSymbolicValue {
VPRegionBlock *DefiningRegion;
- Type *Ty;
DebugLoc DL;
public:
VPRegionValue(Type *Ty, DebugLoc DL, VPRegionBlock *Region)
- : VPValue(VPValue::VPRegionValueSC), DefiningRegion(Region), Ty(Ty),
+ : VPSymbolicValue(VPValue::VPRegionValueSC, Ty), DefiningRegion(Region),
DL(DL) {}
~VPRegionValue() override = default;
@@ -227,9 +260,6 @@ class VPRegionValue : public VPValue {
/// Returns the region that defines this value.
VPRegionBlock *getDefiningRegion() const { return DefiningRegion; }
- /// Returns the type of the VPRegionValue.
- Type *getType() const { return Ty; }
-
/// Returns the debug location of the VPRegionValue.
DebugLoc getDebugLoc() const { return DL; }
@@ -281,36 +311,6 @@ struct VPConstantInt : public VPIRValue {
uint64_t getZExtValue() const { return getAPInt().getZExtValue(); }
};
-/// A symbolic live-in VPValue, used for values like vector trip count, VF, and
-/// VFxUF.
-struct VPSymbolicValue : public VPValue {
- VPSymbolicValue(Type *Ty) : VPValue(VPVSymbolicSC, nullptr), Ty(Ty) {}
-
- static bool classof(const VPValue *V) {
- return V->getVPValueID() == VPVSymbolicSC;
- }
-
- /// Returns the scalar type of this symbolic value.
- Type *getType() const { return Ty; }
-
- /// Returns true if this symbolic value has been materialized.
- bool isMaterialized() const { return Materialized; }
-
- /// Mark this symbolic value as materialized.
- void markMaterialized() {
- assert(!Materialized && "VPSymbolicValue already materialized");
- Materialized = true;
- }
-
-private:
- /// The scalar type of this symbolic value.
- Type *Ty;
-
- /// Track whether this symbolic value has been materialized (replaced).
- /// After materialization, accessing users should trigger an assertion.
- bool Materialized = false;
-};
-
/// Abstract base class for VPValues defined by a VPRecipeBase.
class VPRecipeValue : public VPValue {
friend class VPValue;
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index 723977595938f..f3d3a40e9629b 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -1894,6 +1894,32 @@ TEST_F(VPUtilsTest, IsUniformAcrossVFsAndUFsForSingleScalarOpcodes) {
vputils::isUniformAcrossVFsAndUFs(FirstActiveLaneNonUniform.get()));
}
+TEST_F(VPBasicBlockTest, VPRegionValueClonePropagatesMaterialized) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Preheader = Plan.getEntry();
+ VPBasicBlock *Header = Plan.createVPBasicBlock("header");
+ VPBasicBlock *Latch = Plan.createVPBasicBlock("latch");
+ VPRegionBlock *Region = Plan.createLoopRegion(Type::getInt32Ty(C), DebugLoc(),
+ "loop", Header, Latch);
+ VPBlockUtils::connectBlocks(Header, Latch);
+ VPBlockUtils::connectBlocks(Preheader, Region);
+ VPBlockUtils::connectBlocks(Region, Plan.getScalarHeader());
+
+ VPRegionValue *CanIV = Region->getCanonicalIV();
+ EXPECT_TRUE(isa<VPSymbolicValue>(CanIV));
+ EXPECT_FALSE(CanIV->isMaterialized());
+
+ // Materialize the canonical IV by replacing all uses, then verify clone
+ // propagates the materialized state.
+ CanIV->replaceAllUsesWith(Plan.getConstantInt(32, 0));
+ EXPECT_TRUE(CanIV->isMaterialized());
+
+ std::unique_ptr<VPlan> Clone(Plan.duplicate());
+ VPRegionValue *ClonedCanIV = Clone->getVectorLoopRegion()->getCanonicalIV();
+ EXPECT_NE(CanIV, ClonedCanIV);
+ EXPECT_TRUE(ClonedCanIV->isMaterialized());
+}
+
#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
TEST_F(VPInstructionTest, VPSymbolicValueConstructUserAfterMaterialization) {
VPlan &Plan = getPlan();
More information about the llvm-commits
mailing list