The export turned YNw9Xk5iNWo from an unresolved import into a stub that throws. Dreaming Sarah (PPSA02929) calls it during start-up and now aborts with "YNw9Xk5iNWo not implemented" about 2 s in. Reverting until the function is implemented.
Titles run natively, so a host CPU without an extension that compiled Zen 2 code uses faults with an illegal instruction deep inside a title (#3719: SHLX, BMI2). libkernel now reads CPUID and XCR0 when it loads on Linux and Windows, logs the host CPU's extensions and throws, naming the CPU and the missing extensions, before any title code runs.
On Windows every direct host import leaves the write watch, and a storage
image over it compares its whole surface with its guest-byte copy at every
collect epoch. #4432 measured 7-8.6 s of every 10 s in those compares on a
GTA V scene, with 98% of the compared surfaces unchanged.
Tracker blocks that leave the watch now keep their write-watch marks as a
hint. Leaving the watch gives a block a fresh hint stamp, and
UntrackedHint walks a range of such blocks, at most once per collect epoch
and thread, stamping the blocks whose pages a CPU store marked; it
returns the newest stamp of the range. compareUntracked puts that stamp in
its memo key in place of the collect epoch, so an image is compared again
after a mark in its range, a driver store, a mapping change or a recorded
GPU write. The byte compare still decides whether anything changed: a mark
the driver makes without a CPU store costs one compare and drops nothing.
Nothing else reads the hint. Collects, Watched, UnchangedSince,
ChangedBlocks and NewestStamp still treat those blocks as untracked, and
Linux is unchanged.
agc_driver_dcc_keys_first_write refreshes an imported storage image over
50 collect epochs with no store: it compared the image 50 times before and
0 times now. A CPU store is still uploaded on the next refresh, and a store
of the bytes already there costs one compare and keeps the pending
results. agc_write_tracking checks the hint itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shared range larger than one chunk now takes the sparse path only when
the device binds sparse memory, udmabuf is open, the range's backing
offset is on the sparse block size, the sparse buffer accepts every
memory type a chunk import can have and the backing file covers the
last chunk. All of that is known before a chunk is imported. Once the
path is chosen, a failed chunk import, fence creation, bind, fence wait or device
address throws instead of going on to the whole-range udmabuf and copies.
agc_host_import_sparse checks the import against a mocked device with
the real udmabuf: a range above size_limit_mb becomes one sparse buffer
bound to one chunk import per 64 MiB, an unaligned backing offset
imports no chunk, and failures of UDMABUF_CREATE and vkQueueBindSparse
after the decision throw.
A draw made outside the GPU lock (agc_driver_recorded_kept_bytes) imports a target's memory and leaves the watch there; the hook threw instead of saving. It now takes the lock, as FlushPending does.
On Linux a shared-backed guest range is imported through one udmabuf
covering the whole range, and udmabuf refuses buffers above its
size_limit_mb (64 MiB by default). Larger ranges fell back to copies; an
address-based dispatch then synced its whole mirror, storing back every
pending storage image inside it each time.
Such a range is now one sparse buffer bound to the existing 64 MiB chunk
imports. The bind runs on a sparse binding queue of its own when the
device has one, so imports made at map time (without the GPU mutex) can
use it; otherwise only under the GPU mutex on the main queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, singly mapped direct memory is private since #5596, so its host
import is a direct import that leaves the write watch instead of a shared
alias that stays watched. A storage image whose results were pending when
its memory was imported had no saved guest bytes, and storing them threw.
GuestMemory now calls a hook before Unwatch and before an ImportWatched
that does not stay watched. Every image with results pending over the
range saves the guest bytes of its pending units that have none, then
collects and records the pieces the keep rule leaves to the CPU for the
writes the watch saw. The next store and Refresh leave those pieces to the
CPU and compare the rest with the saved bytes. A map-time import made
without the GPU lock is deferred to the locked retry when pending results
overlap the range.
Writes the 80-byte status described in #2746: the sceVideoOutGetOutputStatus
fields followed by zeros, with the documented codes for a null status and an
unknown handle.
(cherry picked from commit e893efad2d0a7b90243d07bf6669cc9b5f540432)
fstat on a guest socket descriptor passed the guest number to the host,
which has no such descriptor, and threw ("FillFileStat: fstat failed for
fd 268435460"); sceKernelFstat returned EBADF. FreeBSD fills the stat
buffer for a socket through soo_stat (sys/kern/sys_socket.c):
bzero((caddr_t)ub, sizeof (*ub));
ub->st_mode = S_IFSOCK;
...
if ((sb->sb_state & SBS_CANTRCVMORE) == 0 || sbavail(sb))
ub->st_mode |= S_IRUSR | S_IRGRP | S_IROTH;
ub->st_size = sbavail(sb) - sb->sb_ctl;
...
if ((sb->sb_state & SBS_CANTSENDMORE) == 0)
ub->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
and pru_sense_null (sys/kern/uipc_socket.c) adds
st_blksize = so->so_snd.sb_hiwat.
fstat and sceKernelFstat now answer a guest socket from the socket
table: S_IFSOCK, the read bits until the receive side is shut down and
drained, the write bits until the send side is shut down, st_size from
the bytes queued and st_blksize from the send buffer size, other fields
zero. shutdown records which side it closed. A closed socket descriptor
returns EBADF. The differences from the console (peer shutdown, the
host's FIONREAD and SO_SNDBUF, socketpair identity) are listed in
TechnicalDebt.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hull programs whose relative patch index (v1 bits 7:0) reached anything
but a tessellation ring or LDS address threw, because the lowering runs
every Vulkan patch as relative patch 0. The quad terrain hull 0x80002fc700
in Ghost of Yotei is one, and so is the domain 0x8068fbd900 (v7).
Such hulls and domains now take (PrimitiveID mod GE_CNTL.PRIM_GRP_SIZE)
mod VGT_LS_HS_CONFIG.NUM_PATCHES, as if hull threadgroups took NUM_PATCHES
patches at a time out of each primitive group; both stages see the same
PrimitiveID for a patch. This holds only when every instance starts a new
threadgroup, so it needs GE_CNTL.BREAK_WAVE_AT_EOI (bit 18); with the bit
clear the instances share threadgroups and such programs still throw.
PRIM_GRP_SIZE and BREAK_WAVE_AT_EOI join the tessellation configuration
(draw keys, hull and domain cache keys, request version 24). A missing
GE_CNTL throws, and so do local programs, whose relative vertex index
depends on vertex reuse that Vulkan does not expose.
sceNetConnect() on a connected datagram socket rejected an address
whose family is AF_UNSPEC (a null address) with EINVAL before touching
the socket, so the socket stayed connected: sceNetGetpeername() still
reported the old peer and datagrams from any other sender were dropped
by the host.
FreeBSD 11.4 disconnects a connected datagram socket first and only then
looks at the address. sys/kern/uipc_socket.c, soconnectat():
* If protocol is connection-based, can only connect once.
* Otherwise, if connected, try to disconnect first. This allows
* user to disconnect by connecting to, e.g., a null address.
*/
if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&
((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
(error = sodisconnect(so)))) {
udp_disconnect() (sys/netinet/udp_usrreq.c) clears the peer and keeps
the local port. connect(2): "Datagram sockets may dissolve the
association by connecting to an invalid address, such as a null
address." libkernel's connect gets the same change in #4498.
A connected datagram socket given an address whose family byte is 0 is
now disconnected on the host first (AF_UNSPEC on Linux, a zeroed
address of the socket's family on Windows), and the call then fails
with EINVAL as before. Linux also drops an ephemeral local port on that
disconnect, so the port is bound again on the wildcard address, which
is what FreeBSD leaves. A host failure during the disconnect is
returned as the call's error. Stream sockets keep the previous path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>