An evicting request whose block is refused but leaves room for its own
range steps down to a smaller block, and only the last block it tried
was remembered. Once an address-based build had stepped down inside a
block, every later evicting request for the rest of that block scanned
every import at the block's level before it reached the memory below.
A refusal is now remembered before the request steps down, with the
room it left. A remembered refusal answers with the same decision the
scan would make: while the memory is current only a lower budget can
change that room, so the request steps down to the same smaller block
when its range fits in the room, and is refused otherwise.
A join's budget refusal was never remembered, so a buffer bound across
ranges that the budget could not take tried the join, with a scan of
every import, at each upload's pre-pass. A join now remembers its final
refusal like any other evicting request, under the same conditions. The
memory keeps whether it was a join and answers only the same kind of
request, since a join skips the held-import check and never steps down.
An evicting refusal was remembered for the range holding the requested
address and dropped by a request for another range of the same run, so
requests landing in different ranges of one refused block, as uploads
and accesses across a block of adjacent ranges do, scanned the imports
every time. The memory now keeps the room the refusal left and answers
any range of the run that is larger than it: such a range cannot step
down to a block of its own either. A smaller range still recomputes,
and a run that cannot step down at all is answered for every range.
A protect below the main thread's stack mapping mapped only the
protected pages. Linux keeps a growing stack one guard gap (1 MiB by
default) above a readable mapping, so after a read-only protect, or a
protect later restored to read/write, at the bottom of the reported
stack the stack could no longer grow into the megabyte above it and a
deep enough call faulted where the console has stack pages. The pages
between the protect and the top of the stack are now mapped as well,
so the stack never has to grow past them.
glibc reports the main thread's stack as reaching down to the end of
the mapping below it. The pages a protect maps under the main stack
become that mapping, so after the first protect sceKernelIsStack
reported a stack starting above them, and a second protect of the same
pages, such as restoring them to read/write, failed as outside any
stack. Each thread now queries its host stack once, which also stops
every protect on the main thread from reading /proc/self/maps.
MapUnmappedPages probed every 4 KiB page of the protected range with
msync before mapping it. Under an unlimited stack limit the main
thread's reported stack reaches down to the heap, tens of TiB, so a
guest protect over the whole stack ran for hours. Mapped and unmapped
runs are now found by halving the remaining range, and the unmapped
ones are mapped without reserving swap, as the stack itself grows.
The remembered refusal of a request that may evict expired on any budget
change. A smaller budget can only keep the request refused, and #6479
moves the budget in 64 MiB steps once a second, so the memory now ends
only when the budget grows, as #6479's stillRefused does.
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.
The device setup logs each sample count's largest grid. On the RTX 3070
Laptop GPU (617.42) it is 2x4 at 2 samples, 2x2 at 4 and 1x2 at 8,
while maxSampleLocationGridSize is 1x1 for every count; the AMD Radeon
iGPU reports 2x2 both ways. So the 3070's 2x patterns take the native
grid, and the stand-in plus the exact path serve its 8-sample patterns
and devices without a grid.
The throw for a uniform partial sample mask that the order moves stays.
The order is fixed at decode, before compilation shows whether the pixel
program writes memory, and falling back would lose exactness on draws
the exact path gets right. The entry now says when the draw throws, what
#6451's stand-in did with it, and what a fix would take.
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.
The debt entry said the RTX 3070 Laptop GPU reports a 1x1 grid for
every count. That is VkPhysicalDeviceSampleLocationsPropertiesEXT; the
device setup reads each count's VkMultisamplePropertiesEXT, which gives
an even grid at 2 samples, so the 3070 draws 2x patterns on its grid.
The entry also gains the transition that ends a pass drawing depth at
custom sample locations, and the throw for a draw with early depth or
stencil tests whose pixel program writes memory when the order moves a
partial sample mask into the pixel shader.
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]>