Skip to content

fix(shader): use 64-bit buffer atomics for device-memory LDS - #7368

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
CrimsonMac:fix/device-lds-mixed-width-atomics
Oct 18, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
CrimsonMac:fix/device-lds-mixed-width-atomics

Conversation

@CrimsonMac

Copy link
Copy Markdown
Contributor

What

Fixes #7344. When a compute program's LDS does not fit the device's shared-memory limit, the LDS lives in a storage buffer (workgroup_memory), and a program with 64-bit LDS atomics takes the workgroup lock in that buffer. On AMD's Windows driver the device-memory case of agc_driver_lds_lock_budget reads exactly half its sum (0x00000ffffffff800 for 0x00001ffffffff000), on @Amoenus's iGPU and on mine.

The driver's ISA (VK_KHR_pipeline_executable_properties, Ryzen 5 7600 iGPU, 0x164e, driver 32.0.21042.62) shows the cause. Every acquire or release on the storage buffer becomes a workgroup barrier:

buffer_atomic_cmpswap v[3:4], v2, s[16:19], 0 offen glc   ; lock acquire
s_waitcnt vmcnt(0)
s_waitcnt_vscnt null, 0x0
s_barrier
buffer_gl0_inv
...
buffer_store_dword v3, v1, s[16:19], 0 offen offset:4 glc
s_waitcnt_vscnt null, 0x0
s_barrier                                                 ; lock release
buffer_atomic_swap v5, v4, s[16:19], 0 offen

The pipeline has 83 s_barriers; the program needs 3 (its own two and one after the lock is initialized). Each wave runs the lock loop a different number of times, so these barriers pair with the program's own s_barrier before its final ds_read_b64, and lane 0 reads before the other waves have finished their atomics. Unlike #6666 this does not hang, and #6976 does not fix it (checked on #6976): its exchange release and the fences it drops are not the only barriers here.

What I tried on the iGPU, changing only the lock (all rows but the first with #6976 applied, so that the fences inside the lock are gone):

Lock acquire / release s_barriers Result
Workgroup scope, Acquire / Release + WorkgroupMemory + UniformMemory (main) 83 half the sum
the same with #6976 not counted half the sum
Workgroup scope, UniformMemory only 67 wrong sum
Device or QueueFamily scope, UniformMemory 67 wrong sum
Workgroup scope, no storage class 67 wrong sum
AcquireRelease + UniformMemory on both lock atomics 131 wrong sum
Subgroup or Invocation scope, SequentiallyConsistent, or relaxed 3 passes, but the driver emits no store wait before the release
Lock in Workgroup memory, data in the buffer 3 passes, but the driver emits no s_waitcnt_vscnt before the release, so the next holder can read before the stores land
the same plus a Release fence on UniformMemory before the release 35 wrong sum

No form of the buffer lock is both ordered and free of barriers on this driver. The data is already in a storage buffer, which can take native 64-bit atomics, so this PR stops using the lock there:

  • With shaderBufferInt64Atomics (the Int64Atomics capability in the target), a program whose LDS is in device memory no longer requests the lock. A 64-bit LDS atomic (ds_*_u64/_b64/_f64, and a 64-bit flat atomic into the LDS aperture) is a compare-exchange loop (AtomicUpdateTyped) on a 64-bit view of the same binding (workgroup_memory_u64, Aliased and Coherent like the buffers_u64 view of guest buffers). The 32-bit LDS atomics stay native buffer atomics, as in programs without 64-bit atomics.
  • Without the feature, the lock in the buffer is kept as before.
  • The per-workgroup slice is LDS bytes / 4 + 1 dwords rounded up to an even count, so the 64-bit view stays 8-byte aligned in every workgroup. The extra dword is still the lock without the feature.
  • A ds_* 64-bit atomic whose address is 4-byte but not 8-byte aligned (it faults the wave on gfx1035/gfx1036, per shader.md) is skipped there and returns 0; the flat form already records an unaligned fault and skips.

The fence that follows every LDS atomic (Atomic32, and the end of AtomicUpdateTyped) is still emitted, and on this driver it is an s_barrier too. It runs once per atomic outside any loop, so waves that all execute the atomic pass it the same number of times, as for 32-bit LDS atomics in every program today; a wave that skips an atomic other waves execute would miss one. That is unchanged by this PR and recorded in shader.md.

Mixed widths on one slot now rely on the device ordering a 32-bit and a 64-bit atomic on one buffer address. ds_add_u32 against ds_add_u64 sums exactly on both GPUs below. The other pairs were not measured; this is recorded in docs/dev/debt/shader.md, and the slice layout in docs/dev/debt/libSceAgcDriver.md.

agc_lds_lock_budget now also compiles each case with Int64Atomics. It checks that device-memory LDS then uses a 64-bit OpAtomicCompareExchange and no 32-bit lock, and that in-budget LDS, or a target without the capability, keeps the lock. The stride expectations in it, agc_driver_lds_beyond_device_limit and agc_driver_lds_lock_budget move to the even count.

On AMD the second case of agc_driver_lds_lock_budget (limit-4, LDS in Workgroup memory) still fails with this PR alone, because it is the workgroup-lock hang of #6666, fixed by #6976. The two PRs touch different code and merge cleanly together; with both, every LDS test passes on AMD.

Tested

Windows 11 (26100), Ryzen 5 7600, WinLibs GCC 15.2.0 r7, Release, ANYPS5_REQUIRE_VULKAN=1. The rows below are on main b39d90bf1 (which has #7351, so robustBufferAccess2=0 on the AMD driver; the case fails the same with it on, as in #7344) plus this commit, except the full runs, which were on 195d0d08a plus this commit.

AMD Radeon iGPU (RDNA 2, 0x164e, 32.0.21042.62) NVIDIA RTX 4070 SUPER (32.0.16.1742)
agc_driver_lds_lock_budget, main fails 3/3 in limit: lane 0 read 0x00000ffffffff800 passes
same, this PR limit passes (device=8194); limit-4 fails as on main (#6666) passes
same, this PR merged with #6976, --repeat until-fail:20 20/20 20/20
agc_lds_lock_budget, agc_spirv_lds_atomic_lock, agc_driver_lds_beyond_device_limit, agc_driver_lds_mixed_width_atomics, this PR merged with #6976, --repeat until-fail:20 20/20 20/20
full ctest --parallel 4 --timeout 300, this PR on 195d0d08a 1442 tests, 18 skipped; fails only agc_driver_lds_mixed_width_atomics and the limit-4 case of agc_driver_lds_lock_budget (#6666), and agc_driver_subvector_loop, agc_driver_layered_volume_draw, agc_driver_layered_volume_vertex (fail on that main too, fixed by #7351; they pass on b39d90bf1 with this PR) 1442/1442, 14 skipped
  • In the AMD full run 11 unrelated tests exited with 0xc0000142 at the end of the run while the machine ran out of memory; all 11 pass when rerun.
  • agc_driver_oversized_dispatch, which also takes the device-memory path, skips on the iGPU and passes on NVIDIA.
  • The SPIR-V of every case in agc_driver_lds_mixed_width_atomics_tests passes spirv-val --target-env vulkan1.3 (SPIRV-Tools from 3rdparty).
  • Not run on Linux, lavapipe or a device without shaderBufferInt64Atomics.
  • python tools/check_conventions.py --base upstream/main and git diff --check pass.

Checklist

Compute LDS beyond the device's shared-memory limit lives in a storage
buffer, and its 64-bit atomics took a lock in that buffer. AMD's Windows
driver compiles the lock's acquire and release to s_barrier, which inside
the divergent lock loop pairs with the program's own barriers, so the
device-memory case of agc_driver_lds_lock_budget read half its sum.

With shaderBufferInt64Atomics the device-memory LDS now uses a 64-bit view
of the buffer instead: 64-bit atomics are compare-exchange loops on it and
32-bit atomics stay native, so no lock is taken. Without the feature the
lock is kept. The per-workgroup slice is rounded up to an even dword count
so that the 64-bit view stays aligned.
@github-actions github-actions Bot added the fix error Closes an issue (Fixes #N) label Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 7390 docs/dev/debt/libSceAgcDriver.md L75

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4650 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5021 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
docs/dev/debt/shader.md
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5371 docs/dev/debt/shader.md
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5426 docs/dev/debt/shader.md
PR 5499 docs/dev/debt/libSceAgcDriver.md
PR 5500 core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp
core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp
docs/dev/debt/shader.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 5941 docs/dev/debt/shader.md
PR 5958 docs/dev/debt/shader.md
PR 5999 docs/dev/debt/libSceAgcDriver.md
PR 6009 docs/dev/debt/shader.md
PR 6094 docs/dev/debt/shader.md
PR 6177 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6222 core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6235 core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp
docs/dev/debt/shader.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 6416 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6445 docs/dev/debt/libSceAgcDriver.md
PR 6472 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6488 core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
docs/dev/debt/shader.md
PR 6503 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp
PR 6529 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6563 docs/dev/debt/libSceAgcDriver.md
PR 6636 docs/dev/debt/libSceAgcDriver.md
PR 6727 docs/dev/debt/libSceAgcDriver.md
PR 6741 docs/dev/debt/libSceAgcDriver.md
PR 6767 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp
docs/dev/debt/shader.md
PR 6783 docs/dev/debt/libSceAgcDriver.md
PR 6786 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6946 docs/dev/debt/libSceAgcDriver.md
PR 6947 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6973 docs/dev/debt/libSceAgcDriver.md
PR 6977 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 6987 docs/dev/debt/libSceAgcDriver.md
PR 6989 docs/dev/debt/libSceAgcDriver.md
PR 6995 docs/dev/debt/libSceAgcDriver.md
PR 7002 docs/dev/debt/libSceAgcDriver.md
PR 7201 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp
PR 7223 docs/dev/debt/shader.md
PR 7332 core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 7426 docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 7505 docs/dev/debt/libSceAgcDriver.md

@oneandonlydean oneandonlydean self-assigned this Oct 18, 2026

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the new device-memory LDS path guards a 64-bit atomic whose address is 4-byte but not 8-byte aligned by returning zero. The existing LDS atomic debt entry says this case faults the wave on gfx1036. That makes the newly selected path silently succeed where the measured hardware faults. Could you add fault/refusal handling and regression coverage for this case, or explain why returning zero is acceptable for device-memory LDS?

@github-actions github-actions Bot added shader core/shader docs agc libSceAgc* labels Oct 18, 2026
@CrimsonMac

Copy link
Copy Markdown
Contributor Author

Good catch. Returning zero wasn't defensible, since gfx1036 faults the wave there. cb28abeb8 makes the device-memory path record the Unaligned shader fault (LDS byte address, 8 bytes, the instruction's pc) for a 64-bit DS atomic whose address is 4-byte but not 8-byte aligned. It still doesn't update LDS, which matches the flat aperture path. The device-memory choice is only made at emit time, so ShaderInfoCollector requests the fault buffer for any shader that has both device-memory LDS and a 64-bit LDS atomic. Other shaders get no new binding.

Coverage: agc_driver_lds_lock_budget now runs a ds_add_u64 at LDS offset 4 with device-memory LDS and requires the fault (address=0x4 bytes=8 reason=7). It fails with the fault emission removed. agc_lds_lock_budget checks that the fault buffer is present only with device-memory LDS. The debt entry in shader.md is updated.

I kept the workgroup-lock path as it is (it still does the split update, and the debt entry already says the fault isn't modelled there). Making it fault too would be a separate change.

Tested on Windows 11: RTX 4070 SUPER full ctest 1443/1443. Radeon iGPU (gfx1036, 32.0.21042.62) 1440/1443: #5804, plus lds_mixed_width_atomics and lds_lock_budget's limit-4 case, both waiting on #6976. The new misaligned case passes on both GPUs.

AI-assisted: yes

@simonbbby

Copy link
Copy Markdown
Contributor

Linux comparison on exact PR head cb28abeb89b5260f3824932f265c3b1cc9801e63 (no #6976 applied): Ubuntu 24.04, GCC 13.3, Release, bundled dependencies, shader cache and pipeline journal disabled, ANYPS5_REQUIRE_VULKAN=1 and ANYPS5_REQUIRE_ROBUST2=1.

Vulkan device agc_driver_lds_lock_budget on main 6efb7261b5ca879c26083ad36bad92c2a9293a94 On this PR Main → PR device LDS dwords at limit
RDNA 2 gfx1036, 1002:13c0, RADV Mesa 25.2.8 20/20 20/20 16385 → 16386 (64 KiB limit)
RTX 3090, NVIDIA 595.99.02 20/20 20/20 12289 → 12290 (48 KiB limit)
lavapipe, Mesa 25.2.8 / LLVM 20.1.2 20/20 20/20 8193 → 8194 (32 KiB limit)

Each PR run verified GPU readback across all 256 lanes for both limit and limit-4, and printed misaligned ds_add_u64 faulted. Thus the newly added unaligned-fault check executed and passed 20 times per driver, including gfx1036 on RADV. robustBufferAccess2=1 was confirmed in each log.

On this PR, agc_lds_lock_budget and agc_spirv_lds_atomic_lock also pass, and agc_driver_lds_mixed_width_atomics passes once on each of the three drivers.

This supplies the missing Linux/lavapipe comparison: the Windows RDNA 2 failure in #7344 does not reproduce on this RADV gfx1036 device at the pinned main above, and this PR retains passing results here. The devices expose different shared-memory limits, as listed. No claim about Windows, a physical device without buffer int64 atomics, title behavior, or a full-suite run. Khronos validation layers are unavailable on this host; these are readback/fault-check results, not validation-layer coverage.

@oneandonlydean
oneandonlydean merged commit 385ed71 into boykopovar:main Oct 18, 2026
9 checks passed
thuanquang added a commit to thuanquang/AnyPS5 that referenced this pull request Oct 20, 2026
…ot fault

The last sentence of the 64-bit LDS atomics entry said that on the
device-memory path with shaderBufferInt64Atomics a 64-bit atomic at an
address that is 4-byte but not 8-byte aligned is skipped and returns 0.
cb28abe (boykopovar#7368) made that path record the Unaligned shader fault, which
the entry now states, so the sentence described a gap that is closed.

The gap that is still open is on every other path: SharedAtomic64 checks
the alignment only on that path, so with the workgroup lock (in shared or
in device memory) and in the invocation-private LDS of the other stages a
ds_* 64-bit atomic at such an address updates the two dwords there, where
gfx1036 and gfx1035 fault the wave.

Co-Authored-By: Claude <noreply@anthropic.com>
thuanquang added a commit to thuanquang/AnyPS5 that referenced this pull request Oct 23, 2026
…ot fault

The last sentence of the 64-bit LDS atomics entry said that on the
device-memory path with shaderBufferInt64Atomics a 64-bit atomic at an
address that is 4-byte but not 8-byte aligned is skipped and returns 0.
cb28abe (boykopovar#7368) made that path record the Unaligned shader fault, which
the entry now states, so the sentence described a gap that is closed.

The gap that is still open is on every other path: SharedAtomic64 checks
the alignment only on that path, so with the workgroup lock (in shared or
in device memory) and in the invocation-private LDS of the other stages a
ds_* 64-bit atomic at such an address updates the two dwords there, where
gfx1036 and gfx1035 fault the wave.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs fix error Closes an issue (Fixes #N) shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AGC: AMD device-backed LDS lock-budget case returns half of expected sum

4 participants