Skip to content

fix(shader): repeat phi folding until no phi folds - #6332

Open
SleglJan wants to merge 1 commit into
boykopovar:mainfrom
SleglJan:pr/fold-loop-phis
Open

SleglJan wants to merge 1 commit into
boykopovar:mainfrom
SleglJan:pr/fold-loop-phis

Conversation

@SleglJan

Copy link
Copy Markdown
Contributor

What

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 is visited before that phi folds, so it stays a phi of its one definition and itself. An example is an outer loop's header phi whose back-edge value is the header phi of an inner loop in which an S# is copied out and back.

Such phis reach resource tracking, which compares descriptor dwords as IR values. So the phi of an S# and the S# itself become two samplers, and the same holds for T#s. Two PPSA11386 (007 First Light) pixel shaders each planned 21 direct samplers for 6 distinct S# values. At two heap slots each they exceed the 32 sampler slots, and the shaders stop 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. Only phis are repeated: a fold that depends on a phi folded this late waits for the next Fold, which follows read-lane and masked-select elimination as before. #4150's restriction stays as it is.

#6083 raises the sampler heap to 32 pairs, which would also fit this shader's 21 samplers, duplicates included. This change removes the duplicates: the shader plans one sampler per distinct S#. The two do not conflict. #6176 and #5914 also edit ConstantFolder::Fold; both merge cleanly with this change.

Tested

  • Tested on main a08de68, then rebased without conflicts onto main b3f9f89, where tools/check_conventions.py --base upstream/main and git diff --check pass.
  • agc_shader_memory, verifyDescriptorPhis: an S# copied out and back in three nested loops, sampled before and inside them, compiled with and without a masked write. Each now plans one sampler and one sampled pair. On main both plan two of each, and the test fails with descriptor Phi: an S# copied out and back in three nested loops was planned as more than one sampler. One extra phi pass instead of the loop also plans two of each, so the test needs the fixed point.
  • ctest agc|shader|recompiler on lavapipe (Mesa 26.2.4, LLVM 23.1.1; Arch Linux container, GCC 16.2.1): 575 of 580 pass. The five failures (agc_storage_kept_unit_settle, agc_driver_bc_block_copy, agc_driver_depth_compare_copy, agc_driver_native_multisample_depth, agc_driver_storage_write_back_overhang) also fail on main built the same way. The other descriptor-phi and fold tests pass: agc_constant_phi, agc_masked_select, agc_shader_control_flow, agc_driver_relative_ancillary, agc_shader_bindless, agc_vector_window and others.
  • RTX 3080 (NVIDIA 615.78.08), CachyOS (Linux 7.2.10), GCC 16.2.1, Release: ctest agc|shader|recompiler|pm4|draw|position, 583 of 583 pass, two of them skipped as on main.
  • PPSA11386, the two pixel shaders that stop with the error above, replayed offline. main can't read their requests, which come from an older local branch, so they ran on that branch with fix(shader): preserve ancillary proofs across relative VGPR accesses #4150's restriction restored and this change added:
    • before, each: 21 samplers, 47 sampled pairs, 43 images, and the error above;
    • after, each: 6 samplers, 24 sampled pairs, 29 images, and both recompile. Images are keyed by their descriptor source, so the 14 fewer images are T# phis folded the same way.

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
  • Unblocks: PPSA11386 007 First Light (PPSA11386) #5714
  • GPUs tested: NVIDIA RTX 3080, driver 615.78.08, CachyOS (Linux 7.2.10); lavapipe (Mesa 26.2.4, LLVM 23.1.1), Arch Linux container
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

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 boykopovar#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 boykopovar#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>
This was referenced Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 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 1341 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 5021 core/shader/recompiler/Optimization/src/ConstantFolder.cpp
PR 5371 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 5500 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
core/shader/recompiler/Optimization/src/ConstantFolder.cpp
PR 6177 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6235 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6412 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6488 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6503 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6645 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6941 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 7381 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 7388 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 7642 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp

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

agc libSceAgc* shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant