Repository navigation
honeykrisp: give capture and replay buffers their own address range - #9
joshuaswarren wants to merge 3 commits into
Conversation
A buffer created for capture and replay of its device address takes its address from the same heap as every other allocation. The capture device hands out addresses from the top of the heap. The replay device later asks for the same addresses, but in the meantime it hands out the same kind of addresses to other allocations, for example the memory of the buffers it has already replayed. When such an allocation sits on a captured address, the replay of the buffer that owns that address fails with VK_ERROR_OUT_OF_DEVICE_MEMORY. A trace of three conformance cases showed this: the failing request was the last address allocation in each run, and the allocation in its way came a moment before it. Reserve the lowest 4 GiB of the read-write user address window for capture and replay buffers. The range has its own heap, which ordinary allocations never use, so a captured address is free when it is replayed. The range is a fixed part of the window, so a capture made in one process replays in another on the same machine and kernel. Each buffer keeps its guard page. The allocation stores a flag, and free returns the range to the heap it came from. The read-only shadow of an address stays at the same offset, because the range is inside the read-write half of the window. A capture and replay buffer, with its guard, must now fit in 4 GiB. A larger one fails with VK_ERROR_OUT_OF_DEVICE_MEMORY, as any allocation that does not fit does. Ordinary buffers are not limited by this. An address that cannot be in the range, or that is in use, now returns VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS. A capture request with no address that finds the range full still returns VK_ERROR_OUT_OF_DEVICE_MEMORY. An address captured by an earlier driver lies in the ordinary window, so its replay is refused. Add a test of the allocator that runs without a GPU. It covers the range separation, the guard, replay in a fresh device, requests outside the range and in use, exhaustion, and reuse after free.
|
GREEN at 11ae82d, with two non-blocking notes. I read the full diff against main (7cda785) and ran no build. Checked:
Notes:
Merging is the maintainers' call. |
|
Both notes are open items, and the body already states both. I did not change the branch.
|
|
Maintainer routing request for PR 9. This is a request for a merge contact, not a claim that review is finished. It makes capture and replay buffers replayable in a fresh process. What was run on the head Question: who can merge into this repository's |
|
Second routing request for PR 9, about 14 hours after the first. It is still a request for a merge contact, not a claim that review is finished. Nothing on the head has changed since my first note above. The review notes and the list of what was run are in the description and the two review comments. Question: who can merge into this repository's main branch? If there is a different place to ask, a link is enough. My account has pull access only, so I cannot merge or mark anything ready. |
|
Status, 25 Oct: unchanged since 16 Oct, no review yet. It is in both arms of the #6 whole-suite run (main plus #8, #9, #10), so the |
|
Status, 25 Oct, for the landing order (#10 first, then #8, then #9, then #6). Head MEASURED: this change is in the control arm of the whole-suite CTS of the #6 stack (16-inch M1 Max and 13-inch M1), with no Pass to Fail change. NOT measured: the capture and replay CTS groups with only this change. |
|
The version of this change for That branch already has a capture and replay heap, so this keeps it. The commit records the range, adds MEASURED, native arm64 build of this head on a 16-inch M1 Max:
NOT measured: a GPU run and the capture and replay CTS groups on this head. |
|
Batch 1 for main: #10, then #8, then #9, in that order. All three apply to main at
Stack check, one boot on a 13-inch M1, These are targeted checks, not the full conformance suite. The CTS binary's source commit is not independently verified. #6 is not part of this batch. Its run over the touched test groups at its current head has not finished, and three The author account on these PRs has pull-only access, so a maintainer has to merge. Tell us if you want a different order or a different check first. |
|
@maralcbr could you review this current head for landing after #10 and #8? The exact-head test evidence and remaining scope limits are in the PR body. The requested landing order is #10, then #8, then #9. Our account cannot assign formal reviewers or merge here; please route this to a main-branch maintainer if needed. |
chriskearney
left a comment
There was a problem hiding this comment.
The new libasahi_va_tests target does not compile at 11ae82d, so any build of this head with -Dbuild-tests=true fails.
FAILED: src/asahi/lib/libasahi_va_tests.p/meson-generated_.._.._libagx_libagx.cpp.o
src/compiler/glsl_types.h:399:10: fatal error: builtin_types.h: No such file or directory
The same error comes for the generated libpoly.cpp.o.
Cause, in src/asahi/lib/meson.build:
- The target lists
idep_libagxandidep_libpoly. These dependencies carry the generatedlibagx.cppandlibpoly.cppas sources, which Meson compiles into every consumer. - Those sources include
glsl_types.h, which needs the generatedbuiltin_types.hfromsrc/compiler. - The target does not list
idep_nir, which provides that include path.libasahi_lib(line 67), with the same two dependencies, does list it.
Reproduced in two builds:
- a fresh x86_64 build of only this target, from this head alone on main
7cda7850ed; - an aarch64 package build of main with #10, #8 and this PR merged, whose build stage uses
-Dbuild-tests=true. In that tree,src/asahi/lib/meson.buildandtests/test-agx-va.care this head's files byte for byte.
The description already says the meson test target was not built.
Fix tested in a local copy (this branch is unchanged):
- Adding
idep_nirto this target's dependencies works. The target builds, and the test prints 9 PASS lines and0 failure(s)(x86_64 host). - Removing
idep_libagxandidep_libpolyinstead does not work:agx_device.hincludeslibagx_dgc.h.
Please add idep_nir (or an equivalent dependency) and confirm with meson test libasahi_va_tests in a build with tests enabled. This review covers the build only. Results for the allocator change itself are reported separately.
| include_directories : [inc_asahi, inc_virtio_gpu, inc_virtio_vdrm], | ||
| c_args : [no_override_init_args, '-Wno-c2x-extensions'], | ||
| gnu_symbol_visibility : 'hidden', | ||
| dependencies: [dep_libdrm, idep_agx_pack, idep_mesautil, idep_libagx, idep_libpoly], |
There was a problem hiding this comment.
This target needs idep_nir. The generated libagx.cpp/libpoly.cpp sources that idep_libagx and idep_libpoly add include glsl_types.h, which needs the generated builtin_types.h. libasahi_lib (line 67) lists idep_nir for the same reason. With it added, this target builds and its 9 cases pass.
|
Confirmed: the VA test target consumes generated libagx/libpoly sources but omits the idep_nir dependency that supplies builtin_types.h. I am reproducing the target-only build failure, then adding that dependency and running the registered test with build-tests enabled. This does not qualify the allocator or the future stack with the #8 mapping correction; the landing hold remains. |
Generated libagx/libpoly C++ needs builtin_types.h from idep_nir. Match the library dependency instead of relying on another target to produce or expose that header. No driver source changes. Verified: target-only build failed on missing builtin_types.h before the dependency was added; afterward libasahi_va_tests builds and its registered Meson test reports nine PASS cases and zero failures.
|
Fixed the test-target dependency at |
|
Source review at 93f94c8 (second commit, Result: no defect found in the fix.
Nits in the first commit, not new:
Not checked: a Meson build of this tree (my build used the generated headers by hand), any device or CTS run, and #8 or #10. |
[Medium impact] No new code issue blocks these changes; the PR's stated dependency and device-qualification hold still applies. SummaryThe PR gives capture/replay buffers a separate address range. Since the previous review:
No new actionable issues were found. Tests were not run during this review. The stated landing hold for #10 and a corrected, qualified #8 remains unchanged. Reviews (2) · Last reviewed commit: "asahi: reject zero capture replay alignm..." · Reviewed by Greptile |
A zero alignment reached modulo and raised SIGFPE. Reject it in the shared validator. The invalid-request regression fails with SIGFPE before this guard; all nine VA cases pass after it.
|
Independent successor check at a8960b7: the missing-NIR test dependency finding is resolved by 93f94c8, and the zero-alignment guard has a real failing-before control. The exact registered allocator-test source linked with the actual allocator/VMA/futex code passes all nine cases. The same new test with parent 93f94c8 raises SIGFPE; the old test with that parent passes. Candidate and parent retain those respective outcomes with the allocator compiled under NDEBUG. This focused host check uses an aborting substitute only for the unused hardware-bind edge. The full registered-build integration is also verified: the native aarch64 union of #10 b87e745, #8 4e269c5 and this head (local tip 9460a17b877, tree c0404e39) compiles and links the generated libagx and VA-test targets. libasahi_va_tests passes all nine cases; the complete registered suite is 49/49 with check exit 0. Production and test payloads are byte-identical, independently checked. No local dependency overlay was used. This validates the two-fix delta and registered build, not new-head capture/replay device behavior. No Mac/GPU or CTS run of this successor is claimed, and the stated landing dependency on #10 and qualified #8 remains. |
maralcbr
left a comment
There was a problem hiding this comment.
Reviewed at a8960b752b385a7872285993ed657cfa1df41ee6. Requesting changes: please close rather than merge.
- Wrong base.
mainmirrors stock Mesa, per the fork policy inAGENTS.mdonintegration/aurora. - Superseded upstream, with a different design.
b467490b("hk: Allocate capture/replay VA with a separate heap", mesa!45582, 2026-10-29) fixes the samebinding_model.buffer_device_addressfailures. Its capture heap overlaps the whole user range and allocates from the bottom (alloc_high = false). Each reservation is mirrored into the main heap, which allocates from the top. It sets no size cap. If this PR landed,mainwould conflict with upstream on every sync. - Behaviour change that needs a maintainer opt-in. Reserving
min(4 GiB, user_size/4)for capture/replay caps the VA that live capture-replay buffers can hold. Addresses captured by earlier drivers are refused withVK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS. RenderDoc sets the capture-replay bit on BDA buffers while it captures, so large captures could start failing. That needs a deliberate decision. It shouldn't ride in on a CTS fix. - Aurora already has this.
integration/aurorahas its ownAGX_VA_CAPTURE_REPLAYheap, and omacom/mesa#18 covers it there.
If VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS for an in-use or out-of-range fixed address, and the wrap guard before util_vma_heap_alloc_addr, are still worth having, they'd make a small follow-up on top of upstream's design, sent upstream first.
|
Correction to my review above: drop the suggestion that a follow-up be "sent upstream first". Upstream Mesa's The rest of the review stands. |
|
Closing this proposal at The upstream replacement is b467490b84d006845d5da9a01f8b2e1af512b1f9. I read its patch: it uses an overlapping capture heap with reservations mirrored into the ordinary heap. This proposal instead carves out a capped range. It also targets the stock mirror branch. The integration-branch work is tracked in #18. This closure does not qualify that PR or waive its heap and device holds. Existing tests and results remain historical evidence. |
Current status: landing held
Head:
a8960b752b385a7872285993ed657cfa1df41ee6.The VA test now gets the NIR header it needs. The target failed to build before that fix. It now builds, and all nine test cases pass.
This head also rejects zero alignment. The new test raised SIGFPE before the guard. It now passes. These are CPU checks, not proof of device results.
Landing waits for #10 and the fixed, qualified #8. The older results below apply only to their stated heads. They do not prove that a later fix works.
Earlier description and evidence
READY TO MERGE after #10 and #8.
Head:
11ae82d8d8c379c2ae895806b4409537c5ceb25b.GitHub reports MERGEABLE against
main: no text conflicts. This is not a test pass.The main-based head built and passed nine allocator checks.
Device result at this exact head, 13-inch M1, one boot, same CTS binary for both arms, dEQP-VK
binding_modelchunk 0 (1,000 cases):7cda7850ed59c25f62c9d)All 122 differences are ResourceError to Pass. No case gets worse. Before the run, each arm was checked to load its own driver file. The table shows the three PRs together, not #9 alone. It is one targeted chunk, not full CTS, and the CTS binary's source commit is not independently verified. An Aurora #18 result does not stand in for this one. Landing order: #10, #8, then #9.
Earlier review, source and test notes are kept below with their original bindings. The line below that says the main-based build was not run on a device is superseded by the result above. They do not change this status otherwise.
What this fixes
A buffer created with
VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BITcannot be replayed. In the first 1000 cases of the conformance groupbinding_model, every capture and replay case fails.vkCreateBufferreturnsVK_ERROR_OUT_OF_DEVICE_MEMORYfor a buffer that the test created with a captured address.The cause is in the address heap. Capture and replay buffers take their addresses from the same heap as every other allocation. In the old code the capture device hands out addresses from the top of the heap. The replay device later asks for the same addresses. In the meantime it gives the same kind of addresses to other allocations. The trace does not say which ones. When one of those sits on a captured address, the replay of the buffer that owns that address fails.
I traced three conformance cases with a throwaway patch that logs every address allocation. In each one the failing request was the last address allocation of the run. It was a fixed-address request on the replay device. It came after 68, 73 and 2 earlier fixed requests had succeeded. The range it asked for overlapped a range that a non-fixed allocation had taken a moment before. No replay buffer was in the way. In the smallest case the request was for 0x8000 bytes at 0x2ffff7c000. The call just before it was a non-fixed allocation of 0x8000 bytes at exactly that address.
What this changes
VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS. The reference page forvkCreateBufferlists this code. A capture request with no address that finds the range full returnsVK_ERROR_OUT_OF_DEVICE_MEMORY.agx_va_allocrefuses a fixed capture request outside the range without calling the heap code, which asserts on a range that wraps.test-agx-va.c.Limits of this change
VK_ERROR_OUT_OF_DEVICE_MEMORYwhere it did not before. Buffers without the flag are not limited. The ordinary heap is 4 GiB smaller.VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS.Test results
The allocator test is plain C and I ran it by hand with assertions on. All 9 cases pass. On a model of the old behavior, with the reservation turned off, 6 of the 9 cases fail.
A small test program, run on the 16-inch M1 Max, the new arm as a native build and the control as the built package, one boot. It captures 100 buffers in one process and replays them in a fresh process, in reverse order, with a memory allocation between replays, as the conformance test does.
VK_ERROR_OUT_OF_DEVICE_MEMORYseed_0,seed_2and one replay compute caseIn the same program this change returns the expected code for an address in the ordinary window, a captured address moved up by 4 GiB, an address in use, a 5 GiB buffer with an address (
VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS), and a second 3 GiB buffer without an address (VK_ERROR_OUT_OF_DEVICE_MEMORY). A buffer destroyed and created again at the same captured address on the same device gets the same address both times, and both memory binds succeed. That test used one size and one recreate. It did not access the recreated buffer from the GPU. I did not read the return value of the scratch bind, which the code ignores.The first 1000 cases of the
binding_modelgroup (chunk 0 of 151), with the library bytes of built packages, one run per arm. The 16-inch M1 Max and the 13-inch M1 give the same table.Exactly 122 of the 1000 rows differ on both chips, all from ResourceError to Pass, and 878 are unchanged. No row gets worse. The 122 rows are the 10
capture_replay_stressseeds and 112buffer_device_addressreplay cases.The control is the package of main with the change from #8 added. The candidate package is built from the commit on top of #8. This branch carries the same change on main.
git patch-idis equal for the two commits, and the main-based commit built natively without errors. I did not run the main-based build on a device.What this does not show