Skip to content

fix(agc): hold a guest allocation lease while a queue stores idle results - #6246

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
screamm:fix/eden-compare-untracked-teardown
Oct 18, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
screamm:fix/eden-compare-untracked-teardown

Conversation

@screamm

@screamm screamm commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Once every queue has been idle for 50 ms, the queue 0 worker stores pending storage image results on its own (#3777, #4410). That store took no lease of the guest allocations, so a guest unmap could run alongside it, and the compare of memory outside the write watch (StorageTexture::compareUntracked, Texture.cpp:2106) read memory the unmap had released: the driver forgets its cached page states only when the unmap's registry mutation ends (GuestAllocationsEnd → ForgetPages), after the memory is gone, so Accessible() still answered yes.

StoreIdleResults (VulkanDevice.cpp:1916) now holds a GuestAllocationsAcquire lease under the GPU mutex, in the order the driver's other lease holders use. An unmap waits in GuestAllocationsRequireUnpinned until the store is done; a store that starts during an unmap waits for the mutation to end, finds the range no longer registered and drops its results through the existing path. One line in the driver plus a new GPU test, agc_driver_idle_store_over_unmap (about 1 s), which unmaps an untracked arena surface with a pending result while the GPU mutex is released; it faults at the same compare 3 of 3 without the lease and passes 3 of 3 with it.

Title: ProsperoEden PPSA99008 (homebrew catalog, #1578), relinked with --trap-sdk-syscall-stubs and run without app0/eboot.bin. Its launcher cannot read its font, quits, and its GL driver unmaps its direct memory at 0x200000000+0x14000000 while both 1920x1080 color buffers there still have results pending. On main d92a8dad3, 3 of 3 cold runs:

FATAL: unhandled exception 0xc0000005 on thread 69080 'queue worker 0x0'
  rip 0x00007ff8fd22e220 ucrtbase.dll+0xee220
  read of 0x0000000200000000
    #0 libSceAgcDriver.prx+0x257e30   StorageTexture::compareUntracked +0x2a0
    #1 libSceAgcDriver.prx+0x2820d6   StorageTexture::writeBackLayers
    #2 libSceAgcDriver.prx+0x2859e7   StorageTexture::writeBack
    #3 libSceAgcDriver.prx+0x28638e   StorageTexture::FlushPending
    #4 libSceAgcDriver.prx+0x286dd6   StorageTexture::FlushAllPending ("idle")
    #5 libSceAgcDriver.prx+0x7be51    Driver::enqueue worker

Order of events with APS5_TRACE_MEMORY=1 APS5_TRACE_FLUSH=1:

[memory] unmap 0000000200000000+0x14000000
[flush] image 0x200000000+0x10e0000 for idle 0x0+0xffffffffffffffff
[flush] image 0x202000000+0x10e0000 for idle 0x0+0xffffffffffffffff
[memory] release physical 0x8a00000+0x14000000
[ProsperoEden] exit: Launcher closed

With this change the title exits with code 0 in 6 of 6 runs (3 cold, 3 traced) and logs storage image 0x200000000+0x10e0000: memory no longer registered; 1 pending ranges dropped for both images. With app0/eboot.bin staged the launcher shows its menu and renders at 59.9 FPS for 60 s with and without the change (3454 vs 3325 frames).

For reviewers: the lease pins every registered range, so a guest unmap or heap free waits a few ms while an idle write-back runs. Write-backs on other workers that already run without a lease are unchanged. A guest that unmaps a surface and maps new memory at the same address before the idle store runs still gets the stale results written over the new memory; that was already the case and is not addressed here.

Tested

Windows 11 Pro 26220, Ryzen 7 5700X, NVIDIA RTX 4070 (driver 596.21), WinLibs MinGW-w64 GCC 15.2.0 r7, Ninja, Release, BUILD_TESTING=ON, full build including libs, on main d92a8dad3.

  • agc_driver_idle_store_over_unmap: 3 of 3 pass with the change; 3 of 3 fault at the same address and frames against unfixed main's driver.
  • agc_driver_store_idle_results, agc_driver_store_over_untracked: 3 of 3.
  • agc_driver_* and video_out_flip*: 440 of 440 passed, 12 skipped.
  • ctest -j8 --timeout 120 -E "^agc_driver_|^video_out_flip": 811 of 814 passed, 2 skipped; hardware_oracle_compare timed out under load and passes alone in 8.7 s.
  • python tools/check_conventions.py --base main, git diff --check: pass.
  • Not run on Linux; the test skips where the surface cannot be write-watched.
  • Cross-validation by savvaniss (comment below): Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Intel UHD Graphics 770 (driver 32.0.101.7088) and AMD Radeon RX 9070 XT (driver 32.0.31041.1004): agc_driver_idle_store_over_unmap 3 of 3 pass with the change on both GPUs and crashes 3 of 3 without it on both.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw; no silent stubs added
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA99008 ProsperoEden exit path (homebrew catalog, Four open-source homebrew titles stop at start-up on unresolved imports (sysctl, pthread_set_name_np, isatty, mkstemp, signal sets, Opus decoders) #1578)
  • GPUs tested: NVIDIA RTX 4070, driver 596.21, Windows 11 Pro 26220; Intel UHD Graphics 770, driver 32.0.101.7088, and AMD Radeon RX 9070 XT, driver 32.0.31041.1004, Windows 11 Pro 26300 (savvaniss)
  • Fixes: none
  • AI-assisted: yes

🤖 Generated with Claude Code

…ults

Once every queue has been idle for 50 ms, the queue 0 worker stores
pending storage image results on its own (boykopovar#3777, boykopovar#4410). That store runs
at no ordering point of the guest and took no lease of the guest
allocations, so a guest unmap could run alongside it. The compare of
memory outside the write watch (StorageTexture::compareUntracked) then
read memory the unmap had released: the driver forgets its cached page
states only when the unmap's registry mutation ends, after the memory
is gone, so the range still read as mapped.

The idle store now holds a lease (GuestAllocationsAcquire) under the GPU
mutex, the order the other lease holders in the driver use. An unmap
waits in GuestAllocationsRequireUnpinned until the store is done, and a
store that starts during an unmap waits for the mutation to end, finds
the range no longer registered and drops its results.

ProsperoEden (PPSA99008, homebrew catalog, run without app0/eboot.bin)
closes its launcher, its GL driver unmaps its direct memory at
0x200000000+0x14000000 while both 1920x1080 color buffers there still
have results pending, and the queue worker faulted in compareUntracked
under FlushAllPending("idle"):

  FATAL: unhandled exception 0xc0000005 on thread 69080 'queue worker 0x0'
    read of 0x0000000200000000

On main d92a8da that happens in 3 of 3 cold runs. With this change 6
of 6 runs exit with code 0 and log "storage image 0x200000000+0x10e0000:
memory no longer registered; 1 pending ranges dropped" for both images.

New test agc_driver_idle_store_over_unmap unmaps an untracked arena
surface with a pending result and releases the GPU mutex while the unmap
is in progress. Without the lease the queue worker faults at the same
compare (3 of 3); with it the result stays pending until the unmap ends
and is then dropped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added the agc libSceAgc* label Oct 15, 2026
This was referenced Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

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 1395 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4650 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5441 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6259 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6360 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6414 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6503 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6763 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6786 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6788 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7201 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7332 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7390 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7464 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7474 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp

@savvaniss

Copy link
Copy Markdown
Contributor

Intel and AMD Windows data: the new test passes on both GPUs with this change and crashes on both without it, as on the RTX 4070.

Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, every execution test built, ANYPS5_NO_SHADER_CACHE=1, serial CTest. Intel UHD Graphics 770 (8086:a780), driver 32.0.101.7088. AMD Radeon RX 9070 XT (1002:7550), driver 32.0.31041.1004, with VkCache emptied first. "Without" means this PR's test files on main c9e6c45de, with none of its other changes.

UHD 770 RX 9070 XT
This head e3ce9bac0 merged into main 673f7406c: agc_driver_idle_store_over_unmap, ×3 pass pass
Without, ×3 access violation (CTest SegFault) same

Full CTest (--parallel 4) on main c9e6c45de with seven open GPU PRs merged (#6258, #6657, #6324, #6246, #6174, #6360, #6598): UHD 770 1381 of 1389, RX 9070 XT 1383 of 1389. main alone passes 1378 of 1385 and 1381 of 1385. The merge adds only two failures: agc_buffer_pool (#6174, both GPUs) and agc_driver_indirect_dispatch_limit (#6360, RX 9070 XT).

AI-assisted: yes (Claude Code)

@screamm

screamm commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @savvaniss for the Intel UHD 770 and RX 9070 XT runs, including the without-fix crash on both. I added them to Tested and GPUs tested, and added the Fixes: none line the template now asks for. @oneandonlydean, the linux and windows CI jobs were skipped on this PR's runs; could a full CI run be dispatched on e3ce9bac0 when you have time?

@oneandonlydean
oneandonlydean merged commit 77b7b03 into boykopovar:main Oct 18, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants