Skip to content

AGC: BDA written-page tracking has bounded capacity and an eight-probe collision limit #3037

Closed

Description

@Amoenus

Current-main correction (2026-10-12)

On main 8fa5cc486, merged #3175 and #3472 have changed the tracker: 2,048 slots now record chunks of 32 pages (128KiB), with eight probes. The older 4,096 individual-page source/captures below remain historical evidence; they do not establish a current-main title overflow. Main still has an explicit overflow refusal, now more than 2048 chunks of 32 pages stored to through the BDA table in one use are not implemented. The old full-address fix is therefore already upstream. Reproducing the remaining bounded-collision case requires nine colliding chunk keys and fresh title evidence before proposing the older per-range bitmap as a fix. No current-main GPU/title reproduction of that remaining limit is claimed.

Capability gap

The BDA written-page tracker can overflow even while almost all of its 4,096 slots are empty. It probes only eight slots. The GPU store still succeeds, but BdaResources::markWrittenPages throws before publishing any recorded pages:

AGC graphics: more than 4096 pages stored to through the BDA table in one use are not implemented

That message is also misleading for a collision overflow. Both the capacity and probe limits prevent the host from safely completing write tracking. Suppressing the overflow check would lose tracking; increasing capacity alone would retain the bounded-collision failure.

Source / independent GPU reproduction

Main 262c031692a0ce538707436fa1587b671ace682b, Release / BUILD_TESTING=ON. Windows 11 Enterprise 26200, Ryzen 9950X3D, NVIDIA RTX 5080 (driver 32.0.16.1062), WinLibs GCC 15.2.0.

Relevant source:

  • core/shader/recompiler/BdaAbi.hpp: WrittenPageSlots=4096, WrittenPageProbes=8, WrittenPageShift=12.
  • core/shader/recompiler/SpirvBackend/src/SpirvBdaLookup.cpp, DefineBdaNoteWrite: hashes encoded page numbers, checks eight slots, then sets WrittenOverflowWord.
  • core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp, markWrittenPages: refuses nonzero WrittenOverflowWord before visiting the recorded slots.

A new local test on that main uses the existing MakeBdaDwordWriteTestShader and Vulkan Pipeline helper in tests/BdaExecution.cpp:

  1. Find nine page numbers with the same bucket: ((uint32_t(page + 1) * 0x9e3779b1u) >> 20) == bucket, starting at page 0x200000.
  2. Build nine sorted writable BDA ranges, each exposing eight bytes backed by a distinct 16-byte portion of a real GPU buffer initialized to 0xa5. Guest address=uint64_t(page) << 12; each range points at its own device-buffer offset.
  3. Initialize one fault buffer, then perform nine synchronized dispatches through the existing real BDA store emitter without clearing that fault buffer between dispatches. Each writes two known dwords. Repeat the first write once as a duplicate-page control.
  4. After each dispatch, assert exact stored dwords, unchanged adjacent sentinel bytes, empty access fault record, occupied-slot count, and overflow flag.

Results for both bucket 0 and bucket 4095 (the latter exercises wraparound): writes 1–8 occupy 1–8 slots with overflow=0; write 9 leaves exactly 8 occupied slots and sets overflow=1. All nine writes reach their GPU-backed memory, adjacent bytes remain unchanged, and the access fault remains Empty.

agc_driver_bda_device and agc_driver_graphics passed five repetitions each after a full system-library build, with no skips and under 30 s per invocation. A mutation that changed only the overflow publication from 1 to 0 fails the new test with BDA write tracking did not report its probe limit. The source was restored byte-for-byte, full libraries rebuilt and both groups repeated five times successfully. These are native NVIDIA Vulkan BDA writes through AnyPS5's producer, not a CPU simulation or a console-semantics claim.

Conditional title observation: capacity also exhausted

PPSA99902 / Sonic Unleashed Recompiled 01.003.000, on the local prerequisite stack described in #2970 and the validation comments on #1825/#2756, based on main 43d2a7c (aggregate 60d016a). This is not clean-main startup evidence; clean main stops earlier at imports.

Five controlled post-partial-dispatch startup runs log the deferred write-back refusal before the later missing multisampled descriptor-shape fatal stop. Two temporary diagnostic captures save the whole 16,420-byte BDA fault buffer. Both contain:

  • WrittenOverflowWord=1;
  • all 4,096 slots occupied, with 4,096 distinct encoded pages;
  • recorded page addresses between 0x407fa00000 and 0x4080a54000;
  • recorded probe distances 0: 3,749 pages, 1: 209 pages, 2: 138 pages;
  • all eight access-fault header dwords zero.

The capture proves 4,096 distinct recorded pages and overflow; it does not identify the unrecorded failed insertion or prove its address/count. The native nine-page test above separately proves the collision-limit case.

One diagnostic run timed out at 60.345 s and needed later manual baseline restoration after a file lock; the other completed at 29.909 s. Instrumentation was removed byte-for-byte, full libraries rebuilt, relevant restored tests repeated, and 123 baseline runtime files, guest libc and original eboot hashes verified. No rendering/gameplay claim. No new PR or branch push.

Existing work / fix boundary

Searched existing issues/PRs for WrittenPageSlots, WrittenOverflowWord, BDA+overflow, written-page, and the 4096-page refusal. No matching tracker-overflow issue or open implementation was found. #1265/#1280 optimize the existing probe loop; #2447 covers store boundary faults/written pages, and #2972 concerns Intel compiler scratch usage rather than this runtime overflow.

The fix needs lossless write accounting beyond the current capacity and collision limits, while preserving access faults, page invalidation and CPU/GPU coherence. A larger table is only a mitigation; turning this refusal into success without accounting for omitted writes is unsafe. Investigation of scalable tracking is continuing locally.

Investigated and written with AI assistance (Codex). No title shader or dump attached.

The later materialized MSAA image-shape refusal is separately tracked in #3036.

Activity

  1. Amoenus commented on Oct 10, 2026

    @Amoenus
    ContributorAuthor

    Local follow-up: I tested an experimental replacement for the bounded written-page hash table. It remains local; no PR or branch push.

    Proposed approach

    • Give each writable BDA range a packed page bitmap; read-only ranges consume no bitmap words. The header carries the total word count and each writable range carries its offset.
    • After a successful address lookup/store, atomically OR the corresponding bit. The host consumes only set bits, marks those guest pages written, then clears the bitmap.
    • Bump BDA ABI 1 to 2 so legacy tables/shaders cannot be reused. Existing request keys include the ABI version and the source hash includes the changed shader/ABI files. Respect maxStorageBufferRange; malformed layout and unsupported sizes throw.

    Validation on pinned main 262c031692a0ce538707436fa1587b671ace682b
    Windows 11, Winlibs GCC 15.2 Release, RTX 5080, required Vulkan. Full 120-system-library build; 37 selected groups passed five repetitions (185 invocations), no test skips, maximum 4.03 s. Checks include nine pages colliding in the old bucket-0/bucket-4095 hash, duplicate-page writes, 4,353 distinct pages in one use, actual GPU-written values and unchanged surrounding bytes, aligned/unaligned page crossings, exact host page stamping/clearing, ABI-1 refusal, truncated fault buffers, invalid bitmap offsets/counts/tail bits, and storage limits.

    Independent mutations disabling GPU bit recording and host page stamping were both caught. Sources were restored byte-for-byte, full libraries rebuilt, and tests passed again. Convention checks passed.

    Conditional title comparison
    PPSA99902 (Sonic Unleashed Recompiled 01.003.000), using the existing local prerequisite stack rebased onto the same pinned main. Only libSceAgcDriver.prx differs between profiles; the same strict-filter executable, guest module and other runtime libraries are retained. This is conditional diagnostic evidence, not vanilla-main startup.

    Both before controls reproduce the overflow warning (30.254 s, 30.032 s). Five completed after runs omit both the overflow warning and deferred write-back failure (29.015, 29.400, 29.106, 29.040, 29.352 s), but all still terminate at the multisampled descriptor-image-shape refusal tracked in #3036. The initial modified launch timed out at 60.356 s and is excluded from that comparison; its restoration needed 30 attempts, then hash checks passed. A default-conversion control still fails at unresolved import oc98+zOImus from libkernel_sys.prx (0.580 s). No successful startup, presentation or gameplay is established.

    Full libraries were also built for both aggregate profiles: before 39 groups x5 (max 4.15 s), after those groups plus the empty-registry prepared-graphics group, 40 x5 (max 4.10 s). No test skips; registration covered 566 executable targets with no built/unrun targets. Baseline 123 runtime files, guest libc and original eboot were restored/hash checked after the runs.

    This validates internal write tracking. Console behavior and Linux/lavapipe are unmeasured; it is an experimental candidate, not a production ready compatibility claim. The bitmap scales with mapped writable ranges rather than the number of pages actually written, so its memory/scan cost should be reviewed on other workloads. AI-assisted; I reviewed the changes and ran the checks above.

  2. Amoenus commented on Oct 11, 2026

    @Amoenus
    ContributorAuthor

    Upstream tracker changed: original reproduction needs a fresh comparison (2026-10-11)

    Main now includes #3175. I verified the ABI, emitter and host-consumer GitHub blobs at pinned f60923bf.

    The old 4,096 individual-page slots have become 2,048 32-page chunk slots, with eight probes, 64-bit chunk address reconstruction, an explicit writable-address limit, and a list of claimed slots. The host consumes contiguous runs from each chunk mask. Upstream also adds decoder tests near the top of the address space and above 16 TiB, plus 64-workgroup repeated writes.

    Consequently, this issue's old 4,096-page capacity result and nine colliding page-key reproduction are historical evidence on their stated base, not measurements of current main. The source still has a bounded chunk/probe refusal, but its actual title behavior and a regenerated colliding-chunk GPU control need to be retested before claiming the same current failure.

    Our experimental integration retains the separately validated range-sized bitmap ABI consistently across shader, host and tests. Its current integration tests therefore do not independently validate upstream's chunk tracker, and the prior title improvement cannot establish that the bitmap is still necessary after #3175. I have retained the existing historical results and will compare the implementations before proposing further changes or closing this issue.

    This is a verified source update, not a new GPU/title result. No PR created. AI-assisted investigation: Codex.

  3. changed the title [-]AGC: BDA written-page tracking overflows at capacity or after eight colliding pages[/-] [+]AGC: BDA written-page tracking has bounded capacity and an eight-probe collision limit[/+] on Oct 12, 2026
  4. oneandonlydean commented on Oct 14, 2026

    @oneandonlydean
    Collaborator

    Closing: #3175 and #3472 replaced the page slots with 2048 chunk slots, and no title on main is known to reach the remaining overflow refusal. If one does, comment here with the title ID and the message.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions