A hang with threads in Driver::waitMemory takes a ten-minute boot to
reproduce, and the existing switches show only the first submissions of
one queue (APS5_DUMP_QUEUE) or a rejected one (APS5_DUMP_REJECTED).
Capture. APS5_DUMP_SUBMITS=<dir> writes every submission the driver
accepts. <dir>/index.txt starts with "aps5-submissions 1" and "ordinal
queue type serial time_us dwords kind file held conditional", then one
line per submission: acceptance ordinal, queue id, graphics or compute,
serial, microseconds on the APS5_TRACE_GPU clock, dword count, kind,
file name, the dword offsets of the waits held at submit
(Submission::heldAtSubmit) and the COND_EXEC and predicated indirect
buffer ranges (Submission::conditionalEnds) as start:end, "-" for none.
<dir>/submission_<ordinal>.bin holds the copied dwords, indirect
buffers inlined. REWIND tails are listed as rewind with the serial of
their submission, suspend points as suspend without a file.
SubmissionCapture::Prepare copies the commands before the driver lock;
Take, under it after reserveOutputs, sets only the ordinal, time and
held waits and does not allocate; the file and then its index line are
written after the lock. A failed write reports the failure to the driver
(Driver::ReportFailure) and throws, so every later driver call throws
too. Unset, Submit tests a static pointer. An index written before the
held and conditional columns ("ordinal queue type serial time_us dwords
kind file") is read with both unknown.
Analysis. agc_pm4_replay <dir> [--full] decodes each submission and
throws on a packet the driver rejects (a corrupt capture). It does not
model cross-queue timing: a wait is released only when the capture
alone proves that the driver releases it (WaitMemory.cpp,
LabelHistory.cpp), and unknown, with the reason, when its outcome
depends on when another queue's stores run. A memory wait is released
when its compare always passes, when it was held at submit, when the
last store to the awaited bytes before it on its queue covers them with
an accepted value, is not conditional and no other queue stores to
those bytes anywhere in the capture, or when a RELEASE_MEM or
WRITE_DATA label earlier in the waiting submission that the label
history keeps (KeepsLabelStore, not conditional) stores an accepted
value and fewer than LabelStoreHistory other stores to the awaited
bytes can run between it and the wait: those between them on the
waiting queue and every store of another queue to those bytes, since
the history keeps the last stores per dword over all queues and finds
only a store that ran after the waiting submission entered the driver.
A REWIND is released when its tail is in the capture; one valid at
submit without a captured tail is unknown, because the driver reads the
valid bit again when the queue reaches it. A wait is unreleased when it was not held at submit and no
store that can run before it may write an accepted value (a store of
part of the awaited bytes or of a run-time value may; a store later on
its queue runs after it): the GPU does not release it, a CPU or shader
store could. A REWIND without a tail that was not valid at submit is unreleased. Every other wait is
unknown: "depends on cross-queue timing (stores on queue X at #...)"
when another queue stores to the awaited bytes, otherwise because the
last store before it on its queue is conditional, stores a run-time
value or part of the bytes, or overwrites an accepted store the capture
does not prove still in the label history. Without the held column a
wait that would be unreleased is unknown; without the conditional
column every packet after a COND_EXEC or predicated INDIRECT_BUFFER is
conditional to the end of its submission. The outcomes depend on the
order of each queue's stores and on which stores other queues make, not
on the submit order across queues. The report counts the outcomes,
routes and cross-queue unknowns, says when the index lacks the columns,
lists up to 32 unreleased and then unknown waits newest first with the
packets around them and their stores, then the last submissions of
each queue; exit code 1 when a wait is unreleased, 2 on errors.
Pm4 gains DecodeStores, DecodeWait, DecodeConditionalWrite,
DecodeConstantRamDump, the COPY_DATA and DMA_DATA field helpers, the
sample dump layout and RewindValid; Execute, ResolveStore, Validate,
DecodeMemoryCopy, the driver's sample dump, noteHeldAtSubmit and
executeRewindTail use them. LabelStoreHistory and KeepsLabelStore move
to LabelStores.hpp, used by noteLabelStore and the analysis.
Test. agc_driver_pm4_replay submits command buffers built with the
libSceAgc builders through the driver with the switch set and checks the
index, the held waits, the COND_EXEC range, the dwords, the decode and
the outcome of eight waits, including WRITE_DATA 1, WRITE_DATA 0,
WAIT == 1 in one submission, which the driver releases through its
label history. Hand-made captures check each release route, a store of
another queue to the awaited bytes that entered before or after
(unknown), the label history limit with later stores on the waiting
queue or on another queue, a fill the history does not keep, a store
in an earlier submission overwritten later, later stores, rejected
values and a missing rewind tail (unreleased), the six captures of the
reviews (none released but the wait held at submit), five groups of
waits in eight submission orders with identical outcomes, an index
without the held and conditional columns and corrupt captures. A
directory in place of the next submission file makes the write fail,
and the submit, the next submit and shutdown throw.
agc_driver_pm4_replay_tool runs the tool through
tests/Pm4ReplayTool.cmake and checks exit codes 1 and 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A registered range larger than 64 MiB is imported one 64 MiB window at a
time for buffer and fill accesses instead of whole, and the live imports
stay under a budget: APS5_HOST_IMPORT_MIB, else the larger of 6 GiB and
half the physical memory, at most the largest host heap less 2 GiB
(imports and type-3 mirrors share it; #167's rule). An import past the budget
retires the least recently used imports not used in the last 256
lookups.
- An access across window edges imports the cells it touches, grown to
the union of the range's imports it overlaps, so uses across
neighbouring edges share one import instead of replacing each other.
- An address-based build (AcquireRegistered) imports the range whole; a
window over its first byte does not serve it.
- Every refusal (alignment, a failed run, a held import, the budget with
what may be evicted) is decided before anything is retired.
- An import made when a range is mapped never evicts: the map observer
holds no GPU lock.
The window size and the budget have setters for the tests.
The mimalloc in PS5MC (PPSA99420) reads vm.overcommit at start-up and
stopped on the unsupported name. It reports FreeBSD's default, 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dolphin_libretro core of RetroArch (PPSA99169) reads {CTL_HW,
HW_REALMEM} as the machine's memory size and stopped on the unsupported
MIB. It reports the same value as hw.physmem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5CEMU-HAR (PPSA99360, ps5-payload-dev build, relinks with
--trap-sdk-syscall-stubs) imports the data objects sys_siglist
(yCdGspbNHZ8) and sys_nsig (ZD+Dp+-LsGg) from libSceLibcInternal.prx;
the import audit lists both as absent (registry entries 364 and 378,
after pthread_setaffinity_np at 276). No run has reached them yet; the
predicted stop line is
FAIL: unresolved ELF import ZD+Dp+-LsGg (sys_nsig) from libSceLibcInternal.prx
or yCdGspbNHZ8 (sys_siglist), whichever the loader resolves first.
The table and count are FreeBSD 11.4 lib/libc/gen/siglist.c (8c639945):
32 descriptions, signal 0 to SIGUSR2 (NSIG 32), and sys_nsig is their
number, 32. ps5-payload-dev declares both as objects exported by
libSceLibcInternal and its strsignal reads them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A bound buffer is joined across adjacent registered ranges only when every
one of them is larger than the 8 MiB window. PPSA25344's loading binds 16 MiB
and 4 MiB buffers whose backing was registered in two to six adjacent
pieces with the same access, some of them smaller than the window, so each
use of such a buffer was copied on the CPU: up to 348 GB in the first 300 s
of loading.
A bound buffer that starts at the first crossed range's start and ends at the
last one's end now joins those ranges whatever their size. A heap grown in
pieces matches that only for a binding over exactly its current pieces, so
growth beside a joined import does not re-import it. joinedCovers, used when a
joined import retires, accepts any adjacent run of ranges.
importJoinTests binds exactly over two 64 KiB pieces of a partly filled
window and requires one import of both; a binding across them that starts
inside the first piece stays apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 9-word kernel #3816 matches stores one dword per thread, loaded with
s_buffer_load_dword through the descriptor in user[4..7]. Run as a
compute dispatch, its stores are pending writes of unknown value, so a
later read of DCC keys in that range waits for the GPU.
fillBuffer now takes it as a uniform buffer fill when all of these hold:
- the output descriptor's format is 32_UINT, 32_SINT or 32_FLOAT (20-22),
so buffer_store_format_x stores the loaded dword unchanged;
- the value descriptor covers the loaded dword: num_records times the
stride (num_records alone when the stride is 0) is at least 4 bytes,
the range the recompiler gives a scalar buffer load;
- exactly 8 user SGPRs and group id X enabled, so the group id is s8;
- a direct, non-partial dispatch, 64x1x1 threads, one group in Y and Z,
and a 16-byte aligned range of whole 16-byte units.
The value is read when the fill is recorded, after the labels queued
before it, as the pattern-fill kernel's source is. When the value is one
byte repeated and that byte is a DCC key code, the fill goes into the GPU
key-store memo whatever ClassifyFill reports: the memo answers a key
read only while this fill is still the newest pending write over the
keys, and a fill over a color target's keys is not a storage image's
keys fill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
swscanf (HNnWdT43ues) runs the swscanf_s scanner without the buffer
size arguments after %s, %c and %[, as FreeBSD's swscanf.c passes its
arguments to vswscanf. Both entry points share one engine with a flag
for the sizes; swscanf_s behaves as before.
Title: PS5CEMU-HAR PPSA99360 imports swscanf from libSceLibcInternal.prx
(import audit with --trap-sdk-syscall-stubs, registry index 292, right
after pthread_setaffinity_np at 276). Predicted stop line once
pthread_setaffinity_np resolves, without this change:
FAIL: unresolved ELF import HNnWdT43ues (swscanf) from libSceLibcInternal.prx
NID from tools/nid_names.py, confirmed in shadPS4 aerolib.inl:51950
(e0a466753a1a). Prototype in ps5-payload-dev include/freebsd/wchar.h:133,
export in sce_stubs/libSceLibcInternal.c:8582 (f5d40f02770e).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A 2D array storage view of a depth plane from slice 0 threw. It now gets
a storage image over all its slices: each slice with a depth surface at
the plane's address plus a whole depth slice per layer is copied from
that surface before every use and back after the work; the other slices
hold guest memory like any storage image. Cubes, stencil arrays, later
base slices and layer pitches other than the depth slice size still
throw.
GTA V (PPSA04264) stops at compute shader 0x242d4a200 on a 5-slice R32
Z_64KB_X array over its shadow depth surfaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
PS5MC (PPSA99420, Kodi 22) imports getpriority (miQ1wVwSexE) from
libkernel.prx, one reference, next to chflags in its import order.
Predicted stop once the imports ahead of it are covered (not a run):
FAIL: unresolved ELF import miQ1wVwSexE from libkernel.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 sys/kern/kern_resource.c sys_getpriority
(8c639945a44588f6cfdddfb057bb5aad88735da0) for the only guest process,
taken to run at nice 0 and to lead its own process group (as kill and
setsid already assume): PRIO_PROCESS and PRIO_PGRP with 0 or its pid,
and PRIO_USER with 0 or its uid 0, return 0; other ids give ESRCH and
an unknown which gives EINVAL. getpriority is a bare system call in
FreeBSD's libc (lib/libc/sys/Symbol.map), so errno is left unchanged
on success.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 63262245d274342b693fda146ae9feca3752b6e3)
v_cvt_f32_ubyte0 accepts every SDWA source selector, but the other three byte
conversions had no VOP1 SDWA rule, so any SDWA source select on them was refused
("VOP1 SDWA source selector 4 of opcode 0x12 at pc 0xfcc is not supported").
The selected and optionally sign-extended source goes through the same operand
path as for ubyte0 before byte N is extracted: SDWA supplies source 0 (RDNA 2 ISA
guide 6.8 and 13.3.7) and the opcode reads S0.u[8N+7:8N] (12.8).
agc_driver_sdwa_cvt_f32_ubyte: WORD_0 and WORD_1 selects on ubyte1, and
sign-extended WORD_1 and BYTE_3 selects on ubyte2 and ubyte3.
The rebase onto main deleted docs/dev/TechnicalDebt.md, which is now the
index that points into docs/dev/debt/. Restore it from main and add the
missing final newline to docs/dev/debt/libSceNgs2.native.md.
Resolve a host name to its first IPv6 address through the host resolver
(getaddrinfo with AF_INET6) and write the 16-byte address. The lookup
shares the IPv4 path's resolver bookkeeping (abort preservation, error
state); a name without an IPv6 address fails with RESOLVER_ENODNS like a
failed lookup. Nonzero flags throw.
Metal Gear Solid 4 Master Collection (PPSA18194) imports it and failed to
load without it (unresolved zl35YNs9jnI from libSceNet.prx).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5MC (PPSA99420, Kodi 22) imports chflags (ixrw0h2tWuI) from
libkernel.prx, one reference. Predicted stop once the imports ahead of
it (pthread_getthreadid_np, sendfile, wcstok, pause, setrlimit and the
termios calls) are covered (not a run):
FAIL: unresolved ELF import ixrw0h2tWuI from libkernel.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 (8c639945a44588f6cfdddfb057bb5aad88735da0)
sys/kern/vfs_syscalls.c kern_chflagsat and setfflags, and
sys/ufs/ufs/ufs_vnops.c ufs_setattr, for a root caller: the lookup
through the guest resolver gives EFAULT, ENOENT and ENOTDIR (also for a
file reached through a trailing slash or used as a directory), a bit
outside the 17 UFS flags of sys/sys/stat.h (VNOVAL included) gives
EOPNOTSUPP, and SF_SNAPSHOT gives EPERM, since the snapshot flag cannot
be toggled. No host keeps BSD file flags and stat reports st_flags 0,
so flags 0 succeed without touching the file and any other flag throws
with the flags named. UF_READONLY and the other Windows-attribute flags
are not mapped to FILE_ATTRIBUTE_READONLY and its siblings: UFS stores
them without enforcing them, and on Windows the read-only attribute is
already the owner write bit of chmod.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 16e5b7ccd04e6467a5a282b6cfc9271304f42244)