Skip to content

fix(shader): ballot a predicate both wave64 halves share only once - #6638

Closed
mariuszkreft wants to merge 1 commit into
boykopovar:mainfrom
mariuszkreft:fix/shader-wave64-shared-ballot-once
Closed

mariuszkreft wants to merge 1 commit into
boykopovar:mainfrom
mariuszkreft:fix/shader-wave64-shared-ballot-once

Conversation

@mariuszkreft

Copy link
Copy Markdown

What

Found by driving GTA V (PPSA04264) on Linux/RTX 3090 (NVIDIA 580.159.03): creating the compute pipeline for shader c02e5e3f35010fa5 segfaults inside NVIDIA's SPIR-V compiler (SIGSEGV in libnvidia-gpucomp.so+0xe3facc).

A wave64 program that runs two lanes per invocation on a 32-lane host subgroup builds a ballot from one OpGroupNonUniformBallot per half. When both halves read the same predicate id (the constant true, or a scalar value shared by both halves), the second ballot repeated the first one exactly: same instruction, same predicate, same block. The GTA V shader contains 12 such back-to-back pairs (%1115 = OpGroupNonUniformBallot %1113 right after %1114 = OpGroupNonUniformBallot %1113). Replaying the captured pipeline with only those 12 duplicates replaced by a copy of the first ballot (still valid under spirv-val) no longer crashes; the other candidate rewrites tried did not help.

SpirvValueEmitContext::Ballot (core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp) now reuses the low half's ballot for the high half when both predicate ids are equal. The result is identical by construction, the change applies to every program, and it saves one subgroup operation. Distinct predicates still get two ballots.

core/libs/tests/Wave32WideSubgroup.cpp gains CheckTwoLaneBallot: it links two half contexts, interprets the emitted SPIR-V, checks that the ballot words are {lowMask, highMask, 0, 0}, and checks that a predicate shared by both halves is balloted exactly once while distinct predicates are balloted twice.

Tested

Linux, on the host, in the slice worktree. The loop ran this gate itself; the tail of it is
at the bottom of this pull request. No title and no GPU were involved.

cmake -S . -B build-full -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
cmake --build build-full --parallel 12
ctest --test-dir build-full --output-on-failure -j 12
python3 tools/check_conventions.py --base origin/main

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; no silent stubs added
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA04264 Game bring-up #1203 — reported against this title, not verified on it
  • GPUs tested: n/a (host-side change; no GPU execution)
  • AI-assisted: yes
Verification log tail
core/libs/tests/Wave32WideSubgroup.cpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp

=== PROFILE ===
full

=== configure ===
-- Configuring done (11.2s)
-- Generating done (0.3s)
-- Build files have been written to: /home/m2/.herdr/worktrees/AnyPS5/AnyPS5-gta5-shader-wave64-shared-ballot-once/build-full

=== PROFILE CHECK ===
build-full/CMakeCache.txt: ANYPS5_RELINKER_ONLY=OFF (expected OFF)

=== build ===
[3520/3522] Linking CXX executable tests/dynamic_table_bounds_tests
[3521/3522] Linking CXX executable tests/reloc_table_bounds_tests

=== ctest (area: shader|wave|spirv|subgroup|lane) ===
100% tests passed, 0 tests failed out of 70

=== fails before (SpirvEmitter.cpp at origin/main) ===
wave32_wide_subgroup: Failed: a wave64 ballot of one predicate for both halves was not issued once

=== ctest (full, -j 12) ===
99% tests passed, 4 tests failed out of 1269
The following tests FAILED:
	178 - agc_frame_dump_io (Failed)
	393 - agc_driver_flip_stores_small_results (Failed)
	608 - agc_driver_store_idle_results (Failed)
	609 - agc_driver_store_over_untracked (Failed)
Errors while running CTest
rerun alone: agc_frame_dump_io, agc_driver_flip_stores_small_results, agc_driver_store_idle_results passed
agc_driver_store_over_untracked: fails identically with the emitter reverted to origin/main (pre-existing)

=== conventions ===
All conventions checks passed.

=== RESULT ===
PASS

A wave64 program at two lanes per invocation ballots each half separately.
When both halves read the same predicate (a constant or a scalar value),
the second OpGroupNonUniformBallot repeated the first one exactly. Reuse
its result instead. NVIDIA 580's SPIR-V compiler crashed on a GTA V
compute shader with those duplicated ballots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 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 4339 core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 4650 core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 5021 core/libs/tests/Wave32WideSubgroup.cpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6044 core/libs/tests/Wave32WideSubgroup.cpp
PR 6177 core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6222 core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6416 core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp

@github-actions github-actions Bot added the shader core/shader label Oct 16, 2026
@savvaniss

Copy link
Copy Markdown
Contributor

GPU execution data for this change: the first run on GPUs, as far as I can see. It covers a 32-wide host (Intel, which takes the two-lanes-per-invocation path) and a 64-wide host (AMD).

Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, every execution test built, ANYPS5_NO_SHADER_CACHE=1, serial CTest, main 47a266cf9. Intel UHD Graphics 770 (8086:a780, 32-wide subgroups), driver 32.0.101.7088. AMD Radeon RX 9070 XT (1002:7550, 64-wide subgroups), driver 32.0.31041.1004, with VkCache emptied first.

UHD 770 RX 9070 XT
This head f826414e2 merged into main: wave32_wide_subgroup, ×3 pass pass
This PR's test file on main without the fix, ×3 fails fails

Full CTest (--parallel 4) with six open PRs merged into main (#6638, #6604, #6014, #6773, #6772, #6725): the same failures as main alone on both GPUs, nothing new. main alone: UHD 770 1392 of 1398, RX 9070 XT 1396 of 1398. The failures are image tables (#6647) and sample_multisample_target (#6971) on Intel, and quad_pixel_mask (#6255) and layered_volume_vertex_no_gpl (#4768) on AMD.

Merge order: #6677 and #6654 each conflict with this PR in core/libs/tests/Wave32WideSubgroup.cpp, and #6654 also in SpirvEmitter.cpp.

AI-assisted: yes (Claude Code)

@mariuszkreft

Copy link
Copy Markdown
Author

Closed by the contribution loop's guard: this pull request has no checklist and was not opened by the loop's reviewed path. The work will be reopened as a pull request after the local gate and the review pass.

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

Labels

shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants