Skip to content

fix(agc): clear a reused pooled buffer's unused tail on macOS - #11303

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
iamthehimansh:split/macos-pooled-tail
Oct 27, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
iamthehimansh:split/macos-pooled-tail

Conversation

@iamthehimansh

@iamthehimansh iamthehimansh commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

Next part of splitting #8658: agc_driver_pooled_buffer_range fails on macOS. MoltenVK has no robustBufferAccess2, so a robust read past the end of a pooled buffer is clamped into its slot instead of returning zero. A 100-byte buffer placed in a 256-byte slot then reads the bytes an earlier user left there.

On macOS, when a host-coherent mapped slot is handed to a new buffer, Graphics/src/Resources.cpp now clears the bytes past the requested size to zero. That happens on both paths: a reused class allocation, and a new slot in a slab. Linux and Windows compile the same code as before.

I compared the added lines with all 1,377 open PRs; none carries this code.

Tested

Apple M2 Pro, macOS 26.6.2, MoltenVK 1.4.2 (LunarG Vulkan SDK 1.4.363.0), x86_64 Release under Rosetta 2, with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation. The validation layer loaded in every run (loader output shows it inserted, with no "was not found" line). It reports one VUID-VkDeviceCreateInfo-pProperties-04451 error per run on both sides, 3 of 3 without and 3 of 3 with this PR. That error comes from the test fixture creating its own device without VK_KHR_portability_subset (#10920). This PR adds no new VUID.

libSceAgcDriver does not link on macOS on main alone, so the test runs on a local base. It is main at b27cc8416, two commits behind this head's cbee22480, and those two only change libc's tanh. On top of that:

On that base, Resources.cpp was main's file, then this PR's exact file at 1ac3e3561:

Resources.cpp agc_driver_pooled_buffer_range
main fail, 2 of 2: "a robust read of word 28 of a 100-byte pooled buffer returned 0xabababab, the bytes a 256-byte earlier user left in its memory"
this PR pass, 3 of 3

@Gonic0 (M5 Pro) and @maximilianfv (M1 Pro) report the same split above.

The file's text outside __APPLE__ blocks equals main. python3 tools/check_conventions.py --base origin/main passes.

Checklist

MoltenVK has no robustBufferAccess2, so a robust read past the end of a
pooled buffer is clamped into its slot instead of returning zero. A
100-byte buffer placed in a 256-byte slot then read the bytes an earlier
user left there (0xabababab), and agc_driver_pooled_buffer_range failed.
On macOS, when a host-coherent mapped slot is handed to a new buffer,
the bytes past the requested size are cleared to zero.

Linux and Windows compile the same code as before.
@github-actions

github-actions Bot commented Oct 26, 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 7642 core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp
PR 9508 core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp
PR 10193 core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp
PR 11920 core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp

@Gonic0

Gonic0 commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Tested on an M5 Pro, macOS 27.2 (26B5086k), x86_64 Release under Rosetta 2, MoltenVK and the validation layer from the LunarG SDK 1.4.363, with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation.

Base is mugurc's ci/macos-series-2 (27e9689743), where 8a9a741b68 applies cleanly. Only Resources.cpp was toggled. The runs used the same local loader setup as in #10920 (a libvulkan.so.1 symlink, and the MoltenVK ICD marked non-portability), identical in both states. ctest -V covered the 89 tests of the thirteen binaries that create their own device, agc_driver_pooled_buffer_range among them.

agc_driver_pooled_buffer_range other failures Validation Error lines
without this change fails in 16 of 16 runs, with the same 0xabababab message as yours agc_performance_overlay 85 in each of 3 full runs
with this change passes in 16 of 16 runs agc_performance_overlay 85 in each of 3 full runs
  • The validation errors are the same in both states: 81 pProperties-04451 (the tests' own devices, which test(agc): enable VK_KHR_portability_subset in the tests that create their own device #10920 addresses) and 4 imageViewFormatSwizzle-04465. The change adds no new VUID-, and the layer loaded every time.
  • agc_performance_overlay is the private-memory read that fix(agc): read the process's private memory from task_info on macOS #9971 covers.
  • agc_driver_vertex_target_range_imported failed in 2 of the 3 full runs with the change and in none without it. Run directly 40 times, though, it fails in both states with the same message ("the draw did not consume the GPU-produced vertex offset"): 9 of 40 without the change and 6 of 40 with it. So it's an intermittent failure on this setup that predates this PR. --vertex-target-range-copied passed 40 of 40 both ways.

AI-assisted: yes

@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.

I reviewed exact head 8a9a741b684469cce9997266ed44d7e2d0e53aea. The Apple-only helper clears only the mapped, host-coherent allocation tail and bounds the range by both the buffer capacity and allocation size. The author and an independent M5 Pro run report the targeted regression passing with the patch and failing without it; the independent run also reports no new validation errors (the listed validation messages are present in both states).

Before approval, please refresh this branch onto current main (9b197b6ce4583e64bf2427e6beb7270bcfa1528e) and rerun the focused macOS test on that exact head. tools/check_main_sync.py reports this head is 1 commit ahead and 101 behind (merge base a3987a1ec), although a merge preview is clean. The hosted checks currently pass only policy/conventions/intake/labels/report; product jobs are path-skipped. I also manually checked the overlap bot's Resources.cpp warnings against #7642, #9508 and #10193: their changes in that file do not conflict with this tail-clear hunk. I have not approved pending the current-main refresh required by the repository's branch-up-to-date rule.

@github-actions github-actions Bot added the agc libSceAgc* label Oct 26, 2026

@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.

The independent M5 Pro validation is useful: it reports the target test failing 16/16 without the change and passing 16/16 with it, with no new validation-layer errors. The tested tree is still based on #8658’s 27e9689743, though. Against current upstream/main 14c99b52ace303eef1667764670d1d1540140b3d, this exact PR head is 1 ahead and 121 behind (merge preview clean). Please refresh onto current main and rerun agc_driver_pooled_buffer_range on that exact head; the previous approval blocker is current-main validation, not the quality of the reported M5 reproduction.

@maximilianfv maximilianfv 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.

Requesting changes for the PR text only. The code reads correct, and my A/B confirms the fix in both directions with no new validation errors. The body still needs the validation-layer statement and the GPU line that CONTRIBUTING asks for. The run below has the data.

Tested on Apple M1 Pro, macOS 27.0.1 (x86_64 build under Rosetta 2)

Applied at 8a9a741b6 to mugurc's macOS series (27e968974) merged with main at 2742004ed2, with other open macOS fixes, then toggled only this PR. MoltenVK from the Vulkan SDK 1.4.363.0, with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation. The layer loaded in every run (no was not found but was requested line).

agc_driver_pooled_buffer_range agc_static_buffer agc_write_tracking Validation Error lines per run
Without this PR fails 23 of 23: "a robust read of word 28 of a 100-byte pooled buffer returned 0xabababab, the bytes a 256-byte earlier user left in its memory" bus error 3 of 3 (EXC_BAD_ACCESS in sceAgcGetStaticBuffer) fails 3 of 3: "a block the import window did not touch reads as written" 1
With this PR passes 23 of 23 same bus error 3 of 3 same failure 3 of 3 1

On each side, 3 runs of all three tests plus 20 more runs of agc_driver_pooled_buffer_range alone. The one validation error on both sides is VUID-VkDeviceCreateInfo-pProperties-04451, from the test creating its own device without VK_KHR_portability_subset (#10920). The PR adds no VUID-. agc_static_buffer and agc_write_tracking fail the same way without the PR on this tree and do not involve Resources.cpp.

I also checked the premise. MoltenVK reports vkGetBufferMemoryRequirements().size = 100 for the 100-byte buffer: I added a temporary print in BindExactBuffer, then removed it. So word 28 (byte 112) lies past the memory range bound to the buffer. Under robustBufferAccess, MoltenVK should not return the earlier user's bytes there. The read is a driver deviation, not behaviour the spec allows, and clearing the tail is a fair workaround.

Findings

  1. PR body, Tested and GPUs tested: CONTRIBUTING.md:68 is not met. An AGC change needs a run with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1 and ANYPS5_REQUIRE_VULKAN=1 on main and on the change, a statement that the layer loaded and that the change adds no new VUID-, and vendor, model, driver and OS on the GPUs tested line. The body has none of the first three, and its GPU line lacks the OS. Fix: add your own validation run, or cite this one or Gonic0's, and write the line as e.g. Apple M2 Pro, MoltenVK 1.4.2, macOS 26. As MarcZX10 asked, refresh onto current main before that run. The merge base is a3987a1ec, and Resources.cpp has not changed on main since.

  2. Suggestion, not blocking, Resources.cpp:9, 18, 66, 110: key the clear on the driver rather than on __APPLE__. The deviation belongs to MoltenVK, not to the platform. On this Mac, vulkaninfo also lists KosmicKrisp, with robustBufferAccess2 = true, and the #ifdef runs the memset there for nothing. A check of VkPhysicalDeviceDriverProperties::driverID == VK_DRIVER_ID_MOLTENVK (or of the device lacking robustBufferAccess2, which VulkanDevice.cpp:2086 already computes but Graphics::Context does not carry) would be exact and would drop the #ifdef. The test's own fixture fills Context itself, so it would need to set the flag.

  3. Nit, Resources.cpp:9-12, 23: <cstddef> is included only under __APPLE__, but std::byte is used unconditionally in Buffer::Bytes(). There is also a blank line before the #endif at line 24 and none after it.

AI-assisted: yes (Claude Code applied the PR, ran the tests, read the code and wrote this review).

main's Resources.cpp already uses std::byte in Buffer::Bytes() on every
platform without including <cstddef>; it arrives through the project headers
this file includes first. Guarding a second include under __APPLE__ suggested
otherwise, so drop it. Also keep the blank line inside the __APPLE__ block so
the text outside it stays identical to main.
@iamthehimansh

Copy link
Copy Markdown
Contributor Author

@MarcZX10 @maximilianfv thanks both. Done on head 1ac3e3561:

  • Merged current main (cbee22480), so the branch is up to date.
  • Dropped the macOS-only <cstddef> include. main's Resources.cpp already uses std::byte in Buffer::Bytes() without including it. The blank line now sits inside the __APPLE__ block, so the text outside it equals main.
  • Reran the A/B on current main rather than feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658's branch. Since libSceAgcDriver doesn't link on macOS on main alone, the base adds the open macOS PRs that merge cleanly, plus local-only patches for the files still failing. The description lists both. Apple M2 Pro, macOS 26.6.2, MoltenVK 1.4.2, validation layer loaded:
Resources.cpp agc_driver_pooled_buffer_range VUID messages
main fail, 2 of 2 (the 0xabababab read) 0
1ac3e3561 pass, 3 of 3 0
  • Added the validation-layer statement and the GPU line to the description.

On checking for MoltenVK instead of __APPLE__: the clear is cheap and only touches host-coherent mapped slots. Any Apple Vulkan driver we run on today is MoltenVK. I kept it compile-time to match the other macOS branches in this file, but I'm happy to switch it to a robustBufferAccess2 check if you'd prefer that.

AI-assisted: yes (Cana)

@babyhuey

Copy link
Copy Markdown
Contributor

Windows run of this head 1ac3e3561c2, merged onto main 4e43321bd6c (clean merge, local only), with the selection CI's skipped Windows product job uses: Windows 11, Ryzen 7 5800X, RTX 3080, WinLibs GCC 15.2, Release, BUILD_TESTING=ON. The tree was configured fresh after the merge, then select_tests.py select / ninja -f select.ninja selected / --target libs / select_tests.py ctest --parallel 4 --timeout 120, with check_skips.py --job windows (all skips expected).

AI-assisted: yes (Claude Code)

@imellag

imellag commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

@iamthehimansh Building current main on macOS with the open macOS PRs (#10366, #10552, #7631, #7804, #11249), libkernel and libc now compile, but libSceAgcDriver's Lifecycle.cpp, GuestMemory.cpp, GuestBufferMemory.cpp and Texture.cpp still don't, and the only open PR touching them is #7201. Are you planning to split those out of #8658's branch, as this PR's Tested mentions? If not, I can open one; I'd rather ask first than duplicate your work.

@iamthehimansh

Copy link
Copy Markdown
Contributor Author

@imellag yes, I'll split those four out of #8658's branch into one libSceAgcDriver build PR today, and tag you on it. Lifecycle.cpp also needs a small Execution/include/Driver/DriverMemory.hpp header. They're the patches I used for this PR's test base, so they already build there.

#7201 also changes Lifecycle.cpp, GuestMemory.cpp and GuestBufferMemory.cpp, and I've asked @marion909 there about merge order. The new PR will list #7201 under Overlap; Texture.cpp isn't in #7201.

AI-assisted: yes (Cana)

@imellag

imellag commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

@iamthehimansh Thanks. A correction to my question: #7201 isn't the only open PR on those files. My #10676 already makes the same Texture.cpp change (the AtomicSharedPtr.hpp include and AtomicSharedPtr<const Snapshot> for PendingList::published), together with the QueuedIndexReadOrder test fix. So your PR could leave Texture.cpp out, or list #10676 under Overlap. Once it's up, I'll build it on macOS on current main with the other open macOS PRs and report there.

@iamthehimansh

Copy link
Copy Markdown
Contributor Author

@imellag the four libSceAgcDriver files are up as #11447, as promised. FrameRecorder.cpp's environ is left to @maximilianfv's #10540.

AI-assisted: yes (Cana)

@maximilianfv maximilianfv 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.

Approving at 1ac3e3561. Findings 1 (body) and 3 (<cstddef>) from my earlier review are done. The body now states that the validation layer loaded and that the change adds no VUID-, and the GPU line gives the OS. The macOS-only <cstddef> is gone. You asked about finding 2 (the gate), and I answer it in note 2 below. Apart from the main merge, dropping <cstddef> is the only change since 8a9a741b6. My A/B at this head confirms the fix both ways. Current main (185596faff) does not fix the bug: its Resources.cpp is the same blob as this PR's base (ea37fed7ef). The notes below do not block the merge.

Tested on Apple M1 Pro, macOS 27.0.1 (x86_64 build under Rosetta 2)

Applied at 1ac3e3561 to mugurc's macOS series (27e968974) merged with main at 2742004ed2, with other open macOS fixes, then toggled only this PR. The only commit to touch Resources.cpp on main since that base (4bee82086f) adds an include and a DestroyDeferredAtIdle call in CommandBatch::Submit(). Both are unrelated to the Buffer constructor, and BufferPool and the test have not changed. Both states were rebuilt before their runs. MoltenVK from the Vulkan SDK 1.4.363.0, with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation. The layer loaded in every run (no was not found but was requested line).

agc_driver_pooled_buffer_range agc_static_buffer agc_write_tracking Validation Error lines per run
Without this PR fails 23 of 23: "a robust read of word 28 of a 100-byte pooled buffer returned 0xabababab, the bytes a 256-byte earlier user left in its memory" bus error 3 of 3 (AGC driver memory at 0xfe0000000 is held by Rosetta) fails 3 of 3: "a block the import window did not touch reads as written" 1
With this PR passes 23 of 23 same bus error 3 of 3 same failure 3 of 3 1

On each side, I ran all three tests 3 times and agc_driver_pooled_buffer_range alone 20 more times. The one validation error on both sides is VUID-VkDeviceCreateInfo-pProperties-04451. It comes from the test fixture, which creates its own device without enabling VK_KHR_portability_subset (#10920). The PR adds no VUID-. agc_static_buffer and agc_write_tracking fail the same way without the PR and do not involve Resources.cpp.

Notes (non-blocking)

  1. Tested: "reported no VUID messages". With the same SDK, the layer reports this 04451 error once per run of this test on both sides for me (M1 Pro). Gonic0 counted 81 of them across 89 tests on an M5 Pro, even with the MoltenVK ICD marked non-portability. Please recheck how your logs were counted (grep -c 'Validation Error'). If the count is not 0, write "the same validation errors on both sides, none new". CONTRIBUTING's "no new VUID-" requirement is met either way, by my run and Gonic0's.

  2. Resources.cpp:9, 17, 65, 109: __APPLE__ compared with a check on the driver. You asked whether I'd prefer the runtime check, so here is my answer. Yes, but as a follow-up, not on this PR. main has no __APPLE__ block in Resources.cpp or anywhere under Graphics/src. It keys MoltenVK workarounds on driverID == VK_DRIVER_ID_MOLTENVK at runtime (lines on current main: VulkanDevice.cpp:1296, 1300, Float32FlushControl.hpp:15, FragmentBarycentric.hpp:9, TessellationPipelineCache.hpp:9). It also keys some on robustBufferAccess2. A flag on Graphics::Context set from one of those would match main and would cover KosmicKrisp. KosmicKrisp is also listed on this Mac and reports robustBufferAccess2 = true, so it does not need the clear.

  3. Resources.cpp:66: std::min<VkDeviceSize>(capacity, allocationBytes) is always capacity. ClassAllocationBytes never returns less than capacity, and slab allocations set allocationBytes = capacity (line 105). For a non-slab allocation, the mapping starts at offset 0 and covers the whole allocation (line 118). So allocationBytes is the exact bound of the clear. Passing it would remove the std::min and also clear the alignment padding past capacity. I found no way a different-sized earlier user leaves bytes in that padding, so this is a simplification, not a bug.

  4. Scope of the workaround. The clear covers host-visible, host-coherent pooled buffers that are mapped. It does not cover:

    • pooled Buffers in device-local memory, which have no mapping;
    • DeviceBuffer;
    • a fresh non-slab allocation (line 118). Vulkan does not promise zeroed memory there. I have not tested whether MoltenVK returns stale bytes in that case;
    • a read that goes past capacity.

    My probe on the earlier head showed that MoltenVK binds 100 bytes for this buffer but serves a read at byte 112. So the read is not limited to the bound range. If it is only limited by the VkDeviceMemory, a read past the slot in a 2 MiB slab block would return a live neighbour's bytes. I have not tested that. A line in docs/dev/debt/libSceAgcDriver.md would record what this change leaves open, next to the other MoltenVK entries there.

AI-assisted: yes (Claude Code applied the PR, ran the tests, read the code with two independent reviews and wrote this review).

@iamthehimansh

Copy link
Copy Markdown
Contributor Author

@maximilianfv Thanks for the approval and for checking the counts. You were right about note 1. My Rosetta wrapper never pointed the loader at the SDK's layer folder, so the layer was missing in those runs and the 0 meant nothing. I fixed the wrapper and re-ran with the loader's layer output on:

Resources.cpp Runs Layer loaded Validation Error per run Test
main 3 3 of 3 1 (VUID-VkDeviceCreateInfo-pProperties-04451) fail 3 of 3
this PR 3 3 of 3 1 (the same) pass 3 of 3

That matches your M1 Pro count. The description now says the same and names the fixture as the source (#10920).

I'll leave notes 2 to 4 for a follow-up once this lands: the driverID flag on Graphics::Context, passing allocationBytes, and a debt line for what the clear does not cover.

AI-assisted: yes (Cana)

@adriadam10 adriadam10 self-assigned this Oct 27, 2026
@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 27, 2026 08:54

Dismissing a stale review: the requested changes were addressed after it (new head) and the merge queue blocks on it. Please request changes again if anything is still open.

@adriadam10
adriadam10 merged commit 2a46260 into boykopovar:main Oct 27, 2026
18 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the diff (Apple-only, bounded by both capacity and allocation size, both pooled paths covered, text outside __APPLE__ equal to main), the A/B runs on M2 Pro / M1 Pro / M5 Pro (agc_driver_pooled_buffer_range fails on main, passes with the change, no new VUID-) and the Windows run; earlier requests on older heads are met by this head; merged.

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.

7 participants