Skip to content

fix(shader): take a scratch pool slot with a relaxed compare-exchange - #16274

Open
AlfaRojo wants to merge 3 commits into
boykopovar:mainfrom
AlfaRojo:fix/scratch-pool-relaxed-cas
Open

AlfaRojo wants to merge 3 commits into
boykopovar:mainfrom
AlfaRojo:fix/scratch-pool-relaxed-cas

Conversation

@AlfaRojo

@AlfaRojo AlfaRojo commented Nov 6, 2026 •

Copy link
Copy Markdown

What

Fixes #15566. EmitScratchPoolAcquire took a scratch pool slot with an OpAtomicCompareExchange whose semantics were Acquire | UniformMemory at device scope, inside the scan that only invocation 0 of the workgroup runs. AMD's Windows driver compiles an acquire on a storage buffer to s_barrier (recorded in docs/dev/debt/shader.md for the LDS lock), so on a Radeon 0x13c0 that barrier paired with the OpControlBarrier the other waves of the workgroup had already reached after the scan: those waves read scratch_pool_slot from workgroup memory before invocation 0 stored it, took a garbage slot, and every scratch store and load of theirs went out of the pool (robust access dropped the stores and returned 0). Invocation 0's wave kept the right slot, which is why only lanes 64 and up failed in the test, and why the slot counter never reached 0 and the next dispatch of the program found the lock still held.

The compare-exchange is now relaxed, like the atomic load before it, and the OpControlBarrier after the scan acquires at device scope instead of workgroup scope, so the ordering the acquire gave moves into uniform flow, where every wave executes the same barrier. The release store of the last invocation keeps its release semantics; it stays in a divergent branch at the end of the program, which the test did not find harmful on that driver. The debt entry on slot acquisition keeps the gap (relaxed compare-exchange, device-scope acquire barrier, release store measured only by this test); the measurements behind it are here: the failure was seen with driver 32.0.21043.5001 on a Radeon 1002:13c0 and reported with 32.0.21045.20006 on a 1002:164e, and the slot scan's cost was measured on an RTX 4090 with about 9 slots of 114696 bytes (APS5_SCRATCH_POOL_MIB=1), where a dispatch of 32768 workgroups takes 1308 ms against 395 ms with the default 64 MiB pool.

Overlap: tools/pr_overlap.py could not compare #9508, which conflicts with main and edits the lines around OpArrayLength at the top of EmitScratchPoolAcquire, next to the constant this change renames; nothing else in code. Whichever merges first, the other rebases.

Tested

Windows 11 Pro (10.0.26200), WinLibs GCC 15.2.0 r7 UCRT, Release, on main a7e817522f (rebased 2026-11-05; the main column was measured on b611e634f7, the change rerun on a7e817522f), ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1, no validation layer available on this machine.

GPU agc_driver_scratch_pool on main with this change
AMD Radeon Graphics 1002:13c0 (RDNA 2 integrated, Ryzen, driver 32.0.21043.5001, subgroup=64, mesh 0) 0 of 3: scratch pool isolation wave64: round 0 lane 0 dword 0 read 0xdeadbeef, expected 0x00000000 3 of 3 pass
NVIDIA GeForce RTX 4070 (driver 32.0.16.1692, subgroup=32) 3 of 3 pass 3 of 3 pass

How the cause was found, with the test cut down to the wave64 isolation case on one workgroup: the failure does not depend on the slot count, the delay loop, the program's own s_barrier or the lane stride, and the pool read back after the dispatch held the stores of lanes 0-63 only, with the lock word still 1 and the counter still 64. Injecting edited SPIR-V into the test: a constant slot 0 instead of the workgroup-memory read passes; an atomic or Volatile workgroup-memory read still fails; a second OpControlBarrier, or an OpMemoryBarrier before it, passes (that driver compiles both to s_barrier); making the compare-exchange relaxed alone passes 3 of 3, with or without the device-scope barrier.

Also run with the change, RTX 4070 unless noted: agc_driver_lds_tree_reduction (both GPUs), agc_flat_scratch and agc_guarded_memory_loads (both GPUs), agc_recorder_upload_scratch, agc_shader_disk_cache, agc_spirv_branch_targets, agc_spirv_failure_text, agc_spirv_lds_atomic_lock, agc_spirv_module, agc_spirv_narrow_constant_store (all three), agc_spirv_subvector_loop_begin: all pass. Linux, macOS and a title not run; the disk cache rejects entries from another source version, so cached programs are rebuilt.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: AGC: agc_driver_scratch_pool wave64 isolation fails on a Ryzen 7700X integrated Radeon on Windows #15566
  • Unblocks: none
  • GPUs tested: AMD Radeon Graphics 1002:13c0 (RDNA 2 integrated), driver 32.0.21043.5001, Windows 11; NVIDIA GeForce RTX 4070, driver 32.0.16.1692, Windows 11
  • AI-assisted: yes

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added the fix error Closes an issue (Fixes #N) label Nov 6, 2026
@JJenkx

JJenkx commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Windows AMD check of head 372b65b5e8 against plain main 3cb1cd29ec, built from the same tree. It fixes #15566 on this card.

Setup: Windows 11 Enterprise 22621, WinLibs GCC 15.2 UCRT, Release, Radeon RX 6700 XT 12 GB (discrete RDNA 2), AMD driver 26.10.1. Run with ANYPS5_REQUIRE_VULKAN=1 and Vulkan validation layers 1.4.363, through the Windows test queue.

main 3cb1cd29ec this PR merged (19cd578856, clean)
agc_driver_scratch_pool fails: scratch pool isolation wave64: round 0 lane 64 dword 0 read 0x00000000, expected 0x00000100 passes 3 of 3
agc_driver_lds_tree_reduction — passes 3 of 3

The Vulkan device was created on both sides, and there were no VUID- errors. This matches what I saw on the fix/scratch-pool-relaxed-cas branch in #15566. It also failed in this morning's full suites on main 1e24993fa3 and 151a2cb6e2.

AI-assisted: yes.

@AlfaRojo

AlfaRojo commented Nov 6, 2026

Copy link
Copy Markdown
Author

Windows AMD check of head 372b65b5e8 against plain main 3cb1cd29ec, built from the same tree. It fixes #15566 on this card.

Setup: Windows 11 Enterprise 22621, WinLibs GCC 15.2 UCRT, Release, Radeon RX 6700 XT 12 GB (discrete RDNA 2), AMD driver 26.10.1. Run with ANYPS5_REQUIRE_VULKAN=1 and Vulkan validation layers 1.4.363, through the Windows test queue.

main 3cb1cd29ec this PR merged (19cd578856, clean)
agc_driver_scratch_pool fails: scratch pool isolation wave64: round 0 lane 64 dword 0 read 0x00000000, expected 0x00000100 passes 3 of 3
agc_driver_lds_tree_reduction — passes 3 of 3
The Vulkan device was created on both sides, and there were no VUID- errors. This matches what I saw on the fix/scratch-pool-relaxed-cas branch in #15566. It also failed in this morning's full suites on main 1e24993fa3 and 151a2cb6e2.

AI-assisted: yes.

Thanks for the RX6700 XT check with validation layers; that covers the discrete RDNA 2 case and the VUID check this machine could not run.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 372b65b merged onto main 82fcf46 · tested 2026-11-06 06:11 UTC · 20 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,904 passed · 4 failed (0 new, 3 also fail on main, 1 unstable on main) · 56 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,964 of 4,158 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (979 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 1 of the last 10 main runs
Failing on main too (3, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (9 IDs in 594 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (594 tests): agc_driver, agc_driver_address_draw_space_reuse, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, and 586 more
VUID-VkShaderModuleCreateInfo-pCode-08737 (14 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_float16_modifiers, agc_driver_float16_native, and 6 more
VUID-VkShaderModuleCreateInfo-pCode-08739 (14 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_float16_modifiers, agc_driver_float16_native, and 6 more
VUID-VkShaderModuleCreateInfo-pCode-08741 (14 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_float16_modifiers, agc_driver_float16_native, and 6 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (2 tests): agc_driver_prepared_exports, agc_driver_srgb8_color_target
VUID-VkPipelineShaderStageCreateInfo-module-parameter (1 test): agc_driver_float16_unused_nan_payloads
VUID-VkPipelineShaderStageCreateInfo-pSpecializationInfo-06849 (1 test): agc_driver_float16_unused_nan_payloads
Skipped (56)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_stencil_ref_export: skipped, the device lacks VK_EXT_shader_stencil_export
agc_driver_vertex_elected_atomic_max: skipped, the device runs vertex programs in subgroups, so they keep their cross-lane reductions
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_net_ctl_disconnected: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_device_capability: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_disabled: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_four_k: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_height_only: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_limit: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_malformed: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_width_only: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan de
Reproduce
git fetch origin 372b65b5e8da7ab8141031afd3e1da0f68192bfe 82fcf46d250e0ec2f436ceffd607dd2ff04c62d7
git checkout 82fcf46d250e0ec2f436ceffd607dd2ff04c62d7 && git merge --no-ff 372b65b5e8da7ab8141031afd3e1da0f68192bfe
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@Superseabas Superseabas left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Windows run on the GPU #15566 was opened on, the integrated Radeon of a Ryzen 7 7700X (1002:164e), with the validation layer that the description's machine didn't have. It fixes the failure there and adds nothing new across the AGC tests.

Head 372b65b5e8 merged onto main ed41d86d32 (clean merge, local only). Windows 11 Home 10.0.26200, WinLibs GCC 16.2.0 (x86_64-ucrt-posix-seh, r3), Release. ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (Vulkan SDK 1.4.363.0; VK_LOADER_DEBUG=layer shows it inserted for the instance and the device), ANYPS5_GPU selecting the device, confirmed by Selected GPU.

Test GPU main this PR this PR with main's SpirvScratchPool.cpp
agc_driver_scratch_pool Radeon iGPU (1002:164e, subgroup=64), AMD 26.10.2 (32.0.21045.20006) 0 of 5: scratch pool isolation wave64: round 0 lane 0 dword 0 read 0xdeadbeef, expected 0x00000000 5 of 5 0 of 5, same message
RTX 5070 Ti (10de:2c05, subgroup=32), 621.01 5 of 5 5 of 5
agc_driver_lds_tree_reduction both 5 of 5 5 of 5
  • Whole AGC set. ctest -R '^agc' -j 8 on each GPU, 1,388 tests:

    GPU main this PR
    RTX 5070 Ti 1,386 pass 1,386 pass
    Radeon iGPU 1,385 pass 1,386 pass

    agc_driver_scratch_pool on the Radeon is the only difference. The other failures, the same on both trees, are tracked: agc_driver_direct_write_completion (#15564) on both GPUs, agc_driver_quad_pixel_mask (#14341) on the RTX, and agc_driver_sample_order_grid_reference (#9503) on the Radeon.

  • Validation. No VUID- line and no missing-layer line in any run, on either tree.

  • The scratch tests that don't select a GPU (agc_flat_scratch, agc_guarded_memory_loads, agc_recorder_upload_scratch, agc_storage_write_back_scratch, agc_spirv_lds_atomic_lock, scalar_scratch_load) pass on the PR.

Non-blocking. check_conventions.py --base main passes, with one warning: the slot-acquisition debt entry is 1,770 characters, over the 1,000-character debt-length limit (957 before). The measurement details could move to this PR's description and the entry keep the gap: the compare-exchange is relaxed and the post-scan barrier acquires at device scope, because AMD's Windows driver turns a storage-buffer acquire into s_barrier; the release store in the divergent exit branch was measured only with this test.

AI-assisted: yes (Claude Code)

The entry grew to 1770 characters with the barrier measurements; the
debt-length limit is 1000. The relaxed compare-exchange, the device-scope
acquire and the untested release store stay; the driver versions, device
ids and timings move to the pull request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AlfaRojo

AlfaRojo commented Nov 6, 2026

Copy link
Copy Markdown
Author

Pushed af84f36: the slot acquisition debt entry is back under the 1000-character limit (992), keeping the gap only; the driver versions, device ids and slot timings moved to the description. No code change.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 6, 2026
Takes main's copy of the eight technical debt files that af84f36
rewrote with CRLF line endings, so the branch changes only
SpirvScratchPool.cpp and the slot acquisition debt entry.
@AlfaRojo

AlfaRojo commented Nov 6, 2026

Copy link
Copy Markdown
Author

Merged main into the branch (0447545). The docs commit af84f36 had rewritten eight unrelated debt files with CRLF line endings, which is what conflicted; the merge takes main's copies, so the branch again changes only SpirvScratchPool.cpp and the slot acquisition debt entry (2 files, +5/−5). On the merged tree agc_driver_scratch_pool passes on the Radeon 1002:13c0 (subgroup 64) and the RTX 4070 with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1; check_conventions.py --base main passes.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 6, 2026
@Superseabas

Copy link
Copy Markdown

Re-test of the new head, 044754531e, on the GPU from #15566. The code change is the same as at 372b65b5e8, which I approved, and it still fixes the issue on current main.

Head merged onto main d78531d356 (clean merge, local only). Windows 11 Home 10.0.26200, WinLibs GCC 16.2.0 r3, Release, ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_GPU selecting the device. The 5 tests matching scratch, 5 runs each:

Test main, Radeon 1002:164e (AMD 26.10.2) this PR, Radeon main and this PR, RTX 5070 Ti (621.01)
agc_driver_scratch_pool 0 of 5: round 0 lane 0 dword 0 read 0xdeadbeef (4), lane 64 dword 0 read 0x00000000, expected 0x00000100 (1) 5 of 5 5 of 5
agc_storage_write_back_scratch, agc_recorder_upload_scratch, agc_flat_scratch, scalar_scratch_load 5 of 5 5 of 5 5 of 5

No VUID- lines. The branch changes only SpirvScratchPool.cpp and the debt entry, now 992 characters, and check_conventions.py --base main passes.

AI-assisted: yes (Claude Code)

This branch has not been deployed

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

Labels

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: agc_driver_scratch_pool wave64 isolation fails on a Ryzen 7700X integrated Radeon on Windows

5 participants