Skip to content

llvm_passes: lower volatile global accesses to relaxed device-scope atomics - #1513

Open
pvelesko wants to merge 4 commits into
mainfrom
2026-08-29-github-1508-volatile-relaxed-atomics
Open

llvm_passes: lower volatile global accesses to relaxed device-scope atomics#1513
pvelesko wants to merge 4 commits into
mainfrom
2026-08-29-github-1508-volatile-relaxed-atomics

Conversation

@pvelesko

Copy link
Copy Markdown
Collaborator

CUDA gives a volatile access to global memory the meaning of PTX ld.volatile / st.volatile, a relaxed system scope access that bypasses L1, and HIP code such as Kokkos::volatile_load relies on it. chipStar emitted such accesses as plain OpLoad / OpStore with the Volatile memory operand, which only forbids eliminating or merging the access, so IGC serves it from L1 and readers see stale data.

The new HipLowerVolatileAccessesPass rewrites every volatile 32 or 64 bit load and store through a global or generic pointer into a relaxed syncscope("device") atomic, which both SPIR-V producers emit as OpAtomicLoad / OpAtomicStore. Sub word, vector, private, constant, work group local, under aligned and already atomic accesses are left alone. Comes with a runtime reproducer, a SPIR-V inspection test and an opt fixture covering every rule of the pass.

Found with the Kokkos hip.UnorderedMap_insert test on Aurora PVC, which reported 901 keys inserted for 900.

Fixes #1508

… plain loads

A `volatile` load or store of a 32 or 64 bit value in global memory is
emitted as OpLoad / OpStore with the Volatile memory operand, which IGC
serves from L1 like any other access. CUDA compiles the same source to
ld.volatile / st.volatile, which the PTX ISA defines as relaxed memory
operations at system scope, and Kokkos::volatile_load relies on that in
the UnorderedMap insert list walk: on Aurora PVC hip.UnorderedMap_insert
counts 901 keys for 900 because a losing inserter reads a stale key.

TestFixVolatileLoadLoweringSPIRV.bash compiles TestFixVolatileLoadLowering.hip
with --save-temps and checks the lowered device bitcode (and the SPIR-V
module when the Khronos translator produced it) for relaxed atomic forms of
the 32 and 64 bit global accesses, and that 16 bit and work-group local
ones stay as they are. TestFixKokkosUnorderedMapInsert.hip is the insert
idiom itself (claim, publish with __threadfence, relaxed CAS, volatile
list walk) checked on the host for duplicate keys and orphaned slots; it
passes on the CPU OpenCL runtime before and after and is meant for PVC.
…tomics

CUDA compiles a volatile access to global memory into PTX ld.volatile /
st.volatile, which the PTX ISA (8.4.2 "volatile Operation") defines as
"equivalent to a relaxed memory operation with system-scope": a strong
access that bypasses the core's L1 and observes other cores' relaxed
atomics. HIP code relies on that; Kokkos::volatile_load walks the
UnorderedMap insert list with it after the writer's __threadfence() plus
relaxed CAS. SPIR-V's OpLoad with the Volatile memory operand only means
the access cannot be eliminated, duplicated or combined, and IGC serves
it from L1, so on Aurora PVC hip.UnorderedMap_insert reports 901 keys
for 900.

HipLowerVolatileAccessesPass rewrites every volatile load and store of a
32 or 64 bit integer, float or pointer through a global or generic
pointer into `load atomic volatile ... syncscope("device") monotonic`
(floats and pointers through the same-width integer), which both SPIR-V
producers emit as OpAtomicLoad / OpAtomicStore with Relaxed semantics at
Device scope. The explicit "device" syncscope matters: the translator at
LLVM 17 hardcodes Device for atomic loads, newer ones map the default
scope to CrossDevice. 8 and 16 bit values, private, constant and
work-group local objects, under-aligned accesses and accesses that are
already atomic are left as they are.
…ixture

volatile-accesses.ll holds one kernel of accesses HipLowerVolatileAccessesPass
must rewrite (32 and 64 bit integers, floats and pointers through global and
generic pointers) and one of accesses it must leave alone (8 and 16 bit,
vectors, work-group local, private, constant, under-aligned, already atomic).
The runner drives the standalone hip-lower-volatile-accesses pass with opt,
counts both sets, expects the under-aligned warnings, and translates the
result to SPIR-V through spirv-val.
…ss SPIR-V

The Khronos translator built from LLVM 21 keeps an OpName on the kernel
body and emits the OpEntryPoint as a separate wrapper function whose only
instruction is an OpFunctionCall to it, so the entry point id in the
disassembly is neither numeric nor the function that holds the accesses.
TestFixVolatileLoadLoweringSPIRV looked up the body by a numeric entry
point id and counted 0 OpAtomicLoad / 0 OpAtomicStore on the pocl 7.1
(llvm-21) CI lane. Accept any id and follow a wrapper's OpFunctionCall
to the callee before counting.
@pvelesko

Copy link
Copy Markdown
Collaborator Author

/run-aurora-ci

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

volatile global loads are plain OpLoad served from L1, Kokkos UnorderedMap_insert miscounts on PVC

1 participant