Repository navigation
Description
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::UnchangedSinceCollectedreturns false for both units, so the completion drops them as stale (UnitShadow.cpp:272onmain). Units 2 and 3 still hold0x11when 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 forcesAPS5_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_IMPORTSunset, this device compares its host imports, and the test logshost 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
LeandroLP23 commentedon Oct 27, 2026
A Windows data point: the test passes there, but only because it returns before its check, so Windows runs cannot catch this.
main3fea2a937b(includes #9521)idle publish wait not tested801d5b9bc1idle publish wait not testedBoth used WinLibs GCC 15.2.0, Release,
ctest -R '^agc_idle_publish_wait$' -VwithANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation. There were noVUID-lines and no missing layer.To see which condition sends it out, I added two temporary prints around the
HostImportForcall inidlePublishWaitTests(Recorder.cpp) on the 860M:The import is made (
[gpu] host import of 0x200000000+0x80000 ok), butGuestMemory::Watchedturns false for the block once it is imported. On Windows the write-watch coverage excludes an imported block, asWriteWatchCoverage.cppexpects ("the imported block is still watched"). Soimport == 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_waitshould not be read as "not affected": it does not run its check.AI-assisted: yes (Claude Code)
fisherjoey commentedon Oct 28, 2026
The cause you suspected is the one. I reproduced the drop without AMD hardware and checked whether the production path is affected.
What happens:
publishUnitstakeschecked = TrackerGeneration()(UnitShadow.cpp:358), collects the selected units (:375), records a slab-to-host copy, and at completion stores each 64 KiB unit only ifUnchangedSinceCollected(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 aftercheckedmakes 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 thanchecked(yourchecked=4 now=6/7 unchanged=0), andFlushAllPending("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:mainee5e24729cmain+ hookthe idle publish's bytes are wrongSo 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 withAPS5_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
ShadowDestinationForoffers it no unit shadow (UnitShadow.cpp:612) and the CPU publish never runs for it. On LinuximportWatchingdoes 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^agcrun on8df2c3ed0epassedagc_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 withAPS5_WRITE_WATCH_IMPORTS=watchset by hand on such a device, and that configuration already lost publishes before #9521 (#9781).On #12158: it skips the test when
ProbeImportWriteProtectionreports 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 ontomainee5e24729c(caf306a682locally) withANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1:agc_idle_publish_waitagc_import_watch)agc_unit_shadow,agc_storage_idle_writeback_race,agc_driver_sparse_dma_buf_importagc_unit_shadow_completion_storeskips, as onmain)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