Skip to content

fix(agc): destroy settle aliases before their imports are freed - #7629

Open
EagleBearerYS wants to merge 3 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-settle-alias-before-import-free
Open

EagleBearerYS wants to merge 3 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-settle-alias-before-import-free

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

A regression from our #6678. On Windows, agc_driver_host_import_lifetime (from #7339) fails on unchanged main on an AMD Radeon 860M: "host import Vulkan objects were destroyed out of order or more than once", at its first partial unmap. @LeandroLP23 reported it in #7601 and traced it there: the buffer the test tracks isn't the import's own but an alias bound to the import's memory, and two routes free that memory before the alias is destroyed. The second commit is LeandroLP23's own fix for one of them, as they posted it in #7601, with their authorship.

How it happens (main 889cee5, GuestBufferMemory.cpp, Windows only):

  1. A host import under 1 MiB is settled by a batched 4-byte read (perf(agc): settle Windows imports under 1 MiB without waiting #6678). When the settle queue is in another queue family than the import's, the batch reads through an alias buffer bound to the import's memory (~990-1010), and destroys the alias when the batch is reaped.
  2. Settles go to another family when the graphics family has a single queue (VulkanDevice.cpp ~1473-1499), as on AMD's Windows driver. A device with spare graphics queues settles in the same family and makes no alias.
  3. An import retired while its batch holds it waits in the batch's retired list, and the reap destroys the aliases before it drops that list. Two routes skip that order:
    • forgetSettle (~256) hands the batch back only while the GPU is still running it. For a batch that has finished but hasn't been reaped yet, retireImport releases the import at once: its buffer is destroyed and its memory freed, and the alias goes at the next reap. LeandroLP23 found this route.
    • An unmap that needs a retired import (releaseSettleRetired, ~376-395) waits for the batch, then releases the import with the alias still bound.
  4. The test's vkBindBufferMemory hook keeps the last buffer bound to an import's memory, which is the alias. So partialUnmap sees the memory freed before that buffer is destroyed: AGC: agc_driver_host_import_lifetime fails on Windows (Radeon 860M) in partialUnmap: the import's memory is freed before its buffer is seen destroyed #7601's destroyed=0 outOfOrder=1 frees=1.

As @MarcZX10 points out in #7601, Vulkan permits freeing memory while resources are still bound to it, once the commands that use them have completed. Both routes run after the batch has finished and nothing uses the alias afterwards, so this isn't a GPU hazard. LeandroLP23's trace there shows the sequence: the import's own buffer is destroyed, the memory is freed while the fixture tracks the alias (a new buffer reusing an earlier handle value), and the alias is destroyed right after, at the reap.

A fixture that tracked only the import's own buffer would pass as well, but it would no longer check the order for the alias. This change keeps the test as it is and makes both routes keep the order the reap and drain paths already keep. Which of the two to take is the maintainers' call.

The change, in three commits:

  1. releaseSettleRetired destroys a finished batch's aliases before it releases the imports the batch held.
  2. @LeandroLP23's: forgetSettle hands back any busy batch, not only a running one. An import retired after its read finished then stays with its batch until the batch is reaped or an unmap releases it, and both destroy the aliases first.
  3. agc_driver_host_import_lifetime_other_family runs the lifetime tests with the settles forced onto another queue family (settleOnOtherFamily, as agc_import_settle_deferred_other_family does), so a GPU with spare graphics queues takes the alias path too. It's registered on Windows and skips where the device has no other queue family.

Side effects:

  • A finished batch keeps its imports' Vulkan objects until it is reaped, instead of releasing them at once. Not measured.
  • On the [buffers] host imports line (APS5_PROFILE_DRAW), "retired during their read" now also counts imports retired after their read finished but before its batch was reaped. "unmaps waited for it" still counts only reads that were running.

What stays as it is: devices that settle in the import's own family, which make no alias, and Linux, which has no settle batches.

Fixes #7601

Tested

Checklist

EagleBearerYS and others added 3 commits October 18, 2026 10:41
On Windows a host import under 1 MiB is settled by a batched 4-byte
read. When the settle queue is in another queue family than the
import's, as on AMD's Windows driver with its single graphics queue,
the batch reads through an alias buffer bound to the import's memory.
Reaping the batch destroys those aliases before it drops the imports
retired while the batch held them.

An unmap that needs such an import (releaseSettleRetired) waited for
the batch and then released the import, destroying its buffer and
freeing its memory while the alias was still bound to that memory; the
alias went at the next reap. Destroy the batch's aliases first, as
reapSettleBatches and drainSettles do.
forgetSettle handed back an import's settle batch only while the batch
was still running. A batch the GPU had finished but that had not been
reaped yet still held its aliases, so retireImport released the import
at once, freeing its memory before the alias bound to it was destroyed.

Hand back any busy batch. The import then stays with its batch until
the batch is reaped or an unmap releases it, and both destroy the
aliases first. A finished batch now keeps its imports' Vulkan objects
until it is reaped (boykopovar#7601).
agc_driver_host_import_lifetime takes the settle alias path only on a
device whose graphics family has a single queue, such as AMD's Windows
driver; elsewhere the settles share the graphics family and make no
alias. agc_driver_host_import_lifetime_other_family runs the same tests
with the settles forced onto another queue family, as
agc_import_settle_deferred_other_family does, so a device with spare
graphics queues covers that path too. It is registered on Windows and
skips where the device has no other queue family.
@github-actions

github-actions Bot commented Oct 18, 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 1191 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 1324 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 1341 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5426 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5499 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5656 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6548 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6588 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6611 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6741 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6747 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6868 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6887 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6937 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6946 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6947 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6965 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6970 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6971 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6973 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6987 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6989 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6995 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7002 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7085 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7233 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7258 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7332 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7473 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7505 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7615 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7800 core/libs/prx/libSceAgcDriver/CMakeLists.txt

@EagleBearerYS
EagleBearerYS marked this pull request as ready for review October 18, 2026 18:17

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the lifetime paths and the diff. Keeping any busy settle batch with its retired imports covers the completed-but-not-yet-reaped case, and releasing the aliases before dropping overlapping retired imports covers partial unmap. The other-family Windows test exercises the alias path on devices that otherwise settle on the graphics family. I did not run the Windows GPU tests locally; the reported 860M and RTX 3070 results provide the hardware validation for this review.

@LeandroLP23

LeandroLP23 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Rechecked the PR head 8a1e73718 on the 860M. Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), Windows 11 26300, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON. Build: main c3591adf9 with #7629, #7630 and #7627 merged together (each merge clean, built once).

The 11 host_import tests, among them agc_driver_host_import_lifetime and agc_driver_host_import_lifetime_other_family, and agc_import_settle_deferred_other_family pass 3 runs of 3. agc_host_import_sparse skips itself as before.

AI-assisted

This branch has not been deployed

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

Labels

agc libSceAgc* build fix error Closes an issue (Fixes #N)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AGC: agc_driver_host_import_lifetime fails on Windows (Radeon 860M) in partialUnmap: the import's memory is freed before its buffer is seen destroyed

3 participants