Skip to content

fix(shader): preserve ancillary proofs across relative VGPR accesses - #4150

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
eljackjoestar-cmd:codex/ancillary-relative-ssa
Oct 12, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
eljackjoestar-cmd:codex/ancillary-relative-ssa

Conversation

@eljackjoestar-cmd

@eljackjoestar-cmd eljackjoestar-cmd commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

After #3856, a Lost Soul Aside (PPSA26358) pixel shader aborts with packed pixel ancillary input has an unsupported live use (used by StoreVectorWindow ?). The relative VGPR window stores its packed ancillary prologue input in a private array, hiding its definition from field lowering and the masked-write proof.

Keep the relative span in SSA when it contains the loaded pixel ancillary input. Indexed reads, writes and split-offset swaps use compare/select chains with the same unsigned M0 bounds, v0 fallback, EXEC gating and DPP update rules as the existing private-window path. Other programs keep that path. Fold again after masked-select elimination only when a PackedAncillary builtin still has live uses, so newly exposed sample/layer extracts can lower without changing unrelated programs. Unsupported whole-word uses still throw; no primitive-class or unknown-bit values are invented. The SSA span includes implicit entry inputs as well as explicitly named registers, matching main's expanded private-window limit.

The opcode behavior is inherited from main's private-window implementation and its RDNA2/gfx1035 measurements recorded in TechnicalDebt and #3856. This changes the IR representation and pass order. Console comparison and the performance cost of selecting over this span remain unverified and are recorded in TechnicalDebt.

Checked open PRs before publication. #4120 mirrors direct SSA writes into read-only windows to preserve sampler definitions; this change removes the window for the ancillary case and supports indexed writes. #4031 composes the whole ancillary word; this change keeps unsupported reads refused. #4057 is now merged and extends private windows for implicit inputs/fetches; this change also extends its ancillary SSA span over the entry inputs. These are overlapping areas, with different fixes.

Tested

Rebased on main 9aa5fd290; candidate ae049e775. #3739 is merged and its commit is absent from this PR diff. Windows 11 x64, Ryzen 7 7800X3D, 32 GB RAM, NVIDIA RTX 5070, WinLibs GCC 15.2, Release, BUILD_TESTING=ON, emulator SPIRV-Tools OFF.

  • Separate PR checkout builds the driver, replay tool and selected tests. agc_driver_relative_ancillary, agc_driver_relative_indexing, agc_masked_select, agc_vector_window and pixel_ancillary all pass, without skips; the original isolated repeat took 0.79 s total. The latest ten-test dependency stack, including the new implicit-input regression and source-IR reclamation, passes in 1.89 s. No shader-cache dependence; tests use the repository's cache-disabled CTest configuration.
  • New native Vulkan readbacks cover wave32/wave64 relative reads, indexed writes, split-offset swaps, out-of-range reads/writes and one-sided invalid swaps, inactive writes, enabled/disabled DPP masks, and unchanged pixels. Negative cases require refusal of a live unsupported ancillary word. The updated wave32/wave64 regression also loads four implicit position inputs before ancillary and reads ancillary relatively beyond the explicitly named register span; this now renders correctly instead of throwing. The new test completes in under one second locally.
  • Restoring the private-window choice on the same base fails both the native regression and the captured 5,016-code-word wave64 request at StoreVectorWindow. Restoring the candidate passes. Before adding the post-mask fold, the new native test additionally failed with an exposed BitFieldUExtract use.
  • The final captured request recompiles to 309,229 SPIR-V words and passes standalone spirv-val --target-env vulkan1.1spv1.4, built from the repository's bundled sources. The integration capture used format 17 solely for a vertex extension; this pixel-only request was checked to have no vertex/compute context, and only its format version was changed to main's 16 for isolated replay. Shader code, inputs and memory bytes were unchanged.
  • A title run with the local graphics/audio integration stack moved past the compilation failure to the rendered menu and level loading. Later startup attempts can still hit an Unreal garbage-collection assertion; another level-load run crashed in guest memory access, and a further run was stopped when host memory was nearly exhausted. Gameplay, complete rendering, the reported green flashes and audio quality are not claimed fixed. Integration testing also used fix(agc): render a color view of several array slices into its first slice #3633, feat(agc): render layered draws into 3D color targets #1077, fix(agc): render all attachment layers with dynamic rendering #3763, feat(agc): depth planes sampled as 2D arrays, cubes and integer views #425, fix(agc): read zero for pixel inputs of parameters the vertex stage does not export #3523 and feat(ajm): decode Opus surround mapping family #3831; those changes are excluded from this PR.
  • Conventions and git diff --check pass. No game assets, captured requests, PCM, screenshots or investigation files are committed.

Linux, macOS, console hardware and collaborator-triggered full CI were not run. Earlier broad integration testing had existing failures and a hanging layered-volume submission test; the full suite is not claimed green.

Checklist

  • Based on current main; no other open PR implements this scoped fix (overlaps described above)
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except namespace endings; known gaps recorded in TechnicalDebt
  • Unimplemented paths throw; no silent stubs added
  • No notes, investigation files or images added to the repository
  • No new third-party code
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA26358 Lost Soul Aside (PPSA26358): bring-up and remaining blockers #4401
  • AI-assisted: yes (Codex)

Review follow-up: #4057 is now in main. Its implicit-input window growth is preserved, and the ancillary SSA path separately includes entry-prologue register definitions in its inferred limit. This resolves the ancillary-plus-implicit-input intersection raised in review. #4120 adds a direct SSA mirror for read-only windows; this branch has not been tested merged with its vectorWindowIndexedWrites changes. #4162 forwards private-window stores for half-wave reduction matching; it is a separate mechanism and has not been combined with this branch. #4031 composes the whole ancillary word and inserts lowering beside the same recompiler pass; this branch instead retains unsupported whole-word refusal. #3739 is merged into main.

Review validation on the rebased branch: all eight ancillary, masked-select and relative/window tests pass (2.03 s, no skips). Of 255 local requests with no ancillary input, 245 successfully generated modules are byte-identical to main; the other ten fail on the same unavailable SRT runtime-source reads on both branches. The reviewer’s separate 156-request corpus was not available locally.

The captured 5,016-code-word wave64 pixel request was timed in seven alternating, fresh-process agc_shader_replay invocations per branch with ANYPS5_NO_SHADER_CACHE=1, Release/GCC 15.2, no concurrent build or game. Candidate wall time including process startup and request loading: median 399.30 ms, range 387.08–406.89 ms, successfully emitting 309,229 SPIR-V words. Main’s #3856 array path: median 182.26 ms, range 179.89–209.31 ms, but every invocation rejects StoreVectorWindow before module generation. These are successful replay versus time-to-rejection measurements; GPU time and a valid select-chain/array performance comparison remain unavailable because the array form cannot compile this request.

@Dextroid17

Copy link
Copy Markdown
Contributor

The fix itself is right for the case it targets, and it's genuinely narrower than #4120/#4057/#4162 — it only engages when a pixel shader's packed ancillary input specifically falls inside the relative-addressing span, keeping that span in SSA with compare/select chains instead of the private window array.

The PR body's own overlap diligence deserves real credit for existing at all, but it understates two things worth being precise about, since this is the fourth PR tonight touching this exact subsystem:

Two more textual collisions worth flagging to the author directly: this diff's Recompiler.cpp hunk lands at the identical anchor as #4031's ComposePackedAncillary insertion (both are, in fact, competing fixes for the same packed-ancillary-word problem), and this branch contains #3739's changes verbatim byte-for-byte — an undeclared stacked dependency, not a coincidence.

None of this blocks the fix on its own merits — it's self-consistent, throws rather than silently miscomputing on the one case it can't yet handle, and records its gaps in TechnicalDebt. But given four PRs tonight all touch InstructionTranslator::Translate's window-setup region, the useful ask here is sequencing, not a hold: land these one at a time rather than back-to-back, and have whichever of #4150/#4057 lands second explicitly handle the ancillary-plus-implicit-input intersection instead of leaving it as a new throw.

Written with AI assistance.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 12, 2026
@eljackjoestar-cmd
eljackjoestar-cmd force-pushed the codex/ancillary-relative-ssa branch from 723d61c to 1942bda Compare October 12, 2026 03:42
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 12, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Please rebase onto main: #3739 is merged, so its commit drops out of this diff.

One thing on the second ConstantFolder::Fold in Recompiler.cpp: it runs for every program. Replaying 156 requests of a title that never loads the ancillary VGPR, 120 come out different with this PR (about 1% fewer words) and none without that hunk, so it changes generated code everywhere, which is a second topic. Without the hunk agc_driver_relative_ancillary fails with (used by SelectU32 0 ?), so some re-fold is needed: folding again only when a PackedAncillary builtin still has uses after the masked-select pass keeps the related tests passing (pixel_ancillary, agc_masked_select, agc_vector_window*, agc_driver_relative_*) and leaves other programs byte-identical.

Could you also time the captured request with the select chains against the array form from #3856?

@eljackjoestar-cmd
eljackjoestar-cmd force-pushed the codex/ancillary-relative-ssa branch from 1942bda to ae049e7 Compare October 12, 2026 10:09
@eljackjoestar-cmd

Copy link
Copy Markdown
Contributor Author

Rebased onto main (9aa5fd290), so #3739 is no longer in the diff. Pushed ae049e775 with the second fold gated on a PackedAncillary builtin still having uses after masked-select elimination.

All eight related tests pass: pixel_ancillary, agc_masked_select, agc_vector_window, agc_vector_window_stage_inputs, and the four agc_driver_relative_*/scalar-relative tests (2.03 s, no skips). I don’t have your 156-request corpus, but replayed 255 local requests without ancillary input against main and this branch: all 245 successfully generated modules are SHA-256 byte-identical. The other ten fail on the same unavailable SRT runtime-source reads on both branches.

I timed the captured 5,016-code-word wave64 request with seven alternating fresh-process replays per branch, shader cache disabled, Release/GCC 15.2 on the Ryzen 7 7800X3D, with no concurrent build/game:

Path Median wall time Range Result
This PR’s select chains 399.30 ms 387.08–406.89 ms 309,229 SPIR-V words; standalone spirv-val --target-env vulkan1.1spv1.4 passes
Main’s #3856 array path 182.26 ms 179.89–209.31 ms Rejects packed pixel ancillary input ... (used by StoreVectorWindow ?) before emitting a module

These timings include request loading and process startup. The array result is time to rejection, so this does not establish a speed ratio or GPU-time comparison between working shaders. The same captured request still cannot produce a valid array-form module on main. No whole-ancillary composition or invented ancillary bits were added to make that path run.

@oneandonlydean oneandonlydean self-assigned this Oct 12, 2026
@oneandonlydean
oneandonlydean merged commit 8751a29 into boykopovar:main Oct 12, 2026
9 checks passed
adriadam10 pushed a commit that referenced this pull request Oct 18, 2026
ConstantFolder::Fold visits each instruction once, in block order. A loop
header phi whose back-edge value is a later phi that folds to it in the
same pass, such as the header phi of an inner loop in which an S# is
copied out and back, is visited before that phi folds and stays a phi of
its one definition and itself. Later folds run only after read-lane or
masked-select elimination changed the program, and since #4150 the one
after masked-select elimination only runs in programs where a
PackedAncillary builtin still has live uses.

These phis reach resource tracking, which compares descriptor dwords as IR
values, so the phi of an S# and the S# itself become two samplers. Two
PPSA11386 (007 First Light) pixel shaders each had 21 direct samplers for
6 distinct S# values; at two heap slots each they exceed the 32 sampler
slots, and the shaders stopped at their first draw with "shader sampler
pairs exceed runtime heap capacity".

After its pass over the instructions, Fold now folds the phis of its
blocks again until a pass folds none. Each fold turns a phi into an
Identity, so the loop ends. Replayed on an older local branch with #4150's
restriction in place, the captured requests plan 6 samplers each, one per
distinct S#, and recompile. agc_shader_memory compiles an S# copied out
and back in three nested loops and sampled before and inside them, with
and without a masked write: one sampler and one sampled pair each, two of
each before.

Co-Authored-By: Claude Opus 5.5 <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 shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants