Skip to content

fix(shader): mask EXEC copies to the host subgroup's lanes - #1264

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
Celegans12:fix/shader-wave64-exec-host-lanes
Oct 8, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
Celegans12:fix/shader-wave64-exec-host-lanes

Conversation

@Celegans12

Copy link
Copy Markdown
Contributor

What

On a 32-lane host, wave64 graphics stages run one guest lane per invocation, but an EXEC written as a value still has bits for lanes 32-63. An s_mov_b64 copy of it keeps lanes no ballot clears, so a waterfall loop over the copy never ends.

  • readRawU32 reads EXEC as EXEC & ballot(true) (hostExecWord) for s_mov_b64 sN, exec, ALU sources and EXECZ, and s_*_saveexec and s_*_wrexec store that copy. EXEC's own words, threadBit and the saveexec combination stay raw, so s_orn2_saveexec_b64 vcc, exec still sets all 64 bits.
  • Where the host holds the whole wave (wave32 on 32 lanes, compute at two lanes per invocation) nothing changes. Partly filled or 8-lane llvmpipe subgroups now leave out the lanes they lack, so agc_driver_scalar_sop1 masks its EXEC_LO copies there. TechnicalDebt notes the console's EXEC holds the other lanes too.
  • PPSA26344 v1.008: the G-buffer draws' wave64 NGG vertex shaders 0x806ef8ba00 and 0x806ef34b00 run this loop after the prologue's s_lshr_b64 exec, -1, 64 - count sets EXEC to -1. With our pending null-SRT work those draws run and hung the GPU at about 695 s. Under a 65,536-pass loop guard only these two shaders tripped it (102 times in 2,700 s).
  • feat(shader): implement the 32-bit DS src2 operations #938 and feat(shader): implement v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 #697 add declarations elsewhere in TranslationContext.hpp without conflicting.

Tested

  • Windows 11, MinGW-w64 GCC 15.2.0, based on main c5e1018: configure -DBUILD_TESTING=ON, full build, --target libs, ctest:
  • 396/396 tests pass (ctest --output-on-failure)
  • Same build on lavapipe (Mesa 26.2.4 llvmpipe, 8-wide subgroups, VK_DRIVER_FILES): 395/395 pass; agc_driver_flat_store is left out there because it takes several minutes on Windows llvmpipe
  • New agc_driver_exec_host_lanes: the title's loop (loads cut to one s_buffer_load_dword, capped at 128 passes) takes at most 6 passes in a 189-vertex wave64 draw (32+ lanes) and a 192-thread wave64 dispatch. It fails on main with 128: the draw on NVIDIA, the dispatch on llvmpipe.
  • PPSA26344 v1.008: in a 2,700 s run with no loop guard, both vertex shaders were recompiled for G-buffer draws (at about 1,410 and 1,650 s) and the GPU did not hang: no batch ran 30 s, the G-buffer pass kept advancing, and the game exited 12 s after the stop.
  • Not tested: Linux (CI), AMD hosts, console behaviour.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

A wave64 graphics program on a 32-lane host runs one guest lane per
invocation, yet an EXEC written as a value keeps all 64 bits: the NGG
prologue's s_lshr_b64 exec, -1, 64 - count reads count 0 and sets -1.
A waterfall loop that copies EXEC with s_mov_b64 and retires one key
per pass then never empties its copy, since no ballot sets the lanes
the host lacks, and its v_readlane picks lane 32 and up.

readRawU32 now ANDs EXEC with a ballot of true when it is read as a
value (s_mov_b64, ALU sources, src_execz), and s_*_saveexec and
s_*_wrexec store that masked copy. EXEC's own words, threadBit and the
saveexec combination keep the raw words, so s_orn2_saveexec_b64 vcc,
exec still sets all 64 bits. On subgroups under 32 lanes the EXEC_LO
copies of agc_driver_scalar_sop1 now hold only the subgroup's lanes.
@fisherjoey

Copy link
Copy Markdown
Contributor

Tested on Linux and an AMD host, both listed as not tested.

Nobara 44 (Linux 7.2), GCC 16.2.1, Release, BUILD_TESTING=ON. This PR merged onto main df16c4c, plus #1329 (without it the NVIDIA proprietary driver fails 100 GPU tests on main from the dma-buf probe).

result
CPU tests (-E agc_driver) 241 / 242, only guest_force_tls_destructor, which fails on main too
RTX 5070 Ti, driver 595.91.07, -R agc_driver -j1 167 / 167
RADV, Ryzen 5 7600X iGPU (gfx1036, pixel subgroup 64, compute wave32 on 32), Mesa 26.2.1 167 / 167

agc_driver_exec_host_lanes and agc_driver_scalar_sop1 pass on both GPUs.

@Celegans12

Copy link
Copy Markdown
Contributor Author

Thanks for running this on the 7600X. A 64-lane pixel subgroup is the case I can't reach on my NVIDIA laptop, so it's good to see agc_driver_exec_host_lanes pass there. I'm looking at the RADV failure you found on #1268 next.

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked: merged onto current main, full build and ctest 434/434 green. agc_driver_exec_host_lanes fails with the source change reverted (waterfall never terminates) and passes with it. CI is green on this head (linux, windows, conventions), and fisherjoey's NVIDIA and RADV (64-lane pixel subgroup) runs also pass. No conflicts with #1217 or #1268. Merging.

@adriadam10
adriadam10 merged commit 915e3b7 into boykopovar:main Oct 8, 2026
4 checks passed
@Celegans12
Celegans12 deleted the fix/shader-wave64-exec-host-lanes branch October 8, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants