Skip to content

AGC: root cause of the AMD proprietary failures behind the robustBufferAccess2 exclusion (#7351) is still open #7376

Closed

Description

@oneandonlydean

#7351 keeps robustBufferAccess2 off on VK_DRIVER_ID_AMD_PROPRIETARY so three execution tests pass again on AMD's Windows driver. It works around the failures and does not explain them. This issue is to find the root cause, so the exclusion doesn't stay forever.

What #7351 changed. robustBufferAccess2 is now enabled on every device that has it except AMD's proprietary driver. On that driver, guest memory loads keep the selected-load form that devices without the feature use. The device feature enable, the SPIR-V target flag (and so the shader cache key) and the load guards all follow the same predicate. Other drivers are unchanged.

What the author measured (RX 6700 XT, Windows, driver 32.0.21045.5002, from #7351). #4048 was bisected as the cause. A three-way split on main 5229b5b82:

main with layered_volume_draw layered_volume_vertex subvector_loop
nothing changed fails fails fails
robustBufferAccess2 not enabled (so no selected loads) passes passes passes
feature enabled, target.robustBufferAccess2 = false (loads keep their selections) fails fails passes
  • The two layered-volume tests fail as soon as the device feature is enabled on this driver, whatever the recompiler emits. The volume keeps its sentinel, so nothing reached it.
  • agc_driver_subvector_loop fails only with the selected loads: the high half of the wave64 never runs the loop body (lane 0 exec hi after the loop is 0, expected 4294967295). A wave64 compute probe that loads Input[lane] and stores it back is correct in every lane, and so is a probe of the vertex shader's two loads, so the load itself is not simply wrong.

Open questions

  1. Is the layered-volume failure an AMD driver bug with the feature enabled, or something AnyPS5 does with layered or volume images that only breaks under robustness2?
  2. Is the subvector_loop failure an emission problem in perf(recompiler): select guest memory loads instead of branching around them #4048's selected loads that other drivers mask? lavapipe passes the first two tests and skips subvector_loop (its subgroup is too small), so CI says nothing about it.

Cost of the exclusion. On AMD Windows, shader block counts go back to what they were before #4048. For one test title the debt doc records 16,983 blocks against 5,907 with #4048's branch-free loads, so compile time returns to what it was a day earlier. It is not a regression against main from before #4048.

How to check a fix. On an AMD proprietary driver, run agc_driver_layered_volume_draw, agc_driver_layered_volume_vertex and agc_driver_subvector_loop with the exclusion removed. There is no AMD device in CI, so this needs a contributor with an AMD Windows machine. The predicate has a unit test (agc_robust_buffer_access), which only covers the driver-ID check.

@Mouad-Scriptz-0, you wrote #4048; could you look at whether the selected loads can produce the subvector_loop result? @thuanquang has the AMD setup from #7351.

Related: #4048, #7351.

Activity

  1. LeandroLP23 commented on Oct 18, 2026

    @LeandroLP23
    Contributor

    Data point from RDNA 3.5: with robustBufferAccess2 turned back on, all three tests pass on AMD's proprietary driver here. So the failures do not seem to follow the driver ID alone.

    Radeon 860M (RDNA 3.5, 1002:1114, APU), AMD driver 32.0.13058.8001 (Vulkan driver info 24.30.58.08, LLPC), Windows 11 Home 26300, WinLibs GCC 15.2.0, Release. Build: main 1c523bdb4 + #6308 + #7264 (neither touches these paths).

    RobustBufferAccess2Usable layered_volume_draw layered_volume_vertex subvector_loop
    as on main (off on VK_DRIVER_ID_AMD_PROPRIETARY) 3/3 pass 3/3 pass 3/3 pass
    forced to true 3/3 pass 3/3 pass 3/3 pass

    With the predicate forced to true, agc_driver_lds_lock_budget prints robustBufferAccess2=1, so the feature and the recompiler target both followed it. The failures measured in #7351 came from RDNA 2 on drivers 32.0.21045.5002 (RX 6700 XT) and 32.0.21036.18 (iGPU, #7344). That points at an architecture or driver branch rather than at every AMD proprietary device.

    AI-assisted

  2. Mouad-Scriptz-0 commented on Oct 18, 2026

    @Mouad-Scriptz-0

    The recompiler does not emit the subvector loop differently when loads are selected; the two forms of this test program differ only around the one guest load, before the exec ballot.

    What I compared. agc_driver_subvector_loop's ExecKeptCode, recompiled with the test's own request at a 64-lane host subgroup (the RDNA 2 path, not the 32-lane split this Intel machine takes), once with target.robustBufferAccess2 = true and once with false, both on 33f191da8. Both modules pass spirv-val --target-env vulkan1.3. After ID renumbering, the whole diff is:

    -               OpSelectionMerge %N None          ; exec guard on the load, constant true here
    -               OpBranchConditional %true %N %N
    +               OpBranch %N
    ...
    -               OpSelectionMerge %N None          ; if (active && index < length) load
    -               OpBranchConditional %N %N %N
    -               ... OpLoad ...
    -         %N = OpPhi %uint %N %N %uint_0 %N
    +         %N = OpSelect %uint %N %N %uint_4294967294
    +               ... OpLoad ...
    +         %N = OpSelect %uint %N %N %uint_0
    

    The v_cmpx ballot, the s_subvector_loop_begin/_end bookkeeping, the loop construct and the nine stores are identical. So on this driver the compiler generates different code for the loop from the same SPIR-V once the load upstream is a select over a robust index rather than a branch.

    What the printed columns say. The failing case is "initial exec high half": lanes 0 to 31 inactive after the compare. For lane 0 the columns before the failing one matched, including s38 after the loop = 0. The begin instruction only writes s38 when exec is nonzero, and with exec_lo = 0 it writes exec_lo, so the begin saw exec_hi ≠ 0: the ballot at v_cmpx was right. Yet lanes 32 to 63 never ran the body and exec_hi after the loop is 0. Both fit the end instruction reading exec_hi as 0: highSet false, counterZero true, so the loop exits with exec_lo = s38 = 0 and exec_hi unchanged at 0. The value tracked for exec_hi is lost between begin and end, with exec_lo equal to zero, in the driver's codegen.

    Three things that would locate it, all needing an AMD Windows machine:

    1. @thuanquang, capture the pipeline with APS5_DUMP_COMPUTE_PIPELINES=<dir> in both forms and compile the two SPIR-V modules with Radeon GPU Analyzer for gfx1031. The ISA around the loop is the only place the difference can show.
    2. The same RX 6700 XT on the branch @LeandroLP23 passes on, 32.0.13058. If it passes there, this is a compiler regression and the exclusion should key on the driver version rather than the driver ID, which fix(agc): keep robustBufferAccess2 off on AMD's proprietary driver #7351's predicate can carry.
    3. A probe that keeps the selected load but feeds v_cmpx from an ALU-derived lane mask (v_mbcnt ≥ 32) instead of the loaded value. If it passes, the trigger is the load-to-exec dataflow; if it still fails, the code shape alone.

    I can attach the four disassemblies (both forms at 32 and 64 lanes) if useful.

  3. thuanquang commented on Oct 18, 2026

    @thuanquang
    Contributor

    A data point that splits the three failures in two. Setup: RX 6700 XT, AMD driver 32.0.21045.5002, Windows 11, main 3e3e8b905 with RobustBufferAccess2Usable forced to true.

    Run under their usual names, all three fail, as in #7351. AMD's Windows driver keeps an on-disk pipeline cache per executable, so I also ran renamed copies of the test executables, which start with an empty cache:

    test usual name renamed copy, 1st run renamed copy, 2nd run
    agc_driver_layered_volume_draw fails passes passes
    agc_driver_subvector_loop fails fails fails

    For point 2, I can't switch this machine's driver. For point 1, I don't have Radeon GPU Analyzer installed. I can dump both pipelines with APS5_DUMP_COMPUTE_PIPELINES if someone with RGA wants the SPIR-V.

    If this holds up, #7351's exclusion could narrow to what subvector_loop needs, rather than every AMD proprietary device.

  4. LeandroLP23 commented on Oct 18, 2026

    @LeandroLP23
    Contributor

    @thuanquang good point. My earlier runs reused the same executables with the feature off and then on, so they could have hit cached pipelines. I reran here with renamed copies, so the driver cache starts empty, and with RobustBufferAccess2Usable forced to true on main 6efb7261b. Radeon 860M (RDNA 3.5), driver 32.0.13058.8001, Windows 11 26300, same ANYPS5_NO_SHADER_CACHE=1 APS5_NO_PIPELINE_JOURNAL=1 as ctest:

    test (renamed copy) 1st run 2nd run
    agc_driver_layered_volume_draw passes passes
    agc_driver_layered_volume_draw --vertex-only passes passes
    agc_driver_subvector_loop passes passes
    agc_driver_lds_mixed_width_atomics --lock-budget (robustBufferAccess2=1) passes passes

    So subvector_loop passes from an empty cache here too, while it fails on your RX 6700 XT on 32.0.21045.5002. On this machine that is not a cache artefact. It fits @Mouad-Scriptz-0's reading of a codegen difference for the selected-load form. Whether that difference comes from the RDNA 2 target or from the 32.0.21045 compiler, these two machines can't separate: both the architecture and the driver differ.

    AI-assisted

  5. MarcZX10 commented on Oct 18, 2026

    @MarcZX10
    Contributor

    The four disassemblies would help narrow this down. Could you attach the 32-lane and 64-lane modules for both target.robustBufferAccess2 settings, with the exact source commit and AMD driver/device recorded for each? The existing comparison suggests the relevant difference is around the exec loop after the selected load, so preserving the surrounding instructions and the SPIR-V-to-ISA mapping would make the comparison actionable. I will review them if you post them.

  6. thuanquang commented on Oct 18, 2026

    @thuanquang
    Contributor

    @MarcZX10 @Mouad-Scriptz-0 I read the ISA back from AMD's driver with VK_KHR_pipeline_executable_properties: RX 6700 XT, driver 32.0.21045.5002, main 3e3e8b905, ExecKeptCode. The pipeline runs at 64 lanes on this device, so there is no 32-lane module to compare here. In the robustBufferAccess2 form, the driver computes exec_lo == 0 in s_subvector_loop_begin from the SCC of the guard's 64-bit s_and_b64. So it tests the whole mask, and with exec_lo 0 and exec_hi set, exec_hi is selected to 0. That is the lost high half. The SPIR-V is right and the driver miscompiles it. Testing the empty mask as (exec_lo | exec_hi) == 0 avoids it: #7623, with the three ISA excerpts.

    With that, subvector_loop passes here with robustBufferAccess2 on. The layered-volume failures are the cache-order effect from my earlier comment, so I left the exclusion in place.

    Point 3 (probe), for the record: feeding the v_cmpx from v_mbcnt, with the selected load kept but unused, passes 2 of 2. The unchanged program fails 2 of 2. That fits the reading above, though I did not read that probe's ISA.

  7. MarcZX10 commented on Oct 18, 2026

    @MarcZX10
    Contributor

    Thanks for the ISA trace. This isolates the subvector_loop failure to RADV’s lowering of the empty-mask check: it reads the combined 64-bit mask through SCC, so an empty low half can incorrectly discard the nonempty high half. Open PR #7623 tests the empty exec mask with one compare and includes the AMD ISA evidence; that part now has a concrete proposed fix. The layered-volume failures remain a separate cache-order question, so keeping the AMD-driver feature exclusion until that path is explained still seems warranted.

  8. thuanquang commented on Oct 18, 2026

    @thuanquang
    Contributor

    The layered-volume part, measured: it is the GPL cache-order effect from #4768, and #6436 removes it. robustBufferAccess2 doesn't cause it. (@MarcZX10, a note on the driver: this is AMD's Windows driver, amdvlk64.dll 32.0.21045.5002, not RADV.)

    Setup: RX 6700 XT (gfx1031), Windows 11 26200, main 023106ae4 plus a local environment switch that turns robustBufferAccess2 on for AMD (off when unset). Each row is one renamed copy of agc_driver_layered_volume_draw_tests, so the driver cache starts empty, run step by step in the modes of the three ctest cases (draw, --vertex-only, --vertex-only with APS5_NO_GPL=1), with ANYPS5_NO_SHADER_CACHE=1 and APS5_NO_PIPELINE_JOURNAL=1. Every row ran twice, with the same result both times.

    Steps, from an empty driver cache main main + #6436
    GPL off; GPL on (draw, vertex); GPL off. robust2 on both GPL-on steps fail all pass
    the same, robust2 off both GPL-on steps fail all pass
    GPL on (draw, vertex); GPL off; GPL on. robust2 on the GPL-off step fails all pass
    the same, robust2 off the GPL-off step fails all pass
    robust2 off, on, off; GPL on all pass all pass
    robust2 off, on, off; GPL off all pass all pass

    Every failure prints vertex draw: slice 1 texel (0, 0) channel 0 is 64, expected 255 (the band of layer 0). A run that compiles takes about 0.42 s, a run served from the cache about 0.26 s. The first run after robust2 changes takes the compile time, so robust2 gets cache entries of its own, and changing it never failed a run. Which mode fails depends only on which GPL mode compiled the pipelines first. That also fits the #7351 bisect: turning robust2 on gave these pipelines new cache entries, and the GPL mode that compiled them first decided which tests failed from then on.

    With #6436 and #7623 both applied, on the same base with robust2 forced on, agc_driver_subvector_loop passes and every sequence in the table passes, twice. A full ctest --parallel 4 --timeout 120 with robust2 forced on (1508 tests) fails these AGC tests: agc_driver_lds_mixed_width_atomics and agc_driver_lds_lock_budget, which fail on main here without the switch too (#6976). agc_driver_layered_volume_vertex_no_gpl, which fails on main here (#4768), passes. log_macros and log_macros_timestamps fail in every local build here, because ccache rewrites __FILE__. The other six failures were CPU-bound tests that timed out or aborted while parallel builds held the CPU at 100%: guest_* and audio_out2_timing, which link libkernel, libc or libSceAudioOut but not the AGC driver. Rerun one at a time with the switch on, five of them pass; guest_pthread_start (10,000 thread starts, libkernel and libc only) timed out again under the same load.

    So once #6436 and #7623 are in, nothing behind #7351's AMD exclusion fails on this GPU and driver, and the exclusion could be dropped. As for caches users already have: in these runs, a run with robust2 on that started from a cache filled with it off compiled anew (0.42 s) and never failed. I can open the PR that drops the exclusion, with Depends on both, once they merge.

  9. MarcZX10 commented on Oct 18, 2026

    @MarcZX10
    Contributor

    Thanks for the measurements, and for correcting the driver identification. This separates the reports into two paths: #7623 addresses the AMD proprietary driver miscompile in subvector_loop, while #6436 addresses the GPL cache-order effect seen by the layered-volume tests. Since both PRs are still open, #7351’s AMD robustBufferAccess2 exclusion should remain until the combined changes are on main and the four reported execution tests plus the layered-volume cases are rerun on the affected AMD driver. The issue now has a concrete explanation for both failure groups; the remaining check is their combined post-merge behavior.

  10. thuanquang commented on Oct 20, 2026

    @thuanquang
    Contributor

    @MarcZX10 #7623 and #7828 are both on main now, so I reran the cases from this issue on the RX 6700 XT (AMD's Windows driver 32.0.21045.5002) with the exclusion removed: #8267.

    With robustBufferAccess2 on (agc_driver_lds_lock_budget reports robustBufferAccess2=1):

    • agc_driver_layered_volume_draw, agc_driver_layered_volume_vertex, agc_driver_layered_volume_vertex_no_gpl and agc_driver_subvector_loop pass 3 of 3.
    • The layered-volume sequences from my earlier comment (renamed executables, empty driver cache, both library-mode orders, twice each) pass at every step.
    • agc_driver_subvector_loop_tests passes with libraries on then off, and off then on.

    #6436 is still open. With #7828 on main the device is the same in both library modes, which removes the cross-mode cache effect behind the layered-volume failures, and the sequences pass without #6436. The full suite fails the same 4 tests with and without the change, and the Khronos validation layer reports no VUID- message on either side. Details are in #8267.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions