Skip to content

AGC: agc_idle_publish_wait fails on RADV since #9521: the idle flush's CPU-side publish drops both units as changed #11889

Closed

Description

@Lander0606

What happens

agc_idle_publish_wait fails on RADV since #9521 merged, with AGC graphics: the idle publish's bytes are wrong; it passes on lavapipe.

Title, test or build step

agc_idle_publish_wait (ctest)

AnyPS5 commit

main 801d5b9bc1 (and 8df2c3ed0e). First bad: c831db72df (merge of #9521). Last good: 22a8590492, its first parent.

System

Fedora 44, kernel 7.2.11, AMD Ryzen AI 7 350 with Radeon 860M (RADV KRACKAN1), Mesa 26.2.5. Built in an Ubuntu 24.04 container with GCC 13.3, Release, BUILD_TESTING=ON, ANYPS5_ENABLE_SPIRV_TOOLS=ON; tests run on the host.

Steps to reproduce

ctest --test-dir build -R '^agc_idle_publish_wait$' -V

The test sets APS5_PIN_WAIT_MS=3000 and APS5_WRITE_WATCH_IMPORTS=watch itself. It fails after 0.03 s.

Log

[write-watch] host imports stay watched (APS5_WRITE_WATCH_IMPORTS=watch)
[libc] unhandled exception 'St13runtime_error' thrown from 0x...
host imports stay watched
AGC graphics: the idle publish's bytes are wrong

What was checked

build RADV (Radeon 860M) lavapipe
main 801d5b9bc1 fails 3/3 passes 3/3
main 8df2c3ed0e fails 5/5 passes 3/3
c831db72df (merge of #9521) fails 3/3 passes 3/3
22a8590492 (its first parent) passes 3/3 passes 3/3
main 48c160c58b (2026-10-26) passes 5/5

#11696 lists the same failure on main f5e3dc5e3b on an RX 9070 XT (RADV, Mesa 26.2.5), with the same message, so it isn't specific to this GPU. With VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (1.4.341) it still fails, and no VUID- line is logged. In the full ^agc run on 8df2c3ed0e, the only other failure is agc_driver_tessellation, the Mesa 26.2.5 crash. #9521's own agc_storage_idle_writeback_race passes here.

Where the bytes go. I added two fprintf diagnostics on c831db72df (attached as diagnostic.patch, and removed afterwards): one in the completion of recordCopiesOnCpu (UnitShadow.cpp), one around FlushAllPending("idle") in idlePublishWaitTests. With APS5_TRACE_SHADOW=1:

RADV:

[diag] before idle flush: generation 4
[shadow] publish hook: 0x7efda8b90000+0x20000, 2 units, 0.12 MiB, 1 slabs (packet 0xfffffffe queue 0xffffffff)
[diag] on-cpu publish completion 0x7efda8b90000+0x10000 checked=4 now=6 unchanged=0 first-byte=0x22
[diag] on-cpu publish completion 0x7efda8ba0000+0x10000 checked=4 now=7 unchanged=0 first-byte=0x33
[diag] idle flush returned: generation 7, unit 2 byte 1 = 0x11, unit 3 byte 0 = 0x11

lavapipe:

[diag] on-cpu publish completion 0x7f2e4c430000+0x10000 checked=4 now=6 unchanged=1 first-byte=0x22
[diag] on-cpu publish completion 0x7f2e4c440000+0x10000 checked=4 now=10 unchanged=1 first-byte=0x33
[diag] idle flush returned: generation 13, unit 2 byte 1 = 0x22, unit 3 byte 0 = 0x33
  • On both drivers the idle flush publishes the two retiled units through the new on-CPU path. The completion runs before FlushAllPending("idle") returns, so before the test's own CPU stores, and the host buffer holds the right bytes (0x22, 0x33).
  • On RADV, GuestMemory::UnchangedSinceCollected returns false for both units, so the completion drops them as stale (UnitShadow.cpp:272 on main). Units 2 and 3 still hold 0x11 when the flush returns. On lavapipe it returns true and the bytes land.
  • A likely cause, not confirmed: on this device the write watch sees host-imported pages as written after GPU work. The startup probe here logs [write-watch] host imports resolve write protection: yes (4 of 4 scratch pages written after a GPU read, 4 after a GPU write, 4 after the import); imported ranges are compared. The test forces APS5_WRITE_WATCH_IMPORTS=watch, so the import stays watched, and those page writes then look like guest stores made after the publish collected the units. I did not check which access marks the pages.
  • Without the forced mode: with APS5_WRITE_WATCH_IMPORTS unset, this device compares its host imports, and the test logs host import of the idle publish test block refused or not watched: idle publish wait not tested. I have not checked whether a title is affected.
  • CI uses lavapipe, where the check passes, so it can't catch this.

cc @Raghav314 (#9521), @fisherjoey (#7984)

Logs (main, the merge and its parent, on both drivers), the patch and its output, with an index in README.txt:

anyps5-agc-idle-publish-wait-radv-logs.zip

AI-assisted: yes

Activity

  1. LeandroLP23 commented on Oct 27, 2026

    @LeandroLP23
    Contributor

    A Windows data point: the test passes there, but only because it returns before its check, so Windows runs cannot catch this.

    Host Driver main Runs Result
    Radeon 860M (the same GPU as the report), Windows 11 26300 AMD 32.0.13058.8001 3fea2a937b (includes #9521) 5 Passed, each printing idle publish wait not tested
    RTX 5060 Ti, Windows 11 26300 NVIDIA 621.01 801d5b9bc1 5 Passed, each printing idle publish wait not tested

    Both used WinLibs GCC 15.2.0, Release, ctest -R '^agc_idle_publish_wait$' -V with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation. There were no VUID- lines and no missing layer.

    To see which condition sends it out, I added two temporary prints around the HostImportFor call in idlePublishWaitTests (Recorder.cpp) on the 860M:

    DBG before watched=1 hostRegion=0
    DBG after import=1 watched=0 hostRegion=0
    

    The import is made ([gpu] host import of 0x200000000+0x80000 ok), but GuestMemory::Watched turns false for the block once it is imported. On Windows the write-watch coverage excludes an imported block, as WriteWatchCoverage.cpp expects ("the imported block is still watched"). So import == nullptr || !Watched(...) is always true on Windows, and the check of the idle publish's bytes is never reached there.

    The RADV failure itself needs a Linux host. On Windows, a green agc_idle_publish_wait should not be read as "not affected": it does not run its check.

    AI-assisted: yes (Claude Code)

  2. fisherjoey commented on Oct 28, 2026

    @fisherjoey
    Contributor

    The cause you suspected is the one. I reproduced the drop without AMD hardware and checked whether the production path is affected.

    What happens: publishUnits takes checked = TrackerGeneration() (UnitShadow.cpp:358), collects the selected units (:375), records a slab-to-host copy, and at completion stores each 64 KiB unit only if UnchangedSinceCollected(at, bytes, checked) says nothing wrote it (:272). That function starts with its own uncached collect (GuestMemory.cpp:2196), so any page the write watch reports as written after checked makes the unit read as a CPU store, and the slab bytes are dropped. On RADV, amdgpu validates a host-pointer (userptr) import's pages with write faults at every submission, which clears the userfaultfd write protection of the whole import; #9781 was the same thing at a different check. Here the idle flush's own submission of the publish batch does it, so both units are stamped newer than checked (your checked=4 now=6/7 unchanged=0), and FlushAllPending("idle") returns with 0x11 still in guest memory. Nothing wrote those bytes. The pages only read as written.

    To reproduce it on other drivers I put a temporary hook on SetPublishSelectedHook (added by #9521, runs after unit selection and before the copy is recorded) that touches one byte per page of the selected range with the byte it already holds. The contents do not change, but the pages read as written:

    driver main ee5e24729c main + hook
    llvmpipe (Mesa, lavapipe ICD) passes 3/3 fails 3/3, the idle publish's bytes are wrong
    RTX 5070 Ti, NVIDIA 595.91.07, Linux 7.2.0 passes 3/3 fails 3/3, same message

    So the completion check cannot tell a resolved write-protection fault from a store, on any driver. That is by design: the write watch is supposed to report only stores, and decideImportWatch (GuestBufferMemory.cpp:1297) compares host-pointer imports instead of watching them on exactly the devices whose probe reports pages written after a GPU read. The ctest overrides that with APS5_WRITE_WATCH_IMPORTS=watch (CMakeLists.txt:1819) so the test can run on lavapipe, whose default would also be compare (4 of 4 pages written after a GPU write there, 0 after a GPU read).

    As far as I can tell this is test-only. In the default mode on RADV a host-pointer import is compared, not watched, so ShadowDestinationFor offers it no unit shadow (UnitShadow.cpp:612) and the CPU publish never runs for it. On Linux importWatching does not keep aliases watched either (GuestBufferMemory.cpp:1319), so that is not a second way in. The one import kind that stays watched on RADV is dma-buf, and the GPU reaches those through the DMA mapping rather than the CPU page table, so there is no re-fault: the dma-buf probe reports 0 of 4 on the RX 7900 GRE per #12158, and your full ^agc run on 8df2c3ed0e passed agc_driver_sparse_dma_buf_import, which since #9521 publishes a unit of a dma-buf import on the CPU path (the log does not show whether that pass ran its check, so I cannot say more from here). A title would only hit this with APS5_WRITE_WATCH_IMPORTS=watch set by hand on such a device, and that configuration already lost publishes before #9521 (#9781).

    On #12158: it skips the test when ProbeImportWriteProtection reports pages written after a GPU read, which is the condition above, and ignores the GPU-write counter, which is what keeps lavapipe running the test. I ran that head cherry-picked onto main ee5e24729c (caf306a682 locally) with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1:

    driver agc_idle_publish_wait probe (agc_import_watch) agc_unit_shadow, agc_storage_idle_writeback_race, agc_driver_sparse_dma_buf_import
    llvmpipe runs, passes 3/3 0 of 4 after a GPU read, 4 after a GPU write, 0 after the import pass
    RTX 5070 Ti runs, passes 3/3 0 of 4 after a GPU read, 0 after a GPU write, 4 after the import pass (agc_unit_shadow_completion_store skips, as on main)

    The skip does not turn the test off anywhere it ran before. The cost is the one its description names: RADV no longer runs the idle publish wait at all, and with Windows out too (the block leaves the watch at import there, as above), lavapipe and NVIDIA are its remaining coverage. Getting RADV back would mean a dma-buf import in this test, which is what the driver watches there in practice. I did not run the validation layer; it is not installed on this machine.

    I have no second PR for this. #12158 is the right shape.

    AI-assisted: yes

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions