Repository navigation
fix(shader): follow pointer hops and fixed-offset V# reads behind a record pointer - #9457
EagleBearerYS wants to merge 40 commits into
Conversation
…mers A T# read from a nested child table stayed a runtime read when one of its words also fed another instruction. That read goes through a V# that exists only in the snapshot, so the dispatch read a garbage address; lavapipe crashed in the shader. The nested branch now refuses a child whose descriptor words are used by anything but equal handles, as it already does for a V# with another consumer. (cherry picked from commit 0df7d32)
An S# read from a V# that a scalar record selects through a pointer was refused, so recompiling failed with "GetSamplerResource dword 0 is not a valid runtime value". The nested branch now takes sampler columns: they get child tables per distinct V# like nested T#s, share equal children, count against the nested key budget and the S# entry budget, and fault on an unmapped, non-buffer or uncaptured parent. The plan decoder accepts a nested sampler column; the plan encoding is unchanged.
An S# or T# loaded through a pointer that a scalar record holds was refused when a Phi merged the pointer's low word with constants, so recompiling failed with "GetSamplerResource dword 0 is not a valid runtime value". Such a column is now pointer-keyed: the snapshot captures every record's pointer and the descriptor behind it, the map holds boykopovar#7223's sorted pointer dictionary with descriptor records as its codes, and the GPU looks up the actual 64-bit pointer. A pointer that was not captured faults as outside the snapshot, and a null one as unmapped. The plan gains TableColumn::pointerKeyed and the snapshot gains ImageTableColumnSnapshot::pointerKeyed, so the disk-cache format moves to 43 and the image-table ABI to 4.
A T# loaded through a record pointer whose low word passes nested Phis was refused when one Phi arm held a word of a Ballot lane mask, as in a waterfall loop that reuses the pointer's SGPR for an s_and_saveexec or v_cmp mask, so recompiling failed with "GetImageResource dword 0 is not a valid runtime value". A pointer-keyed column now accepts such a word as an arm that is not a record pointer, as it does for constants, and still refuses any other foreign arm. At run time a pointer built from a lane mask is not a captured pointer and faults as outside the snapshot. A pointer-keyed column also accepts low-word reads, and a high-word read, of the same record word under different keys: same table, record offset, stride and addend. The GPU looks the pointer up by its value, so the key does not matter there. The column takes its table and record offset from the high-word read, which dominates the descriptor load. A shader that planned before plans the same way, so the disk-cache format stays at 43.
…ed record pointer A T# loaded through a record pointer was still refused with "GetImageResource dword 0 is not a valid runtime value" when the pointer's low word passed ANDs with lane masks, as a waterfall loop that copies EXEC into the pointer's SGPR builds them, or when its high word was a Phi of the record's high word read under several keys. For a pointer-keyed column, the walk of the low word now takes an AND with a lane-mask operand (a Ballot word, or an AND or Phi of only Ballot words and immediates) and keeps walking its other operand, so its record reads must still match the column and anything else still refuses. The high word is walked the same way: its record reads must be the record word at the low word's offset plus 4. A high word that is one read still gives the column its table and record offset, as before. Otherwise the table comes from a record read whose V# words are defined before the descriptor load, and the record offset from the high word, which the GPU does not read for a pointer-keyed column. A high-word arm read from another buffer, such as one whose V# is loaded through a pointer, stays refused. A shader that planned before plans the same way, so the disk-cache format does not change.
… a pointer-keyed record pointer A T# loaded through a record pointer was still refused with "GetImageResource dword 0 is not a valid runtime value" when a Phi of the pointer's low word merged the record read, a zero, and the waterfall's lane mask with the handled lane's bit cleared, BitwiseAnd32(CompositeExtractU32x4(Ballot(...), 0), BitwiseNot32(ShiftLeftLogical32(1, index))), as s_ff1, s_lshl and s_not with s_and, or s_andn2, build it in the SGPR the pointer reuses. The walk took only Ballot words and ANDs of them as lane masks, so it walked into the NOT and refused. A lane-mask operand is now a Ballot word or the loop's lane bit, a shift of 1 by an index from FindILsb32, FindUMsb32 or ReadFirstLane (through the AND, add, subtract and select that s_ff1_i32_b64, s_bitset0 and the shift's own mask put around it), or an AND, OR, NOT or Phi of only those and immediates. An arm of the pointer that is entirely such an operand is skipped. An AND with a record read in it still has the read matched against the column, and anything else still refuses. agc_driver_pointer_phi_lane_not_table builds that shape in a wave64 waterfall: the low word's Phis merge the record read at +0, a zero and the lane mask without the handled lane's bit, cleared by s_not and s_and and by s_andn2, and the high word is one read at +4. Each lane samples the T# of its record, and every masked word faults outside the snapshot. agc_driver_pointer_vsharp_select_table reads a record keyed by ReadFirstLane(SelectU32(LogicalAnd, IAdd32, Phi)), a pointer from it, a V# through the pointer and the T# through the V#; it passes without this change and keeps that chain checked, including a record pointer moved to the other V# without a recompile. Shaders that planned before plan the same way, and a refusal is never cached, so the disk-cache format does not change.
…keyed records A pointer-keyed record pointer now also takes lane-mask ANDs, the waterfall loop's lane bit and its OR and NOT forms, and a high word merged from the record's next word. A high word read from a second buffer stays refused.
The low and high words of a pointer-keyed record pointer were walked separately, so a high-word arm on a path whose low word is a lane mask refused the plan even though that pair is no pointer. Ratchet & Clank: Rift Apart (PPSA01474) stops this way at LoadAddressU32@pc=0x9a30: one high-word arm reads a second buffer, and on that edge the low word is a lane mask, the waterfall loop reusing the SGPR pair. Walk the two words together. Where both are Phis of one block, each low-word edge takes the high word of the same predecessor. An edge whose low word is a constant or a lane mask is skipped with its high word. A record low word still needs every high word it can meet to be a constant, a lane mask or the same record column at offset + 4, so a second-buffer high word paired with the record word stays refused. agc_driver_pointer_phi_high_second_lane_table adds the lane-mask low word to the second-buffer arm: it plans, each group samples the T# its record selects, and that arm faults outside the snapshot.
…nter The two words of the pointer are now walked edge by edge, and an edge whose low word is a constant or a lane mask is skipped with its high word. A high word read from a second buffer stays refused on an edge whose low word is the record word.
Brings boykopovar#8991 (match image-table T# halves offset before and after readfirstlane). ResourceTracker.cpp: main's FirstLaneOffset, ParseFirstLaneOffset and SameFirstLaneOffset go before TryMakeTableColumn, which keeps this branch's signature with the buffer parameter. Main's first-lane base check in the column loop merged on its own.
…ane-masks Brings boykopovar#7395's merge of main bd4d801, with boykopovar#8991. It merged without conflicts.
TryMakeIndirectColumn refused every pointer hop once the record's pointer words were merged, so a T# read through a pointer loaded from a pointer-keyed record pointer stayed unplanned (Gran Turismo 7's World Map compute shaders, issue boykopovar#6191). Only buffer handles still refuse hops: the materializer keys the records by the innermost pointer before it follows the hops, and the plan's parent pointer is that innermost one.
…one-key child From hugodearaujo's change on boykopovar#8939 (Marvel's Spider-Man 2, PPSA03016).
Spider-Man 2's shape: a record keyed by a first-lane read, its dwords 2-3 a pointer, a V# loaded at that pointer plus 0xa0, and the T# read at offset 0 or 0x40 of that V#'s buffer with no second key. Two records point at V#s of different T#s; a moved record pointer and a moved V# must change the result without recompiling.
|
@frankea @hugodearaujo @spiff637, this draft is the next step on #6191's chain for your titles, stacked on #8939 (
Its tests pass on lavapipe. It hasn't run on either title or on real hardware yet. |
|
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.
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.
|
MarcZX10
left a comment
There was a problem hiding this comment.
The new lavapipe tests exercise both reported resource-chain shapes, include negative controls and relocation-without-recompile cases, and the PR reports Windows compilation plus the relevant focused CI results. I cannot treat the AGC/shader validation protocol as complete from the current report: it does not show the same focused tests run on current main and this change with ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, and VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, confirm the requested validation layer loaded in both runs, or compare new VUID- messages. Please attach those paired logs/results and name the GPU/driver/OS as required. Also, this head is based on 0c6a5bd rather than current main (110a18ba01); please rebase and rerun conventions, overlap and the focused validation suite on the updated tree. I did not run local GPU tests.
Keeps this branch's FormatVersion above main's 45 (boykopovar#7831's barycentric bump).
|
@EagleBearerYS Gran Turismo 7 (PPSA01317) data for this stack: the World Map's 29 compute shaders from #6191, replayed offline ( Both builds are my GT7 integration branch (
RTX 4090, Windows 11, replay only. In the title, the same branch without the stack and with a local switch that skips dispatches whose shader is refused draws the World Map, so these shaders' output is what's missing there. I can post the IR of 0x120aea1900 around 0x4a4c, or rerun the 29 on a new head. AI-assisted: yes (Claude Code) |
|
Thanks, @frankea, that's the replay this PR was waiting for: 28 of the 29 World Map shaders get past the 0x2404 read and stop at their next T#, where the second hop can start from the first hop's result. It's in the Tested section. The IR of 0x120aea1900 around 0x4a4c would be the place to start on that shape, if you can post it. |
adriadam10
left a comment
There was a problem hiding this comment.
The change itself looks right to me: the hop guard now refuses only buffer columns, the fixed-offset case is limited to a pointer-loaded V# (a runtime V# still refuses), and the three new tests each fail on the base chains shown in the description.
What blocks it:
- This branch stacks on #7223, #7395 and #8939, which are all open and conflict with
mainsince #7880 merged (TryMakeIndirectColumnnow reads throughUnwrapTableRead). This PR inherits that conflict and can't land before them. - Once the bases are in, please rebase and re-check the
if (!hops.empty() && buffer) return false;guard inTryMakeIndirectColumnagainstmain'sUnwrapTableReadpath, rerun the three new tests, and renumberFormatVersionto whatever is next onmain.
main took 46 for the 4_4_4_4 target merge (6ab7bd2) after this branch took it for nested tables, so a cache written by main would be read here under the same number with a different layout.
…th immediates The pointer walk skips an edge whose low word is an immediate, as a waterfall loop's Phi gives one beside the record column read. Where the pointer's two words are Phis of one block, an edge on which both are immediates is a constant pointer that no record holds, such as a null pointer, so the walk now refuses the column there instead of planning it.
|
@adriadam10 an update ahead of the bases landing: the stack now merges
When the bases land I'll merge |
Apply PR boykopovar#9969 at d01f290 and its nested-table prerequisites, including PR boykopovar#9457 pointer hops. Preserve local mip-query handling and isolate the resulting shader cache format.
Apply PR boykopovar#9969 at d01f290 and its nested-table prerequisites, including PR boykopovar#9457 pointer hops. Preserve local mip-query handling and isolate the resulting shader cache format.
Apply PR boykopovar#9969 at d01f290 and its nested-table prerequisites, including PR boykopovar#9457 pointer hops. Preserve local mip-query handling and isolate the resulting shader cache format.
What
Two shapes from #6191's family that the planner still refuses on #8939. Each gets its own commit and test.
1. Pointer hops after a pointer-keyed record pointer (Gran Turismo 7 (PPSA01317), frankea on #6191, comment).
The chain: 28 of the World Map's 29 refused compute shaders read a record (an SRT V#, a
readfirstlanekey in a waterfall loop) whose dwords 2-3 are a pointer P1. P1's words are a Phi of the record read under several keys and aBallotlane-mask word. ThenP2 = [P1 + 0]and the T# at[P2 + 0x340]:Why it refuses:
TryMakeIndirectColumnrefuses any pointer hop once the record pointer is merged:if (!hops.empty() && (buffer || merged)) return false;.What the steps after it already do for a pointer-keyed column: the snapshot stores each record's pointer before it applies
pointerHops. The shader looks the T# up by the record-level pointer (parentPointer).ShaderDiskCachealready serializes and accepts hops on such columns.The change: the guard now refuses hops only for buffer columns.
2. A T# at a fixed offset of a pointer-loaded V# (Marvel's Spider-Man 2 (PPSA03016), hugodearaujo on #8939, comment).
The chain: a record keyed by
readfirstlane << 4, dwords 2-3 a pointer, a V# loaded at that pointer plus an immediate, and the T# read at a fixed offset of that V#'s buffer, with no second key:Why it refuses:
TryMakeTableColumnrefused it, becauseMatchAffineOffsetfails on an immediate offset.The change: hugodearaujo's change, kept as their commit. A fixed offset under a pointer-loaded V# becomes a one-key child of the nested path: key 0, stride the descriptor size, addend the immediate. A fixed offset under a runtime V# still refuses, as before.
Not in this PR:
0x120aed7f00, a table V# carried around a loop that callees overwrite). It looks like the shape fix(shader): split descriptor webs whose paths fold or hold invalid arms #8077 handles.Overlap:
TryMakeIndirectColumnwhen every arm is the same table read. The shapes here have arms under different keys and aBallotword, which fix(shader): resolve table column pointers through Phi nodes #7880 leaves refused. The two compose: an equivalent-arm Phi can take fix(shader): resolve table column pointers through Phi nodes #7880's plain column, and the rest the pointer-keyed one. The textual overlap is in feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's and fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer #8939's lines, which this PR stacks on.main47 (since fix(shader): answer image_get_resinfo on any T# dimension #7550), feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395 50 (which this PR keeps), fix(shader): follow a conditional pointer chase through a V# behind a pointer-keyed record pointer #9633 51 and fix(shader): read a chased pointer's high word at its own offset #9749 55, with 48 taken by feat(shader): resolve nested image tables through runtime pointers #7223 and 49 by perf(agc): GPU descriptor heap on VK_EXT_descriptor_heap (APS5_GPU_HEAP) #9508. Whichever lands next takes the next number, and the ones above it renumber in their rebase.Tested
tests/execution/ImageTable.cpp(lavapipe, Linux, CI's flags):agc_driver_pointer_hop_table: Gran Turismo 7's shape, aBallot-merged P1, a hop at +0 and the T# at +0x340, across four key groups. It checks every lane's color. The constant arm and theBallotarm each fault outside the snapshot. A re-pointed link block is followed without a recompile.GetImageResource dword 0 is not a valid runtime value; chain: LoadAddressU32@pc=0x000000b4(GetAddressResource(LoadAddressU32@pc=0x000000a8(GetAddressResource(Phi(Immediate, Phi(ReadConstBuffer@pc=0x00000074(...), CompositeExtractU32x4(Ballot(LogicalAnd), Immediate))), ...agc_driver_pointer_vsharp_fixed_tableandagc_driver_pointer_vsharp_fixed_offset_table: Spider-Man 2's shape, with the T# at offsets 0 and 0x40. A moved record pointer and a moved V# are both followed without a recompile.GetImageResource dword 0 is not a valid runtime value; chain: ReadConstBuffer@pc=0x0000008c(GetBufferResource(LoadAddressU32@pc=0x00000080(GetAddressResource(ReadConstBuffer@pc=0x00000074(...image_table|pointer(23 tests) ×5 and the disk-cache and ShaderMemory tests (10) pass. The full CI ctest gives 1834/1838; the 4 failures areagc_driver_pipelined_fill(a flake on main too) and 3 IPv6 socket tests the machine can't run. Registration and conventions pass.ResourceTracker.cpp.precise_wait(libc, Linux), which also failed on both sides in fix(agc): let draw relocation aliases name a cached key, not hold its entry #9396's runs, andguest_raise_in_system_call(libkernel, Windows), a 30 s timeout seen in other runs on that runner.maind6eafa5 (clean).668118771and0843cca39, tests kept), the 3 new ctests fail on both GPUs with the chains above. The other 13nested_/pointer_tests pass.agc_driver_(nested_|pointer_)tests pass 3 of 3 on each GPU, none skipped, andimage_table|pointer|disk_cache|shader_memory|sampler_table(27 tests) passes 3 of 3 on each.^agcsuite on the 3070 (877 tests, withoutdisplay_buffer_dcc,driver_flipandlds_atomics64): 874 pass and 1 skips. The 3 failures,agc_driver_sample_order,_grid_referenceand_recipe_depth_locations, fail the same way onmainon that card.maincc00785 merged in (clean; the PR's own diff is unchanged), on GitHub's runners (run):nested_/pointer_tests and the disk-cache tests pass 10 of 10, none skipped. libkernel's suite passes. libc's one failure,precise_wait, fails the same way withmain's implementation.check_conventions.pypasses on the merged head.mainagainst this head (run): one GitHub Linux runner, Ubuntu 24.04.5, llvmpipe (Mesa 25.2.8, LLVM 20.1.2),ANYPS5_REQUIRE_VULKAN=1,ANYPS5_NO_SHADER_CACHE=1andVK_LAYER_KHRONOS_validation. This head's arm ran 3 times, its 20 tests passing each time, andmain's once, its 4 tests passing; none skipped or failed.main, so its arm runs the image-table tests it has,agc_driver_image_table_vouched_lookupsandagc_driver_image_table_vouch_queued_draws, with the disk-cache tests. This head's arm runs those and the 16.VUID-VkDeviceCreateInfo-pNext-pNext, and this head adds noVUID-message.main0c6a5bd with fix(agc): resolve indexed indirect draws on a queue that never set INDEX_BUFFER_SIZE #9424.ReadConstBuffer@pc=0x8560, at 142 s and 146 s. With fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer #8939 (a7d1e62) added it stops there too, at 137 s in both runs.ReadConstBuffer@pc=0x8fa0overLoadAddressU32@pc=0x8f54.agc_shader_replayof the request that stopped at 0x8560 in a fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer #8939 run refuses at 0x8fa0 as well.main, because the merged head doesn't get that far on this title: since fix(libs): libs funcs abi debt 2026 10 20 #9137 it throws in the title's first 40 s onsceSystemServiceParamGetInt(parameter 100, then 208 and 210). The PR's own diff is the same in both heads.main0db47b2. The only conflict wasShaderDiskCache.hpp'sFormatVersionline:mainmoved to 45 (fix(shader): normalize offset perspective barycentrics after interpolation #7831), and this branch keeps its 46. On GitHub's runners (run), the pointer, nested, image-table and disk-cache sets (25 tests) pass 10 runs in a row on Linux. On Windows the set has 27, with two Windows-only disk-cache tests: they build, 5 pass 10 runs in a row, and the other 22, which need a Vulkan device, skip. The libc and libkernel suites pass on both, run withAPS5_SKIP_TIMING_TESTS=1, which relaxesprecise_wait's median bounds on GitHub's hosted runners. An earlier run linked here didn't run this set, so it's replaced.agc_shader_replay --preparewithANYPS5_NO_SHADER_CACHE=1on the requests the driver dumped at use, RTX 4090, Windows 11. Replay only, on their GT7 integration branch (main513ca7e with the GT7 pull requests in Gran Turismo 7 (PPSA01317) #4697's list and local diagnostics), without and with fix(shader): index a pointer-loaded V#'s T# array by a word read from the same buffer #9969 at 7588a53, which carries this PR at 7f3a25e with fix(shader): plan several T#s read through one pointer-loaded V#, keeping the V# real for their other uses #9790, fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer #8939, feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395 and feat(shader): resolve nested image tables through runtime pointers #7223.Ballotword with arms that are the pointer loaded at 0x20ec, so on some edges the second hop starts from the first hop's result. This PR doesn't plan that shape.0x120aed7f00is unchanged (0x19a0 behind 0x198c), the shader this PR leaves out.main379b1dc). It's clean, and this PR's own diff is unchanged. The planner behaviour changes underneath it: It brings the planner changes of feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's and fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer #8939's merges: a record pointer counts as merged only when its Phi isn't a single record read; an unmerged pointer's low word is unwrapped as fix(shader): resolve table column pointers through Phi nodes #7880 does, and refused when it isn't a table read; and fix(shader): take lane-mask arms and a merged high word in a pointer-keyed record pointer #8939's new commit refuses a pointer-keyed Phi edge whose two words are both immediates.agc_shader_bindless,agc_shader_memoryand the^agc_driver_pointer_,^agc_driver_nested_,^agc_driver_image_tableand^agc_shader_disk_cachetests. Linux: 27 run, 27 pass, 0 skip, 0 fail. Windows: 29 run, 7 pass, 22 skip (Vulkan device tests; the runner has none), 0 fail.APS5_SKIP_TIMING_TESTS=1, which relaxesprecise_wait's median bounds on GitHub's hosted runners.mainc9b2660). Clean:FormatVersionfollows feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's 50, and this PR's own diff is unchanged. On GitHub's runners, 10 runs (run) of the pointer, nested, image-table and disk-cache sets (agc_shader_bindless,agc_shader_memory,^agc_driver_pointer_,^agc_driver_nested_,^agc_driver_image_table,^agc_shader_disk_cache): Linux: 27 run, 27 pass, 0 skip, 0 fail. Windows: 29 run, 7 pass, 22 skip (no Vulkan device), 0 fail. The libc and libkernel suites pass on both, run withAPS5_SKIP_TIMING_TESTS=1, which relaxesprecise_wait's median bounds on GitHub's hosted runners.mainc8d34aa). Clean:FormatVersionfollows feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's 50, and this PR's own diff is unchanged: the merge brings in exactly what feat(shader): read samplers through nested and pointer-keyed descriptor tables #7395's merge did (249 files). On GitHub's runners, 10 runs (run) of the pointer, nested, image-table and disk-cache sets (agc_shader_bindless,agc_shader_memory,^agc_driver_pointer_,^agc_driver_nested_,^agc_driver_image_table,^agc_shader_disk_cache): Linux: 27 run, 27 pass, 0 skip, 0 fail. Windows: 29 run, 7 pass, 22 skip (no Vulkan device), 0 fail. The libc and libkernel suites pass on both (143 and 206 tests on Linux, 170 and 222 on Windows), run withAPS5_SKIP_TIMING_TESTS=1, which relaxesprecise_wait's median bounds on GitHub's hosted runners.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)ReadConstBuffer@pc=0x8560(hugodearaujo's run above); the next stop, 0x8f54/0x8fa0, needs a follow-up. Ratchet & Clank: Rift Apart (PPSA01474, Ratchet & Clank: Rift Apart (PPSA01474) on Linux #2172) gets past 0x10b84 with fix(shader): follow a conditional pointer chase through a V# behind a pointer-keyed record pointer #9633 on top, which builds on this PR's hop and fixed-offset handling. Gran Turismo 7's shape is untested on that titleRelated: #6191