A handler's blocks are now holds kept apart from the mask sigprocmask sets, and released when the handler returns, so a sigprocmask(SIG_BLOCK) from another thread meanwhile survives even for a signal in the handler's sa_mask. sigprocmask reports the holds; SIG_UNBLOCK of a held signal releases its hold and SIG_SETMASK, which longjmp uses, releases them all, while sigsuspend's own restore keeps them.
A draw that hits through a relocation alias reuses its donor's stages
under a key the cache doesn't hold. It isn't inserted, since
cacheDrawStages skips hits, and lookupDraw moves an entry up the LRU
only when the entry it hit is the one cached under the draw's own key.
So a donor that serves a relocated draw every frame ages to the back of
the LRU like an unused entry, and the cache evicts it once other draws
insert. Since aliases name a key instead of holding the entry, that
eviction also ends the alias, and the next relocated draw misses,
compiles and is admitted again, only to age out the same way.
A relocated hit now moves its donor to the front of the LRU, with the
same rate limit as a direct hit (once per an eighth of the cache's
entries in hits).
agc_driver_draw_cache_relocation_kept (--kept, with
APS5_DRAW_CACHE_ENTRIES=2) teaches a relocation alias, then runs three
rounds: two draws of another vertex shader that insert an entry, then a
moved draw with the taught contents. Every moved draw has to hit.
Without the refresh, the second round's insert evicts the donor and its
moved draw misses.
A run view maps the section at read-write-execute over the whole run and
commits the run through a read-write window. A file mapping passes the
protection its file allows and the bytes its file still has, so a
read-only file or a file that ends inside the run cannot be mapped that
way. File mappings now take the per-page path they took before the run
views: one view per page at the file's view protection, with the last
page cut at the end of the file. Mappings that pass no section size,
direct and shared memory, still map each run as one view.
guest_memory's regular file mapping case maps a read-only file and a
file tail shorter than a page on Windows.
Adapts fable-void's 3d695fbba to main's file mappings.
Mapping shared direct memory while shared maps are imported placed one
view per 16 KiB page: a placeholder split, a map and a protect for every
page. A 1 GiB map made about 200,000 system calls, held the mapping
thread for about 0.4 s and took and dropped the mappings mutex on every
page, so threads taking write faults queued behind it.
Map now maps each run of shared pages that need a view as one view: one
placeholder split, one map at read-write-execute (the cap later guest
protections need) and one protect per run, and builds the per-page
bookkeeping under the mutex while yielding to waiting faults. The views
move the run's page references instead of copying them, so releasing the
run does not hold the mutex. Protections and write tracking change
subpages inside the view as before. A view is split only where a release
or a remap cuts it: reset unmaps the view, maps the two pieces again over
the same section offsets and restores every 4 KiB protection, so armed
write tracking and guest protections survive the split. An alias of a
page elsewhere and a per-page protection leave the view whole.
Tested: windows_write_watch gains a case that maps 1 GiB of shared
memory (65536 views before, one after), then protects one page, aliases
one page, arms write tracking on one page, releases one page and remaps
another: the run splits only at the released and remapped pages (2 and
4 views), every other page keeps its bytes and protection, and the armed
page still faults and is collected. It fails before ("a shared gibibyte
was mapped as 65536 views instead of one") and passes after.
agc_driver_committed_imports: the 1 GiB shared map and import takes
190-196 ms on the mapping thread instead of 418-427 ms, and the longest
write fault on another thread stays at 46-97 us. Built; the write watch,
committed import, guest memory, write guard, host and window import,
residency and mapping tests pass (58 tests).
Committing the section range through one read-write window before the
pages are mapped works for direct and shared memory, whose sections are
committable and at least as long as the mapping. A file mapping passes
the bytes its file still has: the window can run past the end of a short
file, and a read-only file section refuses a read-write window, so every
file mapping would fail. Only mappings that pass no section size commit
the range up front; file mappings keep committing each view as before.
guest_memory's regular file mapping case maps a read-only file and a
file tail shorter than a page on Windows.
Adapts fable-void's 027bff519 to main's file mappings.
Mapping shared guest memory page by page queried the address space
three times for every 16 KiB page: twice to find the placeholder the
page splits from, and once more to see whether the section page still
had to be committed, which it then committed on its own. For a range of
fresh direct memory that is seven system calls a page, and with shared
imports attached every direct mapping takes this path on the guest
thread that maps it, under the mapping lock.
A mapping that goes page by page now commits its section range once
through one window, and splits each page off the placeholder the
previous page left, whose extent it already knows; only a page after a
resident run or a demotion looks the placeholder up again. A page now
costs a placeholder split, the view and its protection.
Tested: built; windows_write_watch gains a case that maps 256 shared
pages and counts the address-space queries the mapping makes, then
stores to and reads back every page. With the per-page lookups it fails
(775 queries for 256 pages) and with this change it passes.
agc_driver_committed_imports, agc_write_watch_coverage,
agc_private_collect, agc_write_guards, agc_driver_dcc_keys_first_write,
agc_driver_residency_dirty_mirrors, agc_driver_residency_driver,
guest_memory and the host import, window import and alias store tests
pass.
A %u, %o or %x scanned through the 64-bit temporary now writes an unsigned long long and is stored through an unsigned destination, as the rewritten %llu, %llo and %llx require.
main now keeps all four words of the guest signal mask. A handler's sa_mask above signal 32 is added too, and SIGKILL and SIGSTOP stay unblocked, as sigprocmask leaves them.
sceAudioPropagationSourceRender accepted only format 2. The RenderInfo
word at +0x28 is SharpProspero's AudioPropagationOutputFormat: 0, 1 and 2
are first- to third-order Ambisonics (4, 9 and 16 float channels), 4 and 5
are 8 interleaved float channels. SAROS (PPSA07631) renders format 0.
Other values still throw, and an output that is not a whole number of
frames is rejected. Based on db91769497 from the JJenkx fork.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dump with sce_sys/playgo-scenario.json and no chunk table lists chunks 0
to 999 through scePlayGoGetChunkId, but every other id was still reported as
installed. Destruction AllStars (PPSA02630) asks for chunk ids from 0 up until
one is rejected; because no id ever was, the walk wrapped the 16-bit id and
grew an array of 16-byte entries until a 5.18 GiB allocation failed about one
second into startup ("Ran out of memory allocating 5565054976 bytes").
An id outside the listed 0 to 999 is now answered like an unlisted id of a
dump without any playgo file: APS5_PLAYGO_UNLISTED_CHUNKS=installed or
=absent answers it, and without an answer the call throws naming the missing
chunk table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
windows_shared_map_cost maps 256, 16384 and 65536 pages of shared memory
(4 MiB, 256 MiB and 1 GiB) through WindowsMappings::Map, then releases
them, five times each, and prints the median map and release times, the
time per page and the number of mapped views. It only measures and
always passes, so a build of this commit and of a later one can be
compared on the same machine.
The address-based build named the copy reason of every copied releasable
range on each plan, and a heap's reason asked GuestMemory::Watched, which
takes the tracker's global mutex and walks its coverage and the arena's
host regions, only to label a FATAL line that is rarely printed. A copy
now records that it is a heap and whether the budget refused its import,
and AddressCopyOverflow names the reason while it builds the report, for
the copies the report lists. The report's text is unchanged.
agc_address_copy_cap registers a 2 MiB heap, a 1 MiB idle range and a
64 KiB busy range, imports the idle and busy ranges, and sets the import
budget so the heap fits only if the idle import is evicted. One 64 KiB
block of the heap is then removed from the write watch, so the heap can be
neither imported without eviction nor mirrored, and its copy is past
APS5_ADDRESS_COPY_MAX_MIB=1.
The build must serve the heap through an import or a mirror that holds the
guest bytes. Before the eviction ahead of the copy cap it copied the heap
and aborted the process.
agc_address_copy_cap_remembered first builds with the block already out of
the write watch and the copy cap raised above the heap: the heap's import
is refused and remembered, no mirror serves it, and it is copied. An
unrelated registration then makes the next build plan its ranges again,
under the 1 MiB cap: that build is answered from the remembered refusal
and must still evict instead of copying past the cap.
Titles register and check NetCtl callbacks without importing
sceNetCtlInit: libSceNetCtl is an internal sysmodule the system has
started before the title runs. 007 First Light traps at start-up when
sceNetCtlRegisterCallback answers NOT_INITIALIZED.
mazen320 mapped the relocated slots of KfcTPbeaOqg in the console's
libSceLibcInternal: offset-to-top, typeinfo, the two destructors,
locale::facet::_Incref and _Decref, and the eleven do_get. The debt
entry now says so instead of leaving the two extra entries unknown.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AcquireRegistered imports every registered GPU range whole without
eviction, so a range the import budget refuses and no mirror serves is
copied on every build, and copies past APS5_ADDRESS_COPY_MAX_MIB (64 MiB
by default) abort the process. Resident Evil Village (PPSA01556) aborts
this way on a 768 MiB heap 8 MiB over the budget.
The pass now retries such a range's import with eviction when, and only
when, copying it would take the build's copies past the cap, that is when
the build would abort otherwise. Imports the build already looked up or
made count as held (buildFloor) and are never evicted; the retire moves
the import epoch, which Upload and the address-space cache already honour,
and a retired import lives through the hold. Every other build still
imports without eviction, so the set of imported ranges stays as stable
across builds as before. A rate-limited [gpu] line reports such an import.
The copy reason of a heap now names what refused it (an import over the
host import budget, a heap not fully write-watched, a refused heap mirror)
instead of "outside the write-watched arena" for every heap.
Resolve the conflicts with main's libSceNetCtl initialization and
callback validation: the shared check now returns NOT_INITIALIZED before
sceNetCtlInit, as sceNetCtlCheckCallback does on main, so the NpToolkit
check does too; sceNetCtlTerm stays the no-op main measured; the NpToolkit
register and unregister keep their implementations with main's parameter
names. The NpToolkit entry joins main's rewritten debt file, and
GuestNetCtl checks the three before initialization.