[llvm] [SandboxVectorizer] Fix out-of-bounds SeedBundle access (PR #215976)
Anshil Gandhi via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 13 01:57:49 PDT 2026
https://github.com/gandhi56 updated https://github.com/llvm/llvm-project/pull/215976
>From e53cb0d289efca91251d3d516e34bf6694c158df Mon Sep 17 00:00:00 2001
From: Anshil Gandhi <gandhi21299 at gmail.com>
Date: Thu, 13 Aug 2026 03:27:29 -0400
Subject: [PATCH] [SandboxVectorizer] Fix out-of-bounds SeedBundle access on
cross-bundle erasure
SeedCollection::runOnFunction() indexed a SeedBundle at
Seeds.getFirstUnusedElementIdx() (and, separately, at a hardcoded 0) without
first checking whether the bundle was already fully used.
getFirstUnusedElementIdx() returns Seeds.size() -- one past the end -- once
everything is used, so indexing there reads out of bounds.
This was always a latent bug, but -sbvec-collect-seeds=stores,loads is what
actually exposes it: store and load seeds share one erase-instruction
callback (SeedContainer::erase()), so when vectorizing a store chain erases
a load it packed as an operand, that erase also marks the same load "used"
in its *independent* LoadSeeds bundle. Store seeds are processed first, so
by the time the outer loop reaches that LoadSeeds bundle, it can already be
fully consumed -- or, since cross-bundle erasure can mark any index used
(not just sequentially from the front), have its element 0 specifically
erased while other elements remain live, which broke the hardcoded Seeds[0]
address-space lookup the same way.
Fixed by skipping a fully-used bundle up front, and by looking up the
address space via the first genuinely unused element instead of assuming
index 0 is always live.
Added seed_bundle_cross_erasure.ll, a minimal repro: a two-store,
two-load chain where vectorizing the stores consumes both loads, which are
also independently collected as a load-seed bundle. Verified this crashes
without the fix and passes with it, on the exact seed-collection<tr-save,
bundle-vec(bottom-up),load-store-vec,tr-accept-or-revert> pipeline that
-passes=sandbox-vectorizer uses by default.
check-llvm Transforms/SandboxVectorizer passes (32/32).
---
.../Passes/SeedCollection.cpp | 7 ++++--
.../AMDGPU/seed_bundle_cross_erasure.ll | 25 +++++++++++++++++++
2 files changed, 30 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Transforms/SandboxVectorizer/Passes/LoadStoreVec/AMDGPU/seed_bundle_cross_erasure.ll
diff --git a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/SeedCollection.cpp b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/SeedCollection.cpp
index 48b6af9e4a203..f51b3cb223b45 100644
--- a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/SeedCollection.cpp
+++ b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/Passes/SeedCollection.cpp
@@ -60,11 +60,14 @@ bool SeedCollection::runOnFunction(Function &F, const Analyses &A) {
AllowDiffTypes);
for (auto &SeedRange : {SC.getStoreSeeds(), SC.getLoadSeeds()}) {
for (SeedBundle &Seeds : SeedRange) {
+ if (Seeds.allUsed())
+ continue;
+ unsigned FirstUnusedIdx = Seeds.getFirstUnusedElementIdx();
unsigned ElmBits =
Utils::getNumBits(VecUtils::getElementType(Utils::getExpectedType(
- Seeds[Seeds.getFirstUnusedElementIdx()])),
+ Seeds[FirstUnusedIdx])),
DL);
- unsigned AS = getLoadStoreAddressSpace(Seeds[0]);
+ unsigned AS = getLoadStoreAddressSpace(Seeds[FirstUnusedIdx]);
unsigned VecRegBits = OverrideVecRegBits != 0
? OverrideVecRegBits
: A.getTTI().getLoadStoreVecRegBitWidth(AS);
diff --git a/llvm/test/Transforms/SandboxVectorizer/Passes/LoadStoreVec/AMDGPU/seed_bundle_cross_erasure.ll b/llvm/test/Transforms/SandboxVectorizer/Passes/LoadStoreVec/AMDGPU/seed_bundle_cross_erasure.ll
new file mode 100644
index 0000000000000..da1663ed029cb
--- /dev/null
+++ b/llvm/test/Transforms/SandboxVectorizer/Passes/LoadStoreVec/AMDGPU/seed_bundle_cross_erasure.ll
@@ -0,0 +1,25 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=sandbox-vectorizer -sbvec-passes="seed-collection(enable-diff-types)<tr-save,bundle-vec(bottom-up),load-store-vec,tr-accept-or-revert>" -sbvec-collect-seeds=stores,loads -mtriple=amdgpu12-amd-amdhsa %s -S | FileCheck %s
+
+define void @seed_bundle_cross_erasure(ptr addrspace(1) nocapture noalias %a, ptr addrspace(1) nocapture readonly noalias %c) {
+; CHECK-LABEL: define void @seed_bundle_cross_erasure(
+; CHECK-SAME: ptr addrspace(1) noalias captures(none) [[A:%.*]], ptr addrspace(1) noalias readonly captures(none) [[C:%.*]]) {
+; CHECK-NEXT: [[C_0:%.*]] = getelementptr inbounds i32, ptr addrspace(1) [[C]], i64 0
+; CHECK-NEXT: [[A_0:%.*]] = getelementptr inbounds i32, ptr addrspace(1) [[A]], i64 0
+; CHECK-NEXT: [[VECL:%.*]] = load <2 x i32>, ptr addrspace(1) [[C_0]], align 4, !sandboxvec [[META0:![0-9]+]]
+; CHECK-NEXT: store <2 x i32> [[VECL]], ptr addrspace(1) [[A_0]], align 4, !sandboxvec [[META0]]
+; CHECK-NEXT: ret void
+;
+ %c.0 = getelementptr inbounds i32, ptr addrspace(1) %c, i64 0
+ %a.0 = getelementptr inbounds i32, ptr addrspace(1) %a, i64 0
+ %c.1 = getelementptr inbounds i32, ptr addrspace(1) %c, i64 1
+ %a.1 = getelementptr inbounds i32, ptr addrspace(1) %a, i64 1
+ %ld0 = load i32, ptr addrspace(1) %c.0, align 4
+ store i32 %ld0, ptr addrspace(1) %a.0, align 4
+ %ld1 = load i32, ptr addrspace(1) %c.1, align 4
+ store i32 %ld1, ptr addrspace(1) %a.1, align 4
+ ret void
+}
+;.
+; CHECK: [[META0]] = distinct !{!"sandboxregion"}
+;.
More information about the llvm-commits
mailing list