[libc-commits] [libc] [libc][GPU] Restore release/acquire ordering on the allocator bitfield (PR #238208)
Spencer Bryngelson via libc-commits
libc-commits at lists.llvm.org
Mon Nov 2 20:05:11 PST 2026
https://github.com/sbryngelson created https://github.com/llvm/llvm-project/pull/238208
Fixes #238205.
`Slab::deallocate` clears a chunk's bit with a relaxed `fetch_and` and `Slab::allocate` claims it with a relaxed `fetch_or`. The release fence in `GuardPtr::unlock` runs after the clear, so the freeing thread's stores to the chunk are not ordered before the chunk can be claimed again. On MI300A a thread on another XCC can take the chunk, and the previous owner's dirty L2 lines are then written back over the new data.
This makes the clear a `RELEASE` and the claim an `ACQUIRE`, so `free` synchronizes with a later `malloc` of the same chunk. e9350dc had removed the equivalent fences.
Testing: the reproducer in #238205 on MI300A with `HSA_XNACK=1`, 10 launches of 20M allocations: damaged records in 10 of 10 launches before, 0 of 10 after. No slowdown was seen in a kernel-time micro-benchmark. `RELEASE` on the clear alone also cured it. There is no unit test: the failure needs several XCCs and heavy concurrent allocation. The libc GPU integration tests were not run; the library was built with AFAR 24.3.0's clang.
>From 9c835cff782a0a9a47a5487f22148175df6f80a2 Mon Sep 17 00:00:00 2001
From: Spencer Bryngelson <sbryngelson at gmail.com>
Date: Mon, 2 Nov 2026 19:42:27 -0800
Subject: [PATCH] [libc][GPU] Restore release/acquire ordering on the allocator
bitfield
Slab::deallocate cleared the chunk's bit with a relaxed fetch_and and
Slab::allocate claimed it with a relaxed fetch_or. The release fence in
GuardPtr::unlock runs after the clear, so the freeing thread's stores to
the chunk are not ordered before the chunk can be claimed again. On
MI300A (gfx942, one L2 per XCC) a thread on another XCC can take the
chunk and write to it, and the previous owner's dirty lines are written
back over the new data.
Use RELEASE on the clear and ACQUIRE on the claim, so that free
synchronizes with a later malloc of the same chunk.
---
libc/src/__support/GPU/allocator.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libc/src/__support/GPU/allocator.cpp b/libc/src/__support/GPU/allocator.cpp
index 75b9ea73e609ca..ec28167269ef84 100644
--- a/libc/src/__support/GPU/allocator.cpp
+++ b/libc/src/__support/GPU/allocator.cpp
@@ -313,7 +313,7 @@ struct Slab {
uint32_t before = 0;
if (gpu::get_lane_id() == leader)
before = cpp::AtomicRef(get_bitfield()[slot])
- .fetch_or(bitmask, cpp::MemoryOrder::RELAXED);
+ .fetch_or(bitmask, cpp::MemoryOrder::ACQUIRE);
before = gpu::shuffle(lane_mask, leader, before);
if (~before & (1u << bit))
result = ptr_from_index(index, chunk_size);
@@ -343,7 +343,7 @@ struct Slab {
uint32_t bit = index % BITS_IN_WORD;
cpp::AtomicRef(get_bitfield()[slot])
- .fetch_and(~(1u << bit), cpp::MemoryOrder::RELAXED);
+ .fetch_and(~(1u << bit), cpp::MemoryOrder::RELEASE);
}
// The actual memory the slab will manage. All offsets are calculated at
More information about the libc-commits
mailing list