The graphics pipeline store kept a fixed 1024 entries and the pipeline journal replayed at most
1024 records, and a replayed pipeline that found the store full was dropped. A title whose
working set is larger rebuilt the rest at the draw on every run, and evicted pipelines it kept
using were rebuilt again and again.
The bound now starts at 1024 and doubles up to 4096: a journal replay that does not fit grows it
instead of dropping the pipeline, and so does eviction churn (a ghost list of evicted keys, checked
every bound/32 evictions: more than half of them came back). It never shrinks during a run;
APS5_PIPELINE_STORE_ENTRIES still fixes it. The journal loads its last 4096 records by default
(APS5_PIPELINE_JOURNAL_ENTRIES). Resident pipelines are bounded by count, not bytes.
agc_driver_recorder_tests --benchmark-pipeline-store-memory [count] (opt-in, not registered)
builds count graphics pipelines with one fragment module each through CachedPipeline and prints
process private bytes and VK_EXT_memory_budget heap usage before and after: with the triangle
shaders about 59 KiB host and no device memory per pipeline on an RTX 4090, 100 KiB host and
8 KiB device-local on a UHD 770.
Tests: agc_pipeline_store_growth (fails on main), agc_pipeline_store_fixed_bound,
agc_pipeline_store_replay_growth (replays on one thread: the mock Vulkan is not thread-safe).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017SNUYRd7sRKZrMYpquAsnU
The range, direct offset and protection checks of the AMM map, unmap,
remap, multimap and protection commands, and the records they build,
move from libSceAmpr's Export.cpp into the APR command buffer header
that libSceAmpr and libkernel already share. libSceAmpr records the
same bytes and returns the same errors in the same order; the kernel
writers can now encode the same records without a second copy.
The debt-file rule read only the first line of an added entry, so an
entry whose first core/ link sat on an indented continuation line was
rejected in docs/dev/debt/<name>.md and accepted in the index, the
opposite of what debt_split.py does with the same entry. The rule now
joins the continuation lines as debt_split.parse does before naming
the file.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
fcntl(fd, F_DUPFD, min) with min at or above RLIMIT_NOFILE duplicated the
descriptor repeatedly until the CRT table was full and then failed with
EMFILE, after opening thousands of descriptors. FreeBSD checks the minimum
first: kern_dup returns EINVAL for FDDUP_FCNTL when new < 0 or
new >= maxfd (sys/kern/kern_descrip.c:836-840). Linux already answers
EINVAL through the host fcntl. Check the minimum against the guest
RLIMIT_NOFILE before the Windows loop.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
sceUsbdGetDevice, sceUsbdGetDeviceSpeed, sceUsbdGetMaxPacketSize,
sceUsbdGetMaxIsoPacketSize, sceUsbdOpenDeviceWithVidPid,
sceUsbdSetInterfaceAltSetting, sceUsbdClearHalt and
sceUsbdDetachKernelDriver follow FreeBSD 11.4 libusb10.c over the host
libusb. The packet sizes and the alternate setting checks are done as
FreeBSD does them, so the library records which interfaces are claimed,
and a device stays known while a handle to it is open.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A compound statement that is not a conditional, loop or lambda always runs, so its calls are collected like the body's own and its exits stop the scan. An export whose only throw sits in such a block stays a stub and fails a strict audit.
A conditional return, co_return or goto before the throwing call leaves a path that succeeds, so the audit stops collecting calls at it and the export, or a helper it calls, is partial. A guard that throws still leaves every path throwing, so it stays a stub.
The previous check called any stub call inside an #if block partial,
so an export that throws in every #if, #elif and #else alternative was
reported as partial. Each combination of preprocessor branches is now
expanded into its own body, an #if without #else gets an empty
alternative, and the export is a stub when every combination reaches
the throw. More than 256 combinations in one function, or unbalanced
directives, are reported as errors.
A NotImplemented_nid_no_patch call inside an #if, #ifdef or #ifndef block
opened in the export's own body runs only on some hosts, so the export
is partial, not a stub. An export wrapped in a preprocessor block from
outside its body keeps its class.
An export whose body reaches NotImplemented_nid_no_patch on every path,
directly or through a helper that always reaches it, stays a stub. One
that reaches it only under a condition (if, else, loop, switch, ternary,
short-circuit or lambda) is now classified as partial, so implemented
exports with guards for unknown behaviour are no longer counted as stubs.
progress.py and the progress page are unchanged.
An ITIMER_REAL expiry with a guest SIGALRM handler throws, since guest
handlers are never run from the timer thread. On Linux and Windows that
throw goes through libc's exception runtime, so an uncaught one runs the
guest's std::set_terminate handler. On macOS the host keeps libc++abi,
so the throw ended in libc++abi's terminate and the guest handler never
ran. Throw through the guest runtime there, and record the general
macOS gap in the libc debt file.
Ported from #8658.
Co-authored-by: Himansh Raj <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>