[llvm] [VPlan] Add poisoning value handle for VPValue. (NFCI) (PR #185054)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 10 06:39:03 PDT 2026
https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/185054
>From 49a029f23d91baf6c101fba9bc37ede4d62b2812 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 5 Mar 2026 11:32:24 +0000
Subject: [PATCH 1/2] [VPlan] Add poisoning value handle for VPValue. (NFCI)
Add a PoisoningVPValueHandle (similar to PoisoningValueHandle) that gets
poisoned when the underlying VPValue gets erased. It can be used to
catch dangling pointer bugs in maps. For now just used in VPTypeInfo,
which as per recent discussions (see
https://github.com/llvm/llvm-project/issues/created_by/184650) can
run into such issues.
For now, this just poisons VPRecipeValues, as the backing recipes allow
retrieving the VPlan containing the map tracking the poisoning handles.
That should cover the main class of error cases, as VPIRValues and
VPSymbolicValues generally only get deleted on VPlan destruction
---
llvm/lib/Transforms/Vectorize/VPlan.cpp | 56 +++++
llvm/lib/Transforms/Vectorize/VPlan.h | 7 +
.../Transforms/Vectorize/VPlanAnalysis.cpp | 5 +-
llvm/lib/Transforms/Vectorize/VPlanAnalysis.h | 4 +-
.../lib/Transforms/Vectorize/VPlanRecipes.cpp | 5 +
llvm/lib/Transforms/Vectorize/VPlanValue.h | 137 ++++++++++++
.../Transforms/Vectorize/VPlanTest.cpp | 201 ++++++++++++++++++
7 files changed, 411 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.cpp b/llvm/lib/Transforms/Vectorize/VPlan.cpp
index 0ceeb570e8b1f..1272c5412ffb6 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlan.cpp
@@ -152,6 +152,62 @@ VPRecipeValue::~VPRecipeValue() {
Def->removeDefinedValue(this);
}
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+const VPlan *PoisoningVPValueHandle::getVPlan(const VPValue *V) {
+ const VPRecipeBase *Def = V->getDefiningRecipe();
+ if (!Def || !Def->getParent())
+ return nullptr;
+ return Def->getParent()->getPlan();
+}
+
+void PoisoningVPValueHandle::addToList() {
+ Plan = getVPlan(VP);
+ if (!Plan)
+ return;
+ auto &Map = Plan->PoisoningHandles;
+ PoisoningVPValueHandle *&Head = Map[VP];
+ Next = Head;
+ Head = this;
+}
+
+void PoisoningVPValueHandle::removeFromList() {
+ if (!Plan)
+ return;
+ auto &Map = Plan->PoisoningHandles;
+ auto It = Map.find(VP);
+ assert(It != Map.end() && "VPValue not in handle map");
+ PoisoningVPValueHandle **Cur = &It->second;
+ while (*Cur != this) {
+ assert(*Cur && "Handle not found in list for VPValue");
+ Cur = &(*Cur)->Next;
+ }
+ *Cur = Next;
+ if (It->second == nullptr)
+ Map.erase(It);
+ Next = nullptr;
+ Plan = nullptr;
+}
+
+void PoisoningVPValueHandle::poisonAll(const VPlan *Plan, const VPValue *V) {
+ if (!Plan)
+ return;
+ auto &Map = Plan->PoisoningHandles;
+ auto It = Map.find(V);
+ if (It == Map.end())
+ return;
+ PoisoningVPValueHandle *Cur = It->second;
+ while (Cur) {
+ assert(!Cur->Poisoned && "Handle already poisoned");
+ Cur->Poisoned = true;
+ Cur->Plan = nullptr;
+ Cur = Cur->Next;
+ }
+ Map.erase(It);
+}
+#else
+void PoisoningVPValueHandle::poisonAll(const VPlan *, const VPValue *) {}
+#endif
+
// Get the top-most entry block of \p Start. This is the entry block of the
// containing VPlan. This function is templated to support both const and non-const blocks
template <typename T> static T *getPlanEntry(T *Start) {
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.h b/llvm/lib/Transforms/Vectorize/VPlan.h
index 36728d101196f..3bd2c8d753798 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.h
+++ b/llvm/lib/Transforms/Vectorize/VPlan.h
@@ -4596,6 +4596,13 @@ class VPlan {
/// from IR Values to VPIRValues.
SmallMapVector<Value *, VPIRValue *, 16> LiveIns;
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ /// Map from VPValues to PoisoningVPValueHandle lists for dangling pointer
+ /// detection.
+ mutable DenseMap<const VPValue *, PoisoningVPValueHandle *> PoisoningHandles;
+ friend class PoisoningVPValueHandle;
+#endif
+
/// Blocks allocated and owned by the VPlan. They will be deleted once the
/// VPlan is destroyed.
SmallVector<VPBlockBase *> CreatedBlocks;
diff --git a/llvm/lib/Transforms/Vectorize/VPlanAnalysis.cpp b/llvm/lib/Transforms/Vectorize/VPlanAnalysis.cpp
index 998e48d411f50..d229c8e8c3825 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanAnalysis.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanAnalysis.cpp
@@ -273,8 +273,9 @@ Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPReplicateRecipe *R) {
}
Type *VPTypeAnalysis::inferScalarType(const VPValue *V) {
- if (Type *CachedTy = CachedTypes.lookup(V))
- return CachedTy;
+ auto CachedIt = CachedTypes.find_as(V);
+ if (CachedIt != CachedTypes.end())
+ return CachedIt->second;
if (auto *IRV = dyn_cast<VPIRValue>(V))
return IRV->getType();
diff --git a/llvm/lib/Transforms/Vectorize/VPlanAnalysis.h b/llvm/lib/Transforms/Vectorize/VPlanAnalysis.h
index dc4be4270f7f1..69f9f93fe2e43 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanAnalysis.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanAnalysis.h
@@ -9,6 +9,7 @@
#ifndef LLVM_TRANSFORMS_VECTORIZE_VPLANANALYSIS_H
#define LLVM_TRANSFORMS_VECTORIZE_VPLANANALYSIS_H
+#include "VPlanValue.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/MapVector.h"
@@ -17,7 +18,6 @@
namespace llvm {
class LLVMContext;
-class VPValue;
class VPBlendRecipe;
class VPInstruction;
class VPWidenRecipe;
@@ -40,7 +40,7 @@ class Type;
/// be constructed once a VPlan has been modified in a way that invalidates any
/// of the previously inferred types.
class VPTypeAnalysis {
- DenseMap<const VPValue *, Type *> CachedTypes;
+ DenseMap<PoisoningVPValueHandle, Type *> CachedTypes;
/// Type of the canonical induction variable. Used for all VPValues without
/// any underlying IR value (like the vector trip count or the backedge-taken
/// count).
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index 26183f15306f1..d6ffe8a2ad801 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -253,6 +253,11 @@ void VPRecipeBase::removeFromParent() {
iplist<VPRecipeBase>::iterator VPRecipeBase::eraseFromParent() {
assert(getParent() && "Recipe not in any VPBasicBlock");
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ if (const VPlan *Plan = getParent()->getPlan())
+ for (auto *Def : definedValues())
+ PoisoningVPValueHandle::poisonAll(Plan, Def);
+#endif
return getParent()->getRecipeList().erase(getIterator());
}
diff --git a/llvm/lib/Transforms/Vectorize/VPlanValue.h b/llvm/lib/Transforms/Vectorize/VPlanValue.h
index 4ef78341e0654..085e30c3e837d 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanValue.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanValue.h
@@ -20,6 +20,7 @@
#ifndef LLVM_TRANSFORMS_VECTORIZE_VPLAN_VALUE_H
#define LLVM_TRANSFORMS_VECTORIZE_VPLAN_VALUE_H
+#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/TinyPtrVector.h"
@@ -35,10 +36,12 @@ class raw_ostream;
class Type;
class Value;
class VPDef;
+class VPlan;
class VPSlotTracker;
class VPUser;
class VPRecipeBase;
class VPPhiAccessors;
+class PoisoningVPValueHandle;
/// This is the base class of the VPlan Def/Use graph, used for modeling the
/// data flow into, within and out of the VPlan. VPValues can stand for live-ins
@@ -427,6 +430,140 @@ class VPDef {
unsigned getNumDefinedValues() const { return DefinedValues.size(); }
};
+/// Value handle that poisons itself when the referenced VPValue is deleted,
+/// catching dangling pointer bugs (e.g. stale DenseMap keys). Only
+/// VPRecipeValues with a retrievable VPlan are tracked; other VPValue types
+/// work but are not poisoned. Does *not* follow RAUW.
+class LLVM_ABI_FOR_TEST PoisoningVPValueHandle {
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ const VPValue *VP = nullptr;
+ const VPlan *Plan = nullptr;
+ bool Poisoned = false;
+ PoisoningVPValueHandle *Next = nullptr;
+
+ /// Returns true if \p V is not null and not a DenseMap sentinel.
+ static bool isValid(const VPValue *V) {
+ return V && V != DenseMapInfo<const VPValue *>::getEmptyKey() &&
+ V != DenseMapInfo<const VPValue *>::getTombstoneKey();
+ }
+
+ /// Return the VPlan owning \p V, or nullptr if not available.
+ static const VPlan *getVPlan(const VPValue *V);
+
+ const VPValue *getRawValPtr() const { return VP; }
+ void setRawValPtr(const VPValue *P) {
+ if (isValid(VP) && !Poisoned)
+ removeFromList();
+ VP = P;
+ Poisoned = false;
+ Plan = nullptr;
+ if (isValid(VP))
+ addToList();
+ }
+
+ void addToList();
+ void removeFromList();
+
+ friend struct DenseMapInfo<PoisoningVPValueHandle>;
+#else
+ const VPValue *VP = nullptr;
+
+ const VPValue *getRawValPtr() const { return VP; }
+ void setRawValPtr(const VPValue *P) { VP = P; }
+#endif
+
+ const VPValue *getValPtr() const {
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ assert(!Poisoned && "Accessed a poisoned VPValue handle!");
+#endif
+ return VP;
+ }
+
+public:
+ PoisoningVPValueHandle() = default;
+ PoisoningVPValueHandle(const VPValue *V) : VP(V) {
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ if (isValid(VP))
+ addToList();
+#endif
+ }
+
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ PoisoningVPValueHandle(const PoisoningVPValueHandle &RHS)
+ : VP(RHS.VP), Poisoned(RHS.Poisoned) {
+ if (isValid(VP) && !Poisoned)
+ addToList();
+ }
+
+ ~PoisoningVPValueHandle() {
+ if (isValid(VP) && !Poisoned)
+ removeFromList();
+ }
+
+ PoisoningVPValueHandle &operator=(const PoisoningVPValueHandle &RHS) {
+ if (this == &RHS)
+ return *this;
+ if (isValid(VP) && !Poisoned)
+ removeFromList();
+ VP = RHS.VP;
+ Poisoned = RHS.Poisoned;
+ if (isValid(VP) && !Poisoned)
+ addToList();
+ return *this;
+ }
+#endif
+
+ operator const VPValue *() const { return getValPtr(); }
+ const VPValue *operator->() const { return getValPtr(); }
+ const VPValue &operator*() const { return *getValPtr(); }
+
+ static void poisonAll(const VPlan *Plan, const VPValue *V);
+};
+
+template <> struct DenseMapInfo<PoisoningVPValueHandle> {
+ static inline PoisoningVPValueHandle getEmptyKey() {
+ PoisoningVPValueHandle Res;
+ Res.setRawValPtr(DenseMapInfo<const VPValue *>::getEmptyKey());
+ return Res;
+ }
+
+ static inline PoisoningVPValueHandle getTombstoneKey() {
+ PoisoningVPValueHandle Res;
+ Res.setRawValPtr(DenseMapInfo<const VPValue *>::getTombstoneKey());
+ return Res;
+ }
+
+ static unsigned getHashValue(const PoisoningVPValueHandle &Val) {
+ return DenseMapInfo<const VPValue *>::getHashValue(Val.getRawValPtr());
+ }
+
+ static bool isEqual(const PoisoningVPValueHandle &LHS,
+ const PoisoningVPValueHandle &RHS) {
+ if (!DenseMapInfo<const VPValue *>::isEqual(LHS.getRawValPtr(),
+ RHS.getRawValPtr()))
+ return false;
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ assert(!LHS.Poisoned && !RHS.Poisoned &&
+ "Accessed a poisoned VPValue handle!");
+#endif
+ return true;
+ }
+
+ // Allow lookup by raw VPValue* via find_as().
+ static unsigned getHashValue(const VPValue *Val) {
+ return DenseMapInfo<const VPValue *>::getHashValue(Val);
+ }
+
+ static bool isEqual(const VPValue *LHS, const PoisoningVPValueHandle &RHS) {
+ if (!DenseMapInfo<const VPValue *>::isEqual(LHS, RHS.getRawValPtr()))
+ return false;
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+ assert(!RHS.Poisoned && "Accessed a poisoned VPValue handle!");
+#endif
+ return true;
+ }
+};
+
} // namespace llvm
#endif // LLVM_TRANSFORMS_VECTORIZE_VPLAN_VALUE_H
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index 254755bb2bf4c..376710a71cef1 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -1745,5 +1745,206 @@ TEST_F(VPRecipeTest, CastToVPSingleDefRecipe) {
// TODO: check other VPSingleDefRecipes.
}
+#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+using PoisoningVPValueHandleTest = VPlanTestBase;
+
+TEST_F(PoisoningVPValueHandleTest, BasicOperation) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ Entry->appendRecipe(I1);
+ const VPValue *VPV = I1->getVPSingleValue();
+ PoisoningVPValueHandle H(VPV);
+
+ // Dereferencing a live handle returns the VPValue.
+ EXPECT_EQ(VPV, static_cast<const VPValue *>(H));
+ EXPECT_EQ(VPV, H.operator->());
+}
+
+TEST_F(PoisoningVPValueHandleTest, NullHandle) {
+ PoisoningVPValueHandle H;
+ EXPECT_EQ(nullptr, static_cast<const VPValue *>(H));
+
+ PoisoningVPValueHandle H2(nullptr);
+ EXPECT_EQ(nullptr, static_cast<const VPValue *>(H2));
+}
+
+TEST_F(PoisoningVPValueHandleTest, DenseMapKey) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ VPInstruction *I2 = new VPInstruction(VPInstruction::VScale, {});
+ Entry->appendRecipe(I1);
+ Entry->appendRecipe(I2);
+ const VPValue *VPV1 = I1->getVPSingleValue();
+ const VPValue *VPV2 = I2->getVPSingleValue();
+
+ DenseMap<PoisoningVPValueHandle, int> Map;
+ Map[VPV1] = 1;
+ Map[VPV2] = 2;
+
+ EXPECT_EQ(1, Map.find_as(VPV1)->second);
+ EXPECT_EQ(2, Map.find_as(VPV2)->second);
+ EXPECT_EQ(Map.end(), Map.find_as(nullptr));
+}
+
+TEST_F(PoisoningVPValueHandleTest, MultipleHandlesAllPoisoned) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ Entry->appendRecipe(I1);
+ const VPValue *VPV = I1->getVPSingleValue();
+
+ PoisoningVPValueHandle H1(VPV);
+ PoisoningVPValueHandle H2(VPV);
+ PoisoningVPValueHandle H3(VPV);
+
+ EXPECT_EQ(VPV, static_cast<const VPValue *>(H1));
+ EXPECT_EQ(VPV, static_cast<const VPValue *>(H2));
+ EXPECT_EQ(VPV, static_cast<const VPValue *>(H3));
+
+ // Erasing from parent deletes the instruction, poisoning all handles.
+ I1->eraseFromParent();
+
+ // Verify all three handles are poisoned.
+ EXPECT_DEATH(
+ { (void)static_cast<const VPValue *>(H1); },
+ "Accessed a poisoned VPValue handle!");
+ EXPECT_DEATH(
+ { (void)static_cast<const VPValue *>(H2); },
+ "Accessed a poisoned VPValue handle!");
+ EXPECT_DEATH(
+ { (void)static_cast<const VPValue *>(H3); },
+ "Accessed a poisoned VPValue handle!");
+}
+
+TEST_F(PoisoningVPValueHandleTest, DenseMapEntryPoisoned) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ VPInstruction *I2 = new VPInstruction(VPInstruction::VScale, {});
+ Entry->appendRecipe(I1);
+ Entry->appendRecipe(I2);
+ const VPValue *VPV1 = I1->getVPSingleValue();
+ const VPValue *VPV2 = I2->getVPSingleValue();
+
+ DenseMap<PoisoningVPValueHandle, int> Map;
+ Map[VPV1] = 1;
+ Map[VPV2] = 2;
+
+ // Delete I1; the map entry keyed by VPV1 is now poisoned.
+ I1->eraseFromParent();
+
+ // The entry for VPV2 is still live.
+ EXPECT_EQ(2, Map.find_as(VPV2)->second);
+
+ // Accessing the poisoned key triggers the assert.
+ EXPECT_DEATH(
+ { (void)Map.find_as(VPV1)->second; },
+ "Accessed a poisoned VPValue handle!");
+}
+
+TEST_F(PoisoningVPValueHandleTest, DenseMapContainsAndFind) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ VPInstruction *I2 = new VPInstruction(VPInstruction::VScale, {});
+ Entry->appendRecipe(I1);
+ Entry->appendRecipe(I2);
+ const VPValue *VPV1 = I1->getVPSingleValue();
+ const VPValue *VPV2 = I2->getVPSingleValue();
+
+ DenseMap<PoisoningVPValueHandle, int> Map;
+ Map[VPV1] = 1;
+
+ // contains and count use isEqual(handle, handle), not find_as.
+ EXPECT_TRUE(Map.contains(VPV1));
+ EXPECT_EQ(1u, Map.count(VPV1));
+ EXPECT_FALSE(Map.contains(VPV2));
+ EXPECT_EQ(0u, Map.count(VPV2));
+
+ // Direct find with a handle key.
+ EXPECT_NE(Map.end(), Map.find(VPV1));
+ EXPECT_EQ(1, Map.find(VPV1)->second);
+ EXPECT_EQ(Map.end(), Map.find(VPV2));
+}
+
+TEST_F(PoisoningVPValueHandleTest, DenseMapContainsPoisoned) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ Entry->appendRecipe(I1);
+ const VPValue *VPV = I1->getVPSingleValue();
+
+ DenseMap<PoisoningVPValueHandle, int> Map;
+ Map[VPV] = 1;
+ PoisoningVPValueHandle H(VPV);
+
+ I1->eraseFromParent();
+
+ // Probing with a poisoned handle triggers the assert in isEqual.
+ EXPECT_DEATH(
+ { (void)Map.contains(H); }, "Accessed a poisoned VPValue handle!");
+}
+
+TEST_F(PoisoningVPValueHandleTest, ReassignHandle) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ VPInstruction *I2 = new VPInstruction(VPInstruction::VScale, {});
+ Entry->appendRecipe(I1);
+ Entry->appendRecipe(I2);
+ const VPValue *VPV1 = I1->getVPSingleValue();
+ const VPValue *VPV2 = I2->getVPSingleValue();
+
+ PoisoningVPValueHandle H(VPV1);
+ EXPECT_EQ(VPV1, static_cast<const VPValue *>(H));
+
+ // Reassign H to track VPV2 instead.
+ H = PoisoningVPValueHandle(VPV2);
+ EXPECT_EQ(VPV2, static_cast<const VPValue *>(H));
+
+ // Deleting I1 no longer affects H.
+ I1->eraseFromParent();
+ EXPECT_EQ(VPV2, static_cast<const VPValue *>(H));
+
+ // Deleting I2 poisons H.
+ I2->eraseFromParent();
+ EXPECT_DEATH(
+ { (void)static_cast<const VPValue *>(H); },
+ "Accessed a poisoned VPValue handle!");
+}
+
+TEST_F(PoisoningVPValueHandleTest, CopyAndAssignment) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ Entry->appendRecipe(I1);
+ const VPValue *VPV = I1->getVPSingleValue();
+
+ PoisoningVPValueHandle H1(VPV);
+ PoisoningVPValueHandle H2(H1); // copy construct
+ EXPECT_EQ(VPV, static_cast<const VPValue *>(H2));
+
+ PoisoningVPValueHandle H3;
+ H3 = H1; // copy assign
+ EXPECT_EQ(VPV, static_cast<const VPValue *>(H3));
+}
+
+TEST_F(PoisoningVPValueHandleTest, DeathOnAccessAfterDelete) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *Entry = Plan.getEntry();
+ VPInstruction *I1 = new VPInstruction(VPInstruction::StepVector, {});
+ Entry->appendRecipe(I1);
+ const VPValue *VPV = I1->getVPSingleValue();
+ PoisoningVPValueHandle H(VPV);
+
+ I1->eraseFromParent();
+ EXPECT_DEATH(
+ { (void)static_cast<const VPValue *>(H); },
+ "Accessed a poisoned VPValue handle!");
+}
+#endif
+
} // namespace
} // namespace llvm
>From ab7f7bbc52d9ccf1020563726585afffba2d1c28 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Tue, 10 Mar 2026 13:37:44 +0000
Subject: [PATCH 2/2] !fixup address comments, thanks
---
llvm/lib/Transforms/Vectorize/VPlanValue.h | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanValue.h b/llvm/lib/Transforms/Vectorize/VPlanValue.h
index 085e30c3e837d..3e52a2adca6be 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanValue.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanValue.h
@@ -435,7 +435,7 @@ class VPDef {
/// VPRecipeValues with a retrievable VPlan are tracked; other VPValue types
/// work but are not poisoned. Does *not* follow RAUW.
class LLVM_ABI_FOR_TEST PoisoningVPValueHandle {
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+#if !defined(NDEBUG)
const VPValue *VP = nullptr;
const VPlan *Plan = nullptr;
bool Poisoned = false;
@@ -473,7 +473,7 @@ class LLVM_ABI_FOR_TEST PoisoningVPValueHandle {
#endif
const VPValue *getValPtr() const {
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+#if !defined(NDEBUG)
assert(!Poisoned && "Accessed a poisoned VPValue handle!");
#endif
return VP;
@@ -482,13 +482,13 @@ class LLVM_ABI_FOR_TEST PoisoningVPValueHandle {
public:
PoisoningVPValueHandle() = default;
PoisoningVPValueHandle(const VPValue *V) : VP(V) {
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+#if !defined(NDEBUG)
if (isValid(VP))
addToList();
#endif
}
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+#if !defined(NDEBUG)
PoisoningVPValueHandle(const PoisoningVPValueHandle &RHS)
: VP(RHS.VP), Poisoned(RHS.Poisoned) {
if (isValid(VP) && !Poisoned)
@@ -522,15 +522,11 @@ class LLVM_ABI_FOR_TEST PoisoningVPValueHandle {
template <> struct DenseMapInfo<PoisoningVPValueHandle> {
static inline PoisoningVPValueHandle getEmptyKey() {
- PoisoningVPValueHandle Res;
- Res.setRawValPtr(DenseMapInfo<const VPValue *>::getEmptyKey());
- return Res;
+ return {DenseMapInfo<const VPValue *>::getEmptyKey()};
}
static inline PoisoningVPValueHandle getTombstoneKey() {
- PoisoningVPValueHandle Res;
- Res.setRawValPtr(DenseMapInfo<const VPValue *>::getTombstoneKey());
- return Res;
+ return {DenseMapInfo<const VPValue *>::getTombstoneKey()};
}
static unsigned getHashValue(const PoisoningVPValueHandle &Val) {
@@ -542,7 +538,7 @@ template <> struct DenseMapInfo<PoisoningVPValueHandle> {
if (!DenseMapInfo<const VPValue *>::isEqual(LHS.getRawValPtr(),
RHS.getRawValPtr()))
return false;
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+#if !defined(NDEBUG)
assert(!LHS.Poisoned && !RHS.Poisoned &&
"Accessed a poisoned VPValue handle!");
#endif
@@ -557,7 +553,7 @@ template <> struct DenseMapInfo<PoisoningVPValueHandle> {
static bool isEqual(const VPValue *LHS, const PoisoningVPValueHandle &RHS) {
if (!DenseMapInfo<const VPValue *>::isEqual(LHS, RHS.getRawValPtr()))
return false;
-#if LLVM_ENABLE_ABI_BREAKING_CHECKS && !defined(NDEBUG)
+#if !defined(NDEBUG)
assert(!RHS.Poisoned && "Accessed a poisoned VPValue handle!");
#endif
return true;
More information about the llvm-commits
mailing list