[llvm] a4cff75 - [SandboxVec][VecUtils] Introduce DeadInstrMorgue (#221482)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 10 08:51:50 PDT 2026
Author: Anshil Gandhi
Date: 2026-09-10T21:21:45+05:30
New Revision: a4cff758b654b7c49c35e7445a025681b8e7a7f2
URL: https://github.com/llvm/llvm-project/commit/a4cff758b654b7c49c35e7445a025681b8e7a7f2
DIFF: https://github.com/llvm/llvm-project/commit/a4cff758b654b7c49c35e7445a025681b8e7a7f2.diff
LOG: [SandboxVec][VecUtils] Introduce DeadInstrMorgue (#221482)
Move dead instructions collector and erasor into VecUtils, so that
it is usable by both, BundleVec and LoadStoreVec, vectorizers. NFC
Added:
Modified:
llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.h
llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h
llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.cpp
llvm/lib/Transforms/Vectorize/SandboxVectorizer/VecUtils.cpp
llvm/unittests/Transforms/Vectorize/SandboxVectorizer/VecUtilsTest.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.h b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.h
index 8cc2b8ffe5cd8..62ff4a527d1f7 100644
--- a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.h
+++ b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.h
@@ -44,20 +44,18 @@ class LLVM_ABI BundleVec final : public RegionPass {
static constexpr StringRef BottomUpArgStr = "bottom-up";
/// Direction for vectorization, set from the mandatory aux argument.
SchedDirection Dir;
- /// The original instructions that are potentially dead after vectorization.
- DenseSet<Instruction *> DeadInstrCandidates;
/// Maps scalars to vectors.
std::unique_ptr<InstrMaps> IMaps;
/// Counter used for force-stopping the vectorizer after this many
/// invocations. Used for debugging miscompiles.
unsigned long InvocationCnt = 0;
+ VecUtils::DeadInstructionMorgue DeadInstrMorgue;
+
/// Creates and returns a vector instruction that replaces the instructions in
/// \p Bndl. \p Operands are the already vectorized operands.
Value *createVectorInstr(ArrayRef<Value *> Bndl, ArrayRef<Value *> Operands);
- /// Erases all dead instructions from the dead instruction candidates
- /// collected during vectorization.
- void tryEraseDeadInstrs();
+
/// Creates a shuffle instruction that shuffles \p VecOp according to \p Mask.
/// \p UserBB is the block of the user bundle.
Value *createShuffle(Value *VecOp, const ShuffleMask &Mask,
@@ -65,11 +63,6 @@ class LLVM_ABI BundleVec final : public RegionPass {
/// Packs all elements of \p ToPack into a vector and returns that vector. \p
/// UserBB is the block of the user bundle.
Value *createPack(ArrayRef<Value *> ToPack, BasicBlock *UserBB);
- /// After we create vectors for groups of instructions, the original
- /// instructions are potentially dead and may need to be removed. This
- /// function helps collect these instructions (along with the pointer operands
- /// for loads/stores) so that they can be cleaned up later.
- void collectPotentiallyDeadInstrs(ArrayRef<Value *> Bndl);
/// Helper class describing how(if) to vectorize the code.
class ActionsVector {
diff --git a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h
index ed1edebc94a79..80e266d1c3022 100644
--- a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h
+++ b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/VecUtils.h
@@ -403,6 +403,38 @@ class VecUtils {
}
};
+ /// Utility class to collect and erase dead instructions.
+ class DeadInstructionMorgue {
+ public:
+ DeadInstructionMorgue() = default;
+ DeadInstructionMorgue(const DeadInstructionMorgue &) = delete;
+
+ /// Record instructions in \p Bndl that may be dead after vectorization.
+ /// For load/store bundles, also record non-first-lane pointer operands;
+ /// the first lane's pointer is skipped because the vector load/store
+ /// reuses it. Erased later by \c tryEraseDeadInstrs().
+ void collectPotentiallyDeadInstrs(ArrayRef<Value *> Bndl);
+
+ /// Erase candidates recorded by \c collectPotentiallyDeadInstrs() that
+ /// now have no uses, then clear the candidate set.
+ void tryEraseDeadInstrs();
+
+#ifndef NDEBUG
+ void print(raw_ostream &OS) const {
+ OS << "DeadInstrCandidates:\n";
+ for (auto *I : DeadInstrCandidates)
+ OS << *I << '\n';
+ }
+ LLVM_DUMP_METHOD void debug() const {
+ print(dbgs());
+ dbgs() << '\n';
+ }
+#endif /* NDEBUG */
+
+ private:
+ DenseSet<Instruction *> DeadInstrCandidates;
+ };
+
/// Helper for creating LaneValueEnumerator ranges. Can be used in for loops
/// like: `for (auto [Lane, V] : enumerateLanes(Range))`
template <typename ValueContainerT>
diff --git a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.cpp b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.cpp
index 19a9c05c9c69d..5f4ca6fe12686 100644
--- a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.cpp
+++ b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/BundleVec.cpp
@@ -161,26 +161,6 @@ Value *BundleVec::createVectorInstr(ArrayRef<Value *> Bndl,
return NewI;
}
-void BundleVec::tryEraseDeadInstrs() {
- DenseMap<BasicBlock *, SmallVector<Instruction *>> SortedDeadInstrCandidates;
- // The dead instrs could span BBs, so we need to collect and sort them per BB.
- for (auto *DeadI : DeadInstrCandidates)
- SortedDeadInstrCandidates[DeadI->getParent()].push_back(DeadI);
- for (auto &Pair : SortedDeadInstrCandidates)
- sort(Pair.second,
- [](Instruction *I1, Instruction *I2) { return I1->comesBefore(I2); });
- for (const auto &Pair : SortedDeadInstrCandidates) {
- for (Instruction *I : reverse(Pair.second)) {
- if (I->hasNUses(0)) {
- // Erase the dead instructions bottom-to-top.
- LLVM_DEBUG(dbgs() << DEBUG_PREFIX << "Erase dead: " << *I << "\n");
- I->eraseFromParent();
- }
- }
- }
- DeadInstrCandidates.clear();
-}
-
Value *BundleVec::createShuffle(Value *VecOp, const ShuffleMask &Mask,
BasicBlock *UserBB) {
BasicBlock::iterator WhereIt =
@@ -241,31 +221,6 @@ Value *BundleVec::createPack(ArrayRef<Value *> ToPack, BasicBlock *UserBB) {
return LastInsert;
}
-void BundleVec::collectPotentiallyDeadInstrs(ArrayRef<Value *> Bndl) {
- for (Value *V : Bndl)
- DeadInstrCandidates.insert(cast<Instruction>(V));
- // Also collect the GEPs of vectorized loads and stores.
- auto Opcode = cast<Instruction>(Bndl[0])->getOpcode();
- switch (Opcode) {
- case Instruction::Opcode::Load: {
- for (Value *V : drop_begin(Bndl))
- if (auto *Ptr =
- dyn_cast<Instruction>(cast<LoadInst>(V)->getPointerOperand()))
- DeadInstrCandidates.insert(Ptr);
- break;
- }
- case Instruction::Opcode::Store: {
- for (Value *V : drop_begin(Bndl))
- if (auto *Ptr =
- dyn_cast<Instruction>(cast<StoreInst>(V)->getPointerOperand()))
- DeadInstrCandidates.insert(Ptr);
- break;
- }
- default:
- break;
- }
-}
-
Action *BundleVec::vectorizeRec(ArrayRef<Value *> Bndl,
ArrayRef<Value *> UserBndl, unsigned Depth,
LegalityAnalysis &Legality) {
@@ -450,7 +405,7 @@ Value *BundleVec::emitVectors() {
// Collect any potentially dead scalar instructions, including the
// original scalars and pointer operands of loads/stores.
if (NewVec != nullptr)
- collectPotentiallyDeadInstrs(Bndl);
+ DeadInstrMorgue.collectPotentiallyDeadInstrs(Bndl);
// Emit unpacks for all external uses, if any.
emitUnpacksForExternalUses(ActionPtr->Bndl, NewVec);
@@ -562,7 +517,6 @@ bool BundleVec::tryVectorize(ArrayRef<Value *> Bndl,
Change = false;
if (LLVM_UNLIKELY(InvocationCnt++ >= StopAt && StopAt != StopAtDisabled))
return false;
- DeadInstrCandidates.clear();
Legality.clear();
Actions.clear();
DebugBndlCnt = 0;
@@ -571,7 +525,7 @@ bool BundleVec::tryVectorize(ArrayRef<Value *> Bndl,
<< "Vec: Vectorization Actions:\n";
Actions.dump());
emitVectors();
- tryEraseDeadInstrs();
+ DeadInstrMorgue.tryEraseDeadInstrs();
return Change;
}
diff --git a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/VecUtils.cpp b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/VecUtils.cpp
index bf8f66d00e7eb..abf3d79966d8f 100644
--- a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/VecUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/VecUtils.cpp
@@ -12,6 +12,7 @@
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/SandboxIR/Instruction.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Transforms/Vectorize/SandboxVectorizer/Debug.h"
#include "llvm/Transforms/Vectorize/SandboxVectorizer/InstrMaps.h"
namespace llvm::sandboxir {
@@ -103,6 +104,54 @@ unsigned VecUtils::getFloorPowerOf2(unsigned Num) {
return Num & ~Mask;
}
+void VecUtils::DeadInstructionMorgue::collectPotentiallyDeadInstrs(
+ ArrayRef<Value *> Bndl) {
+ for (Value *V : Bndl)
+ DeadInstrCandidates.insert(cast<Instruction>(V));
+ // Also collect the GEPs of vectorized loads and stores.
+ auto Opcode = cast<Instruction>(Bndl[0])->getOpcode();
+ switch (Opcode) {
+ case Instruction::Opcode::Load: {
+ for (Value *V : drop_begin(Bndl))
+ if (auto *Ptr =
+ dyn_cast<Instruction>(cast<LoadInst>(V)->getPointerOperand()))
+ DeadInstrCandidates.insert(Ptr);
+ break;
+ }
+ case Instruction::Opcode::Store: {
+ for (Value *V : drop_begin(Bndl))
+ if (auto *Ptr =
+ dyn_cast<Instruction>(cast<StoreInst>(V)->getPointerOperand()))
+ DeadInstrCandidates.insert(Ptr);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void VecUtils::DeadInstructionMorgue::tryEraseDeadInstrs() {
+ DenseMap<BasicBlock *, SmallVector<Instruction *>> SortedDeadInstrCandidates;
+ // The dead instrs could span BBs, so we need to collect and sort them per BB.
+ for (auto *V : DeadInstrCandidates) {
+ auto *DeadI = cast<Instruction>(V);
+ SortedDeadInstrCandidates[DeadI->getParent()].push_back(DeadI);
+ }
+ for (auto &Pair : SortedDeadInstrCandidates)
+ sort(Pair.second,
+ [](Instruction *I1, Instruction *I2) { return I1->comesBefore(I2); });
+ for (const auto &Pair : SortedDeadInstrCandidates) {
+ for (Instruction *I : reverse(Pair.second)) {
+ if (I->hasNUses(0)) {
+ // Erase the dead instructions bottom-to-top.
+ LLVM_DEBUG(dbgs() << DEBUG_PREFIX << "Erase dead: " << *I << "\n");
+ I->eraseFromParent();
+ }
+ }
+ }
+ DeadInstrCandidates.clear();
+}
+
#ifndef NDEBUG
template <typename T> static void dumpImpl(ArrayRef<T *> Bndl) {
for (auto [Idx, V] : enumerate(Bndl))
diff --git a/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/VecUtilsTest.cpp b/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/VecUtilsTest.cpp
index 845d381577c6c..b4f40a1eee201 100644
--- a/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/VecUtilsTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/VecUtilsTest.cpp
@@ -1129,6 +1129,146 @@ define void @vectorized_user(ptr %p) {
.empty());
}
+TEST_F(VecUtilsTest, DeadInstructionMorgueCollectScalar) {
+ parseIR(R"IR(
+define void @scalar(i8 %v) {
+entry:
+ %live = add i8 %v, 1
+ %dead0 = add i8 %v, 2
+ %dead1 = add i8 %v, 3
+ %use_live = add i8 %live, %live
+ ret void
+}
+)IR");
+ Function &LLVMF = *M->getFunction("scalar");
+
+ sandboxir::Context Ctx(C);
+ auto &F = *Ctx.createFunction(&LLVMF);
+ auto &BB = *F.begin();
+ auto It = BB.begin();
+ auto *Live = cast<sandboxir::Instruction>(&*It++);
+ auto *Dead0 = cast<sandboxir::Instruction>(&*It++);
+ auto *Dead1 = cast<sandboxir::Instruction>(&*It++);
+ auto *UseLive = cast<sandboxir::Instruction>(&*It++);
+ auto *Ret = cast<sandboxir::Instruction>(&*It++);
+
+ sandboxir::VecUtils::DeadInstructionMorgue Morgue;
+ Morgue.collectPotentiallyDeadInstrs({Live, Dead0, Dead1});
+ Morgue.tryEraseDeadInstrs();
+
+ // %dead0 and %dead1 had no uses, so they should have been erased. %live is
+ // used by %use_live, so it (and %use_live) should survive.
+ It = BB.begin();
+ EXPECT_EQ(&*It++, Live);
+ EXPECT_EQ(&*It++, UseLive);
+ EXPECT_EQ(&*It++, Ret);
+ EXPECT_EQ(It, BB.end());
+}
+
+TEST_F(VecUtilsTest, DeadInstructionMorgueCollectLoad) {
+ parseIR(R"IR(
+define void @loadtest(ptr %p) {
+entry:
+ %gep0 = getelementptr float, ptr %p, i32 0
+ %gep1 = getelementptr float, ptr %p, i32 1
+ %ld0 = load float, ptr %gep0
+ %ld1 = load float, ptr %gep1
+ ret void
+}
+)IR");
+ Function &LLVMF = *M->getFunction("loadtest");
+
+ sandboxir::Context Ctx(C);
+ auto &F = *Ctx.createFunction(&LLVMF);
+ auto &BB = *F.begin();
+ auto It = BB.begin();
+ auto *Gep0 = cast<sandboxir::Instruction>(&*It++);
+ It++; // %gep1, expected to be erased.
+ auto *Ld0 = cast<sandboxir::LoadInst>(&*It++);
+ auto *Ld1 = cast<sandboxir::LoadInst>(&*It++);
+ auto *Ret = cast<sandboxir::Instruction>(&*It++);
+
+ sandboxir::VecUtils::DeadInstructionMorgue Morgue;
+ Morgue.collectPotentiallyDeadInstrs({Ld0, Ld1});
+ Morgue.tryEraseDeadInstrs();
+
+ // %ld0 and %ld1 are collected directly and have no uses, so they are
+ // erased. Only the pointer operand of the non-first lane (%gep1) is
+ // collected -- %gep0 (pointer of the first lane) is intentionally skipped,
+ // since it would be reused as the pointer of a replacement vector load --
+ // so %gep0 survives even though it is now dead.
+ It = BB.begin();
+ EXPECT_EQ(&*It++, Gep0);
+ EXPECT_EQ(&*It++, Ret);
+ EXPECT_EQ(It, BB.end());
+}
+
+TEST_F(VecUtilsTest, DeadInstructionMorgueCollectStore) {
+ parseIR(R"IR(
+define void @storetest(ptr %p, float %v) {
+entry:
+ %gep0 = getelementptr float, ptr %p, i32 0
+ %gep1 = getelementptr float, ptr %p, i32 1
+ store float %v, ptr %gep0
+ store float %v, ptr %gep1
+ ret void
+}
+)IR");
+ Function &LLVMF = *M->getFunction("storetest");
+
+ sandboxir::Context Ctx(C);
+ auto &F = *Ctx.createFunction(&LLVMF);
+ auto &BB = *F.begin();
+ auto It = BB.begin();
+ auto *Gep0 = cast<sandboxir::Instruction>(&*It++);
+ It++; // %gep1, expected to be erased.
+ auto *St0 = cast<sandboxir::StoreInst>(&*It++);
+ auto *St1 = cast<sandboxir::StoreInst>(&*It++);
+ auto *Ret = cast<sandboxir::Instruction>(&*It++);
+
+ sandboxir::VecUtils::DeadInstructionMorgue Morgue;
+ Morgue.collectPotentiallyDeadInstrs({St0, St1});
+ Morgue.tryEraseDeadInstrs();
+
+ // %st0 and %st1 are collected directly (stores are always "used" 0 times)
+ // and get erased. Only the pointer operand of the non-first lane (%gep1)
+ // is collected, so %gep0 survives even though it is now dead.
+ It = BB.begin();
+ EXPECT_EQ(&*It++, Gep0);
+ EXPECT_EQ(&*It++, Ret);
+ EXPECT_EQ(It, BB.end());
+}
+
+TEST_F(VecUtilsTest, DeadInstructionMorgueClearsCandidatesAfterErase) {
+ parseIR(R"IR(
+define void @clears(i8 %v) {
+entry:
+ %dead0 = add i8 %v, 1
+ ret void
+}
+)IR");
+ Function &LLVMF = *M->getFunction("clears");
+
+ sandboxir::Context Ctx(C);
+ auto &F = *Ctx.createFunction(&LLVMF);
+ auto &BB = *F.begin();
+ auto It = BB.begin();
+ auto *Dead0 = cast<sandboxir::Instruction>(&*It++);
+ auto *Ret = cast<sandboxir::Instruction>(&*It++);
+
+ sandboxir::VecUtils::DeadInstructionMorgue Morgue;
+ Morgue.collectPotentiallyDeadInstrs({Dead0});
+ Morgue.tryEraseDeadInstrs();
+ // The candidate set should have been cleared by the first call, so a
+ // second call must be a safe no-op rather than trying to dereference the
+ // now-erased %dead0 instruction again.
+ Morgue.tryEraseDeadInstrs();
+
+ It = BB.begin();
+ EXPECT_EQ(&*It++, Ret);
+ EXPECT_EQ(It, BB.end());
+}
+
TEST_F(VecUtilsTest, GetNextUserBundle_VectorizedSeedUser) {
parseIR(R"IR(
define void @vectorized_seed_user(ptr %p) {
More information about the llvm-commits
mailing list