MemoryIndexBelongsTo walked every instruction of the program and asked
BufferAccessOf, AddressOpcodeInfoOf and ImageOpcodeInfoOf about each one.
TryMakeTableImage calls it for each of a table handle's eight descriptor
dwords and once more for a material key, and PlanIndirectImages calls
TryMakeTableImage for every image instruction whose handle is not planned
yet, so each such instruction could cost up to nine walks of the whole
program. PlanIndirectImages now indexes the instructions that access
memory by their memory index once, before it plans, and
MemoryIndexBelongsTo looks the index up while that pass runs. The
planning pass adds, removes and changes no instruction, so the index
stays valid until the pass drops it at its end. Outside the pass, where
the function has no caller, it walks the program as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
Add the guest socket descriptor guard to sceKernelFdatasync before any
native syscall, matching the existing sceKernelFchmod pattern. This
prevents high guest socket descriptors (e.g. 0x10000000) from reaching
the Windows CRT _commit, which would trigger the invalid-parameter
handler, and ensures the correct SCE error is returned on all platforms.
Refactor fdatasync_nid_postfix to delegate to sceKernelFdatasync via
PosixResult, removing the duplicated socket guard and native syscall.
Extend GuestFilesystem tests to cover:
- sceKernelFdatasync on a valid guest socket returns SCE EINVAL (0x80020016)
- sceKernelFdatasync on a closed guest socket returns SCE EBADF (0x80020009)
- sceKernelFdatasync on a high invalid descriptor (0x7fffffff) returns SCE EBADF
- closed-ordinary-descriptor tests now run on Windows (verified _commit on
a closed descriptor returns EBADF without provoking the CRT handler)
A recorded dispatch or draw whose use needs completion work goes into the
copied-writer lists (VulkanDevice's copiedWriters, DrawCopiedWriters).
Fills, buffer copies and indirect argument reads treat every write of a
listed object as a store the CPU makes when its batch is reaped, and wait
for that batch. Completion work is also needed for the BDA fault check
alone, and the written sub-ranges of GPU copies are copied back by the
GPU (RecordCopyBacks) before the listing. Such an object has no CPU
write-back (HasCopiedWrites is false), and its stores are ordered by the
barriers like direct writes, yet it was listed and waited for.
Both lists now take an object only when HasCopiedWrites holds after
MarkGpuWrites. The completion action still runs for the fault check.
agc_driver_copied_writer_copy records a dispatch that reads through
global (BDA) loads and stores through a bound buffer into registered
memory, then copies that output on the GPU (VulkanDevice::CopyBuffer)
before the batch ran. It checks that the copy is a transfer between the
host imports without a sync, and the copied bytes. Without the change the
copy waits for the dispatch's batch.
PPSA14632 (LEGO Horizon Adventures): every one of the DMA_DATA copy
waits counted over 75 s came from such writers. Queue 0's GPU-mutex hold
for copies drops from 3.5 s to 30 ms per 10 s, and presents rise from
about 102 to 109 per 10 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An upload of a storage image up to 1 MiB reads the surface's guest bytes
into a baseline (#1599), which a later write-back compares against for
blocks the CPU stamped. When a unit shadow, another image's pending
results or a recorded GPU store covers the surface, that read goes
through the flush hook: the shadow is published and the worker waits for
the GPU, although the upload itself runs on the GPU from the import.
The baseline is now taken only when nothing of that kind overlaps the
surface. Otherwise the image has none, and its write-back keeps the CPU's
bytes for stamped blocks, the rule before #1599. TechnicalDebt's entry
names the condition.
agc_storage_baseline_settled creates a storage image over a watched host
import with a recorded store pending over part of it and checks that the
upload did not submit the open batch; without the change the baseline
read syncs on that store. agc_unchanged_cpu_stamps (#1599) still passes.
PPSA14632 (LEGO Horizon Adventures) refreshes such images about 870
times per 10 s from queue 0 dispatches: the hook's recorded-store syncs
go from about 3400 waiting 4.0 s to about 630 waiting 0.3 s per 10 s,
and a direct dispatch's device call from 356 to 107 us.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A build's sampled image elements often repeat the element just before them
in plan order (the same eight descriptor words). Each still took its own
stage-A record (decode, collect, DCC key read, cache entry) and its own
stage-B lookup.
The lookups run in plan order and nothing runs between two consecutive ones,
so a repeated element's lookup would find the state the previous lookup just
left: the entry it validated or made, current against the same collect. Such
an element now binds the previous element's texture (when the depth-compare
flag matches too, which is part of the cache key), and stage A gives it a
record of the decoded descriptor only. An element repeating an earlier but
not the immediately preceding one still takes its own lookup, as does any
element after a storage image lookup.
A resource build decodes every image element's eight descriptor words,
describes the surface (a mip layout with a vector of levels) and finds the
sampled texture's cache entry by hashing its key, once in stage A and again
in stage B. Most elements repeat a small set of descriptors from build to
build.
Each worker now keeps a small direct-mapped memo by descriptor words of the
decoded resource, its guest size and view component mapping (pure functions
of the words) and of the cache entry the words last found. The entry is
reused only while the sampled texture cache has erased no entry since it
was found (an erase counter bumped by eraseTexture), so the list iterator is
still valid and still holds the same key; anything else takes the hash
lookup.
Buffer and DeviceBuffer created their VkBuffer at the pool's size class, so
a robust read past the requested size stayed in bounds and returned the
bytes an earlier user of the pooled memory left there instead of an
out-of-range result. Each VkBuffer now has the requested size, created with
the pool's usage, over memory of its class (or its slab slot); one taken
from the pool for a different size is recreated over the same memory at
the same offset. agc_buffer_pool follows a pooled buffer by its memory, so
its class and reuse cases hold with the exact-size buffers.
Intersect copied fixed-point sets without allocating each intermediate.
Preserve sorted results and the existing analysis iteration order.
Compact oversized storage only when publishing a changed set.
Keep an allocating control switch outside the cache key.
Compare complete fixed points over empty intersections, loops and DAGs.
Register the same equivalence fixtures for the allocating control.
A DMA_DATA copy larger than the store limit runs as a transfer between
host imports. When the destination is not registered guest memory (the
GPU can neither read nor write it, so no import exists), the transfer
was refused and the packet drained the device: every batch recorded so
far was submitted and waited for, then the CPU copied.
Such a copy only has to see the source's results. When CopyBuffer
refused for want of an import (path 2) and nothing can still store over
the destination (VulkanDevice::StoresPendingOver: a recorded write, a
label, a copied write-back, a storage image's pending write-back or a
unit shadow), the packet now flushes storage results pending over the
source and waits for the batches that write it (Recorder::SyncThrough),
then copies on the CPU without a drain. Anything else still drains as
before. The [sync] line counts these copies.
agc_driver_tests copies a buffer the GPU just wrote into host memory,
once directly and once behind a label into the destination, and checks
the bytes and that the copy did not drain.
PPSA14632 (LEGO Horizon Adventures) copies 256 KiB of GPU results into
host memory this way, about 50 times per 10 s: device drains drop from
about 50 to none per 10 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TestUnsyncedVideoKeepsUpWithAudio built its player without an event
callback, which enables auto-start (autoStartEnabled = init.auto_start ||
callback.event_callback == nullptr). The controller thread then enables the
streams and starts playback shortly after the source is added, racing the
test's own sceAvPlayerEnableStream. When the controller won,
Source::EnableStream saw started == true and the test failed with "enable
video failed".
The test now passes an Events object, as the other tests that drive the
player by hand do.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Jelly5 (PPSA99505) imports sceAudioOutExGetMonitorInfo (PhnNN0UEV6Y),
sceAudioOutExConfigureOutput (VcE+gXSwFXI), sceAudioOutExOpen
(6X6dp+07h4U) and sceAudioOutExClose (0TfjSulCV2A) from libSceAudioOut
and fails to load because no library exports them. It calls them only
for its optional HDMI bitstream output, and their signatures are
unconfirmed, so they are exported without an implementation and throw
if they are called, like sceSaveDataCancel.
Without MAP_FIXED the address is a hint, as for FreeBSD's mmap: the reservation goes at the hint when that range is free, else at the first free range above it. The hint was dropped, so every hinted reservation landed at the lowest free address.
RetroArch (PPSA99169) reserves its cores' direct-memory heap near 0x2000000000 outside the GPU window (0x200000000-0x300000000) and refuses a range inside it; with every reservation at 0x200400000 the heap was never made, and Dolphin's first aligned allocation returned null.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every draw into a resident target refreshes the target's storage image
(StorageTexture::Refresh): a pending-registry scan for an alias,
FlushPending over the surface, the collect, the key proof, a
whole-surface UnchangedSince, four vectors and a generation update over
every 64 KiB unit, nearly always finding the image current.
A refresh that finds the image current now keeps a proof: the image's
generation and uploaded keys, its key proof's generation and the pending
registry serial. The next refresh is answered from it when the image's
own state is still that, the surface and its key range are unstamped
since those generations (CollectWrites over both, then one
UnchangedSinceAll), and no other image has results pending over the
surface: the registry serial is unchanged, or the registry is scanned and
holds none (a newly pending image, a flush in progress). The proof is
taken only under GuestMemory::GpuMutex, without a pending alias, with
stable keys and when no other pending image overlaps the surface; any
full refresh drops it. A CPU write, a driver or GPU store, a key store,
another image's pending results or a different image therefore always
refresh in full, and the image's own pending results (a draw into it)
keep the proof.
agc_driver_recorder_tests refreshProofTests: an unchanged image and one
holding its own pending results are proved; a CPU write, a driver store
into the surface, a key store, another image's pending results, a new
image of the surface and a fast clear of the keys each refresh in full.
A geometry (NGG) strip draw on the mesh path assigned every primitive the next index positions without looking for the restart index: a triangle spanning a restart fetched vertex 0xFFFF and was drawn to wherever that read landed, and the strip parity never reset, so after an odd-length strip every triangle's winding flipped and face culling dropped it.
For an indexed strip draw with restart enabled and the restart index present, the draw now records each position's strip start (one past the last restart at or before it) and passes the table after the mesh arguments, flagged in draw word 3. The mesh prologue reads the start of its primitive's last vertex: a primitive whose start lies past its first vertex spans a restart and is made degenerate, and the others take their parity from their strip's start (or the draw's first index). The restart vertex itself fetches vertex 0. The table is built from the index bytes the draw already reads (its copy, or the guest memory an in-place import reads), next to #1968's restart-aware highest-index scan.
The new Mesh.cpp test draws quads as four-index strips each followed by a restart (16- and 32-bit indices, and an indirect draw starting mid-buffer) with front faces culled: every quad must render whole and the gaps between them stay clear. Without the table the first check fails ("a triangle crossed the restart after quad 0").
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <[email protected]>