Kept both sides of GuestLibcThreads.cpp: main's ThreadHelpers test and this PR's VersionedSync test, both called from main().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every import lookup walked the lease out from the containing range to find
the run of continuing registry ranges, which is linear in the run length.
That walk depends only on the lease snapshot, the containing range and the
import window block, so its first and last index are now memoized per
thread under a weak reference to the snapshot, the range index, the window
and the window's low end. APS5_NO_RUN_MEMO walks every time.
Everything that depends on other state runs on each call as before: the
largest size, the budget, the failed blocks, the remembered refusals and
the halving block search. importRunBounds returns the chosen indices, so
sameRange, runCovers and the whole-run lookup in importAllocation compare
against the lease without copying the run's range pointers.
importRunFailureShrinkTests records a failed 8 MiB block between two
lookups on an unchanged registry and requires the run to shrink to its
4 MiB blocks. importRunGrowthTests and importRunReplacedTests check that a
run grows and shrinks when its ranges are registered or replaced.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A private guest commit on Windows (every anonymous kernel map commits
with a 16 KiB granule) ran reset() on each page before replacing it:
10 VirtualQuery calls, a placeholder split and the VirtualAlloc2, so
12 calls per page. At the arena's first-fit frontier the placeholder is
the arena's free tail, hundreds of GiB.
Commit now queries a placeholder once, cuts the range out of it (one
split where the range starts inside the placeholder, one where the
placeholder extends past the range), and carves the pages from that
bounded placeholder: one split and one VirtualAlloc2 per page, and no
split for the last page. Each page is still its own allocation with the
same protection, write watch and created ranges, so munmap, MAP_FIXED
remaps, views and collects see the same layout as before. A piece that
would cross the end of its placeholder (a whole-range granule over
several placeholders) still takes the coalescing path through reset(),
and committed memory in the range still only changes its protection. A
zero granule now throws std::invalid_argument before touching the
address space.
windows_write_watch counts the queries with a compile-time counter that
only that test defines (APS5_COUNT_MAPPING_QUERIES, copied from #9505)
and checks: 256 fresh pages one page into a 1 GiB placeholder take one
query (main's loop makes 2,561), each page is its own allocation and
the placeholders around them are intact; the release and recommit of
one page; a protection-only commit; the write watch of carved pages; a
whole-range granule inside one placeholder and across two; committed
pieces and adjacent placeholders inside the range; a partial release
and a fixed remap that keep their neighbours; a granule that would
cross the end of the placeholder being carved, which takes the
coalescing path; a short last piece; a commit that runs to the end of
its placeholder; an unwatched commit; and the zero-granule throw.
PPSA14363 boot on an RX 6700 XT, idle PC, 4 runs on main and 3 with
this change: the first 10 s after the first present go from 546-552
presents with a 578-704 ms frame to 588-594 presents with a 97-117 ms
frame.
Co-Authored-By: Claude <[email protected]>
feof_unlocked, ferror_unlocked, getc_unlocked and putc_unlocked are macros
on macOS, so they are called without the global scope qualifier. The
remaining differences are the ones Darwin's headers and libSystem have:
- CLOCK_REALTIME_COARSE and st_mtim do not exist; the write mark uses
CLOCK_REALTIME and st_mtimespec.
- tm_zone is a non-const char*.
- __cxa_thread_atexit returns void, so the wrapper reports success.
- Darwin's stack protector reads __stack_chk_guard from libSystem; the
guard of the guest libc has the postfixed name, which receives the NID
of the original.
- APS5_EXPORT spells the symbols with the leading underscore of Mach-O.
- GuestArenaSetProtection and PosixProtection are built on macOS as on Linux.
- The fclose failure test fills /dev/full, which exists on Linux only.
exception_runtime relies on the Itanium unwinder of libc, which is not
registered on macOS (the host libc++abi is used), so its test is only
registered elsewhere.
Two threads writing to the same stream each ran FileStream::SyncStatus
after their stdio call, outside the FILE lock, with plain writes to the
read and write counters and a read-modify-write of the flag word, so the
flags of one thread could be lost. Publish the counters with atomic
stores and the flags with a compare-exchange loop; the values stay the
same.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
FreeBSD 11 fopen opens the path with _open, and vn_open_vnode refuses a
directory only for FWRITE or O_TRUNC, so fopen of a directory in a read
mode succeeds; Linux hosts already open it. On Windows CreateFileW
without FILE_FLAG_BACKUP_SEMANTICS refused it with EACCES.
fopen now opens such a path as a libkernel directory descriptor, which
libkernel registers in libc when it loads, and fclose and freopen remove
it from libkernel's table. Reads on the stream fail with EISDIR and set
the error indicator, as on a Linux host and FreeBSD's tmpfs: the Windows
CRT fails them already but reports EINVAL. Writes fail with EBADF, and
fseek, ftell and ungetc work, as on any read-only stream.
Trimming every trailing separator turned the Windows drive root "C:\" into
the drive-relative "C:". Trim only while the parent is longer than its
root_path(), and cover "/", "C:\" and "C:/" in guest_filesystem.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
RenameDotError_nid_no_patch resolved the parent with the separator kept
("file/"). On Linux std::filesystem::exists("file/") is false for a regular
file, so unlink, rmdir and rename of "file/." answered ENOENT instead of
ENOTDIR. Trim the trailing separators first (a lone "/" stays) and cover
rename of "file/." and "file/.." in guest_filesystem.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The versioned Dinkumware entries operate on libkernel's own mutex,
condition and thread handles: _Mtx_init(_with_name), _Mtx_destroy,
_Mtx_lock, _Mtx_trylock, _Mtx_unlock, _Cnd_init(_with_name),
_Cnd_destroy, _Cnd_wait, _Cnd_signal, _Cnd_broadcast, _Thrd_detach,
_Thrd_join, _Thrd_yield, _Thrd_id and _Throw_Cpp_error. libkernel
registers the pthread calls with libc next to the C11 forwards.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RbLgECLQ2bpDM7oe2urGZK
_Call_once, _Tss_create, _Tss_delete, _Tss_get, _Tss_set, _Thrd_equal,
_Thrd_current and _Thrd_exit call scePthreadOnce, scePthreadKeyCreate,
scePthreadKeyDelete, scePthreadGetspecific, scePthreadSetspecific,
scePthreadEqual, scePthreadSelf and scePthreadExit and return their
results unchanged, as the console's libSceLibcInternal does.
_Mtx_current_owns returns 1 there. libkernel registers the calls with
libc at load, next to the pthread_create registration.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RbLgECLQ2bpDM7oe2urGZK
- strtol, strtoul, strtoll, strtoull and strtouq return 0 for a base
outside 0 and 2 to 36, leave the end at the start and set EINVAL;
strtoimax, strtoumax, _Stoull, _Stoul and the wide forms do the same
without errno.
- A 0x prefix with no hexadecimal digit after it (base 0 or 16) is no
conversion: the end stays at the start of the string.
- wcstol, wcstoll and wcstoimax take one sign, convert the rest with
wcstoull and clamp to the signed range with ERANGE, so "- 1" is -1
and "+\t-1" overflows.
- atoi, atol and atoll truncate strtoull's result.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RbLgECLQ2bpDM7oe2urGZK
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 <[email protected]>
The crash and abort reports printed the last 64 traced calls of the whole
process, so a thread that loops on a traced import (a mutex lock/unlock
pair, a profiler marker) pushes the calls of the faulting thread out of the
report. The ring already keeps the last 65536 calls with their thread.
CopyThreadCallTrace_nid_no_patch returns the most recent calls of one
thread from that ring, oldest first, and both reports (Windows crash and
abort, Linux abort) list up to 32 of them for the reporting thread after
the process-wide list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>