Skip to content

fix(shader): keep pixel invocations running after a BDA fault in a program that reads lanes by index - #14866

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
milescoviello:fix/bda-stop-lane-reads
Nov 3, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
milescoviello:fix/bda-stop-lane-reads

Conversation

@milescoviello

@milescoviello milescoviello commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

What

A pixel program without workgroup barriers stops an invocation whose BDA read faults. The read records the fault, sets the stop flag and returns from the guest code (StopBdaInvocationIf), and the invocation is terminated after the guest code returns. The other lanes keep running with the masks they took before, and those still hold the stopped lane's bit.

A waterfall over such a mask is a loop that walks the lanes of a mask copied from exec:

  1. s_ff1_i32_b64 picks the lowest remaining lane;
  2. v_readlane_b32 reads that lane's value;
  3. v_cmp_eq finds the lanes with the same value;
  4. s_andn2_b64 removes them from the mask;
  5. s_cbranch_scc1 repeats while lanes remain.

When the lowest remaining lane is the stopped one, v_readlane_b32 reads nothing from it, no running lane matches, and its bit is never removed. The loop spins until the GPU's context-switch timeout loses the device.

Ghost of Tsushima Director's Cut (PPSA02225) is where this showed up. These runs used my development stack (main plus local GoT work), with and without this change, on one machine (Linux, RTX 4070 Ti, NVIDIA 615.71.09):

  • Without it, every in-game run that day lost the device: 5 of 5, 791 to 1344 s in (two of them had an unrelated recompiler experiment switched on). Where I checked dmesg, it showed Xid 109 CTX SWITCH TIMEOUT for the title. In the three runs where I traced the batch that never finished, it held the same two pixel programs, and each of them has three such waterfalls and about 200 stop points.
  • With it, four runs went the full 2400 s without a device loss. Those four used an earlier version of this change that applied to every stage. With this head's pixel-only version, four more runs of 1800 s each went to the end without a device loss. Some of those four had unrelated driver experiments switched on.

The two runs with it whose logs I checked (the first two) never logged a BDA fault report, so this shows the device loss going away, not a stop caught in the act.

How

BdaInvocationsMayStop already returns false for programs with workgroup barriers, where a stopped invocation would leave a barrier incomplete. It now also returns false for pixel programs with ReadLane or WriteLane. Those programs keep faulting invocations running: the read returns zero, and the fault is still recorded and reported. ReadFirstLane alone keeps the stop, since it picks among the lanes that are still running.

Other stages keep stopping. #6416 covers non-pixel programs with wave operations: it keeps their faulting invocations voting and masks their later writes. It leaves pixel programs out, so the two cover different stages, and this change leaves compute and the other stages as they are on main. Stopping works the same way in every stage, so on main a compute or vertex program with a waterfall can hit the same stale bit. This PR leaves those stages to #6416, which sets bdaFaultMasksWrites only for stages other than pixel and keeps BdaInvocationsMayStop unchanged. That is why the new test still expects BdaInvocationsMayStop to be true for a compute program with ReadLane.

Tested

Linux (Ubuntu 26.04.1, GCC 15.2, Release, RTX 4070 Ti, NVIDIA 615.71.09) unless noted. The Linux and validation-layer runs are at ba404360ad. This head 9172e6c2a5 only drops that commit's edit to a comment, so the code is the same.

  • Unit test in agc_driver_bda_device (BdaContracts.cpp, next to the existing barrier case): a pixel program with ReadLane, and one with WriteLane, must keep faulting invocations running. A pixel program with only ReadFirstLane, and a compute program with ReadLane and no barrier, must still stop them.
    • Windows 11 VM (GT 1030, WinLibs GCC 15.2.0 r7): passes 3 of 3 on this head 9172e6c2a5 (and on ba404360ad); main f2d874c1fd passes 3 of 3 without the new cases.
    • Red control 8b668f002b (main plus only the new cases, branch test/bda-stop-red2 on my fork) fails 3 of 3 on the VM: a pixel program with v_readlane must keep its invocations running: a waterfall over a mask that holds a stopped lane never clears its bit.
    • Linux: passes 3 of 3 at ba404360ad; the red control 8b668f002b fails.
    • Windows 11, AMD Radeon RX 9070 XT (RDNA 4), WinLibs GCC, validation layer on, merged into current main (@BunnyFl): agc_driver_host_import_failure_handles and agc_driver_bda_device_selection_cpu pass; with main's SpirvBdaRead.cpp restored, the new check fails.
  • Khronos validation layer (1.4.363), main f2d874c1fd against ba404360ad, over the BDA, lane-read and wave GPU tests (^agc_driver_(bda_|read_lane|write_lane|read_first_lane|lds_mask_waterfall|pixel_helper_lanes|pixel_wave_reduction|permlane16|wave_uniform$)|^agc_shader_specialized_bda|^agc_bda_staging, 18 tests): 18 of 18 pass on both, 11 Vulkan devices created on each side, no validation IDs on either. The two agc_driver_bda_reads_shadowed_results* tests skip on both sides.
  • Offline replay of GoT's two pixel programs that the hung batches held (agc_shader_replay --prepare on dumped requests): their prepared modules go from 201 returns and an OpTerminateInvocation to 2 returns and none. Both pass spirv-val.

Checklist

  • Based on current main (branched at f2d874c1fd, merges cleanly with 5bae1a4bc2); 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
  • 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. Related: fix(shader): keep faulting invocations in their wave and mask their later writes #6416 (the same stop for non-pixel programs; this PR merges cleanly with its head 4414d7880e)
  • Fixes: none
  • Unblocks: PPSA02225 (Ghost of Tsushima Director's Cut device loss in game)
  • GPUs tested: NVIDIA GeForce RTX 4070 Ti, driver 615.71.09, Ubuntu 26.04.1; NVIDIA GeForce GT 1030, driver 582.78, Windows 11 VM; AMD Radeon RX 9070 XT, Windows 11 (@BunnyFl)
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

…ogram that reads lanes by index

A pixel program without barriers stops an invocation whose BDA read
faults: it records the fault and returns from the guest code. The other
lanes keep the masks they took before, with the stopped lane's bit still
set. A waterfall over such a mask (s_ff1_i32_b64, v_readlane_b32,
v_cmp_eq, s_andn2_b64, s_cbranch_scc1) picks the stopped lane, reads
nothing from it and never clears its bit, so it spins until the device is
lost. Pixel programs that read or write a lane by index now keep faulting
invocations running with the read returning zero, as programs with
barriers already do. Other stages keep stopping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added unblocks title Names a title ID on the Unblocks line shader core/shader agc libSceAgc* labels Nov 1, 2026
@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ AMD GPU test: passed

PR head 9172e6c merged onto main f2d874c · tested 2026-11-01 21:43 UTC · 17 min

GPU AMD Radeon RX 6300 (RADV NAVI24), driver Mesa 25.2.8-0ubuntu0.24.04.4, Vulkan 1.4.318
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-42-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,429 passed · 13 failed (0 new, 9 also fail on main, 4 unstable on main) · 55 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,497 of 3,168 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 (936 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_graphics_maps_fallback 120.1 s, ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_driver_graphics 109.7 s, ⚠️ unwrap_self 52.0 s, ⚠️ public_headers 43.3 s
Unstable on main (4, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_job_manager_compute_capacity: failed in 4 of the last 5 main runs
agc_driver_pipeline_journal: failed in 3 of the last 5 main runs
agc_submit_progress: failed in 4 of the last 5 main runs
agc_submit_progress_no_timeline: failed in 4 of the last 5 main runs
Failing on main too (9, not counted against this PR)
agc_driver_guest_memory_lifetime_direct
agc_driver_image_table
agc_driver_native_depth_bda_clear
agc_driver_native_depth_buffer_clear
agc_driver_native_depth_clears
agc_driver_native_multisample_depth
agc_idle_publish_wait
guest_mmap_executable
guest_socket_options_multicast
Passed on retry (1, failed in the parallel run, passed when re-run with a 360 s timeout; not counted against this PR)
agc_driver_graphics_maps_fallback: Timeout in the parallel run; also passed only on retry in 52 other runs in the last 7 days
Validation IDs already present on main (5 IDs in 484 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (484 tests): agc_driver, 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, agc_driver_bda_span_reads, and 476 more
VUID-vkCmdCopyImage-srcImage-01548 (6 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (6 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (55)
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_copied_writer_label_order: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_image_table_vouched_lookups: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a watched imported table cannot be vouched for
agc_driver_linear_target_direct_write_completion: skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked
agc_driver_ngg_vertex_wave_reduction: skipped, s3 describes a 32-lane wave only on 32-lane subgroups, and the device has 64 lanes
agc_driver_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_driver_unit_shadow_budget: skipped, the device does not import watched host memory for unit shadows
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_pipeline_binary_vulkan: skipped, the device lacks VK_KHR_pipeline_binary
agc_shadowed_key_stores: skipped, the device imports no watched host memory under a unit shadow
agc_small_tiled_shadow_label: skipped, host imports are unavailable: the test block is not imported and watched
agc_storage_clear_hand_over: skipped, the device imports no watched host memory for the clear-key hand-over
agc_unit_shadow: skipped, the device does not import watched host memory for unit shadows
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_video_out_color_space: 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_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 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_in_order: 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_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not 
Reproduce
git fetch origin 9172e6c2a5cfb2d7a331f663be58d3010aa7555a f2d874c1fdccf781c0fa5d86619bb5dd8894b958
git checkout f2d874c1fdccf781c0fa5d86619bb5dd8894b958 && git merge --no-ff 9172e6c2a5cfb2d7a331f663be58d3010aa7555a
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 12 --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 16 --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 AMD machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 9172e6c merged onto main f2d874c · tested 2026-11-01 21:52 UTC · 21 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,446 passed · 5 failed (0 new, 4 also fail on main, 1 unstable on main) · 46 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,497 of 3,168 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 (936 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_driver_graphics_maps_fallback 30.6 s, ⚠️ agc_driver_graphics 30.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 (4, not counted against this PR)
agc_driver_dcc_keys_first_write
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (5 IDs in 484 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (484 tests): agc_driver, 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, agc_driver_bda_span_reads, and 476 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 (1 test): agc_driver_prepared_exports
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (46)
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_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_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_pipeline_binary_vulkan: skipped, the device lacks VK_KHR_pipeline_binary
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_video_out_color_space: 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_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 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_in_order: 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_dcc: 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_deferred: 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_eop: 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_eop_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_eop_main: 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_fps_limit: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL
Reproduce
git fetch origin 9172e6c2a5cfb2d7a331f663be58d3010aa7555a f2d874c1fdccf781c0fa5d86619bb5dd8894b958
git checkout f2d874c1fdccf781c0fa5d86619bb5dd8894b958 && git merge --no-ff 9172e6c2a5cfb2d7a331f663be58d3010aa7555a
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.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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 7392 core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp
PR 7642 core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp
PR 10193 core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp
PR 13928 core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp
PR 13929 core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp

@BunnyFl BunnyFl 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.

Build and test on Windows 11, AMD Radeon RX 9070 XT (RDNA 4), WinLibs GCC, BUILD_TESTING=ON, Khronos validation layer on, PR merged into current main:

  • agc_driver_host_import_failure_handles, which runs RunBdaContractTests, and agc_driver_bda_device_selection_cpu pass.
  • With main's SpirvBdaRead.cpp restored, agc_driver_host_import_failure_handles fails on the new check ("a pixel program with v_readlane must keep its invocations running").

The reasoning holds: a waterfall's v_readlane can't drain a lane whose bit stays in the copied mask after the lane stopped, so a pixel program with cross-lane reads must keep faulting lanes running, as one with a barrier already does.

One question, not blocking: why only pixel? A compute or vertex program that stops a lane the same way (it returns from the guest code while the other lanes keep a mask copied from exec) can run the same waterfall, and the new test pins BdaInvocationsMayStop as true for a compute program with v_readlane. If stopping works differently outside pixel shaders, so the stale bit can't occur there, a sentence in the PR or debt entry saying so would help the next reader.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@BunnyFl thank you for the RDNA 4 run and for checking that the new case fails without the change. I added both to Tested.

On why only pixel: stopping works the same way in every stage. On main, a compute or vertex program that stops a lane can leave the same stale bit in a copied mask, and a waterfall over that mask can spin the same way. This PR doesn't fix those stages because @Celegans12's #6416 (open) does. For programs other than pixel that use wave operations, it keeps a faulting invocation in its wave and masks its later writes (bdaFaultMasksWrites, which it sets only when the stage isn't pixel), so no lane leaves the wave early. #6416 leaves pixel programs out, and this PR covers them. An earlier version of this change applied to every stage. I narrowed it so the two PRs wouldn't overlap.

The compute ReadLane case in the new test expects BdaInvocationsMayStop to stay true, as it is on main. #6416 doesn't change that function; it acts after it. So the two can merge in either order. I've put a sentence saying this in the How section of the PR description.

AI-assisted: yes (Claude Code)

@oneandonlydean oneandonlydean self-assigned this Nov 3, 2026

@XairRrR XairRrR 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.

Reviewed — approved

BdaInvocationsMayStop now returns false for pixel programs that contain ReadLane or WriteLane. The new contract checks pass on the merged tree and fail with main's implementation restored. I found nothing blocking.

What I ran

Head 9172e6c2a5cfb2d7a331f663be58d3010aa7555a merged with --no-ff into main d1ae628bc2f62459cce1e9872ba5632c938c71f2. The merged tree is 020afaf4ebc7c2a24a5ddcf3e7ab1ac9afa94615. I ran everything in a clean worktree on Debian 13 (VM, 32 vCPU Xeon Gold 6136), with GCC 14.2.0 and lavapipe (Mesa 26.1.6).

git diff --check d1ae628bc2f62459cce1e9872ba5632c938c71f2...9172e6c2a5cfb2d7a331f663be58d3010aa7555a
python3 tools/check_conventions.py --base d1ae628bc2f62459cce1e9872ba5632c938c71f2
ctest --test-dir build -j8 --test-load 32 --output-on-failure -R "^agc_driver_(host_import_failure_handles|bda_device|bda_device_selection_cpu)$"
git show d1ae628bc2f62459cce1e9872ba5632c938c71f2:core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp > core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp
ninja -C build -j32 -l32 agc_driver_bda_device_tests
ctest ... (same three tests)
git show 020afaf4ebc7c2a24a5ddcf3e7ab1ac9afa94615:core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp > core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp
ninja -C build -j32 -l32 agc_driver_bda_device_tests
git status --porcelain
ctest ... (same three tests)

Results

Check Result
git diff --check clean
check_conventions.py all checks passed
Configure / build / libs (script) ok
Selected tests (script) 2131 run, 2011 passed, 113 expected skips. No new failures and no timeouts. The 7 failures (agc_driver_division, the denormal-mode tests, vector_constant_factor_tininess, an unwrap_self case) also fail on main and are not this PR's.
Vulkan validation vs main (script) 767 tests, layer loaded in 766, none missing, 0 tests with new VUID- lines
agc_driver_bda_device, agc_driver_host_import_failure_handles, agc_driver_bda_device_selection_cpu pass, each under 1 s

Negative control

I restored only core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp from main and rebuilt. agc_driver_bda_device and agc_driver_host_import_failure_handles then fail on the first new check:

output
BDA device selection CPU tests passed
AGC graphics: a pixel program with v_readlane must keep its invocations running: a waterfall over a mask that holds a stopped lane never clears its bit

After I restored the merged file and rebuilt, all three tests pass again and git status --porcelain is empty. This confirms @BunnyFl's negative control on RDNA 4 with a second, independent run.

Findings

Blocking

None.

Non-blocking

  • Field comment no longer covers every case. core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp:198 says bdaStopsInvocations is "False for programs with workgroup barriers". After this change it is also false for pixel programs with ReadLane/WriteLane. The description says the head deliberately dropped an edit to that comment, because comment changes need a technical-debt change. A maintainer may want to update it in a follow-up so that it matches BdaInvocationsMayStop (core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp:416-426).
  • The rule matches opcodes, not the loop shape. Any pixel program with a ReadLane/WriteLane now loses the early stop, including programs with no waterfall. In those programs a faulting invocation keeps running on zeros and its later writes are not masked; #6416's bdaFaultMasksWrites covers only non-pixel stages. This matches what barrier programs already do, and the fault is still recorded and reported, so I see no correctness regression. Other cross-lane ops (DppMoveU32, Permlane16U32, BpermuteU32, SwizzleU32) keep the stop. I agree that these cannot spin on a stale mask bit the way the s_ff1/v_readlane waterfall does.
  • Missing bring-up discussion on the Unblocks line. The line names PPSA02225 but no Game bring-up discussion number. CONTRIBUTING asks for both (Unblocks: PPSAxxxxx #N).

On @BunnyFl's question about compute and other stages: the author's answer is consistent with the code. SpirvEmitter.cpp:332 combines BdaInvocationsMayStop with the stage-independent conditions, and the new compute check pins the current behaviour. The stale-bit hazard in non-pixel stages is still open on main until #6416 lands. Neither the PR nor the debt docs record this, so it depends on that PR being merged.

Not verified

  • The Ghost of Tsushima (PPSA02225) device-loss result. I did not launch the title, and I had no NVIDIA or AMD hardware; GPU coverage here is lavapipe only.
  • The offline-replay claim (201 returns to 2, OpTerminateInvocation removed). I did not have the dumped requests.
  • Any behaviour on hardware where a faulting pixel invocation keeps running with zero reads.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@XairRrR thanks for the full check with the negative control. Restoring only SpirvBdaRead.cpp is the right isolation: the contract checks in agc_driver_bda_device and agc_driver_host_import_failure_handles pin exactly the pixel/ReadLane/WriteLane case that BdaInvocationsMayStop now refuses.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 3, 2026
@oneandonlydean
oneandonlydean merged commit c915f22 into boykopovar:main Nov 3, 2026
22 of 23 checks passed
@github-actions github-actions Bot removed the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* shader core/shader unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants