The title allocates, maps and releases about 48 direct-memory blocks per frame.
Each allocation created a pagefile-backed section and each release closed it.
A released section whose pages were never stored to (resident pages keep their
data in private memory and saved buffers, not in the section) is still blank,
so up to 256 such sections of at most 16 MiB, 256 MiB in total, are kept by
size and handed to the next allocation of that size instead of a new
CreateFileMappingW. APS5_NO_DIRECT_MEMORY_SECTION_POOL keeps the old behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD 11.4 kern_mprotect masks the argument with VM_PROT_ALL instead of
refusing it (sys/vm/vm_mmap.c:613). sceKernelMprotect already does this
since #5770; the POSIX mprotect export still failed with EINVAL.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
FreeBSD 11.4 kern_mmap maps anonymous memory whenever MAP_ANON is set, with
or without MAP_PRIVATE (sys/vm/vm_mmap.c:339), and fails with EINVAL when
both MAP_SHARED and MAP_PRIVATE are set (vm_mmap.c:252). The guest failed
the first with ENOTSUP (45) and the second with ENOTSUP as well.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A search range with no free direct memory returned EAGAIN (0x80020023).
A PS5 returns ENOMEM (0x8002000c) for an occupied range, as the
PS5_RetroArch author's console capture records, and PS5_PayloadSDK's
ps5_memory_query, which RetroArch uses for its memory status, gives up
on any other error.
LargestFree now returns the error: ENOMEM when no block is found, and
EAGAIN as before for a negative start, an end at or below the start and
an alignment that overflows when rounded to a page. guest_memory expects
ENOMEM for an occupied range and EAGAIN for those arguments. The
unmeasured argument handling is recorded in the libkernel debt file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checked release tested that the range was allocated, released the pool lock and then
unmapped a snapshot of its views. Two concurrent checked releases of one range both passed the
test and took the same snapshot; the second then unmapped views the first had already removed
and terminated on "unmap address is not registered" instead of returning ENOENT.
A release now claims its pages in the pool under the pool lock before it looks at the views,
and frees them, dropping the claim, after the unmap. A checked release of pages another release
has claimed returns ENOENT as if they were already free; a plain release waits for that release
to finish. The pages stay allocated while claimed, so they still cannot be reallocated during
the unmap's lease wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shared mapping is a view of a file section created on the descriptor's
handle, read-write when the descriptor is, otherwise read-only. A private
mapping is a copy of the file read at mmap, as POSIX allows. Anonymous
shm_open objects and JIT shared memory descriptors are temporary files on
Windows, so mmap now maps them too (it failed with EOPNOTSUPP before).
mprotect of a mapped view above the view's protection fails with EACCES
before any bookkeeping changes. The cases Windows cannot represent are
listed in the libkernel debt.
Dusklight (PPSA99305) uses SQLite in WAL mode, which maps its -shm file.
On Windows main f0634088b every start logged "Failed to start
transaction: disk I/O error" for its Dawn and pipeline caches (3 of 3
runs); with this change the caches initialise and their WAL files fill
(4 of 4 runs). The title reaches its menu either way.
A shared mapping may end inside the 16 KiB page that holds the end of
the file, as POSIX mmap allows: the view ends with the file's last 4 KiB
host page, which Windows zero-fills past the end, the rest of the guest
page is private zero pages, and the file never grows. A mapping that
reaches a whole page past the end still fails with EOPNOTSUPP.
PS5LM Plus (PPSA99581) loads its model with llama.cpp, which maps the
whole GGUF file shared and read-only. With a 491,400,032-byte model,
Windows main 5de7a46e0 and this change without the last page logged
"llama_model_load: error loading model: mmap failed: Operation not
supported" (3 of 3 runs); now the model loads and the completion of
"The capital of France is" starts with " Paris" (3 of 3 runs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelReleaseDirectMemory and sceKernelCheckedReleaseDirectMemory returned the range to the
allocator before unmapping its views. The unmap waits for GPU leases on the views, and during
that wait another allocation could take the freed pages and map them while the old view was
still mapped and host-imported, so GPU writes through it landed in the new owner's memory.
Both calls now unmap the views first; the checked call still reports ENOENT for an unallocated
range before it touches any view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the regression originally supplied by Pipodi in closed PR5851 (22091410a), adapted here through e1c6af65b. Current-main-equivalent libc/kernel source returns an alias during the open HostWrite and fails this assertion five times. This test is a reproduction, not a new implementation or an upstream PR.
(cherry picked from commit 312170cd0c)
guest_memory reserves with a hint above the guest arena and with a hint
on the arena's last page for two pages, and checks that both land inside
the arena, the second below its hint.
Follow FreeBSD 11's sys_mincore by rounding unaligned address down to page boundary rather than returning EINVAL. Update guest memory test and document unmodeled residency/referenced/modified bits in libkernel technical debt.
Implement mincore (+hFjyohmOU0) in libkernel following FreeBSD 11's sys_mincore (sys/vm/vm_mmap.c). Validate page alignment, return EFAULT for null vector, check range bounds and residency via sceKernelVirtualQuery, and report MINCORE_INCORE (1) for resident pages. Required by Java 25 LAN / DNS (PPSA99869).
&sceKernelMprotect in the test executable is its import thunk, so the
call protected a page of the executable's own code read-only and the
next call through it crashed. Use the libkernel.prx image instead.
The shared guest_memory_module gained a .elf0 section, so on Windows its
data was no longer counted against flexible memory and
CheckGuestModuleUsesFlexibleMemory failed. Build the .elf0 variant as a
separate guest_memory_elf0_module, keep the shared module as on main, and
check that a .guest.prx module without .elf0 is rejected.
Map fixed private anonymous ranges through the existing guest arena. Preserve occupied ranges for MAP_EXCL and use FreeBSD 11.4 errors.
Cover replacement, exclusivity, range validation, rounding, and memory access.
CheckBreakingAResidentRunKeepsConcurrentAccesses and
CheckRemappedDirectMemoryIsChargedOnce passed on main, where a singly
mapped direct memory page is a shared view and no resident run exists.
Both now require their first mapping to be private committed memory, so
on main they fail there instead of passing without exercising a run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
A direct memory page mapped at one guest address now lives in private
MEM_WRITE_WATCH memory at that address, and its writes are collected with
GetWriteWatch like flexible memory: no fault is raised. A page mapped at a
second address moves its whole resident run into the section, behind a
read-only fence while it is copied, and every address becomes a view of it,
tracked by write protection. Unmapped resident pages that are not blank are
kept in a private reservation until they are mapped again, not in the
section.
Ported onto main's 4 KiB subpage tracking of shared views: resident runs
keep a per-page collected bit and enter the fresh ranges, so Fresh and
Armed collections select them like shared views; a demoted run carries its
collected bits into the views and reports every page once. MapAlias throws
through refuse() when a resident run extends outside the aliased range, and
the caller keeps main's fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
WindowsMappings::Collect handed GetWriteWatch the whole remaining output
capacity for every private region it walked. The call's cost follows that
capacity, not the size of the range: on Windows 11 (26200) a query of one
16 KiB region took 0.35 us with room for 4 entries and 6.5 us with room
for the 65536 the write tracker passes. Flexible memory is committed in
16 KiB allocations, one region each, so a resetting walk over 33 MiB of it
took 14.7 ms, 5.8 times a memcmp of the same bytes.
Each query now gets room for the pages its region spans, which is the most
it can report, so every walk returns the same pages; the 33 MiB walk takes
1.23 ms. guest_memory checks that a walk over eight 16 KiB allocations
reports every stored page once, in order, with room for 32, 5 and 1 pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelVirtualQuery, sceKernelMunmap, sceKernelReleaseDirectMemory and the
reservation paths walked a copy of the whole allocation registry or of every
direct mapping under one lock on each call. The registry now answers lookups
from its ordered map, applies range replacements as patches instead of
rebuilding the map, and direct mappings carry an index by physical offset so a
release finds only the mappings it covers. Minecraft (PPSA17221) holds about
7,000 direct mappings and spent its world generation in these walks.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Windows, a host import of shared guest memory goes through a
read-write alias from WindowsMappings::MapAlias. The alias was a single
MapViewOfFile3 of one section, so MapAlias threw when the views of the
range were not one contiguous run of that section. One
sceKernelMapDirectMemory over two adjacent direct memory allocations
gives such a range, as each allocation has its own section, and so do
pages remapped in place to another physical order.
Such ranges are now aliased run by run: a placeholder reservation is
split at each run of consecutive pages of one section, and each run
maps that section at its offset. UnmapAlias unmaps every run and forgets
the alias even when one unmap fails. A contiguous range keeps the single
view, and a range with a page that is not a shared view is still
refused.
guest_memory maps two adjacent direct memory allocations of two pages
and one page with one call, and two pages in reverse physical order.
It reads and writes every page through the alias. The first test also
checks that the alias has one view per run and that unmapping it frees
its pages. A range whose second page is flexible memory is still
refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fc6868d8b6)