After a vkAllocateMemory failure the default import budget follows the
host heap's VK_EXT_memory_budget report. A report that leaves imports
no room made that budget 0, and the refusal memory reads a budget of 0
as none: an address-based build or an import when a range is mapped then
skipped its remembered refusals and ran the whole import path again for
every refused range, each time. The report's other usage grows with the
copies and mirrors that stand in for refused imports, so the budget can
reach 0 by itself once refusals pile up.
The budget the report sets is now at least 1 byte. Nothing more is
imported than at 0, and refusals are remembered again.
decodeDepth required DB_Z_WRITE_BASE to equal DB_Z_READ_BASE whenever
the depth plane was not read-only, also for draws that never write it.
PPSA31327 01.015.000 stops there on a stencil-only draw:
DB_DEPTH_CONTROL 0x00000701 (Z_ENABLE and Z_WRITE_ENABLE clear),
DB_RENDER_CONTROL 0, DB_DEPTH_VIEW 0, Z read base 0xed11340000 and
Z write base 0, with equal stencil read and write bases.
The bases are now compared only when the depth plane can be written: a
depth clear, a draw with both Z_ENABLE and Z_WRITE_ENABLE (the condition
that sets depthWrite), or an expand pass with DEPTH_COMPRESS_DISABLE or
STENCIL_COMPRESS_DISABLE in DB_RENDER_CONTROL, which still throws. The
surface keeps the read base. The stencil check is unchanged.
DepthStencilTests accepts a stencil-only draw, Z_WRITE_ENABLE without
Z_ENABLE and a depth test without writes at another write base, and
still rejects a written plane, a depth clear and the compress-disable
bits there.
#5882 lets a multisampled scan without MSAA_ENABLE through the earlier
check, so the draw is now refused where its one-sample color target meets
the rasterizer's sample count.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A multisampled rasterizer drawing into one-sample targets keeps
PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY inputs, and with PS_ITER_SAMPLE
also centroid inputs, since only sample 0's coverage reaches the targets.
Coverage inputs, and the sample index with POS_X_FLOAT or POS_Y_FLOAT,
still throw. The input rule for the non-iterated case follows
fdfawef324's proposal on #5907 (PPSA07411).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A draw whose rasterizer has more samples than its color or depth targets
threw "a color target's sample count differs from the rasterizer's" or
"the depth target's sample count differs from the rasterizer's". When
every target has one sample, MSAA_ENABLE is set, and there is no
alpha-to-coverage, no PS_ITER_SAMPLE and no sample-dependent pixel input,
only sample 0's coverage reaches the targets, so draw it with one sample
at sample 0's location in each pixel of the grid. One-sample depth images
are created sample-location compatible when the device allows it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SonyCrt.hpp kept the constraint handler slot in an unnamed namespace,
so every source file that included it had its own slot. A handler
installed with set_constraint_handler_s in WideConversion.cpp was not
the one qsort_s in FileAndHeap.cpp called, and its violations aborted.
The slot and its mutex now have one instance per module, and the
handler type uses the guest calling convention, as the handlers a title
installs and the default handler it gets back are guest functions.
At a full host import budget with nothing evictable, a request that may
evict tried again on every use, and each try walked every live import to
add up what could be evicted. In a title's first scene at its 6400 MiB
budget that was 37,627 refusals per present, each one a walk over about
4,400 imports, while the address-based builds kept every import held.
Such a request now remembers its final refusal, the one with no smaller
block left to try. Later requests for the same run, from the same range,
are answered from that memory, still counted as refused and remembered,
until an import is made or retired, the budget or the import window
changes, the run's ranges change, or an import could have become
evictable. An import becomes evictable only once the use count passes
its last use by the 256-use protection, and a held one also needs a
higher build floor, so the walk records the first such use among the
imports it could not evict, held ones apart: the memory survives a new
build while no held import can age out yet. Requests that may not evict
keep their behaviour.
A Windows import under 1 MiB returns to the write watch once its settle
read has completed, though main-queue work submitted before the read may
have used the import first, and an earlier import of the same pages may
not have run its first use yet. On the NVIDIA driver (617.42) neither
marks the pages once the read has completed, so the rewatch needs no wait
for the main queue. The docs said the marking lasts until the import's
first GPU use completes; they now say that a completed GPU use ends it,
the settle read included.
agc_import_settle_first_use submits 4096 copies of a 64 KiB import on
the main queue before its settle read is queued. The import must return
to the write watch while the copies still run, and the driver must mark
none of its pages afterwards.
agc_import_settle_earlier_import records 4096 copies of a 64 KiB window
import, replaces it with the import of its whole 128 KiB range, and
submits the copies only once the range is back in the write watch. The
driver must mark none of the range's pages while they run. A driver that
keeps marking fails either test.
The sample order checks validate the 2x checkerboard with a uniform
mask of sample 0, early Z, a depth test and a pixel program that writes
memory, on a device that reorders the checkerboard. The order moves the
mask into the pixel shader, after the early tests, so the draw throws.
The same draw validates when the pixel program writes no memory, and on
a device with a 2x2 grid, where the mask stays static.
Retiring a Windows import whose settle read was still running waited for
the read's batch under the imports mutex, so lookups on every thread
waited with it. The retired import is now kept with that batch and freed
once the batch has completed. Only a guest unmap of its memory still
waits for the batch, in the pin waiter, which waits for the main queue
there anyway.
The [buffers] profile line counts these retirements and the unmaps that
waited, apart from the imports of 1 MiB or more settled inside their
window, which the old wait counter mixed with the retirement waits.
agc_import_settle_retire replaces a 64 KiB window import with the import
of its whole range, 64 times, right after its settle read was submitted.
No replacement may wait, at least one must find the read running, and
the last import must return to the write watch.
A pass that draws multisampled depth at custom sample locations ends
with a transition to TRANSFER_SRC_OPTIMAL and back that names them, so
the depth is not read back at other locations. The pass's end runs the
work of the draw that opened it, and Draw sets the locations for it, but
DrawWithRecipe built its RecordedDraw without them. Draw recipes are
refused for multisampled color and for reordering pipelines, not for
depth-only multisampled draws, so a recipe hit that opened a pass, on
the native grid or with a uniform custom pattern, left its depth without
the transition however many draws followed it.
Draw and DrawWithRecipe now take the locations from passDepthLocations,
on the recipe's pipeline for a replay. The recipe keeps its pipeline,
whose key holds the custom locations, and Draw's caller passes the same
depth target, so the helper sees what Draw saw for the recorded draw.
The sample order tests' --recipe-depth-locations records a depth-only
2x draw with a uniform custom pattern and a recipe, reads every sample,
clears the depth, replays the draw through DrawWithRecipe as the first
draw of a new pass and reads again. It requires the replay to leave the
recorded depth bit for bit, and skips without custom sample locations at
2 samples, or where even the recorded draw reads back away from the
guest's locations.
The Quake homebrew's libOSMesa.so.8 imports qsort_s, the only strong
import left unresolved once its symbol names resolve through NIDs.
qsort_s sorts as qsort does, with the context passed as the
comparator's third argument, and returns EINVAL after calling the
constraint handler for the runtime constraint violations of FreeBSD
13's qsort_s. The sort helpers take any comparator so both functions
share the FreeBSD 11 algorithm.
Domain::GuestImport had no weak flag, so WindowsPePatcher handed every
import of a bundled module to the entry stub as a strong one, and a
weak import that no library exports stopped the title with
"unresolved ELF import". The executable's own weak imports already
bind to null there. libOSMesa.so.8 of the Quake homebrew has ten weak
imports that nothing provides, such as ZSTD_trace_compress_begin and
the SDK's __dlopen.
The guest relocation writer now records the ELF weak binding, and the
stub binds such an import to null when no library exports it.
sceNetCtlGetStateV6, sceNetCtlGetResult and sceNetCtlGetNatInfo are
exported and called by titles but no test calls them. The checks pin what
the code does today: a null output is refused with the invalid-address
error, GetStateV6 reports disconnected or IP obtained, GetResult writes
0, and GetNatInfo reports not connected.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PPSA16833 imports it from libkernel, and its executable does not load
while no library exports the NID. The signature is unknown, so it throws
if called, like sceCoredumpWriteUserString.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
macOS's mktime returns -1 for every year before 1900, so a guest mktime of
such a time failed there although localtime converts it. Windows already
moves a time outside its CRT's range a whole number of 400-year Gregorian
cycles, where weekdays and leap years repeat, converts it there and moves
the result back. macOS now does the same below 1 January 1900; each host
keeps its own range, and Windows's is unchanged.
A routing table built by hand with an unscoped default route, a host
route, a host route to 0.0.0.0, a rejecting default route, an
interface-scoped default route, one that is down, one on an interface
that does not exist and a truncated last message must give the unscoped
route first and the scoped one after it, and must count every match
while writing only as many as fit.
sceNetCtlGetInfo found the host's default route in /proc/net/route, which
macOS does not have, so every IPv4 query threw "the host routing table is
unavailable" there. On macOS it now reads the IPv4 routes that have a
gateway with sysctl(NET_RT_FLAGS, RTF_GATEWAY) and keeps those to
0.0.0.0 that are up, neither rejecting nor host routes, on an interface
that still exists. An interface-scoped default route ranks after the
unscoped one, the route macOS sends traffic by. The parsing is exported
as NetCtlDefaultRoutes_nid_no_patch so it can be tested on a routing
table built by hand.
FindSceCodeEnd looked for the PLT only after the last function of the
unwind table and failed with "PLT not found after the last unwind
function" otherwise. The libSceFios2.prx and libc.prx that Until Dawn
(CUSA02636) bundles have their PLT at 0xc8, at the start of the text
segment, before the code. In both, the last unwind function is
followed by the module's DT_FINI routine, which has no unwind entry,
and then by read-only data.
When no PLT follows the last unwind function, the search now also looks
before it. A PLT found there must not overlap an unwind function, and
the code extent then ends at the last unwind function, so what follows
it stays out of the scans and the code analysis, including code
without an unwind entry such as the DT_FINI routine of both modules
(recorded in the debt file). A missing PLT now fails with "PLT not
found before or after the unwind functions".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The guest reader stored DT_INIT as a plain address, and every user of
it read 0 as "no initializer". The libc.prx and libSceFios2.prx that
Until Dawn (CUSA02636) bundles have DT_INIT present with value 0, the
start of their text segment. So libc.prx, which also has a preinit
array, failed with "Guest PREINIT_ARRAY has no module initializer",
and libSceFios2.prx's initializer never ran.
GuestImage now records whether the tag is present (HasInit). The
reader, the Linux, macOS and Windows writers and the code analysis use
it instead of Init != 0, so an initializer at 0 is mapped, started and
analysed like any other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A run of adjacent heap mirrors is collected as one range. Once a refused
heap keeps its mirror after its range has left the write watch, such a run
can no longer be collected whole, and each of its mirrors is collected on
its own: the unwatched one is refreshed in full and its watched neighbour
by the blocks the CPU wrote.
The test registers two adjacent heaps, takes a block of the first off the
watch and writes to both, and checks that the next build keeps both
mirrors and serves both writes, with the address-space cache and without
it. Refreshing the whole run as unwatched instead leaves the neighbour's
mirror without its write. The heaps share one allocation and the mock
device imports nothing, so the test runs on every platform.
The sparse heap mirror unwatch tests were registered only outside Windows.
Their Windows path takes the heap from the guest arena and commits its
first half, as the sparse heap mirror tests already do there, and the heap
is the only range they register, so no neighbouring range can join its
import run. GuestMemory::Unwatch excludes the block from the arena's watch,
which the heap mirror unwatch tests already exercise on Windows. Register
both on every platform, with the libc directory on PATH as the heap mirror
unwatch tests have it.
A heap mirror whose range had left the write watch was dropped and its
range planned again. When the host import budget refused the range, that
plan was a per-build copy, and past APS5_ADDRESS_COPY_MAX_MIB (64 MiB) the
copy stopped the process ("an address-based build copies ... past ..."),
where refreshing the mirror in full would have carried on.
Plan such a range again only when the import path takes it now. A cached
address space whose heap or sparse mirror has left the watch is rebuilt
only if importing the mirror's range succeeds, and the rebuild then finds
that import; otherwise the space is kept and the mirror refreshed in full,
every block counted as written. A build that plans its ranges tries the
import first, so acquireMirror keeps the existing mirror of a range that
reaches it, and a sparse mirror whose pieces are unwatched is refreshed in
full instead of planned again.
SetAddressCopyLimit sets the copy cap in-process, as SetHostImportBudget
sets the budget. The heap and sparse unwatch tests set it below the heap
the old plan would copy: the refused heap's mirror is kept and refreshed in
full (its unwatched block and a watched block the CPU wrote both reach it),
and once the budget holds the heap the next build imports it and releases
the mirror.
A build checks that its heap mirrors' ranges are write-watched before it
reuses them and refreshes them later in the same build. A map-time import
on another thread can take a range off the watch in between, and
refreshHeapMirrors and refreshHeapRuns then threw "a heap mirror's range is
no longer write-watched".
When a run of heap mirrors cannot be collected whole, collect each mirror
on its own. A mirror whose range is no longer watched is refreshed in full,
every block counted as written, and counted as an unwatched heap; the run's
other mirrors keep their per-block refresh.
The sample order planning test builds pipelines from graphics pipeline
libraries on a mock device whose object count, handle counter and
recorded sample masks are plain globals. With link-time optimization on,
the Context default, each linked pipeline also queues an optimized link
on the pipeline library worker thread. That link calls the mock's
vkCreateGraphicsPipelines, and its vkDestroyPipeline when the pipeline
was released first, while the test thread builds the next pipeline and
destroys both. An increment or decrement of the object count can be
lost, so the leak check fails now and then: on Windows, and on Linux in
8 of 20000 runs with eight runs at a time on four cores.
No object is left alive: ClearPipelineLibraries waits for the worker
before the check. Waiting with WaitForOptimizedPipelines after each
pipeline does not close the overlap either, as the pipeline's
constructor still destroys the shader modules a cached library part did
not keep after it queues the link.
The test now builds its library pipelines without the optimized link, as
on a device without link-time optimization, so every mock call stays on
the test thread.
The check that statvfs leaves no descriptor open listed /proc/self/fd,
which macOS does not have. macOS lists the process's descriptors in
/dev/fd; Linux keeps /proc/self/fd.
statvfs opened the path with O_PATH, which macOS does not have, so
libSceLibcInternal did not build there. No open mode stands in for it:
on macOS O_RDONLY and O_EVTONLY fail with EACCES on a file or directory
without read permission, and O_SEARCH on such a directory, where
FreeBSD's statvfs only needs search permission on the path.
On macOS statvfs now asks the host statvfs by path, which needs exactly
that, fills the kernel statfs the way _fstatfs fills it from the host
fstatvfs, and converts it with the code fstatvfs uses. A path that must
name a directory and does not fails with ENOTDIR, as the O_DIRECTORY open
does elsewhere. Other hosts are unchanged.
fstatvfs kept the guest struct statvfs and the sfs2svfs conversion from
the kernel's statfs to itself. Both move to include/Statvfs.hpp so that
statvfs can convert a volume the same way without opening the path.
Nothing changes for fstatvfs.
The case that removes search permission from a directory and expects
fchdir to fail with EACCES, leave the working directory unchanged and
succeed again once search is allowed ran only on Linux. It now runs on
every host but Windows, with the POSIX headers it needs.
fchdir checked search permission on the directory with faccessat only on
Linux, so on macOS a guest could change into a directory it may not
search, where FreeBSD fails with EACCES and keeps the working directory.
faccessat with AT_EACCESS is POSIX and behaves the same on macOS, so the
check now runs on every host but Windows.
unlinkat's rmdir and unlink paths call ::rmdir and ::unlink outside the
Windows branch, but <unistd.h> and the headers beside it were included
only under __linux__, so libkernel_sys did not build on macOS. They are
now included on every host that is not Windows, the same split the code
that uses them already makes.