Skip to content

fix(shader): link a table call whose V# is a slice of an s_load_dwordx8 or s_load_dwordx16 - #8070

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
thuanquang:fix/table-call-wide-descriptor
Oct 23, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
thuanquang:fix/table-call-wide-descriptor

Conversation

@thuanquang

@thuanquang thuanquang commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What

Little Nightmares Enhanced Edition (PPSA10737) stops, after #7413 and a fix for #7395's GetImageResource case, in compute shader 0x200c340000 (@klosixgaming-arch on #7413):

computed/data-dependent s_swappc_b64 call target at program counter 0x000008b4 is not statically resolvable

Four of that shader's six table calls take their V# from s[88:91] and s[92:95], which one s_load_dwordx8 s[88:95], s[0:1], 0x10 loads. FindTableCalls only accepts a descriptor written by an s_load_dwordx4, so it skips them.

It now also accepts a V# that is a 4-dword-aligned slice of an s_load_dwordx8 or s_load_dwordx16: the descriptor registers must start a multiple of 4 dwords into the load's destination and lie inside it. The V# is read at the load's offset plus 16 bytes per slice before it. An s_load_dwordx4 is the one-slice case, so it behaves as before. The debt entry for table callees says so.

This links the calls at 0x0460, 0x08b4 and 0x0c7c, as @klosixgaming-arch found locally. The fourth call (0x0d2c) then stops with the table callees of the call at program counter 0x00000d2c lands out of the 16-bit branch range, because LinkCalls appends each call's chain and callee copies in call order. That is a separate change; @klosixgaming-arch proposed s_branch relay hops for it on #7413.

Tested

Windows 11 26200, AMD Radeon RX 6700 XT (gfx1031), driver 32.0.21045.5002, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON, rebased onto main 1edbe4b5e, which has #7413. Rebased again onto main 4ec064d29b on 2026-10-22 (head 73c9c2a762; the only conflict was the debt entry's neighbouring line): scalar_call and agc_driver_compute_table_calls pass 3 of 3 on it. The runs below use VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (Vulkan-ValidationLayers vulkan-sdk-1.4.363.0, built locally), ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1 and ctest -V, with the implicit overlay layers on this PC switched off (VK_LOADER_LAYERS_DISABLE=~implicit~). No log reports the layer as not found, and a run with VK_LOADER_DEBUG=layer shows the loader inserting it as an instance and a device layer.

  • scalar_call: FindTableCalls on the table caller with its s_load_dwordx4 swapped for an s_load_dwordx8 or s_load_dwordx16 into s[24:…]:
    • slices 0 and 1 of the x8, and slices 2 and 3 of the x16, link with descriptor offsets 0x270, 0x280, 0x290 and 0x2a0;
    • a buffer load through registers past the x8, and one through a misaligned register range, link nothing.
  • agc_driver_compute_table_calls: a caller whose V# is the second half of an s_load_dwordx8 at offset 0x10. The first half is a zero V#, so reading the wrong slice would fail. The first record's callee runs with SCC 0 and the second record's with SCC 1, in wave32 and wave64.
  • With this change, scalar_call, agc_driver_compute_table_calls, agc_driver_compute_fs_table_calls and agc_driver_compute_user_data_calls pass 3 of 3. Eight other tests of s_swappc_b64 calls and control flow also pass 3 of 3: scalar_swappc, agc_driver_scalar_swappc, agc_driver_scalar_swappc_fetch, agc_driver_scalar_static_call, agc_driver_compute_user_data_calls_registered, agc_shader_control_flow, agc_shader_control_flow_iteration_selection_off and agc_shader_control_flow_forced_fallback.
  • With GraphBuilder.cpp from main, scalar_call fails (scalar call regression) and agc_driver_compute_table_calls fails with the first record's callee through the second half of an s_load_dwordx8 (wave64): lane 0 is 3405691582, expected 16. The other two pass.
  • main 1edbe4b5e with its own versions of the tests: all four pass.
  • scalar_call also checks three V#s that an s_load_dwordx8 writes only in part, or not on a 4-dword boundary from its first register; none links: the load into s[26:33] with the V# at s[28:31], the same load with the V# at s[24:27], and a load into s[22:29] with the V# at s[28:31]. With the (descriptor - loaded) % 4u clause deleted, the first links at descriptor offset 0x278 and scalar_call fails (scalar call regression).
  • Deleting descriptor < loaded, the dataDwordCount bound, or both changes nothing: scalar_call passes, and a temporary sweep of s_load_dwordx4/x8/x16 destinations from s0 to vcc_hi against V# bases from s0 to s104 (17172 programs) accepts the same 148 calls. The writer search only returns a load that writes part of the V#, so a V# that starts before the load or runs past its end is never 4-dword aligned to it, and the alignment clause rejects it first. The two bounds stay as the statement that the slice lies inside the load.
  • No log has a VUID- error: not the runs with this change, the run with GraphBuilder.cpp from main, or the run on main.
  • @wachi35676 ran the earlier head b521b3eb5 (the same fix commit, before the test commit with the three slice-guard ScalarCall cases was added), merged into main 9127ea36d, on an NVIDIA RTX 5070 Ti (driver 617.42) and an AMD Radeon iGPU (RDNA2, 1002:13c0, AMD proprietary driver 2026.04.17), Windows 11 Home 26300, ANYPS5_REQUIRE_VULKAN=1: scalar_call, agc_driver_compute_table_calls, agc_driver_compute_fs_table_calls and control_flow_graph pass on both. With GraphBuilder.cpp back to main, scalar_call fails, and agc_driver_compute_table_calls fails on both with the message above. On WSL Ubuntu 24.04 with llvmpipe (Mesa 25.2.8), with this and six other PRs merged, 61 of the 62 related tests pass, these four included; the other fails on a WSL write-watch limit.
  • python tools/check_conventions.py --base origin/main with this title and body reports no errors and one warning: the table callees debt entry is over 1000 characters (2981 here, 2908 on main).
  • python tools/pr_overlap.py --branch fix/table-call-wide-descriptor (on 4ec064d29b, 2026-10-22): feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658 (macOS host support) adds Apple-only lines right after the calls.push_back this PR changes, and fix(shader): link table callees that together exceed s_call_b64 range #9068 rewrites the same Table callees debt entry; neither does what this PR does, and whichever merges second needs a rebase. Among the open PRs it could not compare (they conflict with main), feat: run PS4 games on the AGC driver — Limbo (CUSA01664) in game (integration branch) #6977 also changes GraphBuilder.cpp outside FindTableCalls, and test: organize library tests and label CTest suites #6044 moves ScalarCall.cpp to core/libs/tests/graphics/.
  • Not run: the full suite on this head.
  • Not tested: PPSA10737 itself (its next stop is the branch range above).

Titles, from testers:

Checklist

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 8658 core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp L1553
PR 9068 docs/dev/debt/libSceAgcDriver.md L215
PR 10179 docs/dev/debt/libSceAgcDriver.md L215-216

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 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6044 core/libs/tests/ScalarCall.cpp
PR 6186 docs/dev/debt/libSceAgcDriver.md
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6498 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6612 docs/dev/debt/libSceAgcDriver.md
PR 6804 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6973 docs/dev/debt/libSceAgcDriver.md
PR 6977 docs/dev/debt/libSceAgcDriver.md
PR 6987 docs/dev/debt/libSceAgcDriver.md
PR 6989 docs/dev/debt/libSceAgcDriver.md
PR 6995 docs/dev/debt/libSceAgcDriver.md
PR 7002 docs/dev/debt/libSceAgcDriver.md
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7307 docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7618 docs/dev/debt/libSceAgcDriver.md
PR 7642 docs/dev/debt/libSceAgcDriver.md
PR 7844 docs/dev/debt/libSceAgcDriver.md
PR 7910 docs/dev/debt/libSceAgcDriver.md
PR 7978 docs/dev/debt/libSceAgcDriver.md
PR 8014 docs/dev/debt/libSceAgcDriver.md
PR 8223 docs/dev/debt/libSceAgcDriver.md
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8365 docs/dev/debt/libSceAgcDriver.md
PR 8542 docs/dev/debt/libSceAgcDriver.md
PR 8564 docs/dev/debt/libSceAgcDriver.md
PR 8592 docs/dev/debt/libSceAgcDriver.md
PR 8740 docs/dev/debt/libSceAgcDriver.md
PR 8999 docs/dev/debt/libSceAgcDriver.md
PR 9020 docs/dev/debt/libSceAgcDriver.md
PR 9335 docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/debt/libSceAgcDriver.md
PR 9431 docs/dev/debt/libSceAgcDriver.md
PR 9432 docs/dev/debt/libSceAgcDriver.md
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9672 docs/dev/debt/libSceAgcDriver.md
PR 9673 docs/dev/debt/libSceAgcDriver.md
PR 9674 docs/dev/debt/libSceAgcDriver.md
PR 9675 docs/dev/debt/libSceAgcDriver.md
PR 9676 docs/dev/debt/libSceAgcDriver.md
PR 9748 docs/dev/debt/libSceAgcDriver.md
PR 9891 docs/dev/debt/libSceAgcDriver.md
PR 9892 docs/dev/debt/libSceAgcDriver.md
PR 9893 docs/dev/debt/libSceAgcDriver.md
PR 9895 docs/dev/debt/libSceAgcDriver.md
PR 9898 docs/dev/debt/libSceAgcDriver.md
PR 10064 docs/dev/debt/libSceAgcDriver.md
PR 10193 docs/dev/debt/libSceAgcDriver.md

@wachi35676

Copy link
Copy Markdown
Contributor

NVIDIA and AMD iGPU runs, plus Linux, of b521b3eb5.

#7413 has since merged, and this head merges onto current main without conflicts. On top of the merged #7413, what is left is the 6-line FindTableCalls change and its tests, so "Depends on" can be cleared.

Windows 11 Home 26300, WinLibs GCC 15.2.0, Release, BUILD_TESTING=ON, main 9127ea36d with this head merged. GPUs: RTX 5070 Ti (NVIDIA 617.42) and the AMD Radeon iGPU (RDNA2, 1002:13c0, AMD proprietary driver 2026.04.17, ANYPS5_GPU=AMD). Run with ANYPS5_REQUIRE_VULKAN=1.

AI-assisted: yes (Claude Code); results above were run on this machine.

@thuanquang

Copy link
Copy Markdown
Contributor Author

Thanks @wachi35676 for the NVIDIA, AMD iGPU and Linux runs and the hand decode. #7413 has merged, so I'll rebase this onto main, clear Depends on, and add your runs to the description.

@klosixgaming-arch, on #7413 you suggested s_branch relay hops for the stop that comes after this one on shader 0x200c340000:

the table callees of the call at program counter 0x00000d2c lands out of the 16-bit branch range

Are you opening that change? If you aren't, I'll pick it up after this PR. I'm asking first so we don't both write it.

@thuanquang
thuanquang force-pushed the fix/table-call-wide-descriptor branch from b521b3e to 10832cd Compare October 20, 2026 08:01
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 20, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
thuanquang and others added 2 commits October 22, 2026 00:13
…x8 or s_load_dwordx16

Co-Authored-By: Claude <noreply@anthropic.com>
…'s s_load_dwordx8

Co-Authored-By: Claude <noreply@anthropic.com>
@thuanquang
thuanquang force-pushed the fix/table-call-wide-descriptor branch from eaf9943 to 73c9c2a Compare October 21, 2026 17:23
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
@frankea

frankea commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Ghostrunner data with this PR and #9068 together (data only).

Setup: Ghostrunner (PPSA03685 01.000.005, Unreal Engine 4) on an RTX 4090 (NVIDIA 616.92) and a Ryzen 7 5800X, Windows 11, WinLibs GCC 15.2, Release, APS5_PLAYGO_UNLISTED_CHUNKS=absent, no input: the title plays its intro cinematic by itself. Its compute shader 0x200c520000 has table calls at 0x434, 0x89c and 0xc84. The one at 0x89c takes its V# from the first four dwords of an s_load_dwordx8 s[88:95], s[0:1], the shape this PR adds.

Linking: with #9068 alone, the shader links its calls at 0x434 and 0xc84 and stops at the third: computed/data-dependent s_swappc_b64 call target at program counter 0x0000089c is not statically resolvable. With this PR on top nothing refuses. The shader links into a 104,136-dword program (6 linked-call fallbacks) and is prepared at its first dispatch.

What follows: that preparation takes minutes, and it runs on the thread that processes queue 0. The cinematic plays at about 60 FPS. When it ends, about two minutes in, the screen goes black and presentation stops. Two to six minutes later Unreal aborts with GameThread timed out waiting for RenderThread after 120.00 secs. The driver logs no error and the device isn't lost. In the fatal handler's thread dump, Unreal's RenderThread and RHIThread are waiting, and the driver's queue worker 0x0 is still running:

InvocationFor -> PrepareShaderWithDiagnostics -> PrepareShader -> getSource -> ResourceProgram
  -> PrepareResourceProgram -> ShaderPreparationContext::AcquireFrontend
  -> Structurizer::Structurize -> structurizeAttempts -> structurize -> splitSharedMergeBlocks

A local trace that names each shader prepared at use shows 0x200c520000 starting its preparation and never finishing. The six other shaders prepared at use in that run took 13 to 25 ms each.

Offline: I replayed the request the driver dumped for that preparation with agc_shader_replay --prepare. It is single-threaded, and another emulator run was using the machine at the same time. It takes 207 s, and 212 s with the trace below:

So with both PRs, the title's next two stops are both outside them: the structurizer's time on this 4,750-block graph, which is longer than Unreal's 120 s watchdog, and then #7459.

Runs: five runs past the cinematic all end the same way.

@thuanquang, on the relay change you asked @klosixgaming-arch about above: #9068 has it since e5a02bd. Before the first of an earlier call's callee copies, and after each one, LinkCalls leaves a pair of s_branch relays for every later call. One relay points toward that call's chain and the other back to the instruction after the call, so each hop spans at most one copy. As far as I can tell from your description, that is the case behind the lands out of the 16-bit branch range throw at 0x0d2c. I don't have PPSA10737, so it's untested there.

AI-assisted: yes (Claude Code)

@JJenkx

JJenkx commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

A second Unreal Engine title this fixes: Severed Steel (PPSA05106) 01.000.000. Linux 7.2.9, Radeon RX 6650 XT, RADV Mesa 26.2.4, GCC Release.

Setup

Result

  • Base: stops 24 s in, before any frame:
compute shader 0x2007910000: shader 0x2007910000 stage 0: ShaderRecompiler::Recompile: computed/data-dependent s_swappc_b64 call target at program counter 0x74 is not statically resolvable
  • This PR: no recompiler stop. The run continues to 82 s, where this dump's missing PlayGo files stop it (scePlayGo: sce_sys/playgo-chunk.dat and sce_sys/playgo-scenario.json are missing). That's a property of the dump, not of this PR.

With APS5_PLAYGO_UNLISTED_CHUNKS=installed on the same candidate, it gets past start-up to the campaign "New Game" menu (difficulty, arm cannon, mutators, play) and stays there. That run then stops at 156 s inside the title: Unreal's own PS5 Out of Memory: AvailablePhysical: 0, UsedPhysical: 14180876288, then LowLevelFatalError ... Ran out of memory allocating 2293760 bytes. It also runs at about 2.8 FPS, with this machine's -1000072003 host-import budget collapse (24 failures; #8091). I don't see a link between either of those and this PR, but I haven't looked into the out-of-memory stop.

I haven't checked that the shader is the s_load_dwordx8-slice case your description covers. The evidence is the before/after stop only.

AI-assisted testing and report: yes, Claude Code.

@zaidhawk

Copy link
Copy Markdown
Contributor

A title where this pattern stops a real scene: Persona 3 Reload (PPSA10872), thread #9692. Compute shader 0x2014960000, entering the Iwatodai Station strip mall. Windows 11, RTX 5090 (NVIDIA 617.42), main b27924d + #7048 + #9068, then this PR's head 73c9c2a applied (code files clean; only the debt file conflicted).

The call at 0x854 takes its V# s[88:91] from s_load_dwordx8 s[88:95] at 0x46c, then s_buffer_load_dwordx4 s[0:3], s[88:91], index×16 and s_swappc_b64 s[14:15], s[14:15]. Without this PR: computed/data-dependent s_swappc_b64 call target at program counter 0x00000854 is not statically resolvable. With it, the call links; the next stop is a table callee longer than ReadUserDataCallee's 64 KiB limit (details on #9068).

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked FindTableCalls (slice bounds, 4-dword alignment, +16 bytes per slice), the new ScalarCall and ComputeTableCalls cases (they fail on the old GraphBuilder.cpp per the runs here and by @wachi35676), the debt entry, and the title runs reported by @JJenkx, @frankea and @zaidhawk; merged.

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 unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants