Repository navigation
fix(shader): give a selection a merge when one arm returns through nested branches - #6405
Merged
adriadam10 merged 1 commit intoOct 19, 2026
Conversation
…sted branches
mergeBesideReturns ignores the edges into blocks that only lead to a return
when it looks for the merge of a selection whose arms do not meet. It only
recognized a straight run of single-predecessor blocks ending in a return,
so an arm whose return path branches again (an early return inside a
nested if/else) still counted as a block of its own, the arms had no
common post-dominator, and the whole program fell back to flag
structurization ("conditional block 0 has no structured merge").
A block now returns when it has one predecessor, is dominated by the
header and every successor returns. Flag structurization turns every
original block into a guarded region, and the register phis at the region
joins carry values from paths that never reach a later use: in Resident
Evil 4 (PPSA07411) pixel program 0x20762add00 the T# of an image_load
became Phi(0, load through Phi(user data, compare mask)) and resource
analysis refused it. Structured, the T# is the load through the user-data
pointer and the program prepares, captures and materializes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
11 tasks done
Contributor
|
I ran the test case this PR adds, on Linux. Setup: head a2f497a merged into main at ff23657 (no conflicts; the PR's base is 25 commits behind), Linux, default CMake build (CMAKE_BUILD_TYPE empty, unoptimized), clean environment (
The same-executions check on that graph passes on main too, so main gives the same paths, only through a goto variable. I only ran the structurizer test, not a title. AI-assisted: yes |
11 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
mergeBesideReturns(ControlFlow/src/Structurizer.cpp) finds the merge of a selection whose arms do not meet. It computes post-dominators with the edges into blocks that only lead to a return left out. Onmaina block counted as one that only leads to a return when a straight run of single-predecessor blocks starts at it and ends in a return (hasLinearPathToTerminal,linearTail). An arm whose return path branches again, such as an early return inside a nested if/else, still counted as a block of its own. The arms then had no common post-dominator, and the structurizer gave the whole program to route variables ("conditional block 0 has no structured merge").Why it matters: in the route-variable form every original block becomes a guarded region, and the register phis at the region joins also carry values from paths that never reach a later use. In Resident Evil 4 (PPSA07411) pixel program
0x20762add00, the T# of animage_loadbecamePhi(0, load through Phi(user data, compare mask)), and resource analysis refused it. The title stopped there, insceAgcCreateInterpolantMapping_0100->Driver::ResolveShaderAbion a game thread:Structured, the T# is the load through the user-data pointer, and the program prepares.
The title gets to this program only after other stops:
mainstops it atValue::setTypeMismatchHandler(#6126) and then at its NGG geometry states (#5884). The run above was a private build ofmain8cd937e with #6126 and local changes that get it past the geometry states. The request the driver saved for the program (APS5_DUMP_SHADERS=1) shows the refusal on currentmainwithout the title, see Tested.Overlap: #6025 (dominator sets as bitsets) also changes
Structurizer.cpp;git merge-treemerges the two cleanly. No open pull request changesmergeBesideReturns.Tested
Windows Server 2025 (10.0.26100), AMD Ryzen 9 5900X, NVIDIA GeForce RTX 3060 (driver 591.86), WinLibs MinGW-w64 GCC 15.2.0, Ninja, Release,
BUILD_TESTING=ON.agc_structurizer_tests: a new graph (14 blocks; both arms of the header reach a return through nested branches and share one block) must keep the same executions as the original graph and must not route through a goto variable. Onmain(a5e2a80, with only the test added) it fails:an arm whose returns branch once a tail shared across arms is cloned: block 0 routes through a goto variable instead of a structured selection. With the change it passes.agc_shader_replay --prepareof the request the driver saved for the title's program:maina5e2a80:resource analysis failed: GetImageResource dword 0 is not a valid runtime value; chain: Phi(Immediate, LoadAddressU32@pc=0x00000214(GetAddressResource(Phi(GetUserData(Void), ...prepared: 81214 SPIR-V words.ctest -R "agc|shader|video_out" -j4on the RTX 3060, on this branch (main0e4ea13): 621 of 622 pass, 14 skip. The failure isagc_driver_store_over_untracked(store over untracked: the first result was not stored), Windows: StoreOverUntracked fails at "the first result was not stored" #6347; it fails onmainhere without the change too (5 of 5 runs at a5e2a80).agc_structurizer, its_intersection_off,_iteration_selection_offand_forced_fallbackmodes,agc_shader_control_flowand its two modes pass, and so doescontrol_flow_graph. Before a rebase, onmain71bd287 with the change: 617 of 618, the same failure.tools/check_conventions.pyfrommainwith this title and description: passes.main8cd937e + feat(libSceJson2): implement Value::setTypeMismatchHandler and store the element access failure and special float handlers #6126 + feat(agc): draw line-strip output of NGG geometry programs as mesh lines #5884 + a read-lane change like fix(recompiler): read lane spills back through relative-addressing window registers #6277 + feat(agc): rasterize a multisampled draw into one-sample targets at sample 0's locations #5907 and a follow-up to it): the title no longer stops on this program. It reached the brightness calibration screen (1,485 s run) and the "About the Settings for Sending Play Data" screen (2,096 s run); both runs ended on the harness's timed dump, with no driver failure in the log.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code