[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