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)
A join runs before any region confirms an import and a retired import lives
through the retire-hold, so refusing it for held overlaps kept the joined run
copied for the rest of the build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agc_driver_graphics (BdaResources.cpp), each failing without its change:
- a view in the last window of a range whose first window was imported
binds through an import of the whole range;
- an address-based build over the budget leaves the range's window in
place (3 builds, same import);
- two uses across neighbouring cell edges share one import over 10
rounds;
- a range mapped at the budget is refused at map time and retires
nothing.
agc_driver_host_import_windows: windows follow their range, and
ClearHostImports drops them.
A registered range larger than 64 MiB is imported one 64 MiB window at a
time for buffer and fill accesses instead of whole, and the live imports
stay under a budget: APS5_HOST_IMPORT_MIB, else the larger of 6 GiB and
half the physical memory, at most the largest host heap less 2 GiB
(imports and type-3 mirrors share it; #167's rule). An import past the budget
retires the least recently used imports not used in the last 256
lookups.
- An access across window edges imports the cells it touches, grown to
the union of the range's imports it overlaps, so uses across
neighbouring edges share one import instead of replacing each other.
- An address-based build (AcquireRegistered) imports the range whole; a
window over its first byte does not serve it.
- Every refusal (alignment, a failed run, a held import, the budget with
what may be evicted) is decided before anything is retired.
- An import made when a range is mapped never evicts: the map observer
holds no GPU lock.
The window size and the budget have setters for the tests.
The mimalloc in PS5MC (PPSA99420) reads vm.overcommit at start-up and
stopped on the unsupported name. It reports FreeBSD's default, 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dolphin_libretro core of RetroArch (PPSA99169) reads {CTL_HW,
HW_REALMEM} as the machine's memory size and stopped on the unsupported
MIB. It reports the same value as hw.physmem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5CEMU-HAR (PPSA99360, ps5-payload-dev build, relinks with
--trap-sdk-syscall-stubs) imports the data objects sys_siglist
(yCdGspbNHZ8) and sys_nsig (ZD+Dp+-LsGg) from libSceLibcInternal.prx;
the import audit lists both as absent (registry entries 364 and 378,
after pthread_setaffinity_np at 276). No run has reached them yet; the
predicted stop line is
FAIL: unresolved ELF import ZD+Dp+-LsGg (sys_nsig) from libSceLibcInternal.prx
or yCdGspbNHZ8 (sys_siglist), whichever the loader resolves first.
The table and count are FreeBSD 11.4 lib/libc/gen/siglist.c (8c639945):
32 descriptions, signal 0 to SIGUSR2 (NSIG 32), and sys_nsig is their
number, 32. ps5-payload-dev declares both as objects exported by
libSceLibcInternal and its strsignal reads them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DecodeColorMetadataPass refuses an eliminate-fast-clear or DCC-decompress
pass when DB_DEPTH_CONTROL has any of its low four bits set and depth or
stencil planes are bound. Marvel's Guardians of the Galaxy (PPSA01748)
issues one with the Z test enabled, ZFUNC ALWAYS and no Z write
(DB_DEPTH_CONTROL 0x72) while a Z32 and an S8 target are bound. That test
passes every fragment and leaves depth unchanged, so the pass does no
depth work. Draws already treat this state as a pass-through
(depthPassThrough); the metadata pass now uses the same rule. Depth
bounds, depth writes, stencil tests and DB_RENDER_CONTROL clears or copies
are still refused, and the error names the four registers involved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entry links WindowsMappings.hpp, so the debt-file convention puts it in
docs/dev/debt/libc.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
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
Map made every singly mapped section page private resident memory. A JIT
or shm_open section is file backed and committed: its bytes live in the
file, and its alias descriptors map duplicated handles, which the physical
page slots do not match, so a resident copy diverged from the file, and
committing its view on a demote failed with access denied
(guest_jit_shared_memory). Only direct memory backings, mapped through the
new GuestArenaMapDirect, now become resident; other sections keep main's
shared views. Shared pages are committed only while their section page is
reserved, as on main.
The socket host write tests map their direct memory a second time, so the
write-protected shared path they check is still the one they exercise.
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
After an exact two-piece join, a bind across the join's end got the
joined import from importAllocation and the view failed as "rounded up to
its view range exceeds its GPU owner". UploadFinish now asks importJoined
for a region past the import's end and drops an import that still does
not cover the region, so the region is copied, as on main. The join test
binds across the end of an exact join after a third piece is registered.
Co-authored-by: TideOPL <57710757+TideOPL@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bound buffer is joined across adjacent registered ranges only when every
one of them is larger than the 8 MiB window. PPSA25344's loading binds 16 MiB
and 4 MiB buffers whose backing was registered in two to six adjacent
pieces with the same access, some of them smaller than the window, so each
use of such a buffer was copied on the CPU: up to 348 GB in the first 300 s
of loading.
A bound buffer that starts at the first crossed range's start and ends at the
last one's end now joins those ranges whatever their size. A heap grown in
pieces matches that only for a binding over exactly its current pieces, so
growth beside a joined import does not re-import it. joinedCovers, used when a
joined import retires, accepts any adjacent run of ranges.
importJoinTests binds exactly over two 64 KiB pieces of a partly filled
window and requires one import of both; a binding across them that starts
inside the first piece stays apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A partially resident snapshot cannot be reused from committed write stamps while a recorder batch still owns an overlapping completion store. Make that case compare through the guest-memory flush hook before deciding whether the snapshot is unchanged.
Cover copied completion stores in both open and submitted batches, then verify that unchanged snapshots are reused and an unrelated pending write does not synchronize. The focused regression fails on the published PR head and passes with the guard. The existing fully committed snapshot path is unchanged.
Co-Authored-By: Codex <noreply@openai.com>
A sampled texture over a partially resident surface (a texture array whose
heap the guest commits on demand) never got a write generation: CollectWrites
fails on a range with reserved pages, so every cache hit compared the whole
committed part with its snapshot. CollectCommittedWrites collects the
committed runs one by one and returns the runs with the generation; the cache
entry records them, and a hit skips the compare only when the committed runs
are the same and UnchangedCommittedSince holds over them. A commit, decommit,
view mapping or unmapping inside the surface changes the runs or stamps the
new pages, so it is compared as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read the optional aligned budget from param.json and apply it consistently to configured, available and mapped flexible memory. Add parser and allocation regression coverage.
The 9-word kernel #3816 matches stores one dword per thread, loaded with
s_buffer_load_dword through the descriptor in user[4..7]. Run as a
compute dispatch, its stores are pending writes of unknown value, so a
later read of DCC keys in that range waits for the GPU.
fillBuffer now takes it as a uniform buffer fill when all of these hold:
- the output descriptor's format is 32_UINT, 32_SINT or 32_FLOAT (20-22),
so buffer_store_format_x stores the loaded dword unchanged;
- the value descriptor covers the loaded dword: num_records times the
stride (num_records alone when the stride is 0) is at least 4 bytes,
the range the recompiler gives a scalar buffer load;
- exactly 8 user SGPRs and group id X enabled, so the group id is s8;
- a direct, non-partial dispatch, 64x1x1 threads, one group in Y and Z,
and a 16-byte aligned range of whole 16-byte units.
The value is read when the fill is recorded, after the labels queued
before it, as the pattern-fill kernel's source is. When the value is one
byte repeated and that byte is a DCC key code, the fill goes into the GPU
key-store memo whatever ClassifyFill reports: the memo answers a key
read only while this fill is still the newest pending write over the
keys, and a fill over a color target's keys is not a storage image's
keys fill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-descriptor lock exists only in the Windows transfer path, so ShareDescriptor calls SharePositionedTransfer under _WIN32 like GuestFileLockShare. Linux failed to link libkernel with an undefined reference to it.
swscanf (HNnWdT43ues) runs the swscanf_s scanner without the buffer
size arguments after %s, %c and %[, as FreeBSD's swscanf.c passes its
arguments to vswscanf. Both entry points share one engine with a flag
for the sizes; swscanf_s behaves as before.
Title: PS5CEMU-HAR PPSA99360 imports swscanf from libSceLibcInternal.prx
(import audit with --trap-sdk-syscall-stubs, registry index 292, right
after pthread_setaffinity_np at 276). Predicted stop line once
pthread_setaffinity_np resolves, without this change:
FAIL: unresolved ELF import HNnWdT43ues (swscanf) from libSceLibcInternal.prx
NID from tools/nid_names.py, confirmed in shadPS4 aerolib.inl:51950
(e0a466753a1a). Prototype in ps5-payload-dev include/freebsd/wchar.h:133,
export in sce_stubs/libSceLibcInternal.c:8582 (f5d40f02770e).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A 2D array storage view of a depth plane from slice 0 threw. It now gets
a storage image over all its slices: each slice with a depth surface at
the plane's address plus a whole depth slice per layer is copied from
that surface before every use and back after the work; the other slices
hold guest memory like any storage image. Cubes, stencil arrays, later
base slices and layer pitches other than the depth slice size still
throw.
GTA V (PPSA04264) stops at compute shader 0x242d4a200 on a 5-slice R32
Z_64KB_X array over its shadow depth surfaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
FreeBSD sysctl_find_oid returns ENOTDIR when the MIB continues below a leaf object. The supported hw.ncpu, hw.model, hw.physmem and kern.arandom MIBs threw an unsupported MIB error instead.
(cherry picked from commit 6968aea8af578c4d56dc614cbe449274bb6e76e7)
PS5MC (PPSA99420, Kodi 22) imports getpriority (miQ1wVwSexE) from
libkernel.prx, one reference, next to chflags in its import order.
Predicted stop once the imports ahead of it are covered (not a run):
FAIL: unresolved ELF import miQ1wVwSexE from libkernel.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 sys/kern/kern_resource.c sys_getpriority
(8c639945a44588f6cfdddfb057bb5aad88735da0) for the only guest process,
taken to run at nice 0 and to lead its own process group (as kill and
setsid already assume): PRIO_PROCESS and PRIO_PGRP with 0 or its pid,
and PRIO_USER with 0 or its uid 0, return 0; other ids give ESRCH and
an unknown which gives EINVAL. getpriority is a bare system call in
FreeBSD's libc (lib/libc/sys/Symbol.map), so errno is left unchanged
on success.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 63262245d274342b693fda146ae9feca3752b6e3)
v_cvt_f32_ubyte0 accepts every SDWA source selector, but the other three byte
conversions had no VOP1 SDWA rule, so any SDWA source select on them was refused
("VOP1 SDWA source selector 4 of opcode 0x12 at pc 0xfcc is not supported").
The selected and optionally sign-extended source goes through the same operand
path as for ubyte0 before byte N is extracted: SDWA supplies source 0 (RDNA 2 ISA
guide 6.8 and 13.3.7) and the opcode reads S0.u[8N+7:8N] (12.8).
agc_driver_sdwa_cvt_f32_ubyte: WORD_0 and WORD_1 selects on ubyte1, and
sign-extended WORD_1 and BYTE_3 selects on ubyte2 and ubyte3.
Crimson Desert (PPSA25286) calls sceKernelStat with a null buffer at
start-up, and main throws "sceKernelStat: sb is null", which ends the
title in its first second.
FreeBSD 11.4 looks the path up first and only then copies the result
out, so a null buffer gets EFAULT for a path that exists and the lookup
error for one that does not (sys/kern/vfs_syscalls.c, sys_stat):
error = kern_statat(td, 0, AT_FDCWD, uap->path, UIO_USERSPACE,
&sb, NULL);
if (error == 0)
error = copyout(&sb, uap->ub, sizeof (sb));
sceKernelStat now does the same: the lookup and its errors are as
before, and a null buffer returns SCE_KERNEL_ERROR_EFAULT where the
status would be copied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto main deleted docs/dev/TechnicalDebt.md, which is now the
index that points into docs/dev/debt/. Restore it from main and add the
missing final newline to docs/dev/debt/libSceNgs2.native.md.
Titles that link the character conversion library under its module name
need libSceCesCs-module.prx, which nothing built: Void Terrarium
(PPSA03061, #5055) and Call of Duty: Black Ops (PPSA34501, #5389) were
run with a copy of libSceCesCs.prx under that name. Both libraries are
now built from the same source by add_sce_ces_library, as libScePsml and
libSceFont-module are, and guest_ces_module runs the CesCs test against
the module-named library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve a host name to its first IPv6 address through the host resolver
(getaddrinfo with AF_INET6) and write the 16-byte address. The lookup
shares the IPv4 path's resolver bookkeeping (abort preservation, error
state); a name without an IPv6 address fails with RESOLVER_ENODNS like a
failed lookup. Nonzero flags throw.
Metal Gear Solid 4 Master Collection (PPSA18194) imports it and failed to
load without it (unresolved zl35YNs9jnI from libSceNet.prx).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5MC (PPSA99420, Kodi 22) imports chflags (ixrw0h2tWuI) from
libkernel.prx, one reference. Predicted stop once the imports ahead of
it (pthread_getthreadid_np, sendfile, wcstok, pause, setrlimit and the
termios calls) are covered (not a run):
FAIL: unresolved ELF import ixrw0h2tWuI from libkernel.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 (8c639945a44588f6cfdddfb057bb5aad88735da0)
sys/kern/vfs_syscalls.c kern_chflagsat and setfflags, and
sys/ufs/ufs/ufs_vnops.c ufs_setattr, for a root caller: the lookup
through the guest resolver gives EFAULT, ENOENT and ENOTDIR (also for a
file reached through a trailing slash or used as a directory), a bit
outside the 17 UFS flags of sys/sys/stat.h (VNOVAL included) gives
EOPNOTSUPP, and SF_SNAPSHOT gives EPERM, since the snapshot flag cannot
be toggled. No host keeps BSD file flags and stat reports st_flags 0,
so flags 0 succeed without touching the file and any other flag throws
with the flags named. UF_READONLY and the other Windows-attribute flags
are not mapped to FILE_ATTRIBUTE_READONLY and its siblings: UFS stores
them without enforcing them, and on Windows the read-only attribute is
already the owner write bit of chmod.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 16e5b7ccd04e6467a5a282b6cfc9271304f42244)
Windows 10 1903 deletes a file with open handles by moving it into
$Extend\$Deleted until the last handle closes, so the next snapshot of a
watched file that a title (or the test) deleted saw a new path, tried to
watch that directory's changes, got ERROR_ACCESS_DENIED and threw from
the watch thread, which aborts the process; the path change was also
reported as NOTE_RENAME next to NOTE_DELETE. A snapshot with a pending
delete or no links now reports NOTE_DELETE alone and drops the parent
directory watch instead of replacing it, as the file has no directory
to be renamed or removed from any more.
A draw with PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES > 0 whose context never
wrote PA_SC_AA_SAMPLE_LOCS_PIXEL_* (context 0x2fe-0x30d) was rejected
with "missing register in context bank at DWORD 0x2fe", because the
registers were absent from the initial context (PPSA07411 stops on it
after two frames). The MSAA_ENABLE single-sample path read them the
same way.
The sixteen registers now start at 0, both initially and after
CLEAR_STATE. That is the value in libSceAgc's own register defaults
(sceAgcGetRegisterDefaults2, every public table that holds context
registers, the tables the initial context already follows for
PA_SU_VTX_CNTL and PA_SC_CONSERVATIVE_RASTERIZATION_CNTL), in amdgpu's
GFX10 clear-state table (clearstate_gfx10.h, gfx10_SECT_CONTEXT_def_8,
loaded by gfx_v10_0_cp_gfx_start before CLEAR_STATE) and the GC 10.3
register reset value (gc_10_3_0_default.h). RADV, radeonsi and PAL
always write the locations themselves and do not settle the unwritten
value.
0 puts every sample at the pixel center. That is not the standard
pattern for any sample count, so such a draw decodes as custom
locations at the center, as a single-sample scan into a multisampled
target already does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An existing directory, or a path ending in a separator, was only
rejected when the executable was written, after the converted modules
had already been written to app0/ beside or inside it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD sysctl_root rejects a write to a read-only node with EPERM before the node handler checks the MIB length, so a write with a short or long kern.proc.pathname MIB fails with EPERM. sysctlbyname for kern.proc.pathname now goes through sysctl with its 3-component MIB so it follows the same order.
#5097 added ShaderInfo::layerExported to the entry encoder, and this
series moves that line into encodeShaderInfo, which plan entries use
too. The entry round trip left the flag false, so an encoder and a
decoder that both skipped it still passed. Set it like the other
ShaderInfo flags: with layerExported dropped from both, the round trip
now fails with "entry round trip: shader info differs".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
windres passes include directories to the preprocessor unquoted, so
relinker.rc failed to compile when the checkout path contains a space.
The resource is now an object library without the relinker's include
directories.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5CEMU-HAR (PPSA99360, ps5-payload-dev build, relinks with
--trap-sdk-syscall-stubs) imports pthread_setaffinity_np (5KWrg7-ZqvE)
from libkernel.prx. babyhuey's run with #3787 merged onto main e7c70a2d
(PR #3787, 2026-10-12T10:54:44Z) stops at it:
Windows: FAIL: unresolved ELF import 5KWrg7-ZqvE (pthread_setaffinity_np) from libkernel.prx
Linux: undefined symbol: 5KWrg7-ZqvE (pthread_setaffinity_np)
It follows FreeBSD 11.4 libthr thr_affinity.c (8c639945) over
kern_cpuset_setaffinity: another thread is looked up first (EINVAL for
null, ESRCH when unknown or finished, as _thr_find_thread without dead
threads), a size other than the 32-byte cpuset_t returns ERANGE, the
mask is clipped to sceKernelGetAvailableCpumask and an empty result
returns EDEADLK, as cpuset_setaffinity does. The affinity is stored on
the guest thread through scePthreadSetaffinity, for the caller and for
any live guest thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan and ShaderInfo encoders write these structures field by field,
and the sizeof asserts are their only reminder to update the encoders. A
field that fits in existing padding keeps the size: main's layerExported
landed in ShaderInfo with sizeof still 232, and the three bools #5089
adds to DescriptorSource::IndirectImage keep it at 28. A warm cache
would then hand back entries without the new field and nothing would
fail.
Count the fields of every aggregate the plan codec reaches (IrResourcePlan,
GuestRegister, MemoryInfo, DescriptorSource and IndirectImage,
ResourceBlock, SrtRead, UniformFill, UniformFillPlan, and ShaderInfo with
its resource and stage I/O records) by brace-initializing each with more
and more convertible-to-anything values until the initializer no longer
compiles, and assert the counts. Unlike the size asserts this does not
depend on the ABI, so it is checked on every platform. IrValue is a
class with private state and keeps only its size assert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The round trip requires some fixture to plan a phi, and only one did:
the loop counter of "bindless sample in a loop latch", which reaches the
plan through the latch condition. The counter is not invariant, so the
CPU walk can never evaluate that condition. A plan that keeps only the
conditions the walk can evaluate, as #5181 aims to, would hold no phi
and fail the check with "no fixture plans a phi".
"descriptor invariant phi" loads the image descriptor from the same SRT
offset on both sides of a branch on an SRT dword. Its eight dwords are
phis of equivalent reads: the tracker keeps them, since it splits only
descriptor phis that are not invariant, and the walk evaluates them
through the invariant read. The plan stores them as descriptor dwords,
whichever conditions it keeps, so the phi encoding stays covered. The
fixture goes after the other compute fixtures, so they keep their seeds
and their damaged-entry checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APS5_NO_PERF_DISK_PLANS=1 is listed as a neutral switch, so it stays out
of the variant key, and it exists to compare warm starts with and
without stored plans. Nothing tested either property: the cross-process
test ran with the switch unset only.
A fourth child process now sets the switch against the cache the first
process filled. It must run the front end for all 14 fixtures, neither
load nor store a plan, still hit all 14 stored variants, and produce the
same SPIR-V, bindings, push constants, snapshots, read traces and plans
as the first process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan tests compared a decoded plan with the original only through
the codec itself (re-encoding must give the same bytes) and through
materialization. Neither catches a field that EncodePlan and DecodePlan
both leave out: the bytes still agree, and the walk does not read every
field. Leaving userDataBase, the plan's sRGB formats, the value ids or
several MemoryInfo fields (secondaryOffset, dataDwords, dataFormat,
planningOnly, idxen) out of both sides passed every test. The hand-built
plan also never materializes (its uniform values cannot be evaluated),
so its 1000 runtimes all failed with the same error, and no plan held an
immediate wider than 32 bits.
samePlan compares every field of two plans without the codec: the value
graph by storage index (opcode, type, id, flags, register, immediate,
arguments, phi blocks), the block ids, the roots, and the plain data
through the structures' own equality. The round trip now requires it
for the 14 fixtures and the hand-built plan, which sets every MemoryInfo
field, all indirect table fields, a second buffer, the whole uniform
fill and a 64-bit immediate to values other than their defaults. The
second process also builds each fixture's plan with its own front end
and requires it to equal the plan it used, so a stored plan must match
what the front end builds in another process, not only its own
re-encoding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodePlan checked every descriptor source index a plan holds except the
two inside an indirect image table: the heap source and the material
source. A damaged entry could therefore decode with a table that points
past the plan's descriptor sources, and only the materializer's own
bounds checks stood between it and an out-of-range read. The front end
interns both sources before the image source that names them, so a plan
it builds always passes the new check.
agc_shader_disk_cache now requires a plan whose table heap or material
source lies past the sources not to decode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getSource built ResourceProgram (PrepareResourceProgram and ExtractPlan)
for the first request of every shader a process saw, before
findOrCompileVariant looked in the disk cache. With a warm cache every
launch still decoded, built the CFG, structurized, translated and ran
SSA, the fold passes, SrtWalker and ResourceTracker for every shader;
only SPIR-V emission was skipped.
getSource now builds the plan key and tries LoadPlan first. On a hit it
sets the plan and leaves the program null: findOrCompileVariant already
rebuilds the program when no stored variant fits the layout. The loaded
plan takes the request's code address as its shader hash, as a plan
built in this process would. On a miss it builds the plan as before and
stores it. A plan the front end refuses is not stored. The disk is not
used when the cache is off, under the debug probe, with APS5_DUMP_IR
(which prints the front-end passes), or with APS5_NO_PERF_DISK_PLANS=1,
a cache-neutral switch for comparisons.
FrontEndBuilds() counts PrepareResourceProgram calls. The new
cross-process test prepares, captures and materializes 14 shader
fixtures in one process and repeats it in a second process sharing the
cache directory: the second process must run the front end 0 times, hit
14 plans and 14 variants, and produce byte-identical SPIR-V, bindings,
push constants, snapshots, read traces and plans. A damaged plan entry
is rejected and rebuilt alone with the same output and stored again; a
changed code word and another switch value miss.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The disk cache keeps compiled variants (SPIR-V, shader info, binding
layout) but not the IrResourcePlan the front end extracts for the
resource walk, so a process can only get a plan by running the whole
front end. Since #471 the plan owns its values (ownPlanValues clones the
graph behind every root into valueStorage and blockStorage), so it can
be written out and rebuilt.
- EncodePlan writes the value graph in storage order, each value as
LEB128 integers: opcode, type, id, flags, register, operand count,
the immediate if it has one, each argument as the zigzag distance to
the value's own index (0 for null) and, for phis, the index of each
predecessor block. The roots (descriptor source dwords, SRT reads,
control-flow conditions, uniform-fill values) are value indices; the
plain data (memory info, indirect image tables, control-flow edges,
materialization sources, guarded, clean and pure flat slots, the
flags, the ShaderInfo through the variant's encoder) follows. A plan
whose roots or arguments point outside its storage is not encoded.
Control-flow conditions can pull most of a program into the plan:
a synthetic 2,038-dword compute shader with a branch every 20 dwords
has 42,775 plan values, which take 518 KB this way and 2.5 MB with
fixed-width fields; its compiled variant entry is 935 KB.
- DecodePlan rebuilds the graph the way ownPlanValues does and rejects
an unknown opcode, an overlong integer, an index past the values or
blocks, a descriptor source, image, buffer or sampler past the
sources, a branch without two successors, an edge past the blocks,
and a uniform fill of more than four words.
- BuildPlanKey is the in-memory source key (RecompileCacheKey::Build and
the host subgroup size), every code word and the switch key, after a
plan entry tag. It leaves out the binding layout, so all layouts of a
shader share one plan.
- EncodePlanEntry/DecodePlanEntry use the variant's header and hashes;
LoadPlan and StorePlan use the same directory and background writer
and count plan hits, misses, writes and load failures on their own.
- Size asserts cover the plan and the structures it is written from.
- FormatVersion 30.
agc_shader_disk_cache now round-trips the plans of 14 in-tree shader
fixtures (SRT reads, guarded and chained pointers, control-flow
conditions, an indirect image table, a loop phi, a fragment shader) and
a hand-built plan with a uniform fill. For each it materializes the
original and the decoded plan over 1000 random user-data and memory
runtimes and requires the same snapshot, poison, null-root count and
read trace, or the same error. Truncated, bit-flipped and
other-format entries are rejected, malformed plans do not decode, and
the key follows the code, the target and the host subgroup size but not
the layout, addresses or user data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A guest handler that called exit threw out of the libc exit path because the exiting thread waited on itself. Like FreeBSD, the nested call now runs the handlers that are still registered and ends the process with the status it passed.
encodeInfo and decodeInfo wrote the ShaderInfo fields inline between the
compiled-shader header and the binding layout, and EncodeEntry and
DecodeEntry built and checked the file header (magic, format, source
version, key and payload hashes) inline. Move both into helpers,
encodeShaderInfo/decodeShaderInfo and wrapEntry/unwrapEntry, so that a
second kind of entry can reuse them.
The bytes of a compiled variant entry do not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
longjmp moves the Windows stack bounds onto the stack of the target jump buffer so that exceptions and stack probes keep working after a jump between guest stacks. siglongjmp restored the same registers without that step, so a jump from a context stack back to the thread stack left the thread bounds describing the stack it had left.
Add missing libc.prx symbols required by Unity IL2CPP runtime:
- Implement _Dtest double classification in Math.cpp
- Implement _Inf double constant in Math.cpp
- Implement sceLibcBacktraceGetBufferSize and sceLibcBacktraceSelf in Backtrace.cpp
- Implement _Cnd_signal in Threads.cpp
- Implement _ZNSt8ios_base7_AddstdEPS_ in RuntimeSupport.cpp
- Implement __pointer_type_info and __function_type_info vtables in TypeInfo.cpp
- Remove duplicate stubs from libSceAmpr/Export.cpp
- Add unit tests in GuestMath.cpp and GuestLibcThreads.cpp
- Document unverified behaviors in docs/dev/debt/libc.md
The cache from #5107 was keyed by Mesa version only, so a runner restored
llvmpipe code compiled on another runner's CPU and about 85 agc tests died
with SIGILL (Illegal instruction) on main.
Co-Authored-By: Claude <noreply@anthropic.com>
A PS5 eboot can keep initial-exec TLS as R_X86_64_TPOFF64 GOT relocations
(SuperTuxKart, STKT00001, has 13, all without a symbol), which the
relocation allowlist rejected. Relocations against the executable's own
TLS block (no symbol, or a defined STT_TLS symbol folded into the addend)
now go to the Linux ELF as symbolless dynamic relocations that the
dynamic loader resolves against where it placed the block; one against an
import stays named and is typed STT_TLS. The Windows and macOS images
resolve TPOFF64 and DTPOFF64 statically against the block
AlignUp(p_memsz, p_align) below the thread pointer, the offset
WindowsTlsBuilder and MacOsImage use, and reject DTPMOD64 and imported
TLS. TPOFF32 stays unsupported.
New test executable_tls_relocations covers the Linux, Windows and macOS
outputs and the rejected cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's sigwait hands the error number back as its return value and leaves errno alone; the guest export returned -1 and set errno.
(cherry picked from commit b627f6c9443dd94a6392959f33e52d837ee4cae4)
FreeBSD msun e_acosf.c returns (x-x)/(x-x) for |x| > 1 and NaN inputs: the
x86 default NaN 0xffc00000, or the quieted input NaN. std::acos left it to the
host libm, and guest_math failed on the runner's glibc 2.39.
Co-Authored-By: Claude <noreply@anthropic.com>
The duplicate-export and silent-stub rules treated every APS5_VABI
definition under core/libs/prx/ as a library export, including mocks
and callbacks in a library's tests/ directory. Those files are built
only into separate test executables, never into the library, so they
are not exports. PR #3533 failed CI because a sceKernelGetProcessTime
mock in libSceAudioOut/tests was reported as a duplicate of libkernel's
export. An sce* fixture with a stub-shaped body was also reported as a
silent stub, and a real export that shares a name with a fixture in
another library's tests/ was reported as a duplicate.
Skip files under a tests directory on both sides of the check, with the
same test code() already uses for title-specific and progress.py uses
for its function counts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A draw with VGT_TF_PARAM.PARTITIONING 0 (PART_INTEGER) was refused by the
state decoder and its hull and domain programs by the recompiler. It is now
drawn with Vulkan's equal spacing, the mode radeonsi programs as
PART_INTEGER, with tessellation factors bounded at 64 instead of
fractional-odd's 63. Power-of-two and fractional-even partitioning still
throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA99611 imports ceilf, floorf, truncf, isnan and isinf from libc.prx and stops at start-up on main. Implement them next to the existing double forms: the rounding results delegate to the standard library like the other float exports, and isnan/isinf reuse the __isnan/__isinf helpers isnanf already uses.
VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1 (mesh-style NGG, as RADV programs
it on gfx10.3) was refused. With a point list and GE_CNTL groups of one
point, it launches one subgroup per input point and the first thread
reads that point's index in the ES vertex id VGPR, which the mesh
prologue already provides. The subgroup's primitive limit comes from
GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR, and the GS and ES VGPR counts are
not required to be full.
Fast launch 2, other group sizes or inputs, and indexed fast-launch
draws still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With --trace-imports the entry stub resolves TraceImport_nid_no_patch
in libc.prx and passes each function import through it before writing
the slot: every PLT slot, and a GOT slot only when the original symbol
is a function, so data imports such as __stdoutp keep their address.
Without the option the output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A draw whose key has no entry captured its stages, built a variant per
stage (a copy of every captured region) and inserted a draw entry. A key
that never comes back paid for the variants and the entry for nothing,
and its entry pushed a live one out of the cache. Draws whose user data
changes every frame are such keys.
The driver now remembers the keys it has seen once, in a table of
four-way buckets holding four times the cache's size in keys. A key gets
its entry at its second sight; a key that already has an entry still
takes new variants at once. A full bucket replaces its oldest key, so
keys that share a bucket do not push each other out until more than four
of them come between two sights of one.
Test: agc_driver_draw_cache_reuse draws three times. The first two draws
find no entry, one entry is inserted, and the third draw hits it. A
sighting table of one bucket keeps two keys drawn in turn and replaces
its oldest key when full.
A keyboard record on the console never counts fewer than one key: with
nothing held, and with no keyboard connected, the count is 1 and that
slot holds code 0. SharpProspero's keyboard reader, PS5_Quake3e's notes
on records read from a console and the PS5 input research notes all say
so. AnyPS5 reported a count of 0. The count is now at least 1; the key
slots themselves are unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sceCoredumpAttachUserFile (5nc2gdLNsok) and
sceCoredumpAttachMemoryRegionAsUserFile (MEJ7tc7ThwM) get the
signatures of Orbital's db_types.json (cc408367) and SharpProspero's
KernelSystem.cs (5dfdb4b0): (uint32_t userValue, const char* path) and
(uint32_t userValue, const void* mem, size_t size, const char* name).
The handler is never invoked here, so both return
SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER (0x81180003, shadPS4
coredump_error.h e0a46675) and attach nothing; a null path or name and
a null, empty or wrapping region throw because the order of the
console's checks is unknown.
Carried from @screamm with author attribution preserved.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A formatted store of an 8- or 16-bit component into a bound buffer
patched the dword around it with a compare-exchange loop (writeBuffer
in SpirvRuntimeBuffer.cpp). Ghost of Yotei's light-culling compute
pass (program 0x800038be00, 240x135 groups per frame) stores millions
of such components per dispatch and took 426 ms of GPU time per
dispatch on an RTX 4070 Ti SUPER.
Compute stores now go through a byte view of the bound buffers
(buffers_u8, an aliased declaration of the buffers binding, defined
on the first narrow store): one OpStore per byte under
StorageBuffer8BitAccess, which the shader device profile already
requires. Without the capability, and in every other stage, the
compare-exchange path remains: the draw-time validator accepts only
u32 and u64 aliased views. Loads, dword stores and stores through
GPU-selected descriptors (BDA) are unchanged.
Ghost of Yotei (PPSA26344 02.002.000), Windows 11, RTX 4070 Ti SUPER,
logo phase: that dispatch 426 -> 10.7 ms of GPU time, 1.9 -> 11 fps.
agc_formatted_buffer_byte_stores compiles a formatted 8_8_8_8 store
as compute with and without StorageBuffer8BitAccess and as a vertex
shader with it, and checks the byte view, the byte stores, the
compare-exchange loops and the fallbacks. agc_driver_formatted_store_unaligned
executes 8- and 16-bit formatted stores at misaligned offsets,
including stores that cross a dword, and checks every byte of the
output including the untouched neighbours.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The signature (uint32_t service label, const NpUnifiedEntitlementLabel*)
follows SharpProspero Interop/AppContent/AppContent.cs (5dfdb4b0). dlc_emu
dlc_content.cpp (fcf69b16) answers NOT_FOUND for a label that is not a
known add-on and PARAMETER for a null label. No add-on content is ever
installed here, as sceAppContentAddcontMount and sceAppContentGetAddcontInfo
already model, so every non-null label answers NOT_FOUND and nothing is
queued. dlc_emu's nonzero-padding check is left out, as sibling functions
do not check it and no console source confirms it.
Carried from @screamm with author attribution preserved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA99058 and PPSA99420 import sceKernelGetCpuTemperature and sceKernelGetSocSensorTemperature and stop at start-up on main. Read the host CPU/package, board and NVMe sensors (hwmon/thermal on Linux, the ACPI thermal zones and the storage temperature property on Windows) and report an unsupported sensor with EINVAL.
Porpoise (PPSA99764) stops on its weak chroot import (#3632, #4497).
Run 5 on main c2565f014, and again on a copy of that runtime:
FAIL: unresolved ELF import AKknh8Cx-V8 from libkernel_sys.prx
Its only call is in put_root_back (src/porpoise_jailbreak.cpp,
elripalda/Porpoise-Dolphin-Emulator-for-PS5 2ed1ab5a06995aa1): after a
jailbreak daemon has made the console's root its root and /app0 is
gone, it calls fchdir on the descriptor of "/" it opened at start, then
chroot("."), and checks the result. So it only ever puts back the root
it started in, and it does not use paths under any other root.
On top of the lookup errors of the previous commit, chroot now follows
FreeBSD 11.4 sys_chroot and pwd_chroot (8c639945, vfs_syscalls.c and
kern_descrip.c) for that case: a directory that is the guest root
("/", "/..", "." after fchdir to "/") succeeds without a change, since
fd_rdir would be set to the vnode it already is. The guest is taken
never to have changed its root, so with chroot_allow_open_directories 1
an open directory does not give EPERM. ENAMETOOLONG is added as in
pathconf, and a ".." through a file reports ENOTDIR instead of
escaping as a filesystem_error. Any other directory still throws: the
resolver's root and mount aliases are fixed, and no title is known that
needs a real new root.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Porpoise (PPSA99764) imports chroot (AKknh8Cx-V8) from
libkernel_sys.prx, not libkernel.prx, one reference. Predicted stop
once pathconf, the pthread_attr calls and ftrylockfile ahead of it are
covered (not a run):
FAIL: unresolved ELF import AKknh8Cx-V8 from libkernel_sys.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 sys/kern/vfs_syscalls.c sys_chroot and change_dir
(8c639945a44588f6cfdddfb057bb5aad88735da0) for a root caller up to the
lookup: EFAULT for a null path, ENOENT for an empty or missing one,
ENOTDIR for a file or a path through a file. A directory that is found
throws with the path named: the resolver's root is fixed and the mount
aliases (/app0, /savedata0) are host paths that do not move with it,
so it cannot be re-based, and a real chroot is not modelled. Nothing
shows that Porpoise calls it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Linux output reused the input program header table in place, so it could
hold no more entries than the input declared. A title whose kept segments
and the five synthetic headers did not fit lost PT_GNU_EH_FRAME with a
warning, or failed with "Not enough program header slots". When more
headers are needed, the table is now written at the end of the extra block,
e_phoff points there and PT_PHDR maps it through the extra block PT_LOAD;
the header block then maps only the ELF header. Bytes after the input table
are left as they were.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
textureClearKeys reads the keys through a raw pointer, past the flush hook, so writes the
recorder holds for the key range are not in the bytes yet. A title that clears the keys of a
target with a kernel and samples it in the same batch had its keys read from the memory's
previous contents: in Palworld (PPSA23126) these were the float data of a reused heap, so the
keys read as mixed and the texels were read as stored.
When the keys read do not decode (not uncompressed, not a clear the format can fill),
WaitForKeyWriters answers from the pending fills and stores it knows, or waits for the batch
that writes the keys, and the keys are read again. Keys that decode are used as before.
RunSampledKeysBehindKernel (agc_driver_dcc_keys_first_write): key memory holding float bytes,
then the 0000 clear kernel recorded without a wait; the sampled texture's keys read Clear0000.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 Ship 2 Harkinian (PPSA99621) imports fchmodat through std::filesystem::permissions and stops at start-up on main. Resolve AT_FDCWD and absolute paths as usual and join other relative paths to the directory descriptor like openat, accept 0 and AT_SYMLINK_NOFOLLOW, and mask the mode to 07777.
TraceImport_nid_no_patch wraps a function of a library named in
APS5_TRACE_CALLS in a thunk that records the import, the thread and
the caller in a ring and counts the call, then jumps to the function.
One target keeps one thunk, so function pointers compare equal. The
crash and abort reports print the last 64 calls and the 32 most called
imports. Nothing calls TraceImport_nid_no_patch yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Porpoise (PPSA99764) stops on its weak chroot import (#3632, #4497).
Run 5 on main c2565f014, and again on a copy of that runtime:
FAIL: unresolved ELF import AKknh8Cx-V8 from libkernel_sys.prx
Its only call is in put_root_back (src/porpoise_jailbreak.cpp,
elripalda/Porpoise-Dolphin-Emulator-for-PS5 2ed1ab5a06995aa1): after a
jailbreak daemon has made the console's root its root and /app0 is
gone, it calls fchdir on the descriptor of "/" it opened at start, then
chroot("."), and checks the result. So it only ever puts back the root
it started in, and it does not use paths under any other root.
On top of the lookup errors of the previous commit, chroot now follows
FreeBSD 11.4 sys_chroot and pwd_chroot (8c639945, vfs_syscalls.c and
kern_descrip.c) for that case: a directory that is the guest root
("/", "/..", "." after fchdir to "/") succeeds without a change, since
fd_rdir would be set to the vnode it already is. The guest is taken
never to have changed its root, so with chroot_allow_open_directories 1
an open directory does not give EPERM. ENAMETOOLONG is added as in
pathconf, and a ".." through a file reports ENOTDIR instead of
escaping as a filesystem_error. Any other directory still throws: the
resolver's root and mount aliases are fixed, and no title is known that
needs a real new root.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Porpoise (PPSA99764) imports chroot (AKknh8Cx-V8) from
libkernel_sys.prx, not libkernel.prx, one reference. Predicted stop
once pathconf, the pthread_attr calls and ftrylockfile ahead of it are
covered (not a run):
FAIL: unresolved ELF import AKknh8Cx-V8 from libkernel_sys.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 sys/kern/vfs_syscalls.c sys_chroot and change_dir
(8c639945a44588f6cfdddfb057bb5aad88735da0) for a root caller up to the
lookup: EFAULT for a null path, ENOENT for an empty or missing one,
ENOTDIR for a file or a path through a file. A directory that is found
throws with the path named: the resolver's root is fixed and the mount
aliases (/app0, /savedata0) are host paths that do not move with it,
so it cannot be re-based, and a real chroot is not modelled. Nothing
shows that Porpoise calls it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#5173 changes the create condition right after the last error check, and #3950 removes the comment after the create block, so clearing the result in either place made the pull requests conflict. The result is still cleared only after every error return and before anything is written to it.
The console's mouse reports relative motion, so the host cursor can never line
up with the cursor a title draws. While a title has a mouse open and the
window is focused the host cursor is hidden and captured, released on focus
loss or while the pad's mouse-to-stick toggle is on. Motion deltas are scaled
from the presented image's size in window units to the title's display
resolution with sub-pixel carry, so the title's cursor travels as far as the
host pointer would, on scaled desktops and letterboxed windows alike.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Linux, glibc maps the stack of every guest thread near the top of the
host address space (around 0x7fff00000000). On the console, application
stacks lie inside the application area below 0xFC00000000, and guest code
relies on that: ChakraCore's garbage collector indexes a 4 MiB table with
address >> 18, so it only covers addresses below 1 TiB and reports
Js::OutOfMemoryException for a stack above it.
Map each guest thread's stack with DoMapAnon, which allocates from the
guest arena, behind a PROT_NONE guard page, and pass it to pthread_attr_setstack.
The stack is freed after a join; the stack of a detached thread is freed once its
host thread is gone from /proc/self/task.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ioctl() on a file, directory, pipe or character device threw "request
0x20006601 ... is not implemented" for FIOCLEX, and the same for
FIONCLEX, although the close-on-exec flag they change is already kept
for these descriptors and reported by fcntl(F_GETFD).
FreeBSD 11.4 handles both requests in kern_ioctl() for every descriptor,
before the request reaches the file's own ioctl, by changing the flag
F_GETFD reads. sys/kern/sys_generic.c, kern_ioctl():
case FIONCLEX:
fdp->fd_ofiles[fd].fde_flags &= ~UF_EXCLOSE;
goto out;
case FIOCLEX:
fdp->fd_ofiles[fd].fde_flags |= UF_EXCLOSE;
goto out;
sys/kern/kern_descrip.c, kern_fcntl():
case F_GETFD:
...
td->td_retval[0] =
(fde->fde_flags & UF_EXCLOSE) ? FD_CLOEXEC : 0;
Both requests now set or clear that flag and return 0. libuv before 1.44
sets close-on-exec this way on FreeBSD (uv__cloexec_ioctl).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5MC (PPSA99420, Kodi 22) imports clock_settime (d7nUj1LOdDU) from
libkernel.prx, one reference, after sigwaitinfo in its import order.
Predicted stop once the imports ahead of it are covered (not a run):
FAIL: unresolved ELF import d7nUj1LOdDU from libkernel.prx
The NID is computed with tools/nid_names.py and matches aerolib.
Follows FreeBSD 11.4 sys/kern/kern_time.c sys_clock_settime and
kern_clock_settime (8c639945a44588f6cfdddfb057bb5aad88735da0) for a
root caller: EFAULT for a null time (copyin), EINVAL for any clock but
CLOCK_REALTIME and for a negative tv_sec or a tv_nsec outside 0 to
999999999. A valid CLOCK_REALTIME request throws with the requested
time named, since the host clock must not be set and no guest clock
offset is kept.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: David <david@example.com>
The Python in PS5MC (PPSA99420) reads sysconf(_SC_CLK_TCK) while
importing its posix module and failed with "cannot read
ticks_per_second". It returns FreeBSD's CLK_TCK, 128.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flip packet now delivers the queue's end-of-pipe event once the work
recorded before it completes, as a RELEASE_MEM with INT_SEL does. Minecraft
(PPSA17221) registers the graphics event, never requests an interrupt in its
releases, and waits on the event after every flip while holding its render
lock, so it presented one frame and then waited forever.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GDS is one memory for the whole GPU on AMD hardware: every work-group's
ds_read and ds_write with gds see the others' writes, with no cache in
between. The recompiler keeps it in a storage buffer that was not decorated
Coherent, so on a GPU whose L1 caches storage buffer loads, NVIDIA's from
Turing on among them, a plain GDS read could return a value cached before
another work-group's GDS write. Atomics were not affected. The gds buffer is
now declared Coherent.
verifyCoherentGds in agc_shader_memory compiles a ds_read_b32 and a
ds_write_b32 on GDS and checks that the gds buffer is Coherent.
#4265 gives each fiber an mmap'd host stack on Linux and leaves Windows
as it was: there a fiber still runs host calls inside the context the
title passed to sceFiberInitialize. Demon's Souls packs its 16 KiB job
fiber contexts back to back. A job that calls
sceAgcCreateInterpolantMapping reaches shader preparation on the fiber
(Driver::ResolveShaderAbi, PrepareRegistered, the RDNA decoder,
BuildShaderStageInputInfo), goes more than 16 KiB deep and overwrites
the frames at the top of the context below it. On the loading screen
after the opening cinematic the fiber that owns that context then
failed the title's stack check, or returned to 0x7fffffff.
On Windows each fiber now gets a host stack from VirtualAlloc, the
context size plus 1 MiB, committed like the 1 MiB host margin of guest
threads, with a PAGE_NOACCESS page at the bottom. While the fiber runs,
the TIB stack base and limit and the deallocation stack describe that
host stack, as Windows does for its own fibers, so stack probes and
GetCurrentThreadStackLimits see the stack the fiber runs on. A host
stack that cannot be allocated, protected or released throws instead
of falling back to the context.
agc_driver_gpu_map_on_fiber: GetCurrentThreadStackLimits on a fiber now
returns its host stack, which has room for the import, so on Windows a
GPU mapping made on a fiber imports on the fiber's stack instead of a
host thread. The fiber's context is still not written.
Test: on Windows, fiber checks that the TIB stack bounds inside a fiber
cover its host stack, at least the context plus 256 KiB. With #4265
alone it fails at "the thread's stack bounds are the fiber's host
stack", and without that check at "a host call from a 16 KiB fiber
writes nothing below its context"; with this change fiber and
agc_driver_gpu_map_on_fiber pass (Windows Server 2025, RTX 3060,
WinLibs GCC 15.2.0, Release).
Demon's Souls (PPSA01342) on that host: at the loading screen the two
16 KiB job fibers above the overwritten one now use 17,592 and 16,936
bytes of their host stacks, the title's stack check no longer fails
there, and the load goes on until a separate Vulkan allocation failure
(vkAllocateMemory of a 4 MiB host-visible buffer, out of device memory).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Host code that a guest fiber calls runs on the fiber's stack. Demon's
Souls (PPSA01341) gives its job fibers 16 KiB contexts packed next to each
other, and a fiber that reaches shader preparation through
Driver::ResolveGraphicsStagesAbi goes about 21 KiB deep when SPIRV-Tools
validates the module. That overwrites the saved frame of the fiber below,
and the next switch to that fiber pops garbage into rbp, rbx, r12-r15 and
rip (a jump to 0x1a in the observed crash).
Fibers now run on an mmap'd host stack of the context size plus 1 MiB,
with a guard page at the bottom, the same margin guest threads get since
#2647. The guest context is no longer written. Context size checks fill
and scan the top context-size bytes of the host stack, so
size_context_margin keeps its meaning. If the mapping fails the fiber
runs in its context as before. Windows is unchanged: it pins and
publishes the guest context as the stack, and that path is not covered
here.
The fiber test now runs a 256 KiB host call from a 16 KiB fiber placed at
the top of a guard-filled area and checks nothing below the context was
written. It fails on main.
MinGW's __cxa_thread_atexit asserts dso is null or its own __dso_handle, so every guest export that forwards a guest module pointer needs the null rule, not only LibcInternalExtCxaThreadAtexit. The test now passes a non-host DSO on Windows too.
Co-Authored-By: Claude <noreply@anthropic.com>
The H+8UBOwfScI import was a throwing stub (libcCyberUnknown08). It is
the NID of "__powidf2", the double-precision integer-power helper that
complements __powisf2.
image_sample_d and image_sample_cd on a cube pass their gradients through the
same conversion as the face coordinates, which subtracts 1.0 to move AMD's
[1, 2] face range to [0, 1]. A gradient is a difference of coordinates, so the
bias turned a one-texel gradient into one of about -1 and the sample read the
smallest mip. Gradients now skip the bias.
agc_driver_image_sample_cube_derivatives samples every face of a 16x16 cube at
five levels through image_sample_d and checks the level and face it read.
TechnicalDebt records that the gradient handling follows Mesa and was not
measured on hardware.
DB_SHADER_CONTROL.DEPTH_BEFORE_SHADER (bit 12) is what a pixel shader compiled
for early depth/stencil sets: the depth and stencil tests and writes happen
before the shader runs, even when it kills pixels or writes memory. The bit
was accepted but never read, so such shaders got EarlyFragmentTests only when
Z_ORDER was EARLY_Z_THEN_LATE_Z and they had no kill or exports. Their killed
pixels then left depth unwritten, and occluded pixels still wrote memory.
EARLY_Z_THEN_RE_Z (Z_ORDER 3) was not treated as early either.
DecodePixelStageInfo now sets earlyZ for DEPTH_BEFORE_SHADER, and for Z_ORDER
1 or 3 under the existing kill and export conditions, including the stencil
reference export. The backend no longer repeats those conditions; it keeps
only the alpha-to-coverage one, so a shader with alpha-to-coverage still runs
its tests late. earlyZ keeps its place in the request, so the serialization
version does not change.
earlyFragmentTestsTests in agc_driver_graphics checks which DB_SHADER_CONTROL
values declare EarlyFragmentTests, and that alpha-to-coverage keeps Z_ORDER 1
and DEPTH_BEFORE_SHADER late, directly and after a serialization round trip.
stencilRefExportTests checks that DEPTH_BEFORE_SHADER with a stencil reference
export still decodes as early.
TechnicalDebt records that the bit follows Mesa and that its combination with
a Z export, a mask export, a stencil export or alpha-to-coverage was not
measured.
XPSemu PPSA97358 imports both from libkernel.prx; with the imports ahead
of them covered, its predicted next stops (relink registry order, not an
observed run) are:
FAIL: unresolved ELF import KaK52lYTBIs from libkernel.prx (execv, 7 call sites)
FAIL: unresolved ELF import kdguLiAheLI from libkernel.prx (execve, 4 call sites)
Today the title stops earlier at the observed
FAIL: unresolved ELF import aRo9AhFUXcM from libkernel.prx (sigsetjmp, merged in #3486)
As for execvp (#2827), the process image cannot be replaced here, so both
throw with the execvp message pattern once FreeBSD 11's execve would start
loading the image (freebsd-src 8c639945, sys/kern/kern_exec.c
exec_copyin_args, do_execve, exec_check_permissions; lib/libc/gen/exec.c
execv passes environ). Before that they fail as FreeBSD does: EFAULT for a
null argument vector or path, ENOENT and ENOTDIR from the lookup as stat
reports it, EACCES for a path that is not a regular file. Paths of
PATH_MAX bytes or more and strings over ARG_MAX throw instead of guessing
the console's limits. The execv calls in XPSemu's sources run in a fork
child (net/tap.c), and its fork fails with ENOSYS (ZiZc3/XPSemu 8eec07b4,
ps5/compat/libc_missing.c, ps5/build-title.sh --defsym).
NIDs computed with tools/nid_names.py and confirmed in aerolib.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 57f0fa37132bc6c78bc02282044a00e2db859484)
getsockopt and setsockopt on a libkernel socket knew four SOL_SOCKET options
(SO_REUSEADDR, SO_BROADCAST, SO_SNDBUF, SO_RCVBUF) and failed every other
one with ENOPROTOOPT. The Mound's EOS SDK reads SO_ERROR on two of its
sockets after a connect, which therefore always failed.
The SOL_SOCKET options now follow FreeBSD 11.4 sosetopt and sogetopt:
- SO_ERROR returns the pending error of the socket as a FreeBSD errno and
reports it once (Winsock does not clear it on read, so the library does).
- SO_TYPE, SO_ACCEPTCONN, SO_KEEPALIVE, SO_DONTROUTE, SO_OOBINLINE and
SO_LINGER are read from and set on the host socket; the boolean options
read back as their own bit, as FreeBSD returns so_options & option.
- SO_DEBUG, SO_USELOOPBACK, SO_REUSEPORT, SO_TIMESTAMP and SO_NOSIGPIPE stay
ENOPROTOOPT until a title needs one.
- A value is read from its first int and fails with EINVAL when shorter, a
getsockopt buffer is filled up to its length (sooptcopyin, sooptcopyout),
SO_SNDBUF and SO_RCVBUF below 1 fail with EINVAL, and SO_LINGER outside
0..65535 seconds fails with EDOM.
A connect on a non-blocking socket now fails with EINPROGRESS on Windows too
(it was EWOULDBLOCK, which a title waiting for the connection does not
expect).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With float16DenormPreserve, EmitF16BitsToF32 rebuilds the bits of a NaN
half after UnpackHalf2x16, because NVIDIA returns the default NaN and
Intel quiets it. When every use of the converted operand is an f32
comparison or a NaN test, directly or through abs and neg, only whether
it is NaN matters, so the conversion returns UnpackHalf2x16's result as
is. v_cmp_*_f16 goes from 11.5 to 4 SASS on an RTX 4060.
agc_driver_f16_to_f32_unpack adds v_cmp_u_f16 and v_cmp_lt_f16 over all
65,536 halves with each decode the device supports. Its SPIR-V check
requires the v_cvt_f32_f16 conversion to rebuild the NaN bits and the
compared conversions not to.
Palworld (PPSA23126, #2648) stops at start-up on the export, imported by
its bundled libdiscord_partner_sdk.prx:
FAIL: unresolved ELF import oiOi7vgPfAY from libSceAudioOut.prx
PPSA28416 imports it from its bundled party-ps5-shipping.prx as well. The
call itself has not been observed yet.
SharpProspero (5dfdb4b0, Interop/Audio/AudioOut2.cs:460-464) declares
`int sceAudioOut2EnableChat()`, "Allows chat audio alongside the
application's own", and the ps5-payload-dev SDK stub (f5d40f02,
sce_stubs/libSceAudioOut.c:62) exports it as well. There is no context or
handle to validate. The host mixes no chat audio and AudioOut2 has no chat
port, so the call records that chat audio is allowed (traced once with
APS5_TRACE_AUDIOOUT2) and returns 0. Whether the console fails it without
a chat session or when repeated is recorded in technical debt, and the
PPSA28416 "signature unknown" entry is dropped.
NID oiOi7vgPfAY matches tools/nid_names.py and the shadPS4 aerolib list.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sceAgcSetAmmSemaphoreMemory(memory, sizeInBytes) registers the AMM semaphore memory once and zeroes the first 8 bytes of each 32-byte label; sceAgcGetSemaphoreLabel(index, label) writes memory + 32 * index for an index inside the registered memory.
Unblocks New Joe & Mac Caveman Ninja (PPSA02801) which imports both symbols. Matches OpenAGC and sharpemu agreements on signatures, 16 KiB alignment, 32-byte stride, and zeroed label counters.
A failing _Pad primitive threw a host std::runtime_error, which no guest
catch can see. threadcall.cpp calls the checked wrappers of xthread
(_Mtx_lockX, _Cnd_waitX, _Thrd_startX), which pass a non-success code to
_Throw_C_error: _Thrd_nomem and _Thrd_timedout become
resource_unavailable_try_again, _Thrd_busy device_or_resource_busy and
_Thrd_error invalid_argument, raised through _Throw_Cpp_error, which main
now implements. A failing thread start is _Thrd_startX's
resource_unavailable_try_again.
_Release now wakes the launching thread with _Cnd_signal, as
threadcall.cpp does; libc has it since the pad was written.
guest_ampr_threads checks, in a forked child whose guest terminate
handler reports the thrown object, that _Release on the thread still
holding the pad's mutex throws std::system_error with EBUSY. It fails
with the previous host exception and with busy mapped to
invalid_argument.
Guest texture formats 51 (SNorm) and 55 (SInt) of the 10-bit packed group were missing from the texture lookup and the descriptor format table, so a descriptor using them threw 'unsupported guest texture format' while the UNorm (50) and UInt (54) siblings worked. Map them to A2B10G10R10_SNORM_PACK32 / A2B10G10R10_SINT_PACK32, classify them for sampling (float / signed integer), and list SINT among the integer formats.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Implement _ZNSt4_PadC2Ev, _ZNSt4_PadD2Ev, _ZNSt4_Pad7_LaunchEPP7pthread
and _ZNSt4_Pad8_ReleaseEv, which Deathloop (PPSA01670) and Minecraft
Legends (PPSA05510) import from libSceAmpr for the std::thread launch path.
The algorithm and object layout follow Dinkumware V6.00 thr/xthread and
thr/threadcall.cpp of Visual C++ 2013 (ganboing/crt.vc12 f1d47126): a
vtable pointer whose first slot is the pure virtual _Go(), then the
condition and mutex handles and bool _Started (0x8, 0x10, 0x18), with the
console's pointer-to-slot handles of libc Threads.cpp (#3079). The pad
uses libc's _Cnd_*/_Mtx_* exports, starts the guest thread with
pthread_create and an entry that calls _Go() and returns its result as
the exit code, as _Call_func does. The unverified layout, thread
attributes and error reporting are recorded in TechnicalDebt.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Track mutations against the current draw dependency mask and preserve device and shader registry identity in the draw key. Validate mutations, projected masks, PM4 packets and prepared graphics against full recomputation.
Written with AI assistance.
scan_library and collect_shaders read CMakeLists.txt, the ISA table and the opcode header with the locale codec. On Windows that is cp1252, so a UTF-8 file containing U+2010 or U+2192 raised UnicodeDecodeError. These reads now pass encoding="utf-8" and errors="ignore", and a subprocess test with an ASCII default codec covers it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The repository check compared the file name to a lower-case set, so a
file added as CLAUDE.md, Agents.md or GEMINI.md passed the check while
claude.md was rejected. Compare the lower-cased name so every spelling
of an agent instruction file is reported.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The per-thread servent storage held std::string and std::vector
members, so getservbyname_nid_postfix registered a thread_local
destructor through __cxa_thread_atexit. On Windows that destructor runs
after emulated TLS freed the object, and prx_thread_local_destructors
(#4029) rejects libScePosixForWebKit.prx for it.
The result now lives in a thread_local struct of fixed-size char arrays
and an alias pointer array, sized for the longest name (submission),
alias (www-http), protocol (sctp) and alias count in the embedded table;
static_asserts check the sizes against the table and that the struct is
trivially destructible. The guest servent layout and the per-thread
result FreeBSD's getservent.c keeps in its NSS TLS state are unchanged.
Nothing is allocated, so the ENOMEM path is gone.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Implement getservbyname (H1bArJPagV0) in libScePosixForWebKit. Cloud
Play (PPSA99600) imports it from this library only (registry index 408,
call site 0x1042124); once fnmatch (#3545) is in, its predicted load
stop is:
FAIL: unresolved ELF import H1bArJPagV0 from libScePosixForWebKit.prx
The ps5-payload-dev SDK stubs (f5d40f02, sce_stubs/) declare
getservbyname in libScePosixForWebKit.c and not in libkernel.c or
libSceLibcInternal.c. The call site is the service-name branch of a
getaddrinfo inside the title: a non-numeric service is looked up with
protocol "tcp", "udp" or "sctp" and the port is read big-endian at
offset 16 of the result, which fixes the servent layout used here
(name, aliases, 32-bit port, protocol at 0, 8, 16, 24).
The guest has no /etc/services and the console's file is unknown, so the
function searches 37 lines of FreeBSD 11.4 etc/services (8c639945):
ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap,
https, rtsp, submission, imaps and pop3s with their aliases and
protocols, in file order. None of their names or aliases appears on an
earlier line of the full file, so these lookups answer as the full file
would. The search follows files_servent in lib/libc/net/getservent.c
(8c639945): exact, case-sensitive match on the name or an alias, a null
protocol takes the first match, the port in network byte order, the
result in per-thread storage like getserv's NSS TLS state. Any other
name returns null without touching errno, as for a name the file lacks.
The repository's getaddrinfo still resolves service names through the
host, so there was no table to share; this is recorded in
TechnicalDebt.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Admit PA_SC_AA_CONFIG bit29 only without active perspective or linear centroid inputs, or with a skipped pixel program. Active/unspecified centroid inputs and unrelated AA fields remain refused. Source: pinned PS5_Mesa 0b2d6d1a GFX10.3 schema/RADV producer; existing ENA/ADDR intersection. Console covered-centroid interpolation remains unmeasured.
Full build/all 124 runtime libraries, conventions and registration pass. Final Windows suite: 965 pass, zero fail, seven skips of 972. Isolated state test passes 20 repeats and fails 5/5 with main State.cpp; four guard mutants fail 5/5 each. Original two title register captures reproduce the main refusal and pass this check with candidate, five times each. NVIDIA relevant groups pass; AMD draw-cache failure also occurs on unchanged main and is reported in issue4435.
AI-assisted: Codex.
TessellationLowering refused every domain-program read of the off-chip
ring outside its patch's control points, so domain programs that read
the hull's patch constants, like the quad terrain domain in Ghost of
Yotei at 0x8000 - 208 * (rel + 1), aborted.
Such reads now lower to a new PatchInput kind and become Patch inputs.
Hull outputs and domain inputs place a constant by its off-chip address
modulo the record stride, in a record-sized array at location
ceil(hsStride / 16); each program derives both on its own, so the two
interfaces match although they compile separately. Domain reads at an
address that does not move with the relative patch, across more than
one record or inside the control-point region still throw, as does a
domain program that reads patch constants but no control point. Hull
patch-constant writes now take the same bound: above the control points
of all NUM_PATCHES patches, not just one patch's.
TessellationLowering proved ring and LDS addresses affine over a
relative patch index of up to 256. A hull that lays its patch-constant
records out downwards, at 0x8000 - 208 * (rel + 1) like the quad
particle hull in Ghost of Yotei, then reached a negative address for
the last slot and aborted as "not affine". The relative patch index of
hull and domain programs stays below VGT_LS_HS_CONFIG.NUM_PATCHES
(bits 7:0, patches per threadgroup), which is 63 for that draw.
DecodeShaderStages now carries NUM_PATCHES as patchesPerGroup in the
tessellation configuration (draw keys, hull and domain cache keys,
recompile request version 22) and refuses zero, and the lowering bounds
the relative patch index by it. Configurations without it keep 256.
8 MiB window, so a range larger than the window is always imported alone. A
buffer bound across two such ranges then has no import and is copied whole on
every use: PPSA25344 binds a 368 MiB buffer across a 320.75 MiB and a 47.3 MiB
range and copies it on almost every frame.
When UploadFinish meets such a region, it imports the run of the ranges the
region crosses, provided every one of them is larger than the window, and
retires their own imports. It does this before any region of the build
confirms an import, so the retire moves the epoch ahead of every pointer the
build would use. A range that large is registered whole, not grown in 64 KiB
pieces like a heap, so the joined import is not re-imported as a heap grows; it
stands in refreshImports while exactly its ranges stand. A later join that
reaches a joined import takes it in, so two joins that share a range never
retire each other in turn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The [present] line divided the GPU busy and idle time of a window by the
presentations that got a swapchain image. That time covers the GPU work
of every frame, so with frames dropped for want of an image it read
several times too high (278.9 ms instead of about 52 ms on a hidden
window, reported on #4684). Those two figures are now divided by the
presented and dropped frames; the per-present blit figures are unchanged.
Every native save reported SAVE_DATA_BLOCKS_MAX blocks, all of them free,
in the sceSaveDataDirNameSearch infos and in sceSaveDataGetMountInfo,
whatever the title created it with and whatever its files held. A save's
size is now the blocks of the sceSaveDataMount3 call that created it,
kept beside the save; a save without a kept size takes that of its next
mount that names one, and otherwise keeps SAVE_DATA_BLOCKS_MAX. Free
blocks are what the save's files leave of it, each file rounded up to
32 KiB blocks, and the blocks and free-blocks sort keys use both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The presenter acquired the next swapchain image with a 5 s timeout
before completing the guest's flip. With FIFO on Wayland, NVIDIA's
driver hands out about one image per second while the window is hidden
(another workspace or occluded): vkAcquireNextImageKHR blocked for
~1 s per flip, and the title ran at exactly 1 frame per second. Stack
snapshots of Uncharted: Legacy of Thieves Collection (PPSA05684) showed
the presenter in drmSyncobjTimelineWait under AcquireImage in every
snapshot taken during such a stall, and in none outside them.
The acquire no longer waits: VK_NOT_READY or VK_TIMEOUT drops that
presentation, as an out-of-date swapchain already does, and the flip
completes. APS5_PROFILE_DRAW's [present] line counts the drops.
Menu with the window moved to an empty workspace for 50 s: 7.5 fps and
about 65 drops per 10 s while hidden (1 fps before), no drops while
visible.
ES-DE in RetroArch (PPSA99169) imports tzname from libSceLibcInternal and
fails to load without it; ICU's uprv_tzname reads it after localtime. It
points at the host's zone names and tzset refreshes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a PS5 (firmware 13.60) pthread_setschedparam and
scePthreadSetschedparam accept only SCHED_FIFO and SCHED_RR with a
priority of 256 to 767 or 896 to 959, return EINVAL otherwise, and
keep the policy for pthread_getschedparam. A created thread takes the
policy of explicit scheduling attributes, or its creator's when it
inherits. Here any policy and priority were accepted and SCHED_FIFO
was always reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wait for overlapping CPU writebacks and completion labels before importing buffers or accepting cached image resources. Cover texture data and compression metadata, retain asynchronous disjoint and GPU-only dependencies, and avoid recursive completion waits.
Reproduced fresh-buffer and cached-image failures with CPU Vulkan before fixing them. Regression covers fresh/reused bindings, dispatch/draw copied writers, texel/metadata updates, labels and no-wait cases; passes CPU Vulkan and RX 6600 synchronization validation. Graphics and guest-memory suites pass. The Skyrim loading GPU fault remains unattributed.
AI-assisted implementation and independent code review.
The kernel stores through the V# FORMAT; with an 8- or 16-bit format it writes 1 or 2 bytes per element, which a zero fill of the whole range would also clear between. Same check as MatchDwordPatternFill (format 20-22), with test rows.
Allow calling threads to query and set affinity using their own thread ID in addition to -1. Return ESRCH for non-matching thread or process IDs and EINVAL for invalid level/which arguments instead of throwing NotImplemented, matching FreeBSD cpuset semantics.
A wave64 compute kernel that stores dword 0 at index group * 64 + thread through a 4-byte
stride buffer is run as a buffer fill. Palworld (PPSA23126) clears its DCC keys with it every
frame; as a shader dispatch its writes were unknown to the DCC key proofs, so every refresh of
those targets waited for the GPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main refuses every storage view of a depth or stencil plane that is not
2D. A 2D array view is now taken as the plane only at layer 0 with one
layer; a view with more layers or another base layer still throws.
A one-layer 2D array view replaces a 2D view of the plane like any other
view, so a store back through the replaced 2D view throws. The depth
storage dispatch test runs one-layer views of both planes and checks
that the depth and stencil tests see what they stored, and that a
two-layer view still throws.
A storage view of a depth or stencil plane was taken for the depth surface
only as a 2D image. A 2D array view of layer 0 with one layer and the
plane's extent and format is the same plane, and now gets the plane's
storage image too; a view with more layers still takes the ordinary path.
The plane's storage image is recreated when the view's dimension changes.
PPSA01289 (Sackboy: A Big Adventure) binds the stencil plane of its
2880x1620 D32S8 surface as a one-layer R8_UINT 2D array from a compute
shader during presentation, and read guest memory instead of the stencil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1463, every f16 operand read decodes sign, exponent and mantissa
with integer operations, 8 to 12 instructions more per read than
UnpackHalf2x16 on NVIDIA. The decode works around an AMD Windows driver
that folds the mask of a byte-selected half into UnpackHalf2x16.
A new target flag, float16DenormPreserve, is set when the device reports
shaderDenormPreserveFloat16 and denormBehaviorIndependence ALL, the same
rule as float64DenormPreserve. With it, EmitF16BitsToF32 uses
UnpackHalf2x16 and declares DenormPreserve for 16-bit floats, under
which SPV_KHR_float_controls keeps unpacked subnormal halves. NVIDIA
returns the default NaN for every NaN half, so the NaN bits are rebuilt
from the half: its sign at bit 31, its payload at bits 22:13. The
DenormPreserve 16 mode is declared once, shared with the native f32 to
f16 conversion. Without the flag (lavapipe; RADV reports 32_BIT_ONLY)
the integer decode stays. Halves of fields narrower than 16 bits, such
as SDWA byte selects, keep the integer decode on every device.
The flag is serialized (request version 17) and part of the cache key.
New test agc_driver_f16_to_f32_unpack converts all 65,536 halves, with
nonzero high bits, through v_cvt_f32_f16, and byte-selected halves
through v_add_f16 SDWA, with each decode the device supports, and
compares the bits with an exact decode. agc_driver_f16_to_f32_unpack_spirv
checks which decode the SPIR-V uses with and without the flag.
A fast clear of a packed float color target (format 36) was refused as unencodable. Give the format
a DCC layout (11, 11 and 10 bit float channels) and its one value, so 0000, 0001, 1110 and 1111
clear codes fill the texels and a texture keeps those keys.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
strftime handed most conversions to the host's strftime, which differs
from FreeBSD 11's for E and O modifiers on conversions glibc does not
pair with them (%Ea was printed literally), padding flags on %+ and %v,
years outside 1000 to 9999 (%Y printed 899 and -1 where FreeBSD prints
0899 and -001), and out-of-range fields (%I of hour 100, %u of weekday
8). On Windows the padding flags printed nothing. strftime now follows
FreeBSD 11 strftime.c with the C locale on every host, and asctime
follows FreeBSD 11 asctime.c, padding years to four digits and setting
longer years apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
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>
When --out was not given, stubgen wrote the generated code and debt lines to
sys.stdout, which translates LF to CRLF on Windows, so redirecting stdout
produced CRLF files. stdout is now reconfigured to utf-8 with LF newlines,
matching the --out path. A subprocess test checks the bytes on stdout.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The --branch mode removed the branch's own open pull request only when its
head equalled the local branch exactly. Checking commits before pushing
left the branch ahead of its pull request, so the tool reported the user's
own pull request as overlapping. The filter is now a function that also
drops any pull request whose head is an ancestor of the branch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The previous commit replaced the out-of-range result with the default
quiet NaN 0x7fc00000, but main and FreeBSD e_acosf.c return 0xffc00000.
acosf now calls the host and restores errno, so only errno differs from
main. The guest test pins the result bits for 2.0f and -2.0f and keeps
the in-range case acosf(0.5f).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
acosf returned 0xffc00000 (errno 33) for 2.0 (0x40000000), where FreeBSD
lib/msun/src/math.h returns 0xffc00000 (errno 0).
It now returns the FreeBSD result for that case. A guest test covers it
and fails on main.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
A pull request body saved with a UTF-8 byte order mark starts with
U+FEFF before "### What", so the first heading is not recognised and the
checker reports the What section as missing. Decode the body file with
utf-8-sig so the mark is dropped and the sections are found.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The stub helper pattern required a leading static and a plain signature,
so exports calling helpers in anonymous namespaces, or helpers with
noexcept, a trailing return type or a pointer next to the name, counted
as done. The pattern now anchors at line start, makes static optional,
skips APS5_VABI exports and control keywords, and accepts those suffixes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
A package with no playgo-* file in sce_sys has no PlayGo data, so only chunk 0
is valid and any other id returns SCE_PLAYGO_ERROR_BAD_CHUNK_ID. A package with
playgo-scenario.json but no chunk table (PPSA01748) keeps accepting every id.
A chunk table (playgo-chunk.dat or playgo-chunkdefs.xml) still decides when it
exists. A sce_sys that holds other playgo-* files without a chunk table or a
scenario file, or a playgo-chunk.dat that is not a plgx table with a nonzero
count, throws naming the file, since no such dump is known.
Without this a title that walks the ids until the first error (Sifu PPSA03001)
never gets the error.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Remove test_built_uses_configured_ninja_outside_path per review: it copied
whatever ninja was on PATH and asserted one existed, so the suite failed on
exactly the setup this PR fixes. The rewritten tests already verify the
configured CMAKE_MAKE_PROGRAM is the program that runs; the suite now passes
with and without Ninja on PATH on Windows and Linux.
Three catalog titles stop on Windows when they poll a guest socket for
POLLPRI|POLLOUT to see whether a non-blocking connect has completed:
- PPSA97510 PSXS5 (socket 0x10000002, thread gamedb, 13 s):
what(): poll: POLLPRI is not supported by WSAPoll
- PPSA98510 SwanStation (sockets 0x10000003/0x10000002, threads gamedb
and update): what(): poll: POLLPRI is not supported by WSAPoll
- PPSA99003 ProsperoTV ({0x10000001, POLLPRI|POLLOUT} at 5.3 s):
what(): poll: POLLPRI is not supported by WSAPoll
FreeBSD's sopoll_generic (sys/kern/uipc_socket.c, freebsd-src 8c639945)
reports POLLPRI and POLLRDBAND alike, only while out-of-band data is
pending (so_oobmark or SBS_RCVATMARK). WSAPoll rejects POLLPRI in events
with WSAEINVAL but reports pending urgent data as POLLRDBAND, so on
Windows the guest POLLPRI is now asked of WSAPoll as POLLRDBAND and
POLLIN as POLLRDNORM (Winsock's POLLIN includes POLLRDBAND, which would
report urgent data as readable). Measured on this host: WSAPoll reports
POLLRDBAND after a peer's send(MSG_OOB), waits for it, and clears it once
the byte is read with MSG_OOB. Linux keeps polling POLLPRI on the host.
select needs no change: libkernel's select passes exceptfds as Urgent to
the libSceNet poller, which already asks WSAPoll for POLLRDBAND, and it
does not take libkernel guest sockets on either host.
The guest send and recv refuse MSG_OOB, so urgent data only comes from a
peer outside the guest's sockets; guest_poll now sends it from a host
socket and checks that a connected pair and a non-blocking connect
report POLLOUT and not POLLPRI. TechnicalDebt records where Winsock's
answer differs from FreeBSD's once the stream passes the mark.
Depends on #3649 (poll on file descriptors), whose two commits this
branch carries, since the titles reach this stop only with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The warning fired for every draw with PA_CL_VS_OUT_CNTL layer bits,
including layered draws that now route the render target index. It is
checked after the color targets are decoded and leaves out the routed
bits and the misc vector enable. The comment that said layer exports
are never routed is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agc_driver_layered_volume_submit submits a DCB whose context registers
alone describe a 3D render target (CB_COLOR_ATTRIB3 resource type 3D,
CB_COLOR_VIEW slices 1-3) and set PA_CL_VS_OUT_CNTL USE_VTX_RENDER_
TARGET_INDX, with both shaders registered before the draw. It checks
that each band lands in its own slice. Removing the layer export from
the vertex stage info in Driver::draw, or the bit-18 read in
DecodeState, fails it. Two more runs draw the volume under DCC keys:
uncompressed keys stay uncompressed, and 0000 fast-clear keys leave
every texel the draw does not write at the clear color and end
uncompressed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A color view of several depth slices of a 3D target (CB_COLOR_VIEW
SLICE_START < SLICE_MAX) was rejected as "color views of several array
slices are unsupported". Titles render such volumes with one draw: an
instanced draw whose vertex stage picks each primitive's slice through
the misc position export (PA_CL_VS_OUT_CNTL USE_VTX_RENDER_TARGET_INDX
with VS_OUT_MISC_VEC_ENA).
Depends on #423, which carries PA_CL_VS_OUT_CNTL to the recompiler.
- DecodeColorBuffer takes the slice range of a 3D target
(ColorTarget::layers, clamped to the surface's last slice), allows DCC
on 3D targets, and sizes a tiled 3D target by its whole surface
(SurfaceForTarget moves to State.cpp for that). DecodeState sets
State::renderLayers and, for a layered draw, USE_VTX_RENDER_TARGET_INDX
(State::layerExports); color targets of different slice counts and
layered draws with a depth target throw.
- The driver adds State::layerExports to the PA_CL_VS_OUT_CNTL bits #423
passes (it already passes VS_OUT_MISC_VEC_ENA), so the render target
index reaches gl_Layer only for layered draws and stays ignored
elsewhere as before.
- A vertex stage writing gl_Layer declares ShaderViewportIndexLayerEXT
and SPV_EXT_shader_viewport_index_layer when the device offers them
(the device enables VK_EXT_shader_viewport_index_layer when present),
instead of SPIR-V 1.5 ShaderLayer, which a Vulkan 1.1 device cannot
take. Graphics validation accepts the capability, the extension and a
vertex Layer output when it is enabled, and a per-primitive Layer
output of a mesh stage.
- The resident target's attachment view is a 2D array view of the
slices, and the framebuffer has that many layers (Framebuffer and
AcquireFramebuffer take the layer count).
The execution test agc_driver_layered_volume_draw draws three bands,
one per layer, into slices 1..3 of a 4-slice 64 KiB-tiled volume and
reads every slice back: slice 0 stays untouched and slice 1 + L holds
only band L. It also recompiles a mesh program with the layer export and
validates it against the layered state. The mesh case does not draw:
lavapipe's LLVM backend aborts compiling that mesh pipeline ("Cannot
emit physreg copy instruction"), which a test cannot catch. On RADV the
same mesh draw renders the bands into the right slices.
PPSA14632 (LEGO Horizon Adventures) renders its 64x64x64 RGBA16F
translucency lighting volumes (two DCC targets, 64 instances) this way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodeShaderStages accepted only clockwise triangle patches and no HOS
level clamp narrower than [1, 63], so terrain drawn as TRI_CCW quad
patches with VGT_HOS_MAX_TESS_LEVEL 9 aborted. Quad domains and TRI_CCW
now decode and emit Quads and VertexOrderCcw; isolines, other
partitionings and point or line output still throw.
VGT_HOS_MIN/MAX_TESS_LEVEL are read per draw and pushed in the last 8
bytes of the pre-rasterization push constant slot, where the hull reads
them to clamp its factors; they stay out of every key and the recompile
request, so one hull serves every level. Zero, negative and NaN outer
factors still cull; clamps outside 0 <= min <= 63, max >= 1,
min <= max throw. Quad hulls write four outer and two inner factors at
a 24-byte stride, the layout Mesa's TF ring store uses.
renameat (2a8e7452) calls rename_nid_postfix when both paths are
AT_FDCWD-relative or absolute. Rename.cpp is built into both libc and
libkernel, and on Windows libkernel_sys bound that import to libkernel,
which comes first on its link line. The NID patcher then rewrote the
import to rename's NID, which only libc exports, as libkernel keeps its
copy under the plain name. So libkernel_sys.prx fails to load with error
127 on Windows, and with it every title that imports it. Linux resolves
the symbol globally and was not affected.
rename's body is now RenameGuestPath_nid_no_patch, which both libraries
export under that name, and renameat calls it, the way libkernel_sys
already reaches libkernel's other helpers. rename_nid_postfix forwards
to it, so its behaviour is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dusklight (PPSA99305) imports atanhf (cPGyc5FGjy0) from
libSceLibcInternal.prx; the libc.prx it bundles only has a zero-return
stub for it.
The implementation follows the atanhf of the libc.prx bundled with
PPSA28319 (0x438a0), read from its disassembly: FreeBSD msun's
e_atanhf.c calling msun's s_log1pf.c, both as of 59bc2f09 (stable/9,
the revision the sinhf port took expm1f and expf from). The log1pf
paths for the positive arguments atanhf passes are inlined, as the
exported log1pf is still the host's. For |x| > 1, the infinities and
NaN, Sony's error helper raises FE_INVALID and sets errno to EDOM
before returning (x - x) / (x - x); for |x| = 1 it sets errno to
ERANGE before returning x / 0. Other arguments leave errno alone.
Running that code from the title's libc.prx on a Windows host gives
the same bits, errno and floating-point exceptions as the built
libc.prx for all 2^32 arguments, and msun's own e_atanhf.c and
s_log1pf.c built with the host compiler give the same bits. With the
host log1pf instead of the msun one, 4.7% of the arguments in [0, 1)
differ. guest_math pins bits for every branch reachable from atanhf,
both signs, and the errno and exception behaviour.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On memory outside the write watch a write-back compares guest memory with
the bytes saved at the upload and leaves the 64 KiB blocks that differ to
the CPU. A store of some of an image's units marked the whole saved copy
invalid, and a fill turned into an image clear or an image copy never
saved the bytes under it, so the next store had nothing to compare and
wrote its layers whole over what the CPU had written since.
The saved copy is now valid per tracked unit. A store, a drop or a stale
eviction invalidates only its own units, a CPU-path store makes the units
it stored valid again, and the write-back compares each run of selected
units that still has saved bytes. FillClear and CopyFrom save the bytes
of the units they fill when the surface is not watched, as an upload does.
Results over a range that left the watch while they were pending still
have no saved bytes; storing them now throws instead of overwriting the
CPU's writes (technical debt).
read_bda_span chooses between the span's in-place dword loads and its
per-dword byte fallback, which calls read_bda_dword_bytes. Each shape of
that choice so far hangs a group of NVIDIA drivers: a dispatch never
finishes and the device is lost.
As the two arms of one selection, with the loads emitted in the arm,
it hung drivers 591.86 and 590.48.01 when lanes took both arms (#3291).
As two selections, the loads under the span's condition and the fallback
under its negation (#4759), it hangs 595.97, 596.36, 610.62, 610.74 and
610.57.04 (#4975).
The loads now live in load_bda_span, a DontInline function that calls
nothing, and read_bda_span is one selection again: one arm calls
load_bda_span, the other is the fallback as before. Probe, loads,
lookups, faults and stops are unchanged for every lane.
On an RTX 4090 (Linux, driver 610.57.04) the two-selection shape hangs
agc_driver_scalar_wide_loads and agc_driver_flat_apertures_0..13 in a
build with SPIRV-Tools, and agc_driver_bda_span_reads and
agc_driver_pixel_interlock in a build without. All pass with this
change in both builds. Drivers 591.86, 590.48.01, 595.97, 596.36,
610.62 and 610.74 are not tested; a shape with the loads in their own
DontInline function was reported passing on 591.86 in #3291.
agc_shader_memory now requires that a span read function holds no load
and calls its load function once, and that the load function holds the
four loads and no call.
Refs #4975
PA_CL_VS_OUT_CNTL's clip and cull enables and its misc and distance vector bits reach the recompiler's vertex info (request version 20), the draw key, the vertex-info comparison and the recompile cache key. Registration and draw-time preparation set them the same way the draw does; a program without pos1..pos3 exports gets none, so its artifact does not depend on the register. Guest clip distances share main's gl_ClipDistance array with the all-zero position distance; cull distances need shaderCullDistance. Up to eight clip and cull distances are accepted.
Co-Authored-By: john.maia <hi@johnmaia.dev>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Initialize and Terminate track whether the library is initialized, and
SetVoiceChatState returns success without recording anything: the host has
no system voice chat for a title's own voice chat to announce itself to,
and the state argument is unknown.
GetVoiceChatUsageState keeps throwing. With the state argument unreadable
there is no recorded usage state, and whether the console returns the state
or writes it through an output pointer, and how it encodes it, is unknown.
Misuse throws rather than guessing an error code, as the library's error
codes are unknown.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guest formatter handed NaN to the host's snprintf, which prints
-nan for a negative NaN and +nan or " nan" with the + and space flags.
FreeBSD 11's vfprintf clears the sign of every NaN and prints nan (NAN
for the upper-case conversions), keeping the width and the - flag and
ignoring 0. The formatter now does the same for a, e, f and g and
their long double forms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
_Unwind_Context took its cursor table from LibcUnwind::CursorSlots, a C++
data symbol of libc. On Windows LibcUnwind::Frame holds an _Unwind_Context,
so libSceAgcDriver, which builds frames for its worker sampler, imported
_ZN10LibcUnwind11CursorSlotsE from libc.prx. The NID patcher renames that
export in the patched libc and leaves the import alone, so the patched
libSceAgcDriver, libSceAgc and libSceVideoOut failed to load with error 127
and patched_libraries failed. The table is now file-local and the context
reads it through UnwindCursorSlots_nid_no_patch, which keeps its name on
both sides.
Reports a full game, not a Play First Trial: 0 is written to the uint32_t output and 0 returned; a null pointer returns 0x817D0002.
Signature, value, one-word write and error match sceNpEntitlementAccessGetGameTrialsFlag and sharpemu src/SharpEmu.Libs/Np/NpEntitlementAccessExports.cs:98-116. 0x817D0002 is the parameter error of KytyPS5, prosper and sceNpEntitlementAccessGetEntitlementKey. The library has no initialization state, so none is checked. Update technical debt documentation and add unit test in guest_offline_imports.
They are modules of the console's own shell (VSH), not libraries a title
links. No title in the open PRs, discussions or code references them, and
every export is a stub that throws. Their Share*, ErrorDialog* and
SystemServiceParamGet* exports duplicate libSceShare, libSceErrorDialog
and libSceSystemService (#228, #241).
Co-Authored-By: Claude <noreply@anthropic.com>
XPSemu (PPSA97358) imports eleven posix_spawn calls from
libSceLibcInternal.prx, one reference each, and no call site is known.
Predicted stop (import order, not measured), behind isnanf (idx 296):
`FAIL: unresolved ELF import 44hlATrd47U from libSceLibcInternal.prx`
(posix_spawnattr_init, idx 347), followed by p--TkNVsXjA, LG6O0oW9bQU,
Sj7y+JO5PcM, Ud8CbISKRGM, Hq9-2AMG+ks, IUfBO5UIZNc, Heh4KJwyoX8 (348-355),
UV4m0bznVtU, Ani6e+T-y6Q and 3Nr9caNHhyg (379-381).
The nine object calls follow FreeBSD release/11.4.0 (8c639945a445)
lib/libc/gen/posix_spawn.c: posix_spawnattr_init allocates a zeroed
attribute object (ENOMEM on failure), _destroy frees it, _setflags and
_setsigdefault store the value; posix_spawn_file_actions_init allocates an
empty list (-1 on failure, as in 11.4), _destroy frees it with its
entries, and _addopen (path copied), _adddup2 and _addclose append in
order after returning EBADF for a negative descriptor. Null or
uninitialized objects, which FreeBSD dereferences, throw.
posix_spawn and posix_spawnp throw "process creation not implemented",
as execvp does: FreeBSD validates no argument before rfork/vfork, and no
child process can be created here. TechnicalDebt records them.
guest_posix_spawn covers the attribute round trips, the recorded action
sequence, the EBADF cases and the throws. NIDs computed with
tools/nid_names.py and present in aerolib.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The relinker's repeatable --executable converts another executable of the
title beside the output, sharing its libs, app0 and guest modules.
sceSystemServiceLoadExec maps /app0 paths to those executables, passes the
argument list on the new command line and finds the main executable through
APS5_EBOOT. RetroArch (PPSA99169) starts its frontend picker this way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mlockall (x7g7Ebeo8-U, aerolib) follows FreeBSD 11.4's sys_mlockall
(sys/vm/vm_mmap.c:1067-1131, freebsd-src
8c639945a44588f6cfdddfb057bb5aad88735da0) for the flag check: 0 or
bits outside MCL_CURRENT (1) and MCL_FUTURE (2) fail with EINVAL. The
title process is taken to pass priv_check(PRIV_VM_MLOCK), as mlock
already does.
Valid flags throw. MCL_FUTURE would need every later guest and host
mapping to be locked; nothing in the mapping paths does that, and
passing it to the host would make later host allocations fail above
the host RLIMIT_MEMLOCK. MCL_CURRENT would have to lock the whole
process, which on AnyPS5 also holds host stacks, heaps, modules and
driver mappings (sceKernelVirtualQuery reports them as guest memory),
with ENOMEM above a guest RLIMIT_MEMLOCK that is not known (getrlimit
throws for it). mlock pins host pages (VirtualLock, mlock) range by
range and keeps no record, so there is no state to extend. Recorded
in TechnicalDebt.
XPSemu (PPSA97358) calls it only with -overcommit mem-lock=on
(ZiZc3/XPSemu 8eec07b4, os-posix.c:330-355, reached from
system/vl.c:831-835):
int flags = MCL_CURRENT | MCL_FUTURE;
...
ret = mlockall(flags);
if (ret < 0) {
error_report("mlockall: %s", strerror(errno));
}
and exits on failure, so that option ends in the throw; without it
the title is not affected. Predicted stop removed (registry index 201,
not run):
FAIL: unresolved ELF import x7g7Ebeo8-U from libkernel.prx
guest_mlock checks EINVAL for 0, 4, 5 and -1 and the throw for 1, 2
and 3.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 87f4aa331b02d098fe27516809495447ff6a9fa6)
guest_rudp_context_queries released its context with sceRudpEnd(context)
and shut the library down with sceRudpTerminate(), and guest_rudp_pump
shut it down with sceRudpTerminate(), the order main had before the
swap. They now call sceRudpTerminate(context) and sceRudpEnd().
sceRudpEnd takes no argument and shuts the library down; sceRudpTerminate
takes a context id and releases that context. main had the two swapped,
so a title calling sceRudpEnd() passed a stray register as a context id
and the library stayed initialised.
Evidence:
- PS5 console measurement by hugodearaujo in #3073 (firmware 13.60):
"After `sceRudpEnd()` all three return `0x80770001` again"
(sceRudpGetMaxSegmentSize, sceRudpProcessEvents, sceRudpNetFlush).
- AlexAltea/orbital cc408367da6a6ebbf5ac1ac559fefeadc73b2831
tools/ida/db_types.json:1971,1996: `int sceRudpEnd(void)`,
`int sceRudpTerminate(int ctxId)`.
- RPCS3 26098fb42d090cd522b4afcf3b754dd987c82978
rpcs3/Emu/Cell/Modules/cellRudp.cpp:108: `cellRudpEnd()` tears the
library down.
- AndnixSH/Vita3K-PS-Vita-OS-Fork c86ac732bc811057e690bed280c5076bdf95385e
vita3k/modules/SceRudp/SceLibRudp.cpp:1032,1614: `sceRudpEnd()` clears
every context, `sceRudpTerminate(int ctx_id)` erases one.
The bodies are unchanged, only exchanged. guest_offline_imports now ends
the library with sceRudpEnd(), and guest_rudp_status checks that
sceRudpTerminate releases one context and sceRudpEnd all of them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pthread_atfork (U9t5kJAWPnA, aerolib) appends the prepare, parent and
child handlers to a process-wide list under a lock and returns 0, or
ENOMEM when the entry cannot be allocated, as FreeBSD 11.4's
_pthread_atfork does (lib/libthr/thread/thr_fork.c:78-100, freebsd-src
8c639945a44588f6cfdddfb057bb5aad88735da0). Null handlers are accepted;
FreeBSD's fork skips them. The handlers never run: fork
(libkernel_sys) throws and vfork is absent, so no child process is
created. The list is kept, in registration order, for a later fork
(PthreadAtforkHandlers_nid_no_patch); recorded in TechnicalDebt.
XPSemu (PPSA97358) calls it from a constructor, before main
(ZiZc3/XPSemu 8eec07b4, util/rcu.c:488-495):
static void __attribute__((__constructor__)) rcu_init(void)
{
smp_mb_global_init();
#ifdef CONFIG_POSIX
pthread_atfork(rcu_init_lock, rcu_init_unlock, rcu_init_child);
#endif
rcu_init_complete();
}
so a throwing export would stop the title before main. Predicted stop
removed (registry index 255, not run):
FAIL: unresolved ELF import U9t5kJAWPnA from libkernel.prx
guest_pthread_atfork checks registration order, null handlers,
concurrent registration from four threads and that no handler runs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit f37bccf6efd76edcde5d582800c89b83fdd30472)
#4988 adds a constant after NET_SO_RCVTIMEO and #4989 adds a function before receive_error, so defining the SCE timeout constants and timeout_option there made the pull requests conflict.
PS5MC (PPSA99420) imports wcsftime (XbVXpf5WF28, aerolib) as the last
absent libSceLibcInternal import in its registry order (index 481), so
its predicted stop is:
FAIL: unresolved ELF import XbVXpf5WF28 from libSceLibcInternal.prx
The stop is predicted from the import order, not observed in a run.
wcsftime follows FreeBSD 11.4 lib/libc/locale/wcsftime.c (8c639945):
the wide format is converted with wcsrtombs, strftime fills a buffer of
maxsize * MB_CUR_MAX bytes limited to maxsize, and mbsrtowcs converts
the result back. A format unit that the single-byte C locale of
lib/libc/locale/none.c cannot encode returns 0 with EILSEQ, a maxsize
whose product with MB_CUR_MAX overflows returns 0 with EINVAL, and an
empty or overflowing result or a null buffer returns 0 with errno
unchanged; the ERANGE or EINVAL set by the Windows CRT's strftime is
discarded. A null format or tm throws, as FreeBSD dereferences it.
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 fc6868d8b6fedd0a43987909cadef618f16ada4b)
Carry the poll-only portion of Doston Abdullayev’s public #1471 fix, preserving its original authorship. The select timeout change is already covered separately by #4754.
Every other export of the C++ runtime is declared APS5_VABI, but these two
were not. On Windows a title calls them with the System V convention while
they read the Microsoft one, so __dynamic_cast took its fourth argument, the
src2dst hint, as the source pointer: an ordinary downcast (hint 0) returned
null, and the negative hints made it read through an invalid pointer.
guest_dynamic_cast calls both through APS5_VABI declarations, as a title
does, and checks a single-inheritance downcast, a failed cross cast, a cast
of a base object and the header __cxa_init_primary_exception fills in.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SpecializeSpirv kept its per-id data (folded values, result types, scalar
types, removed ids, blocks, edges, copies, definitions and the live sets) in
std::map and std::set, and repeats fold, prune and copy propagation over the
whole module until nothing changes. Ids are dense below the header's bound,
so the tables become vectors indexed by id. Lookups of operands that are
literals rather than ids stay bounds-checked, iteration keeps ascending id
order, and an id at or beyond the bound now throws.
SrgbTwinFormat mapped R8_UNORM and R8G8_UNORM to R8_SRGB and R8G8_SRGB
without asking the device. Where 8_SRGB or 8_8_SRGB is decoded in the
shader (context.srgbDecodeFormats: the device cannot sample the sRGB
format, or APS5_SRGB_SHADER_DECODE=1), the forced view would have been
created with a format the device cannot sample. Those two formats now
get no twin there, so a FORCE_DEGAMMA sampler on them keeps the refusal.
The image is no longer created MUTABLE_FORMAT either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Black Myth: Wukong (PPSA23226) exits at start-up with
STATUS_DLL_NOT_FOUND after "Failed to load module:
libs\libSceDbgThreadSanitizer.prx". The executable does not need the
library: its bundled sce_module/libSceJobManager_nosubmission.prx lists
it in DT_NEEDED and imports __tsan_acquire and __tsan_release from it,
plus sceKernelIsThreadSanitizerEnabled from libkernel. The relinker
converts every module in sce_module and loads it at start-up (only
--module-dir directories are deferred), and GuestModuleBuilder adds each
module's host dependencies to the executable's DT_NEEDED, which the
Windows start-up stub loads.
libSceDbgThreadSanitizer now exports __tsan_acquire and __tsan_release
with the void(void* addr) signatures of LLVM compiler-rt's
tsan_interface.h (llvm-project 87f0227cb601, LLVM 20.1.8). Both throw:
AnyPS5 runs no race detector, and the module calls them only after
sceKernelIsThreadSanitizerEnabled reports the sanitizer enabled.
libkernel's sceKernelIsThreadSanitizerEnabled returns 0, as
sceKernelIsAddressSanitizerEnabled does; its int(void) signature is
assumed and recorded in the libkernel debt file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A V# spanning two registered ranges dissolved the shared space into the
build's own regions (about 10k regions copied, sorted and looked up per
build in Killing Floor 3), although the import covering the run binds it
in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
Homebrew built with the ps5-payload-dev SDK links LLVM libunwind
statically but imports _Unwind_Backtrace from libSceLibcInternal. Its
trace callback then reads each frame with its own _Unwind_GetIP, which
treats the context as a libunwind cursor and calls through the virtual
table in its first word. AnyPS5's context started with the saved RAX, so
the call jumped to a register value and the title crashed on its error
path (vkQuake, PPSA99010, without its game data). The context now starts
with a table in AbstractUnwindCursor's slot order that answers register
reads and writes, validFloatReg and isSignalFrame; other slots throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvDbpwUPTa78oCAJqe7m9K
A program with an atomic on a V# it specializes was compiled as
address-based (usesDma) so that the atomic's unaligned-address check could
call the fault function. The atomic itself goes through the element's
storage buffer and nothing in such a program reads the address tables. In
the driver an address-based build leases all registered guest memory and
binds every V# of the program in place inside a host import, so none of
its elements is staged in device memory, the atomic one included.
The atomic now asks for the fault buffer alone (usesFaultBuffer) and
records the fault through RecordBdaFaultWords, which has both forms. The
fault record, the stop of the invocation and the atomic are unchanged. The
64-bit fault address needs shaderInt64, as the scalar buffer load does. A
tessellation-control program with such an atomic is refused as before.
Test: agc_shader_memory compiles a buffer_atomic_inc on a specialized V#
and requires no address table binding, one fault buffer, one call of the
fault function, logical addressing and the atomic element flag; without
shaderInt64 the program is refused.
MarkWritten collects the fresh portions of the written blocks before
each GPU stamp. On Windows that walked every shared view of those
blocks twice under the mappings mutex even when all of them had been
collected before. Keep the never-collected portions as address ranges,
as the Linux tracker keeps _fresh, and return after one lookup when
none overlaps the request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceCoredumpWriteUserData (Dbbkj6YHWdo) and sceCoredumpDebugTextOut
(dei8oUx6DbU) get the signatures of Orbital's db_types.json (cc408367)
and SharpProspero's KernelSystem.cs (5dfdb4b0): ssize_t (const void*,
size_t) and void (const char*, int). WriteUserData moves out of
Unimplemented.cpp.
User data belongs to the core dump handler: the Vita's
sceCoredumpWriteUserData returns 0x800A0003 ("No user-data capture is
active") outside it (vita-headers e66ebe90), and the RED engine's PS4
crash handler makes every coredump call from its handler (Vangaurd
4f86d2b0). The handler is never invoked here, so WriteUserData returns
SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER (0x81180003, shadPS4
coredump_error.h e0a46675) sign-extended and keeps nothing; a null,
empty or wrapping buffer throws because the order of the console's
checks is unknown. DebugTextOut prints its text to stderr with the
[coredump] prefix; a negative length or a null text with a nonzero
length throws.
Evidence: Broken Sword Reforged (PPSA21480) reaches the family through
its sibling, #3704 comment 6113687542: "`ERROR agc_graphics.cpp:266
Sony call failed with code 8a6c9018` (the resource registration error,
followed by the error dialog), then `sceCoredumpWriteUserString not
implemented`." WriteUserString has no declaration in any source and
keeps throwing; its TechnicalDebt entry records the call. 007 First
Light (PPSA11386) imports WriteUserData (#2523) without calling it
before its stop.
Test: guest_coredump_user_data.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
FreeBSD's in_getsockaddr and in6_getsockaddr succeed for a socket that has no
local address and return the wildcard address with port 0. Winsock fails
getsockname with WSAEINVAL until the socket is bound, which the guest saw as
EINVAL. Return the zero address of the socket's family for that error.
link threw "a symbolic link as the existing path is not supported" when
its first argument named a symbolic link, so a valid call aborted the
title. FreeBSD's link follows that link (sys/kern/vfs_syscalls.c,
sys_link: "kern_linkat(td, AT_FDCWD, AT_FDCWD, uap->path, uap->link,
UIO_USERSPACE, FOLLOW)") and hard-links the file it points to; a link to
a directory then fails with EPERM ("if (vp->v_type == VDIR) ... return
(EPERM);") and a dangling one with ENOENT from namei.
link now checks the followed status of the existing path and, when it
is a symbolic link, creates the hard link to the file it resolves to.
std::filesystem::create_hard_link would otherwise link the symbolic
link itself on Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a521101b77c879234308711c5ec8a9f9cf00816f)
- Return SCE_PNG_DEC_ERROR_INVALID_SIZE when PngDecCreateParam::this_size does not match sizeof(PngDecCreateParam), matching libSceJpegDec behavior.
- Add test coverage for invalid create parameter size.
DecodeState mapped points, lists, strips, fans, rect lists and patches
but threw "unsupported primitive type 3" for VGT_PRIMITIVE_TYPE 3, the
line strip (DI_PT_LINESTRIP in Mesa's r600d.h). Record of Lodoss War
(PPSA05347) stops on it after a few minutes.
Vulkan allows primitive restart for line strips without the list
restart extension, and the precheck, the pipeline and the provoking
vertex check already treat the strip class that way; only the decode
lacked the case. The point and line depth bias enable stays rejected on
line strips like on line lists, and a line strip gets the line primitive
class for a pixel shader that reads the ancillary word.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
libkernel numbered guest sockets from GuestSockets::FirstDescriptor
(0x10000000), so a title that puts a socket in a 1024-bit fd_set writes
fd / 8 bytes past it. The Epic Online Services SDK does this on its
RtcNetworkThread and three titles stop there (#1794):
Palworld PPSA23126 (queeeef, main a11c8f7f7):
FATAL: unhandled exception 0xc0000005 on thread 37164 'RtcNetworkThread'
rip 0x00007ffcdb1ea2be eossdk-ps5-shipping.prx.guest.prx+0xdea2be
rcx 0000000010000005 rsi 0000000000400000 r12 0000000010000005
The Mound PPSA26223 (xXPBSXx, main 0a8b1d32b):
FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread',
rip eossdk-ps5-shipping.prx.guest.prx+0xefc6f4, rcx = 0x10000005,
rsi = 0x400000
Dying Light 2 PPSA02262 (tv7):
FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread'
rcx 0000000010000005 ... rsi 0000000000400000 ... r12 0000000010000005
On FreeBSD 11.4 (8c639945) FD_SETSIZE is 1024 (sys/sys/select.h:58-59)
and sockets take the lowest free entry of the one process descriptor
table that files use (kern_descrip.c:1643-1660 fdalloc,
uipc_syscalls.c kern_socket through falloc).
Every socket number is now a real host descriptor: a zero-access NUL
handle registered with _open_osfhandle on Windows (as directory
descriptors already are) and an F_DUPFD_CLOEXEC duplicate of the host
socket on Linux. The socket table maps that number to the socket, so
"is this a socket" is a table lookup instead of a range check at the 31
former FirstDescriptor comparisons. close releases both the socket entry
and the number, dup and dup2 duplicate the number through the host table
and keep the socket shared, dup2 of a file onto a socket number drops the
socket entry, and sceKernelClose now closes sockets too. sceNetSocket and
sceNetAccept take their ids from the same table instead of a counter from
32, so libkernel and libSceNet numbers never collide.
select polls guest sockets alongside libSceNet sockets, the random device,
stdio and (on Linux) host descriptors; poll throws only for an open
descriptor that is not a guest socket and reports POLLNVAL otherwise.
File calls that used to fail with EBADF on the socket range return
FreeBSD's answer for a socket: lseek ESPIPE (kern_lseek), ftruncate and
getdirentries EINVAL (invfo_truncate, getvnode), flock EOPNOTSUPP;
fstat on a socket throws. pread on a closed descriptor returns EBADF.
title failing on host descriptor numbers; these three titles are that
case, and this change keeps host numbering for files and only moves
sockets into it.
Tests: guest_socket_descriptors (new: socket below 1024, select on a
socket and a file together, close frees the number for a file, dup of a
socket, sceKernelClose), guest_select (socket and pipe together, Linux),
guest_poll, guest_dup, guest_socket_pair, guest_fstatfs,
guest_posix_filesystem and guest_sce_net updated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 20378375b31eb5db922db6e55f45e44a4dae6445)
The tiled cases of agc_multisample_guest_access destroyed each target while its layout transition and clear were still recorded, so the recorder later submitted commands for destroyed images and lavapipe crashed executing them. Each case now submits and waits first, as the linear case does. The 2x case runs only when the device renders 2x color, like the 8x case.
The NID -q-vbO859Tw, declared as sceVideoOutUnknown_q_vbO859Tw for
PPSA02530, hashes to sceVideoOutDeleteVrrActiveStatusEvent. It removes the
handle's VRR status registration from the queue like the other Delete*Event
calls, the counterpart of sceVideoOutAddVrrActiveStatusEvent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WeVmjnnPPKdxVTpr2Vjtw
The hardware ignores the LOD for unnormalized coordinates and samples the
base level. A sampler used only by explicit-LOD, implicit-LOD, bias and
gradient samples gets a pipeline specialization constant set from its
unnormalized bit when the binding plan is prepared. When the constant is
set, each of its samples is emitted as OpImageSampleExplicitLod with LOD 0,
which is what Vulkan requires of unnormalized samplers. Otherwise the
sample keeps its own form. Such shaders are no longer refused.
ApplyFastClear took the color fill before the CMASK clear, so a partial
fill of a multisampled target's color memory threw at the next use even
when a CMASK fast clear was pending over the whole target. The CB reads
a cleared tile's color from the clear registers whatever the memory
holds, so the CMASK clear is applied and the recorded fill dropped.
DecodeColorMetadataPass treated a CB_COLOR_CONTROL resolve as one only
when PA_SC_AA_CONFIG has a sample count, and checked the source against
it. A title that resolves with the rasterizer single-sample fell through
to the draw path and threw "only normal color rendering with copy ROP is
supported (... mode resolve)".
A color target's sample count is CB_COLOR_ATTRIB.NUM_SAMPLES, separate
from the rasterizer's; radeonsi itself writes PA_SC_AA_CONFIG 0 for a
multisampled framebuffer when rasterizer multisampling and line
smoothing are off. So a resolve is decoded when either the rasterizer or
CB0 is multisampled, and its sample count is CB0's. When the rasterizer
is multisampled, the source still has to match it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pipe check cached its first answer in the FileStream, so a stream
first used on a regular file stayed classified as a file after its
descriptor was replaced with a pipe (dup2 onto fileno(stream)), and a
later fseek reached the C runtime again and dropped buffered bytes.
fseeko and ftello now ask GetFileType on each call, as lseek on a pipe
does since #4670, and FileStream no longer keeps the state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sampled copy of a depth plane is kept while the surface's write
serial is unchanged. Storing a written storage view back into the
depth image did not bump the serial, so a later draw or dispatch that
sampled the plane as an array or cube kept the values from before the
store. The store-back bumps it now.
The dispatch test samples the plane through a copy, stores to it in a
dispatch and samples it again.
Draw depth and stencil, read and rewrite each plane in compute
dispatches, and check the stored values with depth-tested and
stencil-tested draws, a following dispatch and the flushed guest bytes.
The array view, multisampled and two-view refusals are checked through
dispatches as well.
A 2D array or cube view with the plane's extent and format reached the
ordinary path and read guest bytes the depth was never written to; it
throws again, with the message main has for it. A multisampled surface
is refused before the plane copy, as sampling it already is. A written
view that another view of the same plane replaced had its results
dropped at store-back; that throws now.
Update the debt entry to what is supported and what still throws.
A D16_UNORM_S8_UINT surface is stored as D24_UNORM_S8_UINT or
D32_SFLOAT_S8_UINT on devices without the 16-bit format. Copying its
depth aspect yields 4 bytes per texel, so the 16-bit storage image of
the depth plane would receive the wrong layout. Such an access now
throws, naming the surface and both formats; the stencil plane keeps
its 8-bit layout and stays supported.
Compute shaders bind the depth and stencil planes of a depth surface as
storage images, to read the depth or to write it before later draws
test against it. The driver kept depth on the GPU and refused such
bindings.
A depth surface now owns one storage image per plane (R32_SFLOAT or
R16_UNORM for depth, R8_UINT for stencil). Binding copies the plane
into it through a staging buffer; a written element is copied back into
the depth image during write-back, so the next draw sees the shader's
depth.
A view whose extent differs from the surface's means the memory was
reused for another texture and takes the ordinary texture path; this
applies to sampled views of a depth surface too.
PPSA15304 copies depth and stencil through storage images every frame.
Sampling a depth array, cube or R16_UINT view of depth surfaces went
through DepthPlaneCopy::Refresh on every use. Each use copied every slice
a depth surface holds into the staging buffer and the whole staging buffer
into the copy image, with three ALL_COMMANDS barriers, although nothing
had written the slices since the previous use.
Each depth surface now carries a write serial, bumped when a draw binds it
as its depth target and when a pending fast clear is applied. The copy
keeps the image and serial of every slice it copied. A use copies only the
slices whose surface or serial changed and the guest layers that changed
(as before), and records nothing when none did. A slice whose surface is
the depth target of the latest depth draw is copied at every use until
another depth target is bound, since that draw may still be recorded after
the copy.
Test: agc_driver_depth_plane_guest_layers counts the recorded slice copies
and copy fills; a second use with nothing written records none (it records
one of each without this change), a draw to a slice's surface or a CPU write
to a guest layer copies only that layer.
A multisampled target's samples live only in its host image, so a fill
of its color memory never reached them: a title that clears its MSAA
targets that way (with no CMASK) kept drawing over the previous frame.
A uniform fill over the whole color memory now clears the host image to
that value at the target's next use, the same way a CMASK fast clear
does, and a pending CMASK clear still takes precedence. The color memory
is sized with addrlib's block dimensions for the sample count
(ComputeThinBlockDimension): a 64 KiB block holds width x height x
samples elements, laid out width first for 1x and 4x. A fill over part
of it, over a target whose footprint is not computed, or with a value
that has no exact clear value in the format throws at the next use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA34063 (NHL 27) calls it with a 16-byte buffer outside a coredump
handler and expects 0x81180003 there. The registered handler is never
invoked, so every call is outside one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d04f5d600e52372b968ce0f06139f519fe068724)
With SPIRV-Tools enabled the optimizer replaces a window that is only
indexed by constants with scalars, so the sampler spill case found no
array. It now reads M0 from the thread group id, as the first case does.
The direct access test reads and writes lanes across a wave32, which a
device with 8-lane subgroups cannot hold; it skips there like the
v_readlane and v_writelane tests.
Write window registers directly (VALU, v_writelane_b32, buffer load,
v_movrels_b32 results), read them back directly and through
v_movrels_b32 at several M0 values, including after a direct rewrite,
and feed a v_readlane_b32 of the written lane into the output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
A compute program reads v4 relatively with v_movrels_b32, spills sampler dword
0 into v4 with v_writelane_b32, restores it with v_readlane_b32 and samples
with it. On main the restore reads the window array and resource analysis
refuses GetSamplerResource with ReadLane(LoadVectorWindow(Immediate),
Immediate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
A program that uses relative VGPR addressing placed every register from the
lowest relatively addressed one upward in the window array, so even direct
accesses became array loads and stores. A descriptor spilled with v_writelane
and restored with v_readlane then lost its definition, and resource analysis
refused GetSamplerResource with ReadLane(LoadVectorWindow(Immediate)) (GTA V,
PPSA04264, shader 0x25a889400).
When the program has no indexed writes (v_movreld, v_movrelsd, v_swaprel),
direct accesses stay SSA registers and each direct write is mirrored into the
window for the relative reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
Animal Well (PPSA22520) checks for its save with a CREATE2 mount it never
unmounts, then saves with a second mount of the same directory. That
mount returns BUSY, and the title keeps using the first mount point for
sceSaveDataPrepare, its save file and sceSaveDataUmount2.
sceSaveDataMount3 cleared the result before its checks, so the save was
written to "/save" in the game folder instead of the save directory, and
the first mount was never released. The result is now written only when
the mount succeeds, as shadPS4 does.
The multisampled target guest access test now also reads the target's
memory through ReadCommitted and ReadCommittedUnsynced and expects both
to throw.
ReadCommittedUnsynced skipped the read guard ReadCommitted runs before its
flush hook, so a storage image capture over a multisampled target's
memory read the bytes instead of throwing. Both reads now run the guard,
ReadCommitted still before its flush hook, and share the copy.
FreeBSD's pollscan clears POLLOUT whenever POLLHUP is set, so a write end
whose read end is closed reports POLLHUP alone, with POLLIN and POLLRDNORM
when they were asked for; both host paths reported POLLOUT | POLLHUP.
POLLOUT on a read end now throws. FreeBSD opens both ends FREAD | FWRITE
and pipe_poll reports the read end writable, which a one-way host pipe
cannot back.
guest_poll drained 1 KiB from the full pipe before expecting POLLOUT, but
Linux reports it only once a whole 4 KiB page is free, so the test aborted
there. It now drains the pipe, which is writable on every host.
powf(1.0, nan) returned 0x7fc00001; FreeBSD msun (lib/msun/src/e_powf.c)
returns 0x3f800000. That case now follows FreeBSD; other inputs are
unchanged.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
A compute pass can write a block-compressed surface through an uncompressed
storage view whose element is one block (32_32_UINT for 8-byte blocks,
32_32_32_32_UINT for 16-byte ones). cachedTexture rejected such a storage
image for the BC descriptor, so every sample stored the whole surface to
guest memory, compared it with the snapshot and uploaded it again.
Texture::CanBlockCopyFrom accepts a storage image over the same memory,
tiling and swizzle whose element is the block and whose extent is the
surface's extent in blocks, with whole blocks on every copied level. The
sampled texture then owns a BC image that vkCmdCopyImage refreshes from the
storage image when its version moved, through the same refreshed-on-reuse
path as the depth copy.
No pre-rasterization stage writes Layer: vertex layer exports are
ignored, so the first layer is used and the render target index field
of the packed ancillary word is 0. The lowering read the Layer builtin
for it instead. AMD's Windows driver drops the color writes of a
fragment shader that consumes an unwritten Layer, which made
agc_driver_relative_ancillary fail on AMD hardware (#4800); lavapipe
returns 0 there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodeState threw "blend bypass conflicts with enabled blending" when
CB_BLENDn_CONTROL enabled blending for a target whose CB_COLORn_INFO has
BLEND_BYPASS (bit 16, Mesa src/amd/registers/gfx10.json) set. The bit makes
the color block skip the blender for that target: Mesa sets it "according
to docs" for UINT/SINT and 8_24/24_8/X24_8_32_FLOAT targets, which the
blender cannot process (src/amd/common/ac_descriptors.c, shared by radeonsi
and RADV). Marvel's Guardians of the Galaxy (PPSA01748) issues such draws
right after its loading screen. Blending is now enabled only when it is
requested and not bypassed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main reads target.vertexSubgroups at request version 18 since #4470, so
reading the pixel primitive class at 18 as well read a version 18
request written by main one byte short. The class is now written and
read at version 19. agc_shader_memory loads a version 18 request as
main writes it, and it fails with "truncated data" when the class is
read at 18.
The composed word no longer reads the Layer builtin for bits 16-28.
Draws render to a single layer and guest layer exports are ignored, so
no stage writes Layer, and AMD's Windows driver drops the writes of a
fragment shader that reads it unwritten (#4800). The field is the
constant 0 and pixel_ancillary checks that the composed program has no
Layer read. The debt entry names the bits that have no source and are
assumed zero (2-7, 12-15, 29-31), and says that a change which makes
draws export a layer must restore the read.
A fast-clear materialization threw when a CMASK nibble held a code other
than 0 (cleared) or 15 (expanded). PPSA18391 (engine: Clickteam Fusion) fast
clears a target whose CMASK it never initialized, so some nibbles hold
leftover values such as 1 and 10, and presentation failed on the first
frame.
Such a tile now keeps its texels like an expanded tile, on the CPU path
and in the compute pass. A cleared tile of a DCC target still throws.
The graphics and CMASK fast-clear tests now check that an unknown code
leaves the tile unchanged instead of expecting the throw.
The SPIR-V emitter declares extra views of the guest buffers binding:
buffers_coherent for GLC or DLC accesses (#2369) and buffers_u64 for
64-bit atomics. Each is another StorageBuffer variable on the same set
and binding, decorated Aliased, which Vulkan allows.
ShaderValidation, which checks the stages of every draw, accepted one
StorageBuffer variable per binding with u32 elements only, so such a
graphics shader was refused with "duplicate SPIR-V resource binding".
A second variable is now accepted when the binding is the guest buffers
array and every variable on it is Aliased; an aliased view may hold u64
elements. Other duplicates are still refused. agc_driver_graphics
validates pixel shaders with a GLC store and a 64-bit atomic, and checks
that the same modules without Aliased are refused.
The relative-ancillary test expected the whole-word read through
v_movrels to be refused, which was main's behaviour before the word was
composed. It now converts that read to float and exports it: the
composed word of a triangle draw is 2 (primitive class 2, sample 0,
layer 0), so the pixel is 255, with and without the implicit position
inputs in front of the ancillary VGPR. TechnicalDebt's relative
addressing entry no longer says that whole-word uses throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A packed ancillary word that still reaches an export, or any other read
the masked-select pass cannot drop, no longer stops the recompile. After
that pass every remaining use gets the composed word: the primitive class
at bits 0-1, SampleId at bits 8-11 and Layer at bits 16-28, with every
other bit zero. Those lanes do export the ancillary VGPR on the console,
so the word has to be right, not just present.
The primitive class (0 point, 1 line, 2 triangle, 3 rectangle) comes from
the draw. RasterPrimitiveOf maps rect lists to 3, line lists on the vertex
path to 1 and everything else the driver accepts to 2, because its
geometry and tessellation paths only output triangles. It reaches the
shader as pipeline specialization constant 8233, the way dual-source
blending does: a request field that is folded into the specialized
module and is part of the result memo, the draw key and the serialized
request (version 17).
Only shaders that read the class pay for it. ShaderInfo records the read
(disk cache format 28), and only those shaders put the class in their
module key, so a shader that reads just the sample or layer field keeps
one module for every primitive class. The draw sets the class only when
the pixel shader loads the ancillary VGPR, so other draws keep the draw
cache entries they have today.
The VRS rates (bits 2-5) and the eye id (bit 7) are 0 because the driver
supports neither VRS nor multiview. SampleId is 0 because the driver
rejects MSAA and per-sample shading, and Layer is 0 while it ignores
vertex layer exports. TechnicalDebt.md records the layout sources and
these assumptions.
Tests:
- pixel_ancillary builds an export to target 0 whose two channels select
between a packed value and a phi carrying the word. It checks the word
for each primitive class, sample and layer, checks that the three
shapes the masked-select pass keeps now compile with the composed word,
and that a program whose select the pass drops reads no class.
- agc_driver_pixel_ancillary_word exports the word from a draw and reads
back 2, 3 and 1 for triangle, rectangle and line. A shader that reads
only the sample field shares one module across the classes, and the
whole-word shader gets one per class. It also checks RasterPrimitiveOf.
- agc_shader_memory covers the version 17 round trip, and
agc_shader_disk_cache the new flag.
Both ancillary tests fail with the old refusal when the composition is
disabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sampling a depth surface as a 2D array, a cube or its integer bits
copies its planes into a per-use image. Each use read every slice no
depth surface holds from guest memory and detiled it again, even when
nothing had written it since: in one level of a heavier title that was
about 165,000 layer reads, about 650 GiB, in a five-minute run.
A slice read from guest memory now stays in the copy's staging buffer
with the write generation it was read at. The next use reads and
detiles it again only when its address, size or tiling changed, the
write tracker reports a write since, or recorded GPU work writes the
range; a slice a depth surface held in between is read again. Without
write watching every use reads, as before.
The kernel reads its value with s_buffer_load_dword through the V# in
user[4..7] and stores it with buffer_store_format_x through the V# in
user[0..3]. When the value V# covers less than a dword (NUM_RECORDS
times its stride, or NUM_RECORDS bytes for stride 0) the load returns 0,
and a store format other than 32_UINT, 32_SINT or 32_FLOAT converts the
value, so in both cases the HTILE does not receive the dword read from
memory. The match now requires both. Kena's own dispatch (format
32_UINT, a value V# of one 16-byte record) still matches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE every frame
with its own compute kernel: a 9-word dword fill that stores, through
the stride-4 V# in user[0..3], a value loaded from the pointer in
user[4..5]. Neither the DMA_DATA hook nor the fill kernels the driver
recognizes saw it, so the depth kept the first frames' contents and the
base pass and sky failed their depth test almost everywhere.
The kernel is matched exactly when the dispatch is decoded, the value is
read from guest memory, and the range is passed to NoteDepthMetadataFill
like a fill, so the whole-HTILE and ZMask rules apply unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APS5_SKIP_SHADER=<code address, hex> drops every draw that runs a program
at that address and every dispatch of a compute program at that address, as
if the packet had run. Skipping one pass at a time shows which pass first
writes a wrong value into an image: Epic Mickey's (PPSA15657) green sky
light was bisected this way to the copy into its sky cube (#4551).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 2D-array T# over a depth surface bound to a shader image declared 2D had
no first-layer view, so the descriptor got a null view and the shader read 0.
The depth plane texture now creates the 2D view of its first slice when its
view is a 2D array, for the depth image itself and for the plane copy.
A 16-bit depth surface with stencil may be stored as D24_UNORM_S8_UINT or
D32_SFLOAT_S8_UINT. The plane copy sizes and copies slices by the guest
format, so several slices or an R16_UINT view of such a surface now throw.
Gran Turismo 7 (PPSA01317) fails to load on unresolved imports and on
two needed libraries that AnyPS5 does not build (#4782).
- sceAppContentDownloadDataFormat empties /download0, as
sceAppContentTemporaryDataFormat does for /temp0.
- sceNpAuthSetTimeout returns REQUEST_NOT_FOUND for an unknown request
like the other request calls, and OK otherwise; no request reaches the
network the timeouts would bound.
- sceNpWebApi2PushEventSetHandleTimeout throws for an unknown library
context, like sceNpWebApi2GetMemoryPoolStats, and returns OK
otherwise; push event handles are not tracked and nothing waits on
one offline.
- sceSslDisableVerifyOption, sceSslGetAlpnSelected, sceSslSetAlpn,
sceSslSetMinSslVersion, sceAudioOutDeviceIdOpen and the unnamed
libScePsml NIDs xNZM9Y5lf-M and lcZD9tJraAw are declared without
parameters and throw; their signatures are unknown.
- libSceUsbStorage and libSceUsbStorageDialog exist with no exports,
like libSceXml: the title lists both as needed but imports nothing
from them.
Each assumption is recorded in the library's technical debt file.
Co-authored-by: dawid-swiecicki <8942606+dawid-swiecicki@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
image_load of row 0 through each view, against a live surface drawn at a
known depth and guest-memory slices filled with known values: a 3-slice
D32 2D array, a D32 cube (all six faces), a D16 plane as R16_UINT, and a
D16 depth-bits R16_UINT array (both slices' raw values). A plane array
from base slice 1 is still refused. Fails on main (the array is refused)
and without the depth-bits format gate (GTA V's refusal message).
A texture over a depth surface could only sample one 2D slice of the
plane's own format; 2D arrays, cubes and the R16_UINT view of a D16
plane were refused ("sampling the depth plane of depth surface ... is
not implemented"). A one-slice 2D array now views the depth image as an
array. Several slices, a cube, or an integer view of a D16 plane are
copied into a DepthPlaneCopy image at every use: each slice from the
depth surface bound at its address (base plus one 64 KiB-block depth
slice per layer), otherwise from guest memory, detiled. A depth-bits
R16_UINT view (a depth swizzle) keeps the depth copy, since the shader
converts the sampled depth to its bits itself (GTA V's compute
0x242d4a200 samples a D16 plane array that way, reported by
@tibzejoker). Such a texture is not kept in resource templates.
Storage-image access to depth surfaces is no longer part of this
change; #4500 implements it.
TechnicalDebt (Depth plane copies) and ARCHITECTURE describe the copy and
what still throws.
inotify queues IN_MOVED_FROM and IN_MOVED_TO of a rename one after the
other. The watcher thread could read the first before the second was
queued and report a rename inside the directory as a move to another
directory (NOTE_WRITE | NOTE_EXTEND instead of NOTE_WRITE), which made
guest_kqueue abort on loaded runners.
The watcher now reads the queue until it is empty and, when a move half
has no partner, calls getdents64 on the directory before reading again:
that takes the directory lock rename holds until both halves are queued.
Report the 8-byte GS primitive payload when the shader's context registers
set SPI_SHADER_IDX_FORMAT.IDX0_EXPORT_FORMAT to SPI_SHADER_2COMP, and 0
otherwise. The search runs over every context register, so an earlier
SPI_SHADER_IDX_FORMAT entry with another format does not end it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The libc.prx heap that Animal Well (PPSA22520 01.000.011, SDK 9.04)
bundles maps each arena with sceKernelMapFlexibleMemory(&addr, len, 3,
0x8000). It threw "Unsupported memory mapping flags 0x8000" before the
first frame. The flag is ignored as sceKernelMapNamedFlexibleMemoryInternal
already does; other unknown flags still throw.
Gran Turismo 7 (PPSA01317) passes shader headers at 4-byte aligned addresses to
sceAgcUnknownCreateInterpolantMapping. RegisterShader already reads them through a
copy (#902); ResolveShaderAbi, ResolveGraphicsStagesAbi, ResolveGraphicsAbi and
RegisteredHeader still required 8-byte alignment and threw "null or misaligned
address". Read the fixed fields through GuestShaderFields at those sites.
Writes into shared (direct) guest memory on Windows are caught by write
protecting each 16 KiB view and taking the fault. The fault reopened
and invalidated the whole view, so a collect reported all four 4 KiB
pages of a view whenever any byte of it was written, including pages
nobody wrote. A GPU surface that ends or starts inside such a view
then had its edge pages taken for CPU writes whenever the title wrote
the memory next to it, and the storage write-back kept the guest's
stale bytes there instead of the GPU's results.
Each view now keeps a write generation, a seen generation and the
write protection per 4 KiB page. A fault invalidates and reopens only
its page, a host write only the pages it covers, and a collect reports
and re-arms only the pages it covers. VirtualProtect is called once
per contiguous run of pages whose protection changes, so arming or
pinning a view takes one call when those pages are contiguous and two
when they are not. A guest store that fills a whole view takes up to
four faults instead of one.
A collect does not settle a page that it cannot write protect because
a host write is open on one of the page's views; it reports the page
again until a collect can arm it. main settled such a page and relied
on the end of the host write invalidating the whole view, so a guest
store into it during the host write was reported only after the host
write ended. With only the written pages invalidated there, the store
would not be reported at all.
A collect that runs out of room partway through a view now settles the
pages it already reported there and nothing after them; before, it
settled none of them and reported them again at the next collect.
CheckSharedWritePages writes one byte into the second 4 KiB page of a
view and requires a collect of the view's last two pages to report
nothing; main reports both. It also checks a store across two pages
and a file read into one page. CheckSharedWritesDuringHostWrite stores
into a fault-opened page of a view after a collect while a host write
is open on another page of it, and requires every collect of the view
and of its alias to report that page until the host write has ended.
agc_shared_write_partial_collect resets the two interior pages of a
view after writes to all four and requires the outer two to stay
pending. The existing shared-memory checks in guest_memory,
agc_shared_write_partial_collect and guest_socket_host_write (the
recvfrom sender outputs) now expect the pages written instead of the
whole view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux a guest stderr redirect also moves the host's: guest
descriptors are host descriptors, and the guest stderr stream wraps the
host stderr FILE. After dup2(fd, 2), or freopen(path, mode, stderr) with
full buffering, the libc terminate report, the host fprintf(stderr)
diagnostics and libstdc++'s terminate message go into the title's pipe
or file, and abort() discards what is still buffered. RetroArch
(PPSA99169) reopens stderr onto app0/trace.txt with full buffering, so
its stop on main left no message anywhere.
Before the first guest redirect of descriptor 2 (libkernel dup2, libc
freopen of a stream on descriptor 2), keep a close-on-exec duplicate of
the host descriptor 2 and point the host stderr at an unbuffered stream
on it. The guest stream keeps its own FILE and follows the redirect.
The libc terminate report writes to the kept descriptor with write()
instead of the stderr stream, and libkernel's sceKernelDebugOutText uses
the same kept descriptor.
pr-overlap and area-labels walked every open PR on each run, spending the
1000 requests/hour that GITHUB_TOKEN gets per repository and starving the
other workflows (HTTP 403 'rate limit exceeded for installation').
pr-overlap keeps a hash of each report in actions/cache and only reads the
comments of PRs whose report changed. area-labels keeps head SHA plus the
labeler.yml blob per PR and labels only the changed ones, at most 40 per
run. conflict-label gets a concurrency group so pushes in a row don't stack
full passes.
SECTION matched every heading that starts with ###, so a #### line opened
a new section named "# ...". A Tested section written as #### Linux and
#### Windows subsections was reported as missing or empty, and an
unchecked item after a #### line in Checklist was not reported.
A level-4 heading belongs to the level-3 section above it (CommonMark
4.2), and silent_stub_debt() already keeps deeper headings inside their
section. SECTION now matches only headings with exactly three #.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Return the event's data when EV_ERROR is set in its flags and 0 otherwise, as KytyPS5 does and as FreeBSD 11 kevent stores the errno of a failed change. A null event throws like the other accessors.
Porpoise 2.7 (PPSA99764) stops at load on main 0f863c10:
FAIL: unresolved ELF import oc98+zOImus from libkernel_sys.prx
oc98+zOImus is pathconf (tools/nid_names.py, aerolib.csv at
zecoxao/sce_symbols 2883963a). It is exported from libkernel_sys next to
link and readlink.
The lookup follows FreeBSD 11's kern_pathconf and namei (release/11.0.0,
671fb7df): links are followed, a path of 1024 bytes or more or a
component over 255 fails with ENAMETOOLONG, a missing path with ENOENT.
The values are the constants of FreeBSD 11's UFS ufs_pathconf, which
vop_stdpathconf shares where both answer. Names whose UFS value depends
on the mount, the kernel options or the page size, or on which UFS and
vop_stdpathconf disagree, throw; other names fail with EINVAL. The
console's filesystem values are unverified and recorded in
TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PPSA30601 (Resonance) stops at the throwing stub. The four int* arguments follow
OpenOrbis' libkernel.h, the Orbital type database and SharpProspero; the values
follow KytyPS5 (2048 entries of 2 MiB per side) without its accounting of used
entries, listed in TechnicalDebt.md. A null pointer returns EFAULT.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
There is no PSN sign-in, so the code getter answers SCE_NP_ERROR_SIGNED_OUT
instead of throwing; its parameters are unknown and not read. Minecraft
(PPSA17221) calls it after its sign-in dialog and treats the result as
offline play. The request, token and wait calls are left to #3604.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ProsperoTV (PPSA99003) stops after its menu opens because poll threw for
the read end of a pipe it polls next to its sockets. poll now reports pipe
readiness as FreeBSD's pipe_poll does: readable when data is buffered,
POLLHUP once the other end is closed, writable when PIPE_BUF bytes are
free. On Windows the pipe is checked with NtQueryInformationFile while the
sockets are waited on in 1 ms slices; on Linux the host poll waits on both.
A closed descriptor below the socket range reports POLLNVAL.
Before: MarkPending set the bit and notified while the waits checked the set
under pending.lock, so a signal raised between the check and the wait was lost
and sigwait, sigwaitinfo, a null-timeout sigtimedwait and pause hung forever.
Now: the bit is set while holding that lock, so a wait either sees it or is
notified after it has blocked. GuestSigtimedwait raises with no delay 500 times
without the first sleep the other senders use; on the previous code that hung in
2 of 10 runs, and it passes on the new one.
Before: raise, kill and pthread_kill delivered a signal at once or failed
with EINVAL, nothing stayed pending, and pause, sigwait, sigwaitinfo and
sigtimedwait called NotImplemented_nid_no_patch.
Now: a process-wide pending set holds signals 1 to 128. A signal raised
while it is blocked stays pending and is delivered when _sigprocmask
unblocks it, taken by sigwait, sigwaitinfo or sigtimedwait, or delivered by
pause, which runs its handler on the calling thread and returns -1 with
EINTR. pthread_kill to another thread leaves the signal pending for
whichever thread waits for it and answers ESRCH for a handle that is not a
live guest thread. sigtimedwait follows FreeBSD's sigtimedwait(2): EAGAIN
on timeout, EINVAL for a tv_nsec outside 0 to 1000000000 while nothing is
pending, and an indefinite wait with a null timeout.
Pipeline libraries take ownership of shader modules during prebuild. Treat null handles as a no-op so moved-from modules do not decrement the live-object count twice.
On an RTX 3060 (Windows, driver 591.86) a dispatch never finishes when
the lanes of read_bda_span split between its in-place dword loads and
its per-dword byte fallback, which calls read_bda_dword_bytes: Windows
resets the GPU and the device is lost. The two were the arms of one
selection in the DontInline function. They are now two selections, the
loads under the span's condition and the fallback under its negation,
joined after both. Loads, lookups, faults and stops are unchanged for
every lane; only the SPIR-V control-flow shape changes (SPIR-V
specification 2.11, structured control flow). Every stage except mesh
shaders is emitted as SPIR-V 1.3, and OpSelect chooses between
composites with a scalar condition only from 1.4 on, so the join stays
a selection.
The same hang and fix were reported in #3291 on an RTX 4060 Laptop GPU
(Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01). An
RTX 4070 Laptop GPU on driver 616.92 does not hang with either shape.
The driver-side cause is unknown, so the shape is recorded in
docs/dev/debt/shader.md.
On main the hang fails nine tests on the RTX 3060, in two ways.
agc_driver_{flat,global}_loads{,_x2_x3,_x4} and
agc_driver_flat_apertures_{2,5} abort after 64 to 71 s: the lost batch
keeps its lease, so teardown waits 60 s for the pinned guest allocation.
agc_driver_pixel_interlock fails after about 4 to 7 s with
vkDeviceWaitIdle returning VK_ERROR_DEVICE_LOST (-4) and never reaches
the pin wait. All nine pass with the change.
Fixes#3291
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's vfs_kqfilter checks neither the vnode type nor fflags, so the
registration is valid; it is only delivered for a move between directories,
which the Windows directory handle cannot tell from a removal plus an addition.
Also moves the TechnicalDebt entries to docs/dev/debt after main split the file.
Co-Authored-By: Claude <noreply@anthropic.com>
Cloudberry Kingdom (PPSA23827, #3177) registers its first kevent change
as a file watch: ident 3, EVFILT_VNODE, flags 0x25
(EV_ADD|EV_ENABLE|EV_CLEAR), fflags 0x37
(NOTE_DELETE|WRITE|EXTEND|LINK|RENAME). Palworld's Discord module
(PPSA23126, #2648) imports sceKernelAddFileEvent and
sceKernelDeleteFileEvent, which no library exported.
EVFILT_VNODE watches the host file or directory behind the descriptor
and turns host changes into FreeBSD 11 vnode hints, added whole when
they share a bit with the requested fflags as filt_vfsvnode does
(sys/kern/vfs_subr.c, release/11.0.1 34bd3475): a growing write is
NOTE_WRITE|NOTE_EXTEND, a shrinking truncate or an attribute change
NOTE_ATTRIB, a link count change NOTE_LINK or NOTE_DELETE, a pending
delete NOTE_DELETE, a new path NOTE_RENAME, and in a directory a new,
removed or renamed entry NOTE_WRITE (with NOTE_LINK for a subdirectory,
as vop_mkdir_post/vop_rmdir_post). Windows uses FindFirstChangeNotification
on the parent directory (and the watched directory) and compares handle
snapshots every 50 ms, since a write through an open handle is not
reported until it is flushed or closed (measured); Linux uses inotify.
Closing the descriptor, deleting the event or the queue stops the
watcher.
sceKernelAddFileEvent(eq, fd, watch, udata) and
sceKernelDeleteFileEvent(eq, fd) take the OpenOrbis 0a1aaf9d
(libkernel.h:35,109) and SharpProspero 5dfdb4b0 (KernelEqueue.cs:164,168)
signatures; NIDs cG3t15OK4Ow and UmXngHKB6is confirmed with
tools/nid_names.py. The watch bits are the fflags and the event is added
with EV_CLEAR, which is not confirmed on a title.
Throws: NOTE_REVOKE, NOTE_OPEN, NOTE_CLOSE, NOTE_CLOSE_WRITE, NOTE_READ
and unknown bits, NOTE_EXTEND on a directory on Windows (a move from
another directory cannot be told from a creation) and descriptors that
are not files, directories, pipes or sockets (pipes, sockets and kqueues
fail with EINVAL as in FreeBSD). Recorded in TechnicalDebt.
guest_kqueue adds a vnode watch that sees a write to a temporary file
with Cloudberry's flags, fflags filtering, a re-registration, the sce
pair on an equeue, EINVAL for sockets, pipes and the kqueue itself, the
close of a watched descriptor and a directory watch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cloudberry Kingdom (PPSA23827, #3177) calls kqueue right after start-up
and stopped on the throwing declarations merged with #3337; Palworld's
Discord module (PPSA23126, #2648) imports kevent.
kqueue reserves a host null-device descriptor in the file descriptor
space and registers it as an event queue that reports each ready event
once per call and moves level-triggered ones behind the others, as
kqueue_scan does. close and sceKernelClose close the queue and drop the
descriptor's events from every queue, dup2 drops them for the target,
and dup/dup2 of a kqueue throw, so a reused descriptor number is never
read as a stale kqueue.
kevent follows FreeBSD 11 kern_event.c (release/11.0.1 34bd3475):
kqueue_kevent for the changelist (filter 0 skipped, EV_SYSFLAGS
cleared, EV_ERROR receipts with the errno in data, EV_RECEIPT, the
early return when nevents is exhausted), kqueue_register for EV_ADD,
EV_DELETE, EV_ENABLE, EBADF, ENOENT and EINVAL, kqueue_scan for the
timeout (EINVAL for a bad timespec, zero polls, above INT32_MAX waits
forever) and filt_timerattach, filt_usertouch for the timer and user
filters. Read and write events reuse the guest socket poll of
sceKernelAddReadEvent/WriteEvent.
Throws: other filters, EV_DISABLE, EV_DISPATCH, EV_FORCEONESHOT,
NOTE_LOWAT, EV_CLEAR on a socket, read/write events on non-socket
descriptors or another kqueue, changes to an existing timer (11.0 and
11.4 differ), a NOTE_NSECONDS fraction above one second and a negative
nevents. Recorded in TechnicalDebt.
Test guest_kqueue covers creation and close, timers, a user event
triggered from another thread, read and write events on a socket pair,
EV_ERROR receipts, the timeout and the error cases.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Marvel's Wolverine draws into a 256x256 mipmapped 2D target
(CB_COLOR_ATTRIB3 0x4dc6c000, no array slices) through CB_COLOR_VIEW
0x4046000: slices 0 to 35 at mip 1. The view's last slice is not bounded
by the surface on that path, so the draw renders into slice 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CB_COLOR_VIEW whose SLICE_MAX is past SLICE_START was rejected. Without
a render target index export every primitive goes to SLICE_START, so the
target is that slice, as for a one-slice view. Layered draws into such a
view, and views of several 3D slices, still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With APS5_TRACE_FRAMES or APS5_TRACE_SECONDS set, the trace output keeps
its writer in a static unique_ptr that is only destroyed at process
exit. On Windows its thread no longer runs by then, so the join in
~ProfileOutput never returns and the title does not exit once the trace
was written. Driver::stop now stops it next to the stderr writer, while
its thread still runs; the destructor at exit then has nothing to join.
The GPU-map observer imported a new mapping into Vulkan on the thread
that made the mapping, on whatever stack it ran. Guest fibers run on
contexts the title allocates in guest memory, with no guard page;
Demon's Souls packs its 16 KiB job fiber contexts back to back. A GPU
mapping made on a fiber used 10,616 bytes of the fiber's stack on
NVIDIA (Add, NoteGpuMapping, the registry walk in GuestAllocationsEnd,
the import, the fiber's entry and return), about 7.5 KiB of it for the
import, so a fiber that mapped GPU memory deep in a job could overrun
its context into the next one.
The observer now imports inline when the stack pointer lies inside the
thread's stack bounds with at least 256 KiB below it, and otherwise
starts a host thread for the import and joins it. On Windows the bounds
come from GetCurrentThreadStackLimits, which reads the TIB bounds that
libSceFiber sets to the fiber context while a fiber runs; on Linux from
pthread_getattr_np, whose bounds a fiber's stack lies outside of. A
mapping made while the thread holds the GPU mutex still imports inline:
a host thread could wait for that mutex forever. A host thread that
cannot be started is reported like any failed import, which is still
printed to stderr while the mapping carries on.
agc_driver_gpu_map_on_fiber maps GPU memory from a fiber with a 64 KiB
context and requires that the mapping use at most 8 KiB of it (10,616
bytes inline, 2,960 on a host thread) and start one host thread, then
maps the same memory from the main thread and requires that it start
none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ClearColorFor refused every integer format, so a fast-cleared R32_UINT/R16_UINT/R8_UINT surface was uploaded through the CPU path: a guest read that waits for the GPU writers, then FillDccClear's zero texels. Code 0000 is zero bits in any format, so it maps to a zero integer clear. PPSA28416: ~1.4 s per 10 s of CPU uploads gone, 4.9 -> 5.3 fps.
The trace gets a gpu track: one slice per recorder batch from its timestamp
pair, and with APS5_PROFILE_GPU=1 one per command class range under the
[gputime] class names. The GPU clock is mapped onto the CPU clock from
calibrated timestamps (VK_KHR_calibrated_timestamps or the EXT version,
enabled only when a trace switch is set) taken when the window opens and
when it closes. A traced thread's wait for the GPU mutex becomes a
GpuMutex.wait.<site> slice, and the shader preparation threads are traced
with smaller buffers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The depth image is created with the host format, so the sample locations
check for the image and for the pipeline asks about that format too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 16-bit depth target with stencil maps to VK_FORMAT_D16_UNORM_S8_UINT,
which NVIDIA devices do not support as an attachment, so creating it threw.
The surface, its views and the pipelines now use the first of D16_UNORM_S8_UINT,
D24_UNORM_S8_UINT and D32_SFLOAT_S8_UINT the device can attach and sample.
Guest-side choices (16-bit sampled views, bias units) still follow the guest
format. A constant depth bias is scaled by 256 for D24 storage; on D32 float
storage it throws, as its unit depends on the depth value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5MC (PPSA99420) imports setitimer (hPWDGx8ioXQ, aerolib) at registry
index 472, after tcsetpgrp and before getitimer. Predicted stop:
"FAIL: unresolved ELF import hPWDGx8ioXQ from libkernel.prx".
setitimer follows FreeBSD 11.4 kern_time.c (8c639945) sys_setitimer and
kern_setitimer: EINVAL for an unknown timer and for an itimerfix failure
or more than INT32_MAX / 2 seconds; a zero value clears the interval and
disarms; a null value reads the timer, EFAULT when the output is null too.
ITIMER_REAL shares the alarm state, now with an interval that re-arms it
as realitexpire does; alarm clears it, as FreeBSD's alarm.c does.
Guest SIGALRM handlers are not run, so an expiry with a handler, or with
SIGALRM blocked (signals are never left pending), throws naming the
interval; SIG_IGN drops it and the default disposition exits with 142,
as alarm already did. Intervals below 1 ms throw (itimerfix rounds them
to tick / 16 and the console's hz is unknown). ITIMER_VIRTUAL and
ITIMER_PROF throw when armed and read and disarm as zero.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sceCoredumpAttachMemoryRegion and sceCoredumpAttachUserMemoryFile take
(uint32_t userValue, const void* mem, size_t size): the Orbital IDA type
database and the SharpProspero bindings agree on the first, the second
has only SharpProspero. Attaching belongs inside the core dump handler
the system runs after the process stops; a call from anywhere else gets
SCE_COREDUMP_ERROR_NOT_IN_COREDUMP_HANDLER. The handler is never invoked
here, so every call returns that error and nothing is recorded. Whether
the console checks the region before the calling context is unknown, so
a null, empty or wrapping region throws.
The error value 0x81180003 is inferred from fpPS4's 0x81180001 and
0x81180002 and the numbering of the Vita's SceCoredump error table;
TechnicalDebt records the sources and what remains unverified.
guest_coredump_attach: the error before, during and after a handler
registration, from another thread too, without running the handler or
touching the region; null, empty and wrapping regions throw naming the
function.
UFC 6 (PPSA23566) imports sceNetResolverAbort (#992, #1502); Palworld
(PPSA23126) and LEGO Horizon Adventures (PPSA14632) import
sceNetResolverStartNtoaMultipleRecords.
Signatures and behaviour follow shadPS4 811bfcd7d17e
(src/core/libraries/net/net.cpp, net_resolver.cpp); the abort signature
also matches SharpProspero Interop/Net/Socket.cs.
sceNetResolverStartNtoaMultipleRecords shares the lookup of the Ex form
and, like it, throws for any flag. sceNetResolverAbort returns EBADF for
an unknown resolver and throws for flag bits other than NTOA (1) and
ATON (2) preservation. Host lookups cannot be interrupted, so an abort
during a running lookup makes it fail with EINTR when the host call
returns, without writing its output; with nothing running, the
preservation flags make the next lookup of that kind fail with EINTR.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit bcc8be8a58aa6a9e6b7c288d8a90af6ed63837aa)
Review of #3787 asked for no retries after an error in Jit.cpp.
Linux: memfd_create is called once with MFD_CLOEXEC. MFD_EXEC was
tried first and dropped on EINVAL; the flag only sets the file's execute
permission bits (Linux 6.3 and later), not whether the object maps
PROT_EXEC, which is decided by the mapping protection, as for the direct
memory memfd in DirectMemory.cpp. A one-shot choice from the headers
would fail on older kernels, so the flag is dropped; TechnicalDebt
records it.
Windows: the delete-on-close backing file is named from the process id,
a process-wide atomic counter and the performance counter, so CREATE_NEW
cannot meet an existing name, and any CreateFileW failure throws with
the Win32 error instead of looping up to 64 times on ERROR_FILE_EXISTS.
The descriptor stays a CRT descriptor on the file, so close, dup and
dup2 keep working as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The three JIT shared memory exports (avvJ3J0H0EY, MR221Mwo0Pc,
YKT49TOLQWs) are absent imports of three homebrew titles that cannot
finish loading without them. Predicted next stops (registry order):
- VLC PPSA85300 after getdtablesize (idx 354):
"FAIL: unresolved ELF import YKT49TOLQWs from libkernel.prx"
(Map idx 452, Create idx 462)
- XPSemu PPSA97358 after tcsendbreak (idx 214):
"FAIL: unresolved ELF import avvJ3J0H0EY from libkernel.prx"
(Create idx 398, Map idx 399)
- RetroArch PPSA99169:
"FAIL: unresolved ELF import avvJ3J0H0EY from libkernel.prx"
(Create 476, Alias 477, Map 478)
XPSemu and VLC (ps5/compat/mmap.c, __wrap_mmap) create an object for
every anonymous PROT_EXEC mapping (QEMU's TCG buffer) and map it with
the same protection, falling back to flexible memory on any non-zero
return. RetroArch's eboot runs a probe: Create("ps5-platform-probe",
0x10000, 7, &fd), Alias(fd, 3, &alias), Map(fd, 5, &address), then logs
"jit create=0x%08x alias=0x%08x map=0x%08x".
Signatures from Orbital cc408367 tools/ida/db_types.json (name, len,
maxProt, fdOut; fd, maxProt, fdOut; fd, prot, startOut), matching
ps4sdk 4df9d001 include/sce/kernel.h, ps5-jar-loader 8034177e
LibKernel.java and the three titles' call sites. SharpProspero 5dfdb4b0
declares the alias as returning the descriptor; the RetroArch call site
passes an out pointer.
The object is a host anonymous file (memfd on Linux, a delete-on-close
temporary file mapped as a PAGE_EXECUTE_READWRITE section on Windows),
the alias a duplicate descriptor, and each map maps the whole object
through the direct memory placement path, so a write through one view
is visible through the others. Bad lengths and protection bits are
EINVAL, protection past the descriptor's maximum is EACCES (FreeBSD 11
vm_map_protect), unknown descriptors are EBADF; close, dup and dup2
keep the descriptor table in step. Unverified details are recorded in
TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PRAGMATA (PPSA02530) imports sceSharePlayProhibitController (NID
FzQS6DREDfk), which no library exports on main, so the load fails on the
unresolved import. It is one of the "six declared-only imports" of
xXPBSXx's branch in #3043 (comment 6107100345): "These only stop the
load failure; as described above, the title calls some of them later and
needs their real behavior."
sharpemu src/SharpEmu.Libs/Share/SharePlayExports.cs:9-31
(a2983692930564815a77f81e74c151268f5b935f, used as a source of facts)
takes one value, accepts 0 and 1 and returns 0. Nothing here would read
the setting, as no Share Play session or visitor controller exists, so
it is not stored. sharpemu rejects other values with its synthetic
0x80020003, not a Share Play code, so they throw, as
sceSharePlayInitialize does for its unknown error. Like the other exports
of the library it does not check initialization. The NID hashes from the
name and is in aerolib. Unverified points are in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PRAGMATA (PPSA02530) imports sceRazorCpuSync (NID Ax7NjOzctIM), which no
library exports on main, so the load fails on the unresolved import. The
title calls it after sceConvertKeycodeGetCharacter. From #3043 (comment
6106731532): "`sceRazorCpuSync`: answering 0 and doing nothing was
enough to carry on (the other `sceRazorCpu*` calls in `main` are no-ops
without a capture)."
SharpProspero src/SharpProspero/Interop/Debug/RazorCpu.cs:119-122
(5dfdb4b0b166e83dc9ae275001afb695f12c7174, GPL-3.0, used as a source of
facts) gives int sceRazorCpuSync(void), an unnamed frame-boundary event.
Without a capture there is no event to record, so it returns 0 and does
nothing, like the other sceRazorCpu markers here and the shadPS4 stub
(razor_cpu.cpp:143, 3a9f57265d28975658f1ca941daa5c879c7a74bd). The NID
hashes from the name and is in aerolib. Unverified points are in
TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PRAGMATA (PPSA02530) calls it after sceRazorCpuSync and stops on main
with "sceNpEntitlementAccessGetGameTrialsFlag not implemented". From
called once; I logged four register values but cannot tell which are
arguments. Answering 0 without writing anything carried on." Killing
Floor 3 (PPSA08427) and RE4 also import it.
The title is reported as a full game, not a game trial: 0 is written to
the uint32_t output and 0 returned; a null pointer returns 0x817D0002.
Signature, value, one-word write and error follow sharpemu
src/SharpEmu.Libs/Np/NpEntitlementAccessExports.cs:98-116 and its test
NpEntitlementAccessExportsTests.cs:16-36 (a2983692930564815a77f81e74c151268f5b935f),
used as a source of facts; 0x817D0002 is also the parameter error of
KytyPS5, prosper and sceNpEntitlementAccessGetEntitlementKey here. The
library has no initialization state, so none is checked. The NID
y7JD1gfwyfA hashes from the name and is in aerolib. Unverified points
are in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The include sat next to add_subdirectory(core/libs), which other open pull requests rewrite. Tracy is now added after FFmpeg, still at the top-level scope, so the client is built with the same flags.
APS5_ENABLE_TRACY=ON builds the Tracy 0.14.1 client from the new
3rdparty/tracy submodule as a shared library next to the patched
libraries. Every PerformanceTimer scope of libSceAgcDriver and
libSceVideoOut becomes a Tracy zone, each presented flip ends a frame,
and the queue workers and the presenter name their threads. The client
listens only on localhost and records only while a profiler is
connected; sampling, system tracing, call stacks, code transfer and its
crash handler are off. Without the option nothing of Tracy is compiled.
On MinGW the client is built without thread_local destructors and with
TRACY_DELAYED_INIT, because emulated TLS frees thread_local objects
before their destructors run; a thread's event producer then stays
alive until the process exits instead of being destroyed on freed
memory.
PPSA02530 (PRAGMATA) fails to load without these imports that no library exports. Their signatures are unknown, so they are declared without parameters and throw until they are implemented. Adds a TechnicalDebt entry.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adds tcsendbreak (vXE-dIUIZlE), tcdrain (brGGJWsZauw), tcflush
(FxHW5NuEXsY), tcflow (OG8xRaGIpqk), tcgetpgrp (5OEl1mRhUm0) and
tcsetpgrp (YMuesZTyTl4) next to tcgetattr and tcsetattr in
File/src/Terminal.cpp. The NIDs come from tools/nid_names.py and match
aerolib.csv (zecoxao/sce_symbols 2883963a).
They follow FreeBSD 11.4 lib/libc/gen/termios.c (8c639945), which
implements each one over a terminal ioctl: tcflush returns EINVAL for a
queue other than TCIFLUSH, TCOFLUSH or TCIOFLUSH and tcflow for an
action outside TCOOFF..TCION before the descriptor is used; every other
call fails with the error the merged tcgetattr gives (EBADF for a closed
descriptor, ENOTTY for a file or pipe, EADDRNOTAVAIL or EOPNOTSUPP for a
socket, per soo_ioctl and in_control/in6_control). tcsendbreak fails on
TIOCSBRK before its 0.4 s pause, and tcflow's TCIOFF/TCION fail in
tcgetattr. FreeBSD's pipe_ioctl answers TIOCGPGRP/TIOCSPGRP on a pipe or
FIFO with the SIGIO owner, which is not modelled, so tcgetpgrp and
tcsetpgrp throw on a host pipe above descriptor 2.
Titles (homebrew catalog #1578), predicted stops from registry order:
- PS5MC PPSA99420 imports tcsendbreak (442), tcdrain (443), tcflush
(444), tcflow (445), tcgetpgrp (462) and tcsetpgrp (463); once pause
and setrlimit are in, it stops at
"FAIL: unresolved ELF import vXE-dIUIZlE from libkernel.prx".
- XPSemu PPSA97358 imports tcsendbreak (214), its first uncovered import
after execv, sigwait, setrlimit, mlockall, setuid and getopt:
"FAIL: unresolved ELF import vXE-dIUIZlE from libkernel.prx".
guest_terminal covers each call over stdin, stdout, stderr, a file, both
pipe ends and an AF_INET socket, the EINVAL cases (also on -1), and
EBADF for -1, a closed socket, a closed pipe and a closed file.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A hull program may read its input control points from LDS at an index it
computes from data, such as an index loaded from memory or min(invocation, n).
The tessellation lowering refused such a read because its address is not
affine in the invocation and relative patch.
The address evaluator now carries a dynamic term for a hull LDS read: a value
that is not affine but does not depend on the relative patch index, with the
multiple it is scaled by. Terms of the same value add up ((t << 1) + t of
t = index << 3 is 24 * index); terms of different values keep the greatest
common divisor of their multiples. The read is lowered from the program's own
address arithmetic, and the relative index may reach it only with the stride
of a patch's inputs. A computed offset that is not a whole number of control
points is refused.
The SPIR-V emitter clamps a hull input control-point index to the patch's last
input, recorded as technical debt: on the console an index past the patch
reads LDS of a neighbouring patch.
Require the measured separator after RFC date minutes when seconds are absent. Preserve the destination tick on the parse failure and retain the accepted trailing-space and seconds controls.
AI-assisted; based on guarded PS5 measurements with controlled input backing storage.
The zlib in the tree (libjpeg-turbo's) is built with NO_GZIP, so
deflateInit2 refuses the gzip wrapper (windowBits 31) and no trace was
written. The trace file now writes the gzip header, a raw deflate stream,
and the CRC-32 and size trailer itself. The writer's result line is flushed,
since stderr is buffered in a title process. The file writing moves to
TraceFile, and a test inflates a written file and checks its CRC-32 and
size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APS5_TRACE_FRAMES=<start>:<count> (or APS5_TRACE_SECONDS=<start>:<length>)
records every PerformanceTimer scope and stage of the queue workers and the
presenter inside the window as complete events, named after their frame
timing keys, into a preallocated buffer per thread. Slices under 20 us are
counted per thread instead of written; a full buffer stops that thread and
sets trace_dropped. When the window closes, a ProfileOutput writer thread
writes aps5-trace-<commit>-<pid>.json.gz and appends one line to
aps5-summary.jsonl. Without either switch a PerformanceTimer costs one more
predictable branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run the existing array-layer draw, recompiled shader load, untouched-layer and guest-publication checks with D_X as well as R_X. This exercises the resident array path that carries slice-dependent address XOR; a direct per-slice copy is not an equivalent model.
AI-assisted implementation and validation: Codex.
Apply the source-configured D_X XOR refusal during color decoding as well as direct layout construction. Regression tests reject each raw column-bit combination before storage or rendering is selected. Existing resident-array and packed-mip refusal paths remain in force.
AI-assisted implementation and validation: Codex.
Map SW_64KB_D_X color surfaces through the existing source-backed texture equations and mip geometry. Keep display and render-target equation caches separate. Reject unresolved raw column bits for the new mode instead of accepting them.
Cover the captured PPSA99902 descriptor, independent source offsets, pipe/bank tags, transfers, array/mip placement and resident/non-resident GPU draws. Record unmeasured console behavior in TechnicalDebt.
CPU validation on main e013f8c9: captured-descriptor failure 5/5; changed passes 5/5; pinned AMD addrlib reference 536940 addresses per run, five runs. Full build and GPU validation are tracked externally. AI-assisted implementation and validation: Codex.
connect() on a guest datagram socket checked the address family before
doing anything, so an AF_UNSPEC (null) address failed with EAFNOSUPPORT
and left the socket connected: getpeername() still reported the old
peer, send() without an address still went there, and datagrams from
any other sender were dropped by the host.
FreeBSD 11.4 disconnects a connected datagram socket first and only then
rejects 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 resets
only the local address, keeping the local port:
in_pcbdisconnect(inp);
inp->inp_laddr.s_addr = INADDR_ANY;
after which in_pcbconnect_setup() (sys/netinet/in_pcb.c) returns
EAFNOSUPPORT for the full-size address, as main already does. connect(2)
documents it: "Datagram sockets may dissolve the association by
connecting to an invalid address, such as a null address."
A connected internet datagram socket given an address whose family byte
is 0 is now disconnected on the host first (AF_UNSPEC on Linux, a zeroed
address on Windows) and the call then fails 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>
tools/trace_digest.py reads an APS5_TRACE_FRAMES capture (aps5-trace-*.json.gz)
and prints a markdown report of a few KB, or the same data as JSON: the
summary line from aps5-summary.jsonl, the slowest frames with their largest
slices, the busy, wait and untraced time of each thread, the largest slices
per frame, the longest waits, and with --stacks the hottest frames of an
APS5_SAMPLE_WORKER collapsed-stack file. Python standard library only; a
unittest checks every section on a small synthetic trace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA29866 (Tomba! 2) fails to load without these imports that no library exports. Their signatures are unknown, so they are declared without parameters and throw until they are implemented. Adds a TechnicalDebt entry.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
tools/progress_history.py measures the first-parent history of main with
the rules of tools/progress.py, one point per day, and writes history.json,
history.svg and history.html. The Progress workflow runs it after
progress.py; the README shows the chart under the map and links the page.
Labels and points compact as days accumulate.
Co-Authored-By: Claude <noreply@anthropic.com>
APS5_SAMPLE_WORKER=<file> now also writes <file stem>.collapsed.txt: one
"frame;...;frame count" line per distinct stack of queue worker 0, root
first, frames as module+0xRVA, which speedscope.app and flamegraph.pl
open. Walks go up to 64 frames, and nothing allocates while the worker is
suspended.
The libraries are built with -fno-asynchronous-unwind-tables, so most of
their functions have no .pdata entry, only a DWARF record in .ehfram, and a
walk ended at the first such frame. The worker's walk now steps those
frames through their record, with the CIE/FDE decoding and the call frame
instructions of libc's unwinder, which move from Unwind.cpp into its
header unchanged. A module is indexed after the worker resumes, the first
time a walk meets it, and every register the step restores is read inside
the worker's stack. The process sampler (APS5_SAMPLE_THREADS) keeps its 12
frames through .pdata.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceAgcDcbSetWorkloadStreamInactive and the three ACB workload builders
(sceAgcAcbSetWorkloadsActive, sceAgcAcbSetWorkloadComplete,
sceAgcAcbSetWorkloadStreamInactive) threw for every call, and
sceAgcDcbSetWorkloadsActive read the workload ids before it checked the
stream.
The prosper console oracle (mattias800/prosper df074498,
prosper/tests/data/console_oracle/agcbuild.golden.tsv lines 48-56 and
196-204) measured all six calls on a PS5 with three argument patterns
and no workload stream registered: each returned null and wrote nothing.
Pattern b passes stream 1 with the workload ids at address 2, so the
console checks the stream before it reads the ids; here that call read
address 2 and crashed.
The four builders now return null for a null command buffer or a stream
that is not registered, and still throw for a registered stream: the
packet a registered stream gets is not measured, KytyPS5 has no body for
them, and its IT_NOP layout for the DCB calls differs from the
SET_UCONFIG_REG, WRITE_DATA and SET_WORKLOAD sequence OpenAGC
(3c7e8072, src/agc_workload_packet.c) derives from the driver. The
signatures are OpenAGC's DCB ones (src/game_compat.c), which says the
ACB calls use the same driver builders. sceAgcDcbSetWorkloadsActive now
checks the stream before the ids, as OpenAGC does. Recorded in
docs/dev/debt/libSceAgc.md.
agc_commands gains testWorkloadStreams: the three measured patterns for
all six calls (null, buffer untouched), an exhausted and a null buffer,
and a registered stream that throws without touching the buffer. On
upstream/main it fails with "sceAgcDcbSetWorkloadStreamInactive not
implemented", and the DCB active pattern b alone segfaults reading
0x2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SPIR-V emitters of FPRecip32, FPRecipSqrt32, FPSqrt, FPExp2 and FPLog2
flushed a denormal input to its signed zero before the host operation.
Since #537, unaryFloatSpecials replaces the host result for every input
below 2^-126 with the hardware's result for a zero of the same sign: rcp
and rsq give a signed infinity, sqrt the signed zero, log -inf and exp 1.0.
floatUnary and float16Unary both go through it, so the flushed input could
only change a value that is then discarded. v_rcp_iflag_f32 gets the same
special values and already went without the flush.
The pixel prologue computes PositionW as FPRecip32 of FragCoord.w without
those special values, so it now flushes FragCoord.w in the IR first and
gives the same result as before.
A branch condition that reads through a null user-data pointer is now
treated as not evaluable instead of being caught, so every successor
stays active.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Several image instructions of different dimensions on the same T# address share
one descriptor source, so a source stays active as soon as one of them is taken
and the others are still checked against the T#. Record the images a block uses
as well, deactivate the images used only behind a branch the capture does not
take, and bind them as null descriptors.
PPSA15246 (Teardown) then passes the "incompatible with the static runtime
image interface" check of the three image_store variants that are not taken.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A compute kernel that picks a buffer or an image path from an SRT word reads
the same address as a V# in one path and as a T# in another. The resource
tracker now records which block uses each descriptor source and builds the
resource control flow from the structured block terminators, so a source whose
only uses sit behind a conditional branch with a clean, evaluable condition is
left inactive (bound as zero words, not validated) when the capture takes the
other path. A condition that reads inaccessible memory is not evaluable instead
of throwing.
PPSA15246 (Teardown) stopped on "runtime image descriptor is invalid" for the
untaken path of such a kernel.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The second import-window case of agc_write_tracking makes one page
unreadable with VirtualProtect or mprotect and expects an import that
stays watched to throw before it copies its range. On Windows CheckRange
answers from its table of committed arena pages, which is kept current
by the registry's invalidator. The first case had checked the same
allocation, so the table still said the page was readable, the check
passed and the copy faulted on the page.
ProtectPage now reports the change through GuestAllocationsInvalidate,
as the registry does for a guest protection change and as
tests/BdaResources.cpp does after VirtualProtect.
A host import that leaves its range watched can resolve the write
protection of the imported pages without storing anything, as NVIDIA's
Linux driver does for every page. ImportWatched stamped those pages as a
possible CPU store (BlockMaybeWritten), and only a storage image tracked
by 64 KiB unit could clear the stamp, by comparing with a copy of the
guest bytes it was uploaded from. Since #3372 a fast-cleared upload
therefore reads its whole surface from guest memory wherever such an
import is possible, although nothing but that compare uses the copy.
ImportWatched now takes whether the import leaves its range watched. If
it does, the range is copied before the stores made so far are
collected, and after the import each page whose write protection was
resolved is compared with that copy. A page with other bytes is stamped
as a CPU store. A page with the same bytes keeps only the CPU-side stamp
the label rules read, so UnchangedSince holds for it for every consumer:
storage images tracked by layer, snapshots and caches as well.
If it does not (a host-pointer import on a device whose imports are
compared, a direct import on Windows), nothing is copied or compared:
the pages resolved in the window get the CPU-side stamp and the range
leaves the watch before the tracker is unlocked. Where host-pointer and
dma-buf imports differ in this, the dma-buf import that follows a failed
host-pointer import is a window of its own, so each window knows its
kind before it starts.
BlockMaybeWritten, the written-stamp table, WatchedImportsStampPages and
the unit compare in StorageTexture::Refresh are gone. The fast-clear
upload skips the guest-byte copy whenever its range is watched, as
before #3372.
Cult of the Lamb (PPSA06465) imports libSceContentSearch, including
sceContentSearchGetContentLastUpdateId, OpenMetadataByContentId,
GetTotalContentSize and GetNumOfContent (import audit in #2686), which
were declared without parameters and threw.
Give the nine pending calls the signatures of AlexAltea/orbital
db_types.json (cc408367, lines 555-569) and SharpProspero
Interop/Content/ContentSearch.cs (5dfdb4b0), and implement the content
library the host has, which is empty:
- GetNumOfContent, GetTotalContentSize and SearchContent report 0 rows
and 0 bytes, GetContentLastUpdateId and the search calls report update
id 0. A search limit above 92 returns 0x809D100A and every call before
Init returns 0x809D1001, both from SharpProspero.
- Filters and sort conditions use SharpProspero's layouts (added to
SceTypes.hpp with size checks) and are checked against its enums;
unknown values, null outputs and null arrays with a length throw.
- OpenMetadata and OpenMetadataByContentId throw, as no source gives the
error for content that is not in the library, so no metadata id is
handed out and CloseMetadata, GetMetadataFieldInfo and
GetMetadataValue throw too.
The update id value, the argument errors and the check order are
recorded in TechnicalDebt. The four ContentSearch calls leave the
unimplemented-imports test, and guest_content_search covers the library.
dup and dup2 carry the random device over to the new descriptor, and
select reports /dev/random and /dev/urandom ready, as the select entry
already says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Titles check for a plugin with sceKernelStat before loading it through the engine path. The relinker emits only X.prx.guest.prx, so the check failed and PSNCore started with the raw (0, NULL) arguments. Share the dlopen mapping with sceKernelStat, read-only sceKernelOpen and sceKernelCheckReachability when the original name is absent.
dup and dup2 of a Windows directory descriptor threw. FreeBSD's
kern_dup makes both descriptors refer to one open file, so they share
the read position; the duplicate now points at the same DirectoryState,
which getdents and lseek go through, and keeps it after the original
is closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
image_sample_c on an 8-bit UNORM T# returns 0 for every compare
function on gfx1035, as on 8-bit sRGB (measured on the hardware oracle
in the review of #3438), but the emulated comparison still compared the
red channel. 8, 8_8 and 8_8_8_8 UNORM now return 0 like 8-bit sRGB.
Every texel comparison of those formats is 0, so a bilinear cube
without DISABLE_CUBE_WRAP and an anisotropic S# cannot change the
result; both are accepted for them, as the cube already was for 8-bit
sRGB. Other formats keep both refusals.
The test rows that check real comparisons move to 16_16 UNORM.
collate<char>::do_transform gave the result std::string a capacity of
exactly its length and threw for results of 4095 characters or more.
The console's libc.prx grows the string from its inline capacity of 15
to length | 15, so a 26-character result has capacity 31 and a 32-byte
allocation. Once capacity + 1 reaches 0x1000 bytes it takes the
over-aligned large allocation that the guest's inlined deallocation
expects: operator new(bytes + 0x27), the data aligned to 32 bytes and
the raw pointer stored just before it. This is the scheme of
Dinkumware's basic_string (_Newsize | _ALLOC_MASK) and of its
_Allocate in <xmemory0> (_BIG_ALLOCATION_THRESHOLD 4096,
_BIG_ALLOCATION_ALIGNMENT 32, _NON_USER_SIZE sizeof(void*) + 31),
including its bad_alloc when bytes + 0x27 would wrap.
do_transform now uses that capacity and allocation; the throw for long
results is gone. The debt entry in docs/dev/debt/libc.md now records
the capacity and large storage as unverified on hardware. guest_collate
checks the capacity and allocation size of a 26-character result, the
last small result (4079 characters, a 4080-byte block) and the first
large one (4080 characters, capacity 4095, a 4096 + 0x27-byte block
with the data aligned to 32 and the raw pointer before it). With main's
LocaleSupport.cpp it aborts at the allocation-size check of the
26-character result (lastSize 27 instead of 32).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow FreeBSD sys_setregid and root identity model: accept real and effective group id 0 or -1 (unchanged) and change nothing; any other id throws. Update GuestCredentials test and TechnicalDebt.
On Windows the guest arena is reserved while libc loads, around whatever
the process has already mapped in its interval. About one start in forty
has nothing mapped there, so the arena has no host hole:
agc_driver_graphics then fails with "write-watched arena contained no
host exclusion to test" and agc_write_tracking prints that its host-hole
checks were skipped.
agc_write_tracking now starts itself suspended, reserves one allocation
granule in the middle of the child's arena interval before the child's
loader runs, and the child runs the host-hole checks on that range. The
range is a host hole on every start, so the checks no longer depend on
the address layout. The four queries agc_driver_graphics made on an
enumerated hole are among them and are removed there.
Imported by the Share plugin of Hollow Knight: Silksong (PPSA12544).
Takes the extended parameter and request id like
sceShareCaptureScreenshotExtended, as KytyPS5 does: nothing is
captured, so it stores the invalid request id -1 when the pointer is
not null and returns NOT_SUPPORTED.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 245090904b64354f68fba3b8ef6fd6e232a3b1e6)
UFC 6 (PPSA23566) imports both. Initialization marks the library
initialized and termination clears it, as in shadPS4. Termination
without initialization and a second initialization throw, since no
PS5 source settles their results.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit d2866a886a7702cdaad56d161460a3ce86ede629)
Throw a guest std::system_error for the Dinkumware _Cpp_error codes,
as in VC12 and microsoft/STL thread0.cpp: the error_code holds the
FreeBSD errno and a guest generic error category modelled in libc, and
what() is the table message followed by the category message. Codes
outside the table throw std::out_of_range. The system_error copy
constructor and assignment now copy the error_code too.
Imported by Deathloop (PPSA01670) and Minecraft Legends (PPSA05510).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit bed1deafdee4b50bea9683a1cead0d0320150eb7)
Export getopt (VUtibKJCt1o) and getopt_long (8VVXJxB5nlk) from
libSceLibcInternal together with the guest variables optind
(zCnSJWp-Qj8), optarg (zG0BNJOZdm4), opterr (yaFXXViLWPw), optopt
(FwzVaZ8Vnus) and optreset (CZNm+oNmB-I). The NIDs were computed with
tools/nid_names.py and all seven are in aerolib; the PS5 payload SDK
f5d40f02 lists the functions and the five objects in
sce_stubs/libSceLibcInternal.c.
Titles (predicted stops from registry order, not observed runs):
- XPSemu PPSA97358, after execv/execve, the signal waits, the
credential setters, mlockall and tcsendbreak:
"FAIL: unresolved ELF import VUtibKJCt1o from libSceLibcInternal.prx"
(getopt idx 204, 10 call sites from 0xF6C23D), then optind (207),
optreset (208), optarg (209).
- RetroArch PPSA99169: getopt_long (idx 554, call sites 0xD2FC0C,
0xD3002A), optind (553), optarg (555).
Behaviour follows FreeBSD release/11.4.0 8c639945: getopt is
lib/libc/stdlib/getopt.c (no permutation, GNU "::" optional argument,
lone "-" as an option when listed), getopt_long is getopt_internal
from lib/libc/stdlib/getopt_long.c with GNU_COMPATIBLE (permutation,
POSIXLY_CORRECT read through the guest getenv on the first call and on
optreset, "+" and "-" prefixes, "W;", optind 0 as a reset, abbreviation
and ambiguity rules and the GNU warning texts). Warnings are written to
the guest stderr stream as "<__progname>: <message>", as warnx does.
The struct option layout and argument kinds are include/getopt.h.
The code lives in libSceLibcInternal because __progname is a libkernel
export that libc cannot link.
A null getopt option string, a null argument vector or argument, a
negative optind and an optind moved inside a pending permutation throw
where FreeBSD faults. getopt_long_only is not exported.
guest_getopt runs the FreeBSD getopt_test cases (contrib/netbsd-tests
t_getopt.sh with the h_getopt and h_getopt_long result format) plus
grouped and optional arguments, "--", permutation order, "+" and "-"
prefixes, optreset and optind 0 restarts, POSIXLY_CORRECT, all three
long argument kinds, abbreviations, ambiguity, flag pointers, "W;",
optopt with opterr 0 and the exact stderr text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit a2740166d93920c301e1add23ceea762b4acad27)
FreeBSD accept() copies SS_NBIO from the listening socket (ACCEPT4_INHERIT in
kern_accept4). The accepted guest socket was always recorded as blocking:
fcntl(F_GETFL) never showed O_NONBLOCK, and the host socket kept the host's own
inheritance (kept on Windows, dropped on Linux). Apply the listener's mode to
the accepted socket.
VLC PPSA85300 imports readdir_r (J0kng1yac3M) from
libSceLibcInternal.prx. Predicted stop after getlogin_r, from relink
registry order (idx 343, call site 0x16A0B91):
FAIL: unresolved ELF import J0kng1yac3M from libSceLibcInternal.prx
The NID is readdir_r in aerolib (tools/nid_names.py --nid J0kng1yac3M:
rename-ready readdir_r); the title binds it to libSceLibcInternal, where
the PS5 payload SDK (ps5-payload-dev/sdk f5d40f02,
sce_stubs/libSceLibcInternal.c) lists it beside readdir, so it goes into
libc next to readdir.
FreeBSD 11.4 (freebsd-src 8c639945, lib/libc/gen/readdir.c:106-135):
save errno and clear it, take the stream lock, read the next entry and
copy _GENERIC_DIRSIZ(dp) bytes of it into the caller's entry, set
*result to the entry or NULL at the end, return errno when the read
fails, otherwise restore errno and return 0. The guest struct dirent is
the SDK's FreeBSD 11 layout (include/freebsd/sys/dirent.h, 264 bytes,
name at 8), and _GENERIC_DIRSIZ equals the recordLength readdir already
computes. The read itself is readdir's, moved into a helper that both
call under the per-stream mutex, so readdir and readdir_r share one
position. A null stream returns EBADF, as readdir sets it; a null entry
or result pointer throws instead of faulting.
The call site is libbluray 1.4.1 _dir_read_posix
(videolan/libbluray 7d94f266, src/file/dir_posix.c:73-88), reached when
VLC opens a Blu-ray folder or ISO. The disassembly at 0x16A0B91 passes a
264-byte stack buffer (sizeof(struct dirent)) and reads d_name at
offset 8.
Tested on Windows (MinGW): guest_directory 3/3 (readdir_r over a
temporary directory to the end gives the same names and types as
readdir, copies only recordLength bytes, keeps errno at the end,
continues the position of an interleaved readdir, EBADF for a null
stream, throw for a null entry).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 01085afe122d6d2c6af9e54c745e2a7c448f7b24)
Guest rwlocks were a host std::shared_timed_mutex. On Linux that is a
glibc rwlock, which prefers readers: a reader is let in while a writer
is blocked, so a writer waiting on a lock that readers keep taking in
overlapping turns never gets it (a probe with four readers each holding
the lock for 4 ms still had scePthreadRwlockWrlock waiting after 5 s,
with 4924 read acquisitions meanwhile), and tryrdlock succeeds while a
writer waits.
FreeBSD 11.4 libthr gives waiting writers precedence. A rwlock starts
without URWLOCK_PREFER_READER (rwlock_init() callocs it), and
lib/libthr/thread/thr_umtx.h, _thr_rwlock_tryrdlock(), refuses a reader
while a writer waits:
if ((flags & URWLOCK_PREFER_READER) != 0 ||
(rwlock->rw_flags & URWLOCK_PREFER_READER) != 0)
wrflags = URWLOCK_WRITE_OWNER;
else
wrflags = URWLOCK_WRITE_OWNER | URWLOCK_WRITE_WAITERS;
as does do_rw_rdlock() in sys/kern/kern_umtx.c for the blocking wait.
The only exception is a thread that already holds a read lock, so that a
recursive read cannot deadlock (lib/libthr/thread/thr_rwlock.c,
rwlock_rdlock_common()):
if (curthread->rdlock_count) {
...
flags = URWLOCK_PREFER_READER;
The rwlock is now a mutex and condition with a count of waiting writers:
a reader waits while a writer holds the lock or, unless the thread
already holds a read lock, while a writer waits; a timed writer that
gives up wakes the readers it held back. The TechnicalDebt note about
locking guest rwlocks once for winpthreads no longer applies to them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ProsperoRadio (PPSA99001) stops at 33.7 s on Windows, with #3649
applied so that it gets past poll, on the throw from #3840:
what(): fcntl: record locks are not implemented on Windows, command=12, fd=3
F_GETLK, F_SETLK, F_SETLKW and the F_OGETLK, F_OSETLK and F_OSETLKW
forms now follow FreeBSD 11.4's kern_fcntl and lf_advlockasync
(8c639945): an SEEK_CUR start is resolved against the descriptor offset
(EOVERFLOW past OFF_MAX), F_RDLCK needs read access and F_WRLCK write
access (EBADF), another type is EINVAL, and the range is checked against
the file size for SEEK_END (EINVAL for a negative start or an unknown
whence, EOVERFLOW past OFF_MAX). The guest is one process, and
lf_blocks never lets locks of one owner conflict, so whichever
descriptor or thread asks, F_SETLK and F_SETLKW succeed without waiting
and F_GETLK reports F_UNLCK, as on the console. Nothing is held on the
host: Windows byte-range locks are mandatory and would fail the guest's
own reads and writes through other descriptors. A lock from the end of
a directory throws.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ShaderResources::PrepareDrawBindings called MaxDrawSnapshotBytes() on
every draw, and it read APS5_DRAW_SNAPSHOT_MAX_MIB with getenv each
time. The UCRT's getenv takes the environment lock and scans the whole
block. In Planet of Lana (about 90,000 draws per 10 s) that call was
2.6% of the queue-0 worker's samples.
The cap is now read once when a ShaderResources object is built and
kept in it, so a cached resource template uses it for every draw it
serves. In Planet of Lana the record phase drops from 21.2 to 18.7 us
per draw.
drawSnapshotCapTests also raises the variable after building the
resources and checks that they keep the cap they were built with. With
the previous ShaderResources.cpp it fails there.
setsockopt() on a guest socket failed with EINVAL unless the option
length was exactly sizeof(int), so an option passed as a 64-bit value
(for example SO_BROADCAST or SO_RCVBUF from an int64_t or size_t) was
rejected and silently left unchanged.
FreeBSD 11.4 reads these integer options through sooptcopyin() with the
size of an int as both the wanted and the minimum length, and only a
shorter value fails. sys/kern/uipc_socket.c, sooptcopyin():
* If the user gives us more than we wanted, we ignore it, but if we
* don't get the minimum length the caller wants, we return EINVAL.
...
if ((valsize = sopt->sopt_valsize) < minlen)
return EINVAL;
if (valsize > len)
sopt->sopt_valsize = valsize = len;
and sosetopt() uses it for SO_BROADCAST, SO_REUSEADDR, SO_SNDBUF and
SO_RCVBUF:
error = sooptcopyin(sopt, &optval, sizeof optval,
sizeof optval);
A longer value now sets the option from its first int; a shorter one
still fails with EINVAL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reject invalid initial counts without replacing the semaphore and preserve a saturated count on post, matching the existing guest POSIX limits with encoded SCE errors. Add boundary and unchanged-value guest regressions.
A storage image is written on the GPU and left pending, then a leased
address-based use writes the first 40800 bytes of the same memory as a
buffer and every pending image is flushed: guest memory must hold the
buffer's bytes where it wrote and the image's texels everywhere else.
Runs with watched and with compared imports and returns 77 when host
imports or write watching are unavailable.
Before a dispatch or draw that writes buffer ranges is recorded, store the
results of storage images still pending in those ranges, whatever path
serves the range (in place, copied, or an address-based heap, which the
per-region flush exempts), and include the written ranges in the pending
scan of a revalidated resource set.
Without it the image's write-back could come after the buffer write: a
partial write to a 64 KiB block made the write-back keep the whole block,
dropping the image's results in the rest of it, and a write-back recorded
after the buffer write stored the image's older texels over it. Titles
that reuse transient memory for textures and buffers in the same frame
(Unreal Engine's render graph transient allocator) read corrupted tile
data as a result.
Kart PS5 (PPSA99611) stops on gacfOmO8hNs (ceil, per tools/nid_names.py)
from libc.prx once __umodti3 resolves (title run on #4519). ceil rounds
toward positive infinity whatever the rounding mode, keeps the sign of
zero and of results that round to zero, and returns infinities and NaNs
unchanged, as std::ceil does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
relinker.exe carried only MinGW's default manifest, so Windows kept MAX_PATH for it even with long paths enabled, and an input under a deep directory failed with 'Cannot open file'. Embed a manifest with longPathAware that keeps asInvoker and the Windows 10 supportedOS entry.
The previous commit pointed the untaken side of a folded branch that
keeps its selection merge at the merge block. That adds an edge from the
header to the merge block: the merge block's phis have no operand for it,
and values from the taken arm no longer dominate the merge block, as
fuzzing with validation on found once the repeated label stopped hiding
it.
For SPIR-V 1.6 the branch is now left as it is, so the control flow and
the untaken arm stay; earlier versions keep naming the taken label twice,
which they allow, and drop the untaken arm as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
tools/check_test_registration.py invoked bare ninja from PATH, so the audit failed with FileNotFoundError when the configured Ninja lived outside PATH. Read CMAKE_MAKE_PROGRAM from the build directory CMakeCache.txt and fail honestly when it is missing. Adapt the mocked built tests to a configured build and add a regression that runs a real Ninja from a path with spaces outside PATH.
Pipeline specialization replaced every unreachable block with
OpUnreachable. When a loop's continue target became unreachable, either
because the latch exited on a constant condition or because the emitter
had already folded it, that removed the header's only back-edge, and
validation rejects a loop header targeted by no back-edge block.
An unreachable continue target of a live loop header now keeps an
OpBranch to its header, as SPIRV-Tools' dead branch elimination does.
The header's phis keep an operand for it, naming the phi itself, since
the edge is never taken and the continue block's values are gone.
Found by recompiling mutated compute shaders with SPIR-V validation on,
for loops with a literal bound such as s_cmp_lt_u32 with two constants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
.github/ISSUE_TEMPLATE/performance.yml asks for the title and scene, what is
slow or what changed, the summary lines and traces of APS5_TRACE_FRAMES
captures, the trace digest and optionally the worker's collapsed stacks,
and states the capture protocol: warm caches, the same scene and settings,
no other profiling switch, the same machine and session. The pull request
template and CONTRIBUTING ask a pull request that claims a change in speed
for the summary lines of main and of the change, captured the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The compute dispatch look-ahead noted WRITE_DATA and RELEASE_MEM stores only
as labels, and labels other than 4 or 8 bytes were dropped. A WRITE_DATA that
fills a descriptor or an argument block between two dispatches was invisible,
so the later dispatch was captured ahead with the old bytes. Every such store
now also enters the run's written ranges, which stop the look-ahead before a
dispatch that reads them.
C17 7.22.1.3 and 7.29.4.1.1 interpret NAN(n-char-sequence) as a quiet
NaN. With MinGW-w64, std::strtod, strtof and strtold are mingw-w64's
gdtoa functions, which copy the hex digits into the significand
without the quiet bit, and for long double without the x87 integer
bit: strtod("nan(0x123)") returned the signaling NaN
0x7ff0000000000123 and strtold a pseudo-NaN. A payload whose only set
bit is the implicit or x87 integer bit gave infinity:
strtof("nan(0x800000)"), strtod("nan(0x10000000000000)") and
strtold("nan(0x8000000000000000)") returned +inf. glibc returns quiet
NaNs that keep the payload.
C17 7.12.11.2p2 makes nan("n-char-sequence") equivalent to
strtod("NAN(n-char-sequence)", NULL), and nan and nanf already return
the console's fixed quiet NaNs, ignoring the tag. ParseFloat, which
strtod, strtof, strtold, atof and the wide variants share, now returns
the same values, 0x7ff8000000000000 and 0x7fc00000, negated after a
minus sign, on Windows and Linux. strtold returns the x87 default
quiet NaN, since no console nanl was observed.
SPIR-V 1.6 requires the true and false labels of OpBranchConditional to
differ. Two places produced the same label for both:
- The structured terminator emitter, for a conditional branch whose
targets are the same block, such as s_cbranch_scc0 to the next
instruction. It now emits OpBranch, keeping OpLoopMerge on a loop
header.
- Pipeline specialization, when it folded a branch on a constant and
kept the selection merge because the taken path is not linear. It
wrote the taken label twice; the untaken side now names the
selection's merge block, which the constant condition never takes.
Found by recompiling mutated compute shaders with SPIR-V validation on;
before this, 169 of 20000 were rejected for this rule, and builds
without SPIRV-Tools handed the module to the driver.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
sceNetAccept, sceNetGetsockname, sceNetGetpeername, sceNetRecvfrom and
sceNetRecvmsg stored the full address length (16 or 28) even when the
caller's buffer was shorter and only part of the address was copied.
FreeBSD 11.4 stores the copied length, the smaller of the two
(kern_accept4, kern_recvit, kern_getsockname and kern_getpeername in
sys/kern/uipc_syscalls.c), as libkernel's socket calls do since #4295.
sceNetGetsockname on a socket that is neither bound nor connected failed
with EINVAL on Windows, where Winsock reports WSAEINVAL. FreeBSD 11.4
returns the wildcard address with port 0 (in_getsockaddr and
in6_getsockaddr in sys/netinet/in_pcb.c and sys/netinet6/in6_pcb.c), and
Linux already does, so the call now builds that address for the socket's
family.
sceNetEpollWait copied the whole event a title passed to
sceNetEpollControl, so the ident and reserved fields came back as the
title had left them. The ident of a returned event is the id that was
registered and reserved is 0 (shadPS4 net.cpp sceNetEpollWaitImpl,
KytyPS5 network.cpp EpollWait); a title that registers a socket without
filling ident and looks the socket up by it now finds it.
sceNetEpollWait used Linux's EPOLLOUT bit (4) for writability. The SCE
value is 2 (shadPS4 net_types.h ORBIS_NET_EPOLLOUT, KytyPS5 network.cpp
EPOLL_OUT, SharpProspero Socket.cs EpollOut); IN (1), ERR (8) and HUP
(0x10) already match. A socket registered for OUT was never polled for
writability, so the wait ran out its timeout or blocked forever, and an
IN|OUT registration never reported OUT.
sceNetSetsockopt and sceNetGetsockopt answered EOPNOTSUPP for 0x1105 and
0x1106, the SCE send and receive timeouts in microseconds (shadPS4
net_types.h ORBIS_NET_SO_SNDTIMEO/RCVTIMEO, SharpProspero Socket.cs
SoSndTimeo/SoRcvTimeo, KytyPS5 network.cpp). A title that set a receive
timeout this way then blocked in sceNetRecv for good. Both options now
set and read the timeouts that 0x1005 and 0x1006 already do.
Commit 48dcaace7 introduced PrimedSharedMutex and converted every host std::shared_mutex that existed at the time, because winpthreads initializes a static rwlock on first use and a concurrent first lock returns EINVAL, which std::shared_mutex ignores (docs/dev/TechnicalDebt.md:10).
Two host mutexes were added afterward and missed the conversion:
- Recompiler.cpp registrationModule() static mutex (added by baf3437465)
- MultisampleTarget.cpp guardedMutex() static mutex (added by d109f3966f)
Apply the same priming. Recompiler.cpp reuses the PrimedSharedMutex already defined in that translation unit; MultisampleTarget.cpp gets the identical local helper to match the existing inline precedent.
Tested: Windows 11 x64, WinLibs GCC 15.2.0 (matches CI). Compiled Recompiler.cpp.obj and all three MultisampleTarget.cpp.obj targets with the project build flags; check_conventions.py passes.
InitialEntry reads O_DIRECT from the host status flags, and macOS has
no O_DIRECT, so libkernel did not compile there. It is now read only
where the host defines it. GuestGetrlimit iterates over braced lists
without including <initializer_list>, which libc++ requires.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OutRun2006 (PPSA99106) imports nearbyintl and stops at start-up on main. Add the long double round-to-nearest-integer export next to nearbyintf; std::nearbyint rounds under the current direction and raises no inexact exception.
The title-module variants of sceKernelGetModuleList and
sceKernelGetModuleInfo, following shadPS4: the list leaves out system
libraries and the info call answers EPERM for one. A system library is
a loaded .prx image that is not a relinked title module.
NHL 27 (PPSA34063) imports both and fails to load without them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel #2081 matches stores each pattern dword with
buffer_store_format_x, so what lands in memory depends on the output
descriptor's FORMAT (bits 12-18 of user[3]). Only 32_UINT, 32_SINT and
32_FLOAT (20-22) store the dword unchanged. Other formats convert it,
and an 8- or 16-bit one writes 1 or 2 bytes of every 4-byte element, so
a buffer fill would also overwrite the bytes in between. Such a dispatch
now goes to the recompiler.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
referencedIds() took every operand word of every instruction as a
possible id, so a literal that happened to equal the id of a dead folded
constant or an unreachable block kept it: the OpTypeInt width 32 kept a
dead constant %32, LocalSize literals kept dead labels.
Visit only the <id> operands of each instruction. Literal operands are
skipped for the instructions that carry them: type widths, counts and
image parameters, constant values, storage classes, memory access and
image operand masks (their trailing ids still count), composite and
shuffle indices, OpArrayLength members, extended instruction numbers,
entry point names, execution modes, merge and function controls, branch
weights, OpSwitch case values (two words for a 64-bit selector) and
group operations. An instruction without a listed layout still counts
every word, so an unknown instruction can only keep debris, never drop
something in use.
The SpirvModule case now also folds a switch whose dead constant has id
32 and whose dead blocks' ids appear as LocalSize literals; it fails
without this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
qsort was a heapsort. It sorted correctly, but elements that compare
equal ended in a different order from FreeBSD's Bentley-McIlroy qsort,
in 18947 of 20000 random arrays with repeated keys, and below 7
elements, where FreeBSD's insertion sort keeps equal elements in
place, a heapsort does not. qsort now follows FreeBSD 11 qsort.c,
including its comparator call sequence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
On Linux with write watching (uffd write-protect), the first import asks the device whether a GPU
write clears the protection (ProbeImportWriteProtection): it records a vkCmdCopyBuffer and a
vkCmdFillBuffer on the import and submits them. The test's mock device records no commands, so
agc_host_import_retire failed with "missing Vulkan function: vkCmdCopyBuffer" before it reached
the retire (RADV, reported on #4515). The test now sets APS5_WRITE_WATCH_IMPORTS=unwatch, so its
imports are compared as on Windows and the probe, which a mock cannot answer, does not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS4 executables keep their relocated read-only data, GOT and init/fini
arrays in a PT_SCE_RELRO segment outside every PT_LOAD. The segment filter
dropped it, so Windows and macOS output stopped at the first relocation
into it, and Linux output placed the relinker's extra block over its
addresses without an error. The ELF reader now reports such a segment as a
read-write PT_LOAD. A PT_SCE_RELRO inside a PT_LOAD keeps being skipped,
and one that partially overlaps a PT_LOAD throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Windows GUI build started without standard handles has no descriptor 2.
_dup(2) then reached the invalid-parameter handler, which aborts, before the
"cannot keep the host standard error" error could be thrown. It now goes
through WithoutParameterHandler like the other descriptor calls in this file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guest descriptors are host descriptors, so a guest that moves standard
error with dup2 also moves the stream sceKernelDebugOutText writes to. A
title that forwards its standard error to the kernel log through a pipe
then reads its own debug output back and logs it again without end. Before
the guest's first dup2 onto descriptor 2, the host's standard error is kept,
and sceKernelDebugOutText writes there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EmitBdaWrite, used for buffer stores through GPU-selected descriptors, recorded an
Unaligned fault and dropped every 32-bit store whose address is not 4-byte aligned.
EmitBdaStore and the BDA reads already take the byte path for unaligned addresses, and
raw buffer accesses use the exact bytes, as RDNA does in unaligned mode. The unaligned
case now stores the four bytes one by one like the 8- and 16-bit stores of the same
function.
Sword of the Sea hit this about once per frame from one compute store, so four bytes
were lost and a fault was reported each time.
The BDA store execution table now also runs through EmitBdaWrite; its unaligned dword
cases failed before this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Weak undefined imports in the input ELF became STB_GLOBAL in the relinked
output, so a title that weak-imports a function no module exports stopped
at startup instead of binding the import to null.
Keep STB_WEAK on the relinked dynamic symbols. On Windows, write 0 plus
the addend to the slot of a weak import that no searched module exports.
Fixes#4497
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows unwinder stopped at the first frame of a module without the
project's .ehmeta or .ehfram tables, so an exception could not cross
libstdc++-6.dll and libwinpthread-1.dll (std::call_once through
pthread_once) or system DLLs. For such a frame it now looks up the
function's PE unwind data with RtlLookupFunctionEntry and takes the
caller's registers, including the nonvolatile rsi, rdi and xmm6-xmm15,
from RtlVirtualUnwind. A frame whose unwind data has an exception or
termination handler still stops the walk, since those handlers are not
run.
windows_exceptions gets back the std::call_once case of #1423.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceSaveDataDirNameSearch reads % in the directory name pattern as any
run of characters and _ as one character. The matcher stopped when the
name ran out and then required the pattern to have ended too, so a
trailing % with nothing left to match failed: SAVEDATA% did not find a
save named SAVEDATA, and TRAI_% did not find TRAIL. A % now matches no
characters at the end of a name as it already did in the middle.
In the float modes that flush f32 denormals, writeOperand flushes the result
of v_min/max/min3/max3/med3_f32 and v_floor/ceil_f32. DenormalFlushEliminator
drops a flush only when it can show the value isn't a denormal, and it had
no rule for these opcodes, so their result flush stayed even when every
operand had already been flushed.
FPMin32, FPMax32, FPMinTri32, FPMaxTri32 and FPMedTri32 return one of their
operands' bits, or the and or or of two zeros, so the result is denormal-free
when every operand is. FPFloor32 and FPCeil32 return an integer, a zero, an
infinity or a NaN whatever their operand. In IEEE mode the min and max
results and the floor and ceil results go through selects with a quieted
operand, which the eliminator can't follow, so their flush stays there.
agc_denormal_flush_elimination covers the five min and max forms with
flushed and with unknown operands, floor and ceil of an unknown value, and a
max carried around a loop.
A sampler with FORCE_DEGAMMA decodes the texels of a non-sRGB 8-bit texture
as sRGB. Such a binding was refused; it now samples an sRGB view of the same
image. Snapshot images whose format has an sRGB twin (R8, R8G8, RGBA8, BGRA8,
ABGR8 packed, BC1/2/3/7 UNORM) are created with MUTABLE_FORMAT, and storage
images are already mutable. The view is made on first use for the full,
first-layer or base-level range, with sampled usage only and the descriptor's
minimum LOD. A format without an sRGB twin is still refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AMD-only converter moves the instructions next to a short AMD-only
instruction into its stub, and refuses to move one that a branch
enters. It knew only direct jump and call targets, so a switch case
entered through a jump table could land inside the jump written over
the moved instructions.
The converter now also reads relative jump tables: a RIP-relative LEA
whose address lies in a loaded segment starts a table of 32-bit
offsets from that address, read while each target starts an
instruction of the segment being converted. To read them, Convert
takes the program headers and converts their executable load
segments; the relinker passes all headers, and
ElfReader::ReadCodeSegments, now unused, is removed.
The converter test builds a dispatch through such a table whose second
case enters the instruction after a 4-byte 256-bit store, and checks
that the store takes the instruction before it instead. It also checks
that reading stops at an entry that does not start an instruction,
that only a LEA forms a table address, that tables are read only
inside loaded segments, that only executable load segments are
converted, and that a segment past the end of the file fails the
conversion.
PPSA12544: one 256-bit store in Il2cppUserAssemblies moved the
instruction after it, which is case 1 of a switch; it now goes through
an island. The other relinked files are unchanged.
AI-assisted: yes
lavapipe and MoltenVK leave the vertex stage out of subgroupSupportedStages,
so the driver's shader validation refuses every vertex program that declares
GroupNonUniform ("subgroup capability 61 is unsupported for shader stage 1").
Most do, since the entry EXEC is a ballot.
The device target now records whether the vertex stage has subgroups. When
it does not, vertex and local programs get a host subgroup width of 1: s3
reports one vertex and one primitive, the lane id is 0, a ballot is the
invocation's own bit, a shuffle returns the invocation's value, an elect is
true and a half-wave reduction read is the key itself. Reads of other lanes
keep the existing narrow-host rules and throw where they did. Devices with
vertex subgroups emit the same SPIR-V as before.
The memo stamp held TrackerGeneration, which every collect and driver store
advances, so it almost never matched. Driver stores into uncovered ranges
record their generation per 64 KiB block, and NewestStamp of the surface
replaces TrackerGeneration in the stamp.
The 16-byte fill kernel has a variant that loads its pattern with
s_buffer_load_dwordx4 through the V# in s[4:7] instead of taking it from
the user SGPRs. Vampire Crawlers (PPSA30703) clears its UI canvas HTILE
with it every frame; run as a shader, the depth surface never cleared and
every UI draw failed its GEQUAL depth test, leaving the screen black.
Kart PS5 (PPSA99611, Phi1ow/mkwii-ps5 v1.0.0-rc1) stops on it right
after __divti3. The related open PR #4037 reports: "with this change it
passes `__divti3` and stops at `ELSr5qm4K1M` (`__umodti3`)".
PS5CEMU-HAR (PPSA99360) imports it too: tools/import_audit.py lists
ELSr5qm4K1M __umodti3 as absent from libSceLibcInternal.prx (registry
index 359), so the predicted stop is
"FAIL: unresolved ELF import ELSr5qm4K1M from libSceLibcInternal.prx".
libSceLibcInternal imports fall back to libc.prx, so the export lives in
libc beside __udivti3. compute_nid and aerolib (zecoxao/sce_symbols
2883963a) both give ELSr5qm4K1M = __umodti3.
__umodti3 is compiler-rt's lib/builtins/umodti3.c (llvm-project
0ee439b705e82a4fe20e266bc8fea96d0e60e1ec): the remainder that
__udivmodti4 leaves. The restoring division of SonyUdivti3 already
computes it, so the loop moves to SonyUdivmod128 and both helpers use
it. A zero divisor reaches a 64-bit divide in __udivmodti4 and traps on
x86-64; this throws instead, as __udivti3 does, and names the dividend.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A relocation whose dynamic symbol entry is all zero, like the null symbol,
now takes the value 0 plus its addend, as the gABI defines for STN_UNDEF.
The Linux output imports such an entry as an unnamed weak symbol, which the
host loader resolves to 0. The Windows and macOS outputs write the value
into the slot instead of failing with "Empty import symbol name". Such
references are left out of the call registry. An unnamed symbol entry that
is not all zero still fails the relink.
OliSe Player (PPSA01153) has seven of these entries and calls through the
slots only when they are not 0 (#4437).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read the device-local heap's VK_EXT_memory_budget figures before a texture
or storage image allocation and pick a host memory type when the
allocation does not fit, rather than allocating again after the driver
refused the first attempt. The allocation itself is made once and still
throws when it fails.
RetiredImport now owns the dropped import's map node (std::map::extract), so releaseParkedImports
takes the parked import's range from node.mapped().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implement sceNgs2GeomResetListenerParam and sceNgs2GeomResetSourceParam,
taken from #1021 and limited to the two reset functions. The Geom
structures get their real layouts. sceNgs2GeomCalcListener and
sceNgs2GeomApply stay unimplemented.
Co-Authored-By: Dextroid17 <217507631+Dextroid17@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#4152 dropped the device-wide module list that #3173 added, so Pipeline
destroys the shader modules right after the library parts are created, also
for the parts prebuilt at registration. AMD's Windows driver still reads
them during the background link-time-optimized link and faults inside
amdvlk64.dll. Hand the modules of each new part to the device's library
store again and destroy them in ClearPipelineLibraries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agc_formatted_buffer_once required two more formatted loads from a known
descriptor to add less than a tenth of the module. The module was mostly
what specialization left of the folded format switch. Without those
leftovers it has 1,219 words and the two call sites add 225, so the
ratio failed while nothing was duplicated.
Require the two extra loads to add fewer words than the first one,
which also brings the shared function. A second copy of that function
per load still fails this check, as well as the function count checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA35943 (Graveyard Keeper 2) imports sceHttp2DeleteCookieBox, which no
library exported. On current main the loader stops the title there:
./eboot.elf: symbol lookup error: ./eboot.elf: undefined symbol:
O9ync3F-JVI
sceHttp2DeleteCookieBox(cookieBoxId) erases the box from the handle
registry sceHttp2CreateCookieBox adds it to and returns 0, or the
library's invalid-id error (0x817B1100) for an id that is not a live
cookie box. The signature follows SharpProspero and the behaviour
prosper's sceHttp2DeleteCookieBox; the error code is listed in
TechnicalDebt as not measured on a console.
guest_http2 deletes a box twice, a context id, a box whose context was
terminated, and the two boxes it uses before sceHttp2Term.
With this change the title loads and runs into its asset loading.
SpecializeSpirv hoists every instruction it folds into a module-level
constant and keeps that constant after its last use has folded away
too. It also leaves each block that a folded branch cuts off as
OpLabel; OpUnreachable. A formatted buffer helper's 77-case format
switch leaves about 1,800 such blocks.
Emit only the folded constants that something still refers to, and
remove OpLabel; OpUnreachable blocks that no instruction names. Merge
and continue targets of live constructs are named, so they stay.
The specialized modules lose words, not behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pre-rasterization and fragment shader libraries were created with an
empty VkPipelineRenderingCreateInfo, so their depth and stencil formats
were VK_FORMAT_UNDEFINED even when the output library had a depth target.
On RADV (RX 7600, Mesa 25.2.8) a fast-linked pipeline built that way hangs
the GPU at its first draw: Epic Mickey loses the device as soon as the
level renders its translucency lighting volume, and with
APS5_NO_PIPELINE_LTO=1 it hangs within seconds of boot. The shader parts
now get the view mask and depth/stencil formats of the draw, and those
formats join their cache key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceVideodec2GetPictureInfo searched the live decoders in handle order and
returned the first one that had ever written the queried frame buffer. When
two decoders wrote the same address, the lower handle answered with stale
timestamps, a stale AVC decoder answered for an HEVC or VP9 picture, a stale
HEVC or VP9 decoder made an AVC query throw, and resetting or deleting the
decoder that wrote the buffer last exposed the older decoder's picture again.
Record the decoder that last published a picture into each frame buffer and
answer from that decoder only. Reset and Delete drop only the decoder's own
records, so another decoder's picture is never removed or brought back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With PA_SC_AA_CONFIG at one sample and MSAA_ENABLE off, the hardware scans
each pixel once and gives every sample of a multisampled color target its
result: radeonsi programs exactly this when GL multisampling is disabled on
a multisampled framebuffer (si_emit_msaa_config leaves sc_aa_config 0), and
GL defines it as single-sample rasterization with full coverage. Such a draw
without depth or alpha-to-coverage now rasterizes the target's sample count
with every sample location at the pixel center, which covers all samples
exactly when the center is covered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guest_sce_net expects sceNetResolverStartNtoa and
sceNetResolverStartNtoaMultipleRecordsEx to fail for
guest-sce-net.invalid. The resolver delegates to the host's
getaddrinfo, which retries a name that is not found with the host's
DNS search domains. On an ordinary home network whose router sets a
search domain that answers any name, the lookup then succeeds as
guest-sce-net.invalid.<search domain> and the test aborts.
Use the absolute name guest-sce-net.invalid. so the host applies no
search domain and the lookup fails on any network.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Minecraft (PPSA17221) fails to load on nine absent imports. The authorized-app
dialog runs the common-dialog state machine without UI and reports the user
as having cancelled, like the login and VR setup dialogs. The other two,
sceNpAuthGetAuthorizedAppCode and the unnamed libSceSaveData.native NID
RvID8BVeg+g, are declared as throwing stubs with unknown signatures.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RetroArch 1.0.0-beta.1 (PPSA99169) stops on main 0f863c10 at
FAIL: unresolved ELF import Nx-F5v0-qU8 from libSceLibcInternal.prx
Nx-F5v0-qU8 is asinl (tools/nid_names.py, aerolib).
The implementation follows the asinl of the libc.prx bundled with
PPSA28319, read from its disassembly: FreeBSD msun's e_asinl.c with
the ld80 invtrig.c coefficients (9f043204), which the binary holds as
the same 80-bit constants, computed in the x87 long double the guest
uses. +-1 returns +-pi/2 rounded to 64 bits and +-0 keeps its sign.
For |x| > 1, the infinities and NaN, Sony's error helper raises
FE_INVALID and sets errno to EDOM before returning (x - x) / (x - x),
so a NaN argument sets EDOM too.
20 million random arguments in (-1, 1) plus the branch edges give the
same bits as that code from the title's libc.prx run on a Windows
host. guest_math pins a set of those bits and checks the domain
errors' errno and FE_INVALID, and that errno is untouched otherwise.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ruffle Flash 1.2.0 (PPSA68091) stops on main 0f863c10 at
FAIL: unresolved ELF import 1t1-JoZ0sZQ from libSceLibcInternal.prx
1t1-JoZ0sZQ is sinhf (tools/nid_names.py, aerolib). RetroArch
1.0.0-beta.1 (PPSA99169) imports it as well.
The implementation follows the sinhf of the libc.prx bundled with
PPSA28319, read from its disassembly: FreeBSD msun's e_sinhf.c as of
5aa554c7 (before __ldexp_expf, so expf(|x| / 2) is squared between
logf(FLT_MAX) and the overflow threshold), calling msun stable/9
expm1f and expf (59bc2f09), whose paths for the arguments sinhf passes
are inlined. Past the threshold Sony's error helper sets errno to
ERANGE before returning x * 1e37; NaN and the infinities return x + x
and leave errno alone.
Running that code from the title's libc.prx on a Windows host gives
the same bits as this implementation for every finite argument up to
the threshold, both signs (2,238,032,378 inputs). guest_math pins a
set of those bits, the overflow errno and FE_OVERFLOW, and checks that
errno is untouched otherwise.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Linux, socket() and sceNetSocket() create AF_INET6 sockets in the
host's default dual-stack mode (net.ipv6.bindv6only is 0), so a wildcard
IPv6 bind also claims the IPv4 port. A title that keeps an IPv4 and an
IPv6 socket on the same port fails: with 0.0.0.0:P bound, binding [::]:P
returns EADDRINUSE (48), and an IPv6 socket bound to [::] also receives
IPv4 traffic from v4-mapped peers.
FreeBSD makes every new IPv6 socket IPv6-only. sys/netinet6/in6_proto.c:
VNET_DEFINE(int, ip6_v6only) = 1;
sys/netinet/in_pcb.c, in_pcballoc():
if (INP_SOCKAF(so) == AF_INET6) {
inp->inp_vflag |= INP_IPV6PROTO;
if (V_ip6_v6only)
inp->inp_flags |= IN6P_IPV6_V6ONLY;
}
and sys/netinet6/in6_pcb.c, in6_pcbbind(), looks for an IPv4 owner of
the port only when the flag is clear:
if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 &&
IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
socket() and sceNetSocket() now set IPV6_V6ONLY on every IPv6 socket
they create. Windows already defaults to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux sscanf, vsscanf, fscanf and sscanf_s hand their conversions to
glibc. Since glibc 2.38, code built as GNU C++ calls the C23 entry
points (__isoc23_vsscanf, __isoc23_vfscanf, __isoc23_sscanf), whose %i
reads a 0b/0B binary prefix. On main,
sscanf("0b101 x", "%i%n%7s", &value, &end, rest) stores value 5 and
end 5 and leaves "x" for the %s; " -0B11" gives -3. TechnicalDebt
records this.
FreeBSD's %i knows only the 0 and 0x prefixes. FreeBSD 11.4
lib/libc/stdio/vfscanf.c, parseint, lines 345-353 and 371-380:
case '0':
if (base == 0) {
base = 8;
flags |= PFXOK;
}
...
/* letters ok iff hex */
case 'A': case 'B': case 'C':
case 'D': case 'E': case 'F':
case 'a': case 'b': case 'c':
case 'd': case 'e': case 'f':
/* no need to fix base here */
if (base <= 10)
break; /* not legal here */
so "0b101" stores 0, consumes the "0" and leaves "b101", as C17 does
and as the strtol family here already does.
With glibc the scanf family now calls __isoc99_vsscanf and
__isoc99_vfscanf, the entry points GNU C++ used before glibc 2.38
(exported since 2.7), which read %i without the binary prefix. Other
hosts keep std::vsscanf and std::vfscanf. The TechnicalDebt entry is
removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revoke a checked arena page with mprotect behind the registry and check
that the stored answer comes back, so the test fails without the cache.
Pass the gpu argument that GuestAllocations::Mutation::Add and Protect
now take.
describePages stored the whole VMA that PROCMAP_QUERY or /proc/self/maps returned. Linux merges adjacent
anonymous mappings with equal flags, so an arena VMA can span GiBs, and the first table miss inside it after
an invalidation rewrote every page of it. Only the pages of the request, rounded out to whole pages, are
stored now, as #1791 describes.
When the guest arena is available, RunUnmappedGapTests checks that a check stores no pages outside its
range, and that protecting and unmapping through the registry clears the stored pages. The registry checks
follow the test on the perf/linux-page-cache branch linked from #1791.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refreshImports and importAllocation drop an import whose registered run changed (a title growing a
heap) while the GpuMutex hold that drops it may still hold a HostImport pointer from before: a
build's in-place region (UploadFinish flushes pending images between regions, and a flush may
reconcile the imports), an indirect argument or a vertex import. The holder went to the batch open
at the drop, or was destroyed at once with the recorder idle, and the map node the pointer names
was erased. A Submit or Sync later in the hold put the command using the old buffer or address into
a later batch, so the import could be freed before that batch ran and the GPU read an unmapped
page (VK_ERROR_DEVICE_LOST; VK_EXT_device_fault reports READ_INVALID inside the dropped import).
The dropped import's map node now moves into its holder (std::map::extract), so those pointers stay
valid, and the holder is kept by every batch the same hold opens (Recorder::KeepThroughHold), so
whatever the hold records with them completes first. Drops for pages the guest releases (the lease
waiter) keep the old lifetime, and the waiter lets go of what an ended hold kept so it does not pin
the ranges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two deterministic, GPU-free checks (mock device) of the import lifetime when refreshImports drops
an import whose registered run grew, while the GpuMutex hold that took the import may still record
with it: with the recorder idle (the import was destroyed at once) and with a Submit between the
keeping batch and the batch that records the use (the import was destroyed when the keeping batch
completed, with the later batch still in flight).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_wstat64, the FindFirstFileExW listing and std::filesystem::file_size
report the size of a file symbolic link itself (0). stat therefore gave
0 for linked files, and APR resolved them as empty, so Unity treated a
linked globalgamemanagers as empty and never read it.
stat takes the size from the handle it already opens, which follows the
link. The APR listing skips reparse points, and names missing from it
are sized through stat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaskedSelectEliminator walked forward from each select to see whether the
lanes its mask excludes are read. Since fda4aa90 the walks pass explicit-LOD
gathers, size queries and guarded image writes, so in a large pixel shader
most of them cover the rest of the program, and the cost grows with the
square of its size (#3504).
The pass now computes, for every value, the set of masks under which it is
unread, from its uses backwards: a use guarded by a condition contributes the
masks that condition implies, a lane-local use adds what its result is unread
under, and any other use contributes nothing. A select is dropped when its
mask is in its own set. Loops are solved as strongly connected components.
The rules for guards, lane-local uses, loops, whole quad masks, selected
execs, rebuilt mask words and guarded first-lane reads are the ones the walk
applied. A first-lane read passes the masks its read mask implies when its
own result is unread outside that read mask, which is solved once per read in
the same way. The walk's step limit, its 64-level implication depth and its
first-lane nesting depth are gone, so a proof no longer depends on the order
of the questions.
recvmsg() reads one byte more than the guest buffers hold to see
whether a record did not fit, but only for SOCK_DGRAM sockets. On a
SOCK_SEQPACKET socketpair a record longer than the buffers was cut
short with msg_flags 0: a 6-byte record received into 2 bytes returned
2 and no MSG_TRUNC, and the rest of the record was gone.
FreeBSD's SOCK_SEQPACKET protocol is atomic, sys/kern/uipc_usrreq.c:
.pr_type = SOCK_SEQPACKET,
...
.pr_flags = PR_ADDR|PR_ATOMIC|PR_CONNREQUIRED|PR_WANTRCVD|
PR_RIGHTS,
and its receive routine, soreceive_generic() in sys/kern/uipc_socket.c,
flags the truncation of an atomic record and drops its rest:
if (m != NULL && pr->pr_flags & PR_ATOMIC) {
flags |= MSG_TRUNC;
if ((flags & MSG_PEEK) == 0)
(void) sbdroprecord_locked(&so->so_rcv);
}
which kern_recvit() hands back in msg_flags. recvmsg() now probes for a
longer record on SOCK_SEQPACKET sockets as it does for SOCK_DGRAM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fflush() passed every stream to glibc's fflush(). For a stream that is
reading, glibc seeks the descriptor back and drops the read buffer, and
with a pushed-back character it gets the position wrong: after fgetc()
returned 'a' from "abcdef\n" and ungetc('Z'), fflush() returned 0 and
the next fread() returned "bcdef\n\n". The pushed-back 'Z' was lost and
the last byte came back twice.
FreeBSD's fflush() writes out only a stream that is writing and leaves
every other stream as it is, lib/libc/stdio/fflush.c:
if ((fp->_flags & (__SWR | __SRW)) == 0)
retval = 0;
else
retval = __sflush(fp);
and __sflush():
t = fp->_flags;
if ((t & __SWR) == 0)
return (0);
fflush() on Linux now returns 0 without calling the host for a stream
whose last operation was not a write (__fwriting() is 0). fflush(NULL)
and streams that are writing are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libkernel's setsockopt() hands options for a libSceNet socket to
sceNetSetsockopt(), whose SO_RCVTIMEO and SO_SNDTIMEO take an int of
microseconds. The POSIX call takes a struct timeval, so main read the
low half of tv_sec as microseconds: {1, 250000} set a 1 microsecond
receive timeout, {0, 500000} set 0, which disables the timeout, and an
out-of-range tv_usec was accepted.
FreeBSD reads a struct timeval, sys/kern/uipc_socket.c, sosetopt():
case SO_SNDTIMEO:
case SO_RCVTIMEO:
...
error = sooptcopyin(sopt, &tv, sizeof tv,
sizeof tv);
if (error)
goto bad;
if (tv.tv_sec < 0 || tv.tv_usec < 0 ||
tv.tv_usec >= 1000000) {
error = EDOM;
goto bad;
}
and sooptcopyin() fails a shorter value with EINVAL:
if ((valsize = sopt->sopt_valsize) < minlen)
return EINVAL;
setsockopt() on a libSceNet socket now converts the timeval for these
two options, with EINVAL for a short value and EDOM for a negative or
out-of-range one. sceNetSetsockopt() keeps taking microseconds; both
paths share the code that sets the host timeout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pthread_once and scePthreadOnce removed their reset handler with a pop
that runs whatever handler is on top of the cleanup stack. An
initializer that pushed a handler of its own and then threw left that
handler on top, so the pop ran it with dangling state. Both now record
the stack top before pushing the reset handler; on a throw they restore
the stack to it and reset the control directly, and a normal return
restores it as well.
Reported in review by Hussein Kadhim, who fixed it the same way.
Co-authored-by: Hussein Kadhim <husseinkad.dev@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Goat Simulator 3 draws a triangle list whose indices are all 0xffffffff
with primitive restart off. Each fetch past a descriptor's records reads
zeros (out-of-bounds select 0 or 1), but the zero-padded vertex copies
were sized to the highest index: 160 GiB, which vkAllocateMemory refused.
When the highest index is at least 64Ki vertices past the largest
per-vertex record count, the draw now uses a copy of its indices with
every index past that count moved to it. Each attribute reads zeros
there as at the original index; only the vertex index the shader sees
changes, so indices closer to the records are left as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
command_output patched subprocess._text_encoding, which does not exist on Python 3.10, so all eight tests errored before their assertions. Use subprocess._text_encoding where available and map io.TextIOWrapper locale to cp1252 on older interpreters, so a subprocess with no explicit encoding decodes as cp1252 on both. Add a behavioral check that default decodes as cp1252 while explicit utf-8 stays correct.
Authored by Rujul Talekar, with Muse Spark 1.3 assistance.
A selection outside loops whose one arm only returns and whose other arm
branches to a join it does not dominate gets an empty block on that edge
as its merge (splitOneReturnJoin), but only when every other predecessor
of the join dominates the selection or is dominated by it. When the
parent selection's other arm is a region that ends in the same join, the
selection has no structured merge and the shader fails to recompile.
When every structurization attempt fails, Structurize now runs them again
and accepts that split for any join the selection does not dominate. The
split only inserts an empty block on the edge from the selection to the
join. Graphs that structurize on main keep their result.
A synthetic CFG and a synthetic program cover the shape; both fail on main
with "conditional block N has no structured merge".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
fcntl and sceKernelFcntl read a third argument as a 64-bit integer for
every command on a non-socket descriptor. F_GETFD and F_GETFL pass no
third argument, and the commands that take one pass an int or a pointer.
They now read nothing, an int or a pointer, by command. An int read as
a 64-bit value kept whatever the caller left in the upper half of the
register, so F_READAHEAD -1 enabled read-ahead instead of disabling it.
ioctl and _ioctl answered EBADF for every descriptor outside the
libSceNet socket table. Decoded call sites: PS5MC (PPSA99420, FIONBIO x3,
FIONREAD x2, TIOCGWINSZ x3), ProsperoTV (PPSA99003, FIONBIO on a pipe
once poll on pipes is in), XPSemu (PPSA97358, 21 sites).
Following FreeBSD 11.4 kern_ioctl (sys/kern/sys_generic.c:758),
vn_ioctl (sys/kern/vfs_vnops.c:1488), pipe_ioctl (sys/kern/sys_pipe.c:1346)
and randomdev_ioctl (sys/dev/random/randomdev.c:301), release/11.4.0
8c639945: FIONBIO sets or clears O_NONBLOCK through the same status
flags as fcntl F_SETFL (EFAULT for a null argument before EBADF);
FIONREAD reports the size past the offset of a file, the queued bytes of
a pipe's read end, 0 on its write end, ENOTTY on the random device, and
throws on a directory or another character device; termios requests
(group 't', TIOCGWINSZ included) fail with ENOTTY, as tcgetattr already
takes these descriptors as not a terminal; any other request throws
naming the request and descriptor kind. Sockets keep their path
unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fcntl and sceKernelFcntl answered EBADF (sceKernelFcntl threw) for
every descriptor outside the libSceNet socket table, so files, pipes,
directories and descriptors 0 to 2 could not be queried or changed.
Observed: Yamagi Quake II (PPSA99007) stops with
"Sys_Error: change stdin to blocking Bad file descriptor", from
fcntl(fileno(stdin), F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY)
(yquake2 src/backends/unix/system.c:81-86).
Predicted from the titles' sources and decoded call sites: XPSemu
(PPSA97358, 97 fcntl sites: F_GETFD 12, F_SETFD 20, F_GETFL 8,
F_SETFL 9, F_DUPFD 1, F_DUPFD_CLOEXEC 7, F_GETLK 9) fails in
qemu_init_main_loop, where its pipe2 (pipe plus F_SETFD FD_CLOEXEC) and
glib's g_unix_set_fd_nonblocking (F_GETFL, F_SETFL) build the event
notifier (util/event_notifier-posix.c:44-67); VLC (PPSA85300, 43 sites,
F_RDAHEAD on every file it opens), RetroArch (PPSA99169, 17),
Porpoise (PPSA99764, 13) and Ruffle (9) call it on files and pipes.
The commands follow FreeBSD 11.4 kern_fcntl and kern_dup
(sys/kern/kern_descrip.c:479-799, 812, release/11.4.0 8c639945):
F_GETFD/F_SETFD keep the close-on-exec flag per descriptor, F_GETFL
reports the host access mode with the status flags, F_SETFL changes
only O_NONBLOCK, O_APPEND, O_ASYNC, O_FSYNC, O_DIRECT and FRDAHEAD,
F_DUPFD/F_DUPFD_CLOEXEC give the lowest free descriptor at or above the
start through dup (EINVAL for a negative start), F_DUP2FD goes through
dup2, F_RDAHEAD/F_READAHEAD set FRDAHEAD on files (EBADF on pipes,
ENOTTY elsewhere), and unknown commands fail with EINVAL. Status flags
live in a guest table shared by dup, dup2 and F_DUPFD like an open file
description. O_NONBLOCK switches a pipe (PIPE_NOWAIT on Windows, where an
empty read and a full write now report EAGAIN; the host flag on Linux).
Record locks use the host POSIX locks on Linux and throw on Windows.
Changes the host cannot model throw naming the flag and descriptor kind.
Sockets keep their path unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Probe an optional provider before loading it and skip only direct file or
path absence. A present DLL with a missing dependency must report the
original loader failure instead of being treated as an absent provider.
Check the generated PE on every host: each optional provider's path
reference must reach a GetFileAttributesW call through its IAT slot whose
success branch continues to the LoadLibraryExW call.
Extend the native-provider regression with a generated DLL that imports
a missing dependency, while retaining the successful absent-file case.
pr_overlap only skipped a pair when one pull request named the other
directly. In a stack where #3 depends on #2 and #2 on #1, #1 and #3 were
reported as overlapping, and two pull requests built on the same #1 were
reported for the files and exports both inherit from it. Dependencies are
now followed through open pull requests, and what a shared dependency adds
is left out of the same-file and same-export columns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189deyapHNNqKQJboZBJQei
Draws 32_ABGR exports into a tiled and a linear format 17 target and compares every texel with
the layout X in bits 0-4, Y in 5-9, Z in 10-14, W in bit 15, including the X/Z-swapped pairs, rounding
edges and texels outside the scissor. Reverting the X/Z exchange fails it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A frame presented from guest memory with APS5_DUMP_FRAMES is read back
at the size of the display buffer, and that whole copy went into the
dump writer's queue with the scale beside it (31.6 MiB for a 3840x2160
buffer). The queue holds 64 MiB, so the third frame the writer had not
finished ended the run with "frame dump queue exceeded 64 MiB". A frame
blitted from the resident image is already reduced on the GPU and queued
at the reduced size.
The readback is now reduced by the dump scale when the slot retires and
queued at that size, the same pixels the writer kept before. With
APS5_DUMP_SCALE=1 nothing is reduced and the limit is reached as before.
video_out_frame_dump_queue presents four 3840x2160 frames from guest
memory while the writer is held on the first file and compares every
dump with the decoded display buffer.
Direct memory can already be mapped more than once, so there is no
aliasing to enable and the call returns 0. ProsperoEden (PPSA99008)
imports it and calls it before mapping the same direct memory again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agc): keep the driver's per-thread scratch out of thread_local teardown
With MinGW's emulated TLS, a thread_local with a destructor in a DLL is
destroyed after its storage is gone: when a std::thread ends, winpthread
runs libgcc's emutls key destructor, which frees the thread's thread_local
storage, and only then does the loader's DLL_THREAD_DETACH run the DLL's
tls_atexit list, so the destructors run on freed memory. The queue worker
touches four of them in libSceAgcDriver (the dispatch-ahead map, the
pending waits, the draw-snapshot compare vectors and the image decode
memo), and Driver::stop's join of the worker then corrupts the heap.
Under cdb's checking heap agc_driver_draw_cache_reuse stopped at the
worker's exit with heap corruption in all 127 runs, with graphics
pipeline libraries, with APS5_NO_PIPELINE_LTO=1 and with APS5_NO_GPL=1.
Without it the damage shows intermittently, as exit 0xc0000374 (2 of 143
runs with a cold NVIDIA shader cache) or as a fault in whatever allocates
next, such as NVIDIA's compiler on the optimized-link thread (#2980).
These objects now live in HostThreadLocal, fiber-local storage that owns
them, as the recorder's buffers do since e424b6b7b.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(libs): keep thread_local objects with destructors out of module teardown
The teardown order behind the driver's heap corruption holds in every
module: with MinGW's emulated TLS a thread's thread_local storage is freed
before the module's DLL_THREAD_DETACH runs its thread_local destructors,
so they run on freed objects. PreciseSleepUs's timer (used, among others,
by the video-out vblank thread) then closes a stale handle; libkernel's
directory path, socket waker and UUID random device, libSceRandom's random
device and libScePosixForWebKit's random device and host lookup free or
close stale objects. They move to HostThreadLocal. The other thread_locals
in the modules are trivially destructible or only built for Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(libs): fail when a module registers a thread_local destructor
prx_thread_local_destructors lists the symbols of every built module and,
where MinGW's thread_local destructor support is linked in, disassembles
the module for calls to __cxa_thread_atexit outside libc's registration of
guest destructors. A module that keeps a thread_local with a destructor
fails it with the function that registers one: with main's libSceRandom
it reports sceRandomGetRandomNumber, and with this branch all 122 modules
pass, libc being the only one that links the support.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agc): keep the image memo behind its pointer
The image decode memo went into HostThreadLocal as the whole array. On
Linux, where HostThreadLocal is a plain thread_local, that put 1024 memo
entries into every thread's TLS block instead of one pointer. It keeps
its unique_ptr as before, now owned by HostThreadLocal, so Linux builds
the same thread_local as main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dean Galvin <dean.galvin@live.com>
agc_shader_replay compiles a request through Recompile, the per-draw path.
Shaders the driver prepares at registration go through PrepareShader
instead and produce a different, larger artifact, so a failure or the size
of that artifact could not be reproduced from the dumped request.
--prepare compiles the request with PrepareShader and prints the artifact's
SPIR-V size, or the first line of the failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A device replaced at AttachWindow keeps its host imports in
HostImports::parked until it is destroyed, and each parked import keeps
its guest ranges pinned. A guest munmap, mprotect or free of such a range
spun in the pin waiter until APS5_PIN_WAIT_MS and then failed, because the
waiter only looked at the live imports. The replaced device was already
waited idle by PrepareForReplacement, so the waiter now drops the parked
imports over the range.
agc_parked_import_unmap imports a range on one device, again on a second
one, and unmaps it with a 3 s pin wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain weak identities and the full failed span so a replacement of either owner or a shorter remaining run retries the import. Keep unchanged failures cached across unrelated mapping changes without pinning their allocations. Existing upstream import lifetime, span windows and Windows fallback remain in place. AI-assisted implementation.
Capture the request once before rethrowing from the shared handler. Standard exceptions keep the shader address, stage, optional dump filename and full message; non-standard exceptions remain unchanged after capture.
AI-assisted: yes.
PrepareShaderWithDiagnostics dumped the request but rethrew the raw error, so a failure never said which shader it came from. Wrap it with the shader address and stage, keeping the dump. AI-assisted: yes.
FailureCapture now requires the code address and stage in the diagnosed failure, with and without the request dump. Fails before the wrap, passes after. AI-assisted: yes.
A short fs-prefixed TLS access is patched with a 5-byte jump by
relocating the following instructions into the stub, but any branch
there was rejected outright. NHL 27 hits this on every stack-protector
epilogue: fs:xor rax,[rbx] immediately followed by jne __stack_chk_fail.
Allow relative conditional branches and short jumps: re-encode rel8
forms as their rel32 equivalents, rebase the displacement when the
stub is emitted, and keep rejecting loop/jrcxz, calls, returns, and
segment-prefixed followers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readlink threw for every Windows reparse point, including the links
symlink creates. Read the target of a relative symbolic link from its
reparse data and return it with forward slashes. Absolute targets,
junctions and other reparse points still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vulkan's standard 8x sample 7 is (0.9375, 0.0625), (7, -7) in signed sixteenths; the table had (7, -8), so the
standard pattern (PPSA99902's PA_SC_AA_SAMPLE_LOCS words 0xbd153fd1 / 0x9773f95b) was refused as nonstandard.
Regression row with those words.
Reported by @Amoenus.
Co-Authored-By: Amoenus <5391396+Amoenus@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 9a1a0672cac0f8e3dcaa6e28cbe6b7b684a40a1f)
Resolves NID touqMEt6qXQ, the last import Trails in the Sky 2nd Chapter
(PPSA35169) is left missing on libSceAmpr.prx once the libkernel pair in #4214
is recorded. No reference describes the function's contract, so it throws via
the project's NotImplemented_nid_no_patch pattern rather than guessing a result
that a caller would act on.
sceAmprAmmGetUsageStatsData (KqiWXLgCVe0) is deliberately not included: #3814
exports it and #4207 answers it from a cited layout.
See issue #4214.
A --windows-gui build started from Explorer, or any Windows executable
started without a standard output or error handle, lost everything it
printed: the entry stub's messages, the libraries' stdout and stderr,
uncaught exception messages and crash reports.
Before it enters its directory or loads a library, the entry stub now
checks the standard output and error handles, and gives each missing one
anyps5.log beside the executable. The C runtime of the libraries it then
loads adopts the file. Executables started with handles are unchanged,
and one that cannot create the file starts as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's printf formats %m as the message for the errno value at the
time of the call. On Linux a format with %m went to glibc, which looks
the number up in Linux's table, so guest errno values above 34 got
another error's text: with errno 35 (EAGAIN) "[%m]" printed
"[Resource deadlock avoided]" instead of "[Resource temporarily
unavailable]", and errno 63 (ENAMETOOLONG) printed "Out of streams
resources". On Windows, and on Linux when the format also had a wide
argument, the guest formatter threw on %m.
FreeBSD 11.4, lib/libc/stdio/vfprintf.c:
saved_errno = errno;
...
case 'm':
cp = strerror(saved_errno);
size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp);
sign = '\0';
break;
The guest formatter now saves errno when it starts and formats %m as a
string with the guest strerror_r text, so width, precision and the -
flag apply as FreeBSD's string path applies them. On Linux a format with
%m goes to the guest formatter like one with a wide argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
realloc(NULL, 0) returned NULL without allocating anything, while malloc(0) returns a unique pointer. C11 7.22.3.5p3 says realloc with a null pointer behaves like malloc for the specified size, so code that grows an empty buffer from zero elements (p = realloc(p, n * size) with n == 0) and treats NULL as out of memory failed on its first call.
FreeBSD 11.4 never returns NULL from realloc for a zero size; je_realloc rounds the size up before choosing between resizing and allocating (contrib/jemalloc/src/jemalloc.c, je_realloc).
A null pointer with size 0 now reaches the allocator's realloc, which allocates as malloc(0) does. A non-null pointer with size 0 is still freed and NULL returned, as before.
Co-authored-by: milescoviello <kitslayer29@gmail.com>
The packed integer ops (v_pk_add/sub/mul_lo/mad/min/max/shift) read their
16-bit sources without the VOP3P negate bits. Hardware negates a 16-bit
source by flipping bit 15 for integer ops too, and Sony's compiler relies
on it: UE5's TSR history update clamps its -1 neighbour offsets with
v_pk_max_i16 v14, neg(lit 0x7fff), v14, i.e. max(-1, minY - y). Without
the modifier that became max(0x7fff, x), the x-1/y-1 taps of the 3x3
input filter read out of bounds as zero, and the history lost ~16 % of
its energy every frame (Sword of the Sea's missing glow).
The new execution test covers every packed integer op with neg_lo and
neg_hi, op_sel interplay, clamp and the TSR literal case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013fa2MbmdFLVr8YHuVjucHE
vsyslog formats the message with the host's vsnprintf on Linux, so a
%ls, %S, %lc or %C argument is read as glibc's 32-bit wchar_t instead
of the guest's 16-bit units. glibc fails the conversion and the message
is dropped: on main syslog(LOG_DEBUG, "wide %ls %d %S", u"guest", 5,
u"text") writes "[syslog:15] " with nothing after it. Windows already
formats syslog messages with the guest formatter, and the narrow printf
family does so on Linux for formats with a guest wide conversion.
FreeBSD 11.4 lib/libc/gen/syslog.c line 229 formats the message with
(void)vfprintf(fp, fmt, ap);
and lib/libc/stdio/vfprintf.c (lines 831-843) converts %S and %ls from
the guest's wchar_t:
case 'S':
flags |= LONGINT;
/*FALLTHROUGH*/
case 's':
if (flags & LONGINT) {
wchar_t *wcp;
...
convbuf = __wcsconv(wcp, prec);
vsyslog now formats through the guest's vsnprintf, which hands formats
with a guest wide conversion to the guest formatter (UTF-8 output, as
for printf) and every other format to glibc as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wide formatter behind swprintf, vswprintf, wprintf and snwprintf_s
does not know the ' (thousands grouping) flag: it takes ' for the
conversion and fails, so on main swprintf(buffer, 32, L"%'d", 1234567)
returns -1 with errno EINVAL and an empty buffer. The narrow guest
formatter accepts the flag since #3567.
FreeBSD 11.4 lib/libc/stdio/vfwprintf.c takes it as a flag
(lines 613-615):
case '\'':
flags |= GROUPING;
goto rflag;
and grouping_init (lines 129-147) adds separators only while
*gs->grouping != CHAR_MAX. In the C locale, the only one the guest's
setlocale accepts, lib/libc/locale/lnumeric.c (lines 44-50) has
static char numempty[] = { CHAR_MAX, '\0' };
...
"", /* thousands_sep */
numempty /* grouping */
so the flag prints the number unchanged. The wide formatter now skips
' with the other flags before it hands the conversion to the host's
snprintf, as the narrow one does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wide formatter behind swprintf, vswprintf, wprintf and snwprintf_s
does not parse %n$ or *m$. It reads the position digits as a field
width, takes '$' for the conversion and fails, so on main
swprintf(buffer, 32, L"%2$d-%1$d", 1, 2) returns -1 with errno EINVAL
and an empty buffer. Localized format strings that reorder their
arguments this way print nothing.
FreeBSD formats wide output with __vfwprintf (vswprintf.c line 79),
which takes positions exactly as the narrow vfprintf does. FreeBSD 11.4
lib/libc/stdio/vfwprintf.c lines 635-652:
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
n = 0;
do {
n = 10 * n + to_digit(ch);
ch = *fmt++;
} while (is_digit(ch));
if (ch == '$') {
nextarg = n;
if (argtable == NULL) {
argtable = statargtable;
if (__find_warguments (fmt0, orgap,
&argtable)) {
and GETASTER (lines 509-531) reads a *m$ width or precision from
position m without moving nextarg.
The wide formatter now numbers its arguments with the narrow
formatter's NumberedArgumentClasses and ArgumentPosition, run over an
ASCII copy of the format, so the same FreeBSD rules apply: a position
selects the argument, *m$ does not move it, and a conversion without a
position takes the argument after the previous one. Formats without a
position are formatted as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fopen and freopen leave an "a+" stream where the host CRT puts it, at
the start of the file. On main, after fopen(file, "a+") on a 7-byte
file, ftell returns 0 and the first fgetc returns the first byte of the
file. FreeBSD seeks every append-mode stream to the end when it opens
it, so ftell returns 7 and a read before any fseek returns EOF. Code
that opens a log or save file with "a+" and tests ftell(f) == 0 to see
whether the file is new treats every existing file as empty.
FreeBSD 11.4 lib/libc/stdio/fopen.c (fopen), and the same lines in
lib/libc/stdio/freopen.c (freopen):
if (oflags & O_APPEND) {
fp->_flags2 |= __S2OAP;
(void)_sseek(fp, (fpos_t)0, SEEK_END);
}
lib/libc/stdio/flags.c sets O_APPEND for every mode that starts with 'a':
case 'a': /* open for appending */
ret = __SWR;
m = O_WRONLY;
o = O_CREAT | O_APPEND;
fopen and freopen now seek a stream opened with an 'a' mode to the end
of the file, ignoring a failed seek as FreeBSD does. Writes still go to
the end through O_APPEND.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rosetta 2 ends the process when an unaligned 256-bit store crosses into
a page the store may not write ("rosetta error: unexpectedly need to
EmulateForward on a synchronous exception") instead of raising the
fault. A store inside such a page, and a 128-bit store across one,
fault normally. The macOS write watch keeps watched pages read-only
between collections, so a guest that stores 32 bytes across the start
of a watched page ends the process.
--to-rosetta now lowers VMOVUPS, VMOVUPD and VMOVDQU with a 256-bit
memory destination to a stub that stores the low half with the same
instruction at VEX.L=0 and the high half with VEXTRACTF128 at the
operand plus 16, so each half faults like any other store. RIP-relative
operands are relocated from the instruction's place in the site. A
store whose upper half has no 32-bit displacement fails the conversion.
Aligned stores (VMOVAPS, VMOVAPD, VMOVDQA and the non-temporal forms)
cannot cross a page and are left alone.
Every store is lowered. A 4-byte store has no room for the jump to its
stub, so its stub also takes one neighbouring instruction. The
following one: a return ends the stub, a call pushes its original
return address and jumps, a jump or conditional branch is re-encoded
with a 32-bit displacement, and another 256-bit store is lowered in the
same stub. Otherwise the preceding one, unless it is a call, a jump, a
return or a trap. Register calls, LOOP, JRCXZ and branches with a
prefix that changes them do not move. When neither neighbour can move,
the store becomes a short jump to an island: a run of movable
instructions within reach is moved to its own stub, and the bytes it
frees hold the jump to the store's stub. A store with neither fails the
conversion.
The converter tests compare the halves with clang's encodings for base,
SIB, disp8 overflowing to disp32, disp32, RSP-relative, FS-absolute,
r13-based and RIP-relative operands, and check that aligned stores,
loads, register forms and masked stores are not split. They check the
stub of a short store followed by a return, a call (direct, with an
address-size prefix, and through a RIP-relative pointer), short, near
and RIP-relative jumps, short and near conditional branches and another
store; that a following register call, LOOP, JRCXZ, prefixed branch or
CLWB makes the store take the instruction before it; islands past
instructions already moved, a store that needs its own stub, a branch
target, a register call and the short-jump reach; and that a store with
no room fails.
PPSA12544: 55295 stores split in the executable, 278 of them in the
stub of the store before them; every module converts with none kept.
AI-assisted: yes
guest_avplayer_tests imports __cxa_throw from libc.prx, which is loaded
before libstdc++, but -static-libgcc gave it a private copy of
_Unwind_Resume. A failed Check threw through libc.prx's unwinder and the
landing pad of the test function resumed with the private one, so on
Linux every failure aborted in _Unwind_RaiseException_Phase2 instead of
printing its message from main. This is the unexplained Linux abort in
the intermittent guest_avplayer failures.
The test is now linked without the static runtime flags, as the AgcDriver,
Agc and VideoOut tests already are, so the unwinder comes from the same
library as __cxa_throw. A forced Check failure in TestPlayback aborted
before and now prints its message and exits with 1.
(cherry picked from commit fa5199cd8fef7f9c7ab9e83a441e15d16364a81d)
In wave32 a compare that wrote vcc_lo kept only the ballot word and rebuilt the
lane's bit from it as (word >> lane) & 1, and every read of vcc_lo as a lane
mask (v_cndmask, carry-in) did the same. s_*saveexec_b32 and the 32-bit scalar
logic ops and s_mov_b32 that write exec_lo or vcc_lo also rebuilt the lane bit
from the new word. Wave64 already kept the lane bits.
Every writer keeps Vcc equal to the lane's bit of VccLo and Exec equal to the
lane's bit of ExecLo, so in wave32:
- a compare into vcc_lo sets Vcc to the compared value, and reading vcc_lo as
a mask returns Vcc;
- s_*saveexec_b32 combines the old Exec with the source's lane bit, and tags
the saved mask in the destination SGPR with the old Exec;
- s_and/or/xor/not/andn2/orn2/nand/nor/xnor_b32 and s_mov_b32 that involve
exec_lo or vcc_lo combine the sources' lane bits the way sU64Mask does for
the 64-bit forms, and tag an SGPR destination.
The words are still computed as before for scalar reads, SCC and branches;
where nothing reads them they are dead.
agc_driver_wave32_exec_lane_bits runs a wave32 program with v_cmp into vcc_lo,
v_cndmask, s_and_saveexec_b32 and exec restores through s_or_b32 and s_mov_b32,
checks every lane, and checks that its SPIR-V has no subgroup ballot left.
UploadPrepare sorted the build's Region objects themselves, each a few
hundred bytes of vectors and shared pointers, and grew the merged list
without reserving it, moving every region many times per resource build.
Sorting an index array and reserving the merged list moves each region
once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
On GFX10.3 the DEPTH field of a 1D or 2D non-array linear T# carries the
row pitch minus one (Mesa's ac_descriptors.c sets it for custom pitches,
which must be multiples of 256 bytes). A single-level, uncompressed 2D
linear texture whose DEPTH equals its default 256-byte-aligned pitch now
decodes as that 2D texture; any other pitch still throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fdopen only took a fixed list of 15 mode strings and failed with EINVAL
for any other, so "rt", "r+t", "re", "rbe" or "rw" gave NULL. FreeBSD's
fdopen parses the mode with __sflags (lib/libc/stdio/flags.c): after
r, w or a it takes b, +, x, e and v and stops at the first other
character ("default: known = 0;"), and only a bad first character or x
on a read-only mode fail with EINVAL. fopen and freopen already parse
modes this way through ParseFileMode (#2033).
fdopen now uses ParseFileMode as well and passes the host its normalised
mode without x, which FreeBSD's fdopen ignores.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gai_strerror returned its own wording for most codes, so a resolver
failure was described with text FreeBSD's libc does not use: EAI_NONAME
(8) gave "Name or service not known" instead of "hostname nor servname
provided, or not known", EAI_BADFLAGS (3) "Invalid flags" instead of
"Invalid value for ai_flags", and an unknown code "Unknown address
resolution error" instead of "Unknown error". Twelve of the fifteen
texts differed.
FreeBSD 11.4, lib/libc/net/gai_strerror.c:
static const char *ai_errlist[] = {
"Success", /* 0 */
"Address family for hostname not supported", /* 1 */
"Temporary failure in name resolution", /* EAI_AGAIN */
"Invalid value for ai_flags", /* EAI_BADFLAGS */
"Non-recoverable failure in name resolution", /* EAI_FAIL */
"ai_family not supported", /* EAI_FAMILY */
"Memory allocation failure", /* EAI_MEMORY */
"No address associated with hostname", /* 7 */
"hostname nor servname provided, or not known", /* EAI_NONAME */
"servname not supported for ai_socktype", /* EAI_SERVICE */
"ai_socktype not supported", /* EAI_SOCKTYPE */
"System error returned in errno", /* EAI_SYSTEM */
"Invalid value for hints", /* EAI_BADHINTS */
"Resolved protocol is unknown", /* EAI_PROTOCOL */
"Argument buffer overflow" /* EAI_OVERFLOW */
};
...
if (ecode >= 0 && ecode < EAI_MAX)
return ai_errlist[ecode];
return "Unknown error";
The table and the unknown-code text now match, as 5830d8e44 did for
strerror.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
socket only took a type of exactly SOCK_STREAM or SOCK_DGRAM, so a type
with SOCK_NONBLOCK (0x20000000) or SOCK_CLOEXEC (0x10000000) set failed
with EPROTONOSUPPORT, although socketpair already accepted both.
FreeBSD strips both flags before creating the socket (sys/kern/
uipc_syscalls.c, kern_socket: "if ((type & SOCK_CLOEXEC) != 0) { type
&= ~SOCK_CLOEXEC; oflag |= O_CLOEXEC; }" and the same for SOCK_NONBLOCK
with FNONBLOCK), and makes the new socket non-blocking with
"fo_ioctl(fp, FIONBIO, &fflag, ...)".
socket now does the same as socketpair: it creates the socket from the
plain type and, with SOCK_NONBLOCK, switches it to non-blocking.
SOCK_CLOEXEC needs nothing more, since a guest never executes another
program.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mbstowcs, and libSceAmpr's copy of it, counted at most n characters
when the destination was NULL, so the usual sizing call
mbstowcs(NULL, s, 0) returned 0 and mbstowcs(NULL, s, 1) returned 1 for
a three-character string. A caller that sizes its buffer that way got a
buffer too small for the string and no terminator.
POSIX says that with a null pwcs mbstowcs "shall return the length
required to convert the entire array regardless of the value of n", and
FreeBSD does that: lib/libc/locale/mbstowcs.c passes SIZE_T_MAX as the
source limit, and the C locale's _none_mbsnrtowcs (none.c) returns the
distance to the terminating NUL when "dst == NULL".
With a null destination, SonyMbstowcs now returns the length of the
whole string.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EmitImage selects the table slot of a bindless image before it branches over
the image's runtime modes, and EmitImageMode then searched the table again in
every mode arm. Each search is a binary search of dependent flattened_srt
loads, so every bindless sample ran two. The mode arms now take the selection
EmitImage already made; the modes copy the base image, so the mapping offset,
root and iteration count the search reads are the same.
Kart PS5 (PPSA99611) imports __divti3 and stops at start-up on main. The guest has no signed 128-bit division; add it alongside __udivti3, dividing the magnitudes and negating the quotient, and throwing on a zero divisor like the unsigned routine.
SaveDataDirNameSearchResult.infos points at an array the title reads one
entry per returned directory name, but the search never wrote it, and
SaveDataSearchInfo was declared as 128 opaque bytes. A title that asks for
the infos read whatever its buffer held.
SaveDataSearchInfo is now the 48-byte record shadPS4 declares as
OrbisSaveDataSearchInfo (blocks, free blocks, 32 reserved bytes), and the
search fills one per returned name. Every native save reports
SAVE_DATA_BLOCKS_MAX blocks, as sceSaveDataGetMountInfo and the block sort
keys already assume, so both counts are SAVE_DATA_BLOCKS_MAX and the
reserved bytes are zero.
guest_savedata_search_tests checks the two records, their zero reserved
bytes and that the search writes nothing past them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFVE5WbaUJ2vyj8NAW44CS
OpenOrbis create-fself adds one to the symbol index of every relocation it
copies from .rela.dyn, so each R_X86_64_RELATIVE entry of a homebrew built
with the OpenOrbis toolchain names symbol 1, a local STT_SECTION symbol with
value 0. FreeBSD rtld computes RELATIVE as base + addend without reading the
symbol. The pipeline, the code analysis and the strict filter now accept
index 0 or a STT_SECTION symbol with value 0, and still throw for any other
symbol. The relocation is written with symbol index 0, where the pipeline
previously dropped RELATIVE entries with a nonzero index.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
std::random_device in libstdc++ reads RDSEED on x86 Linux. On an AMD
EPYC 9V45 (Zen 5) CI runner its 32-bit results repeat about 130 times
in 65536 draws, so guest_posix_random fails. libc now exports
HostEntropy_nid_no_patch, which uses getentropy (rand_s on Windows) and
throws when the OS cannot provide entropy. arc4random, arc4random_buf,
sceRandomGetRandomNumber, /dev/random and /dev/urandom reads,
std::_Random_device and sceKernelUuidCreate use it, and the arc4random
and random device TechnicalDebt entries say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture read lane 63 of v0, which the program never writes. Since #3546 a wave64 vertex program on a narrower host subgroup builds that read as 0, so the synchronous registration the test expects to fail succeeds. v5 holds the vertex index, which is not 0 in lanes 32-63, so the read still throws.
On Windows agc_write_tracking has failed since #3594 with
write tracking test failed: an unwatched range reads as not stored over
from the check in CheckSharedBlock:
if (!Watched(outside, unwatched.size())) Require(StoredOver(outside, unwatched.size(), TrackerGeneration()), "an unwatched range reads as not stored over");
The host stack array lies in a host hole of the guest arena. #3594 made
Watched, collectWrites, UnchangedSince, UnchangedSinceAll and storeOwn
reject ranges overlapping such holes, but StoredOver, MarkWritten,
WrittenSince, ChangedBlocks and ImportWatched still accepted them through
WriteTracker::covers(), so StoredOver proved a range not stored over while
Watched called it unwatched (#4087).
The exclusion now lives in WriteTracker::covers() on Windows, which every
query already asks, and the per-function checks are removed. Host holes
are padded to the allocation granularity and the tracker blocks are 64 KiB
from the arena base, so no block is shared between a hole and arena
memory, and ranges outside the holes get the same answers as before.
A new case enumerates the arena for a host hole and checks every query
against it; on the previous code StoredOver, MarkWritten, StoreOwnBytes,
WrittenSince and ChangedBlocks fail it.
Fixes#4087
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On a PS5 (firmware 13.60) scePthreadJoin on the calling thread returns
EDEADLK and scePthreadCreate with a zeroed attribute creates a thread
with the default attributes. Here both threw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program that uses v_movrels_b32 or v_movreld_b32 keeps every vector
register from the lowest relatively addressed one up in a per-invocation
window array, so its plain register accesses become LoadVectorWindow and
StoreVectorWindow. The half-wave reduction matcher followed only SSA values,
so a wave64 scan, v_permlanex16_b32 join and lane 31/63 read through that
array threw that lane 63 is outside the 32-lane host subgroup.
The matcher now reads a window load at a constant slot as the value of the
last store to that slot earlier in its block, unless a store at a runtime
slot lies in between; each invocation owns its array, so that is the value
the load returns. A store in another block, or a v_movreld_b32 at a runtime
M0 in between, still throws. This was the only refused lane 32-63 read left
among Ghost of Yotei's wave64 graphics programs (one pixel program).
On a PS5 (firmware 13.60) the SCE mutex attribute calls return EINVAL
for a null or uninitialized attribute, scePthreadMutexattrSetprotocol
accepts 0 to 2, scePthreadMutexInit takes an uninitialized attribute as
the default one, and destroying a destroyed mutex returns EINVAL. Here
these threw, including for the priority protection protocol.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the tracker initialization fix without an explanatory code comment or technical-debt entry, as requested in review.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On a PS5 (firmware 13.60) the scePthreadAttr setters and getters return
EINVAL for a null or uninitialized attribute and for a null output,
ENOTSUP for a policy other than SCHED_FIFO and SCHED_RR, an inheritance
other than explicit and inherit, and a priority outside 256 to 767, and
EINVAL for an invalid detach state. Here they threw, or stored any
policy, inheritance and priority.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows path from #3788 changed the protection of any committed private host region outside the guest registry, including a range crossing a guest stack's lower bound. Ranges inside a live guest thread stack keep the path from #2982.
sceAgcDcbSetWorkloadsActive writes an 18-dword IT_NOP packet (stream id, 64-bit workload mask) and sceAgcDcbSetWorkloadComplete a 12-dword one (stream id, workload id, 64-bit clear mask), following KytyPS5. Both return null for a null buffer, an unregistered or out-of-range stream, a workload id above 63, and for active a duplicate id or a count of 0 or above 63. sceAgcDriverRegisterWorkloadStream and sceAgcDriverUnregisterWorkloadStream now record the stream ids the DCB calls check.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
sceAgcDcbSetWorkloadsActive and sceAgcDcbSetWorkloadComplete threw. They now write a plain two-dword NOP and keep no workload state.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On a PS5 (firmware 13.60) a user with every accessibility setting at
its default reads chat transcription 0, press-and-hold delay 0, trigger
effect 1, vibration 1, zoom 0 and zoom follow focus 1; all six read 0
here. A user who is not logged in returns 0x80960009 with or without an
output, as sceUserServiceGetAgeLevel does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA33016 imports sceNpWebApi2AddWebTraceTag (NID MgsTa76wlEk), and no
library exported it, so the loader stopped before the title started.
The title does not call it at start-up or in play, so the export throws
like the other unimplemented functions.
A title creating a socket with sceNetSocket sets its options with the BSD
setsockopt, which looked the descriptor up only among libkernel's own
sockets and returned EBADF. libSceNet now registers an option setter with
libkernel, as it does for close and select, and accepts SO_REUSEADDR,
SO_BROADCAST, SO_SNDBUF and SO_RCVBUF like libkernel's own sockets.
PPSA01289 (Sackboy: A Big Adventure) sets SO_BROADCAST on its UDP P2P
socket this way in UIpNetDriver::InitBase. The failure made every listen
fail, and the engine travelled back to its default map with ?closed,
reloading it about every two seconds behind the loading screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libSceNet stored its errno only in its own slot (sceNetErrnoLoc). Titles
that use the BSD socket model read errno after a libSceNet call, as the
console's libSceNet calls are kernel calls that set it; shadPS4 mirrors its
net errno into the kernel errno the same way (SetKernelErrnoHook).
PPSA01289 (Sackboy: A Big Adventure) reads errno through Unreal's
GetLastErrorCode after sceNetRecvfrom. It never saw EWOULDBLOCK, so
UIpNetDriver::TickDispatch kept receiving and the game thread never left
its first networked frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A libSceNet socket is a kernel descriptor on the console, so titles close it
with close(). libSceNet keeps its sockets in its own table, and libkernel's
close passed their numbers to the CRT, which reported an invalid parameter
and left the socket open. libSceNet now registers a closer with libkernel,
as it registers its poller for select. PPSA01289 (Sackboy: A Big Adventure)
closes its P2P socket this way each time it tears its network session down,
leaking one open socket per attempt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceNetEpollControl used Linux's EPOLL_CTL numbering (1 ADD, 2 DEL, 3 MOD).
The SCE values are 1 ADD, 2 MOD, 3 DEL (shadPS4 net_types.h,
ORBIS_NET_EPOLL_CTL_*). A title removing a socket with op 3 and a null
event got EINVAL, and op 2 deleted the registration it meant to modify.
PPSA01289 (Sackboy: A Big Adventure) removes its UDP socket from its epoll
this way before closing it, every time its network session is torn down.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a PS5 (firmware 13.60) a null name returns INVALID_ARGUMENT first,
then a user id the console does not know returns 0x80960105 whatever
the buffer, and a buffer shorter than 17 bytes is zeroed before
BUFFER_TOO_SHORT is returned. Here an unknown user returned
INVALID_ARGUMENT and a short buffer was left untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
difftime and libc_difftime passed their arguments to the host CRT.
UCRT's _difftime64 returns 0 and sets errno to EINVAL when either
argument is negative (common_difftime in ucrt/time/difftime.cpp), so
on Windows difftime(0, -1) returned 0 instead of 1 and every
difference involving a time before 1970 was 0. C17 7.27.2.2 defines
the result as time1 - time0, and FreeBSD 11's difftime
(contrib/tzcode/stdtime/difftime.c) and glibc on the Linux build
compute it for any arguments.
Subtract the smaller time from the larger as uint64_t, which cannot
overflow, convert the difference to double and negate it when time1
is the smaller one, as FreeBSD and glibc's fallback path do. The
result is time1 - time0 rounded once to double on both hosts.
difftime now forwards to libc_difftime like the other aliases in the
file.
Document --help output and status, distinguish no-argument errors, and include help in the successful exit code description.
AI-assisted: yes (OpenAI Codex).
asctime passed the guest tm to the host CRT. UCRT's asctime returns
null and sets errno to EINVAL when tm_year is negative or tm_mday is
past the end of the month (common_asctime_s in ucrt/time/asctime.cpp),
so on Windows asctime of any time in the years 1000 to 1899, which
gmtime converts, or of February 29 in a common year returned null.
C17 7.27.3.1 defines the text for every year from 1000 to 9999 when
each member is in its normal range (tm_mday 1 to 31), and glibc on
the Linux build and FreeBSD 11's asctime
(contrib/tzcode/stdtime/asctime.c) print it.
Format the text with the C17 algorithm's format into a thread-local
buffer that fits any member values. As in glibc and FreeBSD, a
weekday or month outside its range is printed as ???, and the year is
computed in 64 bits.
_Towctrans passed the guest's descriptor to the host towctrans.
Dinkumware's <wctype.h> (xwcc.h) defines towlower(wc) as
_Towctrans(wc, 1) and towupper(wc) as _Towctrans(wc, 2), the values
FreeBSD's wctrans.c also uses for "tolower" and "toupper". UCRT reads
1 as toupper and other nonzero values as tolower, so on Windows
towlower(L'A') returned L'A' and towupper(L'a') returned L'a', while
towlower(L'a') returned L'A'. The host wint_t is 16 bits there, which
also turned WEOF into 0xffff. glibc's wctrans_t is a pointer to a
table that towctrans reads, so on Linux descriptor 1 or 2 is
dereferenced as an address.
_Towctrans now takes the guest's 32-bit wint_t and size_t descriptor.
1 and 2 map 0..255 through the C locale tables behind _Getptolower and
_Getptoupper and return other values unchanged. Any other descriptor
sets errno to EINVAL and returns wc unchanged, as FreeBSD's
towctrans_l does.
With SO_RCVTIMEO set, Winsock fails a receive that waits that long with
WSAETIMEDOUT, which native_error turned into ETIMEDOUT (60). sceNetRecv,
sceNetRecvfrom and sceNetRecvmsg on a datagram socket therefore failed
with 0x8041013C on Windows, where FreeBSD's sbwait returns EWOULDBLOCK
(35) to soreceive, Linux returns EAGAIN, and POSIX (XSH 2.10.16)
requires EAGAIN or EWOULDBLOCK when no data was received.
A datagram socket has no connection that can time out, so WSAETIMEDOUT
there only means that the receive timeout the guest set has expired:
report EAGAIN. Stream sockets keep ETIMEDOUT, since Winsock leaves a
connection in an indeterminate state after a timeout; TechnicalDebt
records that gap and the one for SO_SNDTIMEO.
On a PS5 (firmware 13.60) scePthreadAttrSetsolosched and
pthread_attr_setsolosched_np accept only 0 and 16; every other value
returns ENOTSUP and keeps the stored one. The SCE set and get calls
return EINVAL for a null or uninitialized attribute instead of throwing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An image whose results stay on the GPU is never written back, so
APS5_DUMP_STORAGE saw nothing; APS5_STORE_AT_FLIP=1 stores every pending
image and ran a 4 GB device out of memory at 3840x2160. The selected
images alone are now stored at each flip.
APS5_DUMP_STORAGE also takes <width>x<height>x<layers>[:<format>], for
images whose address changes from run to run (a title's 32x32x32
color-grading LUT), and the dump holds the first mip of every layer or
3D slice instead of the first one only.
fopen opened its stream with _wfopen, which shares read and write
access but not delete access. While the guest held a stream open,
rename of its file failed with EIO and remove with EBUSY. POSIX rename
and remove work on open files, as on Linux and FreeBSD, and since
#3677 the descriptors sceKernelOpen returns share delete access.
fopen now creates the handle with CreateFileW sharing read, write and
delete, with the access and creation disposition of the mode, and
wraps it with _open_osfhandle and _wfdopen, keeping append mode. A
failed CreateFileW sets errno through the error mapping #3677 added to
libkernel's NativeOpen, which moves to libc as
WindowsOpenError_nid_no_patch so that both use it. freopen still
reopens through _wfreopen; TechnicalDebt records it.
With the f32 denormal field of FLOAT_MODE set to keep, v_cmp_X_f32 and
v_cmpx_X_f32 read a denormal operand as zero: NVIDIA, RADV and lavapipe
all flush denormals in their float compares. Compare the operands as
sign-magnitude integers instead, with NaN handled apart. Compares with
flushed denormals are unchanged. This matches RDNA2 (gfx1036) on 256
operand pairs per opcode, with f32 denormals kept and flushed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On a PS5 (firmware 13.60) destroying a rwlock returns 0 even while it
is held, and leaves 1 in the handle; every later call on that handle
returns EINVAL until it is initialized again. A zero handle is
initialized on first use by the scePthreadRwlock calls too. Here a held
rwlock returned EBUSY, a destroyed handle became null and was silently
re-created by the next POSIX call, and the scePthreadRwlock calls threw
on a zero handle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a PS5 (firmware 13.60) sched_get_priority_max returns 767 and
sched_get_priority_min 256 for SCHED_FIFO and SCHED_RR; the two were
swapped. Every other policy, SCHED_OTHER included, returns -1 with
EINVAL instead of a range or a host exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodeInstruction did not treat 0F 0F as having a ModRM byte, so a
RIP-relative 3DNow! operand was not reported to the analyses and the
--to-intel stub relocation that read HasRipRelativeDisp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
A playing sampler voice whose waveform still accepts blocks (CONTINUE)
reported only INUSE|PLAYING after it had played every queued block, so
a title that waits for EMPTY before it reuses the voice never did.
PPSA18391 (engine: Clickteam Fusion) reads state_flags & 0x22 != 2 from
sceNgs2VoiceGetState as the end of a sound; each of its sound effects
played once and never again.
The voice now also reports EMPTY (0x20) in that state and keeps
playing, so blocks added later still play. A waveform that was closed
without CONTINUE does not report it.
scePthreadJoin threw when a joinable thread joined itself and
scePthreadJoin and scePthreadDetach threw on a null thread, so
pthread_join and pthread_detach ended the process where FreeBSD libthr
returns EDEADLK and EINVAL.
Registering a flip or vblank event on a handle that sceVideoOutOpen never
created, or that was closed, threw, which ended titles that register
their flip event before they open the port and ignore the result. The
documented error SCE_VIDEO_OUT_ERROR_INVALID_HANDLE is returned instead,
the same check sceVideoOutSetFlipRate and the other output calls use.
The flip test program gains an "unopened" mode.
0F 0F was decoded as a two-byte instruction. 3DNow! instructions are
0F 0F ModRM [SIB] [displacement] imm8, the trailing byte selecting the
operation (AMD64 Architecture Programmer's Manual Volume 3, Appendix A,
3DNow! opcodes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
An indexed draw that binds its index buffer in place from a host import
(#2253) scans the bound range for its highest index on every draw, where
the copied path reuses the value kept with its snapshot. A draw of many
indices pays for the scan each time, and an indirect draw, which binds
the whole declared index buffer, scans the whole buffer per draw.
The recorder now keeps the highest index of an in-place index range of
4096 indices or more with the write generation collected before the
scan, and a draw takes it while the range is unchanged since
(GuestMemory::UnchangedSince and the allocation registry generation, as
for a reused snapshot). A smaller range is scanned at every draw as
before: the scan costs 0.4 ns per index and the write collect and lookup
0.2 to 1.4 us per draw, so the scan is the cheaper of the two below
about 3,000 indices, as #2253 measured on small ranges.
Resident Evil 4 (PPSA07411, #2776) and PRAGMATA (PPSA02530, #3043) stop
on the absent export with
what(): _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE not implemented
Deathloop, Minecraft Legends and Cyberpunk import the base-object
constructor InitParameter2::InitParameter2(), InitParameterRtti2::
setAllocatorRtti and InitParameterRtti::InitParameterRtti(AllocParamRtti*,
void*, unsigned long), which were NotImplemented stubs.
InitParameter2 is the 40-byte object of SharpEmu and KytyPS5: the
allocator, its user data, the file buffer size and the 32-bit special
float format type at 0x18. Both constructors zero it, setAllocatorRtti
writes the allocation parameters and user data where setAllocator writes
the allocator, and setSpecialFloatFormatType stores the type.
InitParameterRtti's constructor writes its three arguments to the first
24 bytes. The NID WVZBP4IyM+E recomputes from the mangled name and is in
the aerolib database. The enum's values are unknown, so no value is
rejected; the inferred layouts are recorded in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 258f2910808e5ef1636973aaefbdf51615873dcb)
scePthreadKeyCreate and pthread_key_create handed out the slot index,
so the first key in a process was 0. FreeBSD libthr returns slot + 1
(_pthread_key_create: "*key = i + 1") and subtracts 1 again in
pthread_key_delete, pthread_getspecific and pthread_setspecific, so 0 is
never a valid key. Code that keeps 0 as "key not created yet" therefore
created a new key on every call and lost the values it stored under the
previous one.
Keys now start at 1; 0 and other out-of-range keys are rejected with
EINVAL by delete and set and read as NULL, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYhZu5HokHMo9mfi8QU83m
exp2 set errno (34) for 1024.0. FreeBSD msun never sets errno and
reports errors through floating-point exceptions only, so errno is now
saved and restored around the host call.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
sceKernelClose passed every descriptor to the host close. Guest socket
descriptors start at 0x10000000 and are not host descriptors, so closing
an open socket returned SCE_KERNEL_ERROR_EBADF and left it in the guest
socket table with its host socket open. Route them to GuestSockets::Close
as close, sceKernelRead and sceKernelWrite already do; a closed socket
still returns SCE_KERNEL_ERROR_EBADF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest identity predicate accepted .debug_prx while Domain::ImportModule
did not: a dependency named libGuest.debug_prx normalised to
libGuest_debug_prx, so a qualified import declared in module libGuest was
left unscoped. When another guest module exported the same symbol, the
conversion failed with an ambiguous guest import, while .prx, .sprx and
.suprx resolved to the declaring module.
Module suffixes are now one list in the domain layer, used by the identity
predicate, import module normalisation and the default .prx suffix.
Needed-name stem aliasing keeps its own list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceContentExportStart hands out positive session ids, at most 10 live
(SharpProspero MaxExecution), and sceContentExportFinish closes them.
sceContentExportGetProgress, sceContentExportCancel and
sceContentExportFromDataWithThumbnail take their Orbital signatures and
check the session. The host has no media library to export into, so
FromDataWithThumbnail throws before it calls either data callback, and
GetProgress and Cancel throw for a live session since no export ever
runs and their result for an idle session is unknown. An eleventh Start
and Term with live sessions throw.
Titles: Cult of the Lamb PPSA06465 imports GetProgress, Cancel and
FromDataWithThumbnail (#2686); PPSA21564 (#271) and Cyberpunk 2077
PPSA04029 (#266) import Start.
Sources: Orbital tools/ida/db_types.json (cc408367) for the signatures,
SharpProspero Interop/Content/ContentExport.cs (5dfdb4b0) for the
session model and MaxExecution, shadPS4 content_export.h (443f0224) for
the parameter and callback layouts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 473d6dd355d2c0ee22f9792927e0e465846569cf)
PPSA07411 (Resident Evil 4) fails to load without these imports. Their signatures are unknown, so they are declared without parameters and throw until they are implemented. Adds a TechnicalDebt entry.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
End clears the init flag set by sceVoiceQoSInit and returns 0, so the
library can be initialized again. Before an initialization it returns
0x804E0901 (LIBVOICEQOS_NOT_INIT), as measured on a PS5 (firmware 13.60)
in #1407.
LEGO Horizon Adventures (PPSA14632) calls it at shutdown and Ghost of
Yotei (PPSA26344) calls it on its sceVoiceQoSInit failure branch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 25fbc944faddc5fb3519c4ce4b8d3600b3bc9e1b)
Add pthread_cond_reltimedwait_np (K953PF5u6Pc; computed with
tools/nid_names.py and matching aerolib.csv) as scePthreadCondTimedwait
with POSIX error numbers: ETIMEDOUT when the relative timeout expires,
EPERM when the caller does not hold the mutex, and the mutex held again on
return in every waiting case.
Titles: Porpoise PPSA99764 and RetroArch PPSA99169 import it.
Predicted from relink registry order, not observed: Porpoise stops with
"FAIL: unresolved ELF import K953PF5u6Pc from libkernel.prx" (registry
index 384; RetroArch index 490). No call site is resolved in the registry.
The third argument is a relative timeout in microseconds, not a
struct timespec. FreeBSD 11.4 libthr (8c639945a44588f6cfdddfb057bb5aad88735da0)
has no such function. shadPS4 (3a9f57265d28975658f1ca941daa5c879c7a74bd,
src/core/libraries/kernel/threads/condvar.cpp:229-234, 445, 467-468)
registers posix_pthread_cond_reltimedwait_np(cond, mutex, u64 usec) for this
NID and the same function for scePthreadCondTimedwait (BmMjYxmew1w), whose
microsecond argument this repository already implements. The width follows
the SCE twin's 32-bit SceKernelUseconds, as sem_reltimedwait_np does, not
shadPS4's u64. There is no timespec to validate, so no EINVAL case.
Recorded in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit c86ec8417d12f5b12136b0f06eae06493b0e7a62)
Add pthread_spin_init (ZMn3clnAGBA), pthread_spin_destroy (IJIggoPZExk),
pthread_spin_lock (pw+70ClLYlY), pthread_spin_trylock (rCTGkBIHfPY) and
pthread_spin_unlock (LEfMMCT+SlM). The NIDs were computed with
tools/nid_names.py and match aerolib.csv. libkernel has no scePthreadSpin*
twin in aerolib, so the calls are implemented directly, like the barrier.
Titles: Porpoise PPSA99764 and RetroArch PPSA99169 import pthread_spin_lock.
Predicted from relink registry order, not observed: after
pthread_cond_reltimedwait_np, Porpoise stops with
"FAIL: unresolved ELF import pw+70ClLYlY from libkernel.prx" (registry
index 387; RetroArch index 493). No call site is resolved in the registry.
Behaviour follows FreeBSD 11.4 libthr thr_pspinlock.c and thr_umtx.h
(8c639945a44588f6cfdddfb057bb5aad88735da0). The guest pthread_spinlock_t
holds a pointer to an allocated lock. EINVAL for a null or destroyed lock and
for a pshared other than PTHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED,
EBUSY from trylock while any thread holds the lock, EPERM from unlock by a
thread that does not hold it. libthr spins forever when the holder locks
again; that call throws here. A shared lock is allocated like a private one,
since guest code runs in one process. Recorded in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 13ebf7f926fa2ee1c825cb1fcf18451a8781c045)
sceKernelFsync returned the host fsync or _commit result, so a failure
returned -1 instead of an SCE error code, and a guest socket descriptor
reached the host call. fsync then set the guest errno from the host
errno rather than from that result.
Handle sockets and return SCE error codes as sceKernelFdatasync does
(EINVAL for an open socket, EBADF for a closed one or an unknown
descriptor), and have fsync take the errno from sceKernelFsync's result
like the other POSIX wrappers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
Exports pause (EDyhWelj7UY), sigwait (mrbHXqK8wkg) and sigwaitinfo
(Uq5BGthgbl4) from libkernel. NIDs computed with tools/nid_names.py and
matched in aerolib; ps5-payload-dev/sdk f5d40f02 sce_stubs/libkernel.c
places all three in libkernel.
They throw like the merged sigtimedwait (#3430), with the same message
pattern: "sigwait: waiting for a pending signal", "sigwaitinfo: waiting
for a pending signal" and "pause: waiting for a delivered signal". In
FreeBSD 11.4 (8c639945) sigwaitinfo is kern_sigtimedwait without a
timeout (sys/kern/kern_sig.c:1212), sigwait is the same wait returning
the error number (kern_sig.c:1150, lib/libthr/thread/thr_sig.c:676-728)
and pause is sigsuspend of the current mask (lib/libc/gen/pause.c:46-56).
AnyPS5 never leaves a guest signal pending (blocked signals are dropped
in Dispatch, raise delivers at once), so there is nothing to take, and
raise, kill and pthread_kill run a handler only on the calling thread,
so nothing can end a pause.
Titles (predicted stops, registry order; not observed runs):
- Ruffle PPSA68091, observed today
"FAIL: unresolved ELF import 1t1-JoZ0sZQ from libSceLibcInternal.prx"
(sinhf, #3519), predicted next
"FAIL: unresolved ELF import EDyhWelj7UY from libkernel.prx"
(pause, call site 0x8F7266).
- RetroArch PPSA99169: pause (0x30DB1, an address table entry).
- PS5MC PPSA99420: pause (0x3BD80F6, 0x3C9E220), sigwait (0x3C9E4C7),
sigwaitinfo (0x3C9E588).
- XPSemu PPSA97358: sigwait (0xE02AEC, 0x1101294, 0x1101368), QEMU's
dummy vCPU thread and sigwait_compat thread.
guest_sigtimedwait now also checks the three throws, with an empty set,
a null info, a SIGINT raised while blocked, and that neither the output
nor the blocked mask changes. TechnicalDebt records the call sites.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Carry the implementation and regression from lucfx18's PR #2823, retaining
the original Git author. Extend the regression with a leading guard and
typed default-field checks, and record the unverified console layout.
Source: https://github.com/boykopovar/AnyPS5/pull/2823
NativeError listed 17 Winsock and 17 POSIX errors and turned every other
one into EIO. A second connect while the first is pending (WSAEALREADY,
EALREADY) and an option the host rejects (WSAENOPROTOOPT, ENOPROTOOPT)
reached the guest as EIO; on Linux a nonblocking connect (EINPROGRESS),
a send with no destination (EDESTADDRREQ), ENOTSOCK, EAFNOSUPPORT,
EOPNOTSUPP and others did too.
The translation moves to SocketError.hpp and covers the socket errors of
both hosts by name, with the FreeBSD 11 numbers. WSAESHUTDOWN stays EPIPE
as before. socketpair's own table is gone, as NativeError covers it.
(cherry picked from commit a889c98e6a59d62da289f2e6a77355d2b41a1bde)
FindF16x2Math read spirv[offset + 1] for every instruction once the f16 vector type was known, so a final one-word OpFunctionEnd read past the module. Check the opcode and its minimum word count before reading operands.
v_pk_add_f16 and v_pk_mul_f16 computed each half in f32 and rounded it
back to f16, then fixed the NaN, FP16_OVFL and clamp results on the
bits. On a device with native f16 that rounds to nearest even and keeps
f16 denormals (the Float16, RoundingModeRTE and DenormPreserve
capabilities that #1252 already checks), the arithmetic is now one
f16vec2 OpFAdd or OpFMul decorated NoContraction, and the same NaN,
FP16_OVFL and clamp rules run on its result bits.
The results don't change. f32 has at least 2 * 11 + 2 significand bits,
so rounding an f16 sum or product to f32 and then to f16 gives the
correctly rounded f16 result. Vulkan requires f16 OpFAdd and OpFMul to
be correctly rounded, the module keeps f16 denormals, and any NaN they
produce is replaced by the NaN the existing rules select.
v_pk_fma_f16 and v_pk_fmac_f16 keep the f32 path: Vulkan gives GLSL Fma
only the precision of OpFMul followed by OpFAdd, so a native f16 Fma
may round twice.
New test agc_driver_packed_float16_native compiles one shader with
packed add, mul, fma and fmac (op_sel, op_sel_hi, neg_lo, neg_hi and
clamp variants) with and without Float16, checks that only add and mul
use f16vec2 math, and compares every result bit for bit on random and
special inputs (zeros, denormals, infinities, NaNs, values near the f16
maximum) in nine float modes.
The default terminate handler formatted its whole report into a 1024-byte
buffer, so a long what() was cut off without a trailing newline. Driver
refusals that carry a serialized RecompileRequest lost most of it, and the
next log line ran into the truncated text.
The report header still goes through the bounded RawLog, and what() is
now written in full by a raw write to the stderr handle, followed by a
newline. Both keep avoiding the CRT stream lock, as before.
exception_runtime gains a forked child that throws an uncaught
runtime_error with a message over 3000 characters and checks that the
full what() reaches stderr before SIGABRT.
The test only ran in the full recorder binary, which no ctest entry starts. It now has its own mode and entry (agc_multisample_guest_access); with the guard back at the single-sample size its new padding row fails.
The guest-access guard covers CB bytes times samples (the single-sample layout size alone missed the later sample planes' tile padding), with a test row in that padding. Remove two TechnicalDebt lines duplicated by the rebase, document the guard bound and the unevicted targets, and add the multisampled targets to ARCHITECTURE.md.
A multisampled target's samples are only on the host, so its guest memory
never holds them: a CPU read through GuestMemory::Read/ReadCommitted, a
DMA_DATA copy from it and a texture or storage image lookup at its address
throw instead of returning bytes that were never written there. Asked by
oneandonlydean; the test reads a resolved-only target's memory each way.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fragment shader and fragment output libraries are built with the
pipeline's rasterizationSamples, so a 1x and a 4x pipeline with the same
shaders and formats must not share them. Reported by adriadam10 (found by
Raghav314 in #2242).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vulkan's standard 8x sample 7 is (0.9375, 0.0625), (7, -7) in signed sixteenths; the table had (7, -8), so the
standard pattern (PPSA99902's PA_SC_AA_SAMPLE_LOCS words 0xbd153fd1 / 0x9773f95b) was refused as nonstandard.
Regression row with those words.
Reported by @Amoenus.
Co-Authored-By: Amoenus <5391396+Amoenus@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The duplicate ColorTarget::cmaskAddress, decode and size helper are gone: multisampled targets
use main's cmaskAddress/cmaskBytes (whole metablocks). Single-sample targets keep main's rule
(CMASK under FAST_CLEAR); a multisampled one takes it under FAST_CLEAR or COMPRESSION. A zero
fill of the whole CMASK clears at the next use, 0xFFFFFFFF marks it expanded, any other or
partial fill refuses the next use; eliminate/DCC passes over a multisampled target and a CB
resolve into a fast-clear destination are refused. The draw key adds CMASK_BASE_EXT (0x398),
which main decodes but did not key.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Draws with PA_SC_AA_CONFIG multisampling (2, 4 or 8 samples, every target with as many
fragments, the standard sample locations, MSAA_ENABLE) render into host-only multisampled
color images and multisampled depth surfaces; the pipeline rasterizes with that sample
count. A CB_COLOR_CONTROL resolve over a multisampled CB0 runs as a render pass whose
resolve attachment is CB1's resident image. A zero fill over a target's CMASK fast-clears
it to the CB_COLOR_CLEAR_WORD values of its next use. EQAA, coverage conversion,
nonstandard sample locations, sampling a multisampled surface and multisampled mips stay
refused.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vulkan starts only quads holding a non-helper lane, so the bit is set in every
lane it runs. Stray's pixel shader 0x2012f90000 keeps the packed ancillary
word where that bit is clear, and failed with an unsupported live use.
(cherry picked from commit 2068089b3d75fae55d0994a0916a22bffd7ee2a4)
A descriptor replaced before the shared-memory path probe can otherwise
bypass the pinned-object identity check through native fallback. Compare
the negative probe's current file identity to the initial snapshot and
reject a mismatch. Exercise the earlier window with an ordinary file for
all six descriptor operations.
Confidence: high
Scope-risk: narrow
Tested: 18 Linux runtime tests with zero skips; 18 deterministic reuse scenarios; affected three patched modules; standalone ASan/UBSan; Clang analyzer; conventions and diff checks
Not-tested: Full project build/suite, Windows runtime, console hardware and titles
Directive: Identity checks do not synchronize the entire descriptor table or identify separate opens of the same inode
Keep guest creation-mask state in libc behind C-linkage helpers so NID
patching preserves the shared exports. Reject a pinned descriptor whose
file identity differs from the snapshot taken before classification.
Exercise close and reuse from another thread at both acquisition windows
for writes, resize and seek, and create temporary ordinary-file fixtures
so runtime tests work without the source tree.
Constraint: NID exemptions require the literal suffix at the end of the exported symbol
Confidence: high
Scope-risk: narrow
Tested: 18 targeted Linux runtime tests with zero skips; patched_libraries for libc/libkernel/libkernel_sys; standalone ASan/UBSan with warnings as errors; source-prefix-remapped fixtures; Clang analyzer and conventions
Not-tested: Full project build and library set, Windows runtime, console hardware and titles
Directive: Device/inode validation establishes file identity, not open-file-description identity
Pin the descriptor before mmap validation so guest descriptor reuse cannot
redirect the eventual mapping. Apply the canonical guest creation mask when
creating named objects, distinguishing new objects before setting permissions
without changing the host process umask. Add regressions that reproduce both
failures and document the remaining host requirements.
Constraint: Guest umask must not mutate the process-wide host mask
Confidence: high
Scope-risk: narrow
Tested: 17 targeted Linux runtime tests passed with zero skips; conventions and diff checks; Clang analyzer on three changed production translation units
Not-tested: Full project build and suite, Windows runtime, console hardware and titles
Complete the object lifecycle through guest mapping and unlink APIs, and
preserve FreeBSD shared-object size and seek rules across scalar, vector
and asynchronous descriptor writes. Pin descriptors during operations and
coordinate size-sensitive operations across cooperating emulator processes.
Constraint: Linux host support with existing dependencies and allocation tracking
Rejected: Bare descriptor registry | close, duplication and reuse invalidate identity
Rejected: Per-object OFD locks | forked descriptors share the same lock description
Confidence: high
Scope-risk: moderate
Directive: Keep guest resize, truncating open and bounded writes under the same namespace guard
Tested: Ten Linux runtime suites; focused shared-memory ASan/UBSan suite with warnings as errors
Not-tested: Windows build, PS5 hardware and title compatibility
Translate guest named-open flags and permissions to POSIX shared-memory descriptors. Bound guest names and use the existing digest implementation to keep a stable AnyPS5 namespace, including long names and internal slashes. Preserve anonymous and Windows behavior, and record the separate unlink and guest mapping gaps.
Constraint: Builds and tests run only through the enrolled 191 compute service.
Rejected: Direct Linux guest names | cannot represent FreeBSD internal slashes and 1023-byte names.
Tested: Source review and diff checks; baseline job100619 accepted, result retrieval currently times out.
Not-tested: Runtime verification pending on191; full build, Windows and titles.
Confidence: medium
Scope-risk: narrow
Exercise named object creation, reopening, descriptor permissions, truncation and cross-process identity before adding Linux support. The standalone target compiles the production implementation without graphics dependencies.
Tested: Source and staged diff reviewed; failing baseline will run on 191.
Not-tested: Full runtime build and Windows.
sceKernelOpen built the host flags from the bits it knows and dropped
O_NOFOLLOW (0x100), so open and sceKernelOpen with O_NOFOLLOW followed
a symbolic link and opened its target, and with O_CREAT created the
missing target of a dangling link.
FreeBSD looks up the last component without following it when
O_NOFOLLOW is set (sys/kern/vfs_vnops.c, vn_open_cred:
"((fmode & O_NOFOLLOW) ? NOFOLLOW : FOLLOW) | LOCKLEAF", and with
O_CREAT "if ((fmode & O_EXCL) == 0 && (fmode & O_NOFOLLOW) == 0)
ndp->ni_cnd.cn_flags |= FOLLOW;"), and then refuses the link itself in
vn_open_vnode: "if (vp->v_type == VLNK) return (EMLINK);".
open now fails with EMLINK when O_NOFOLLOW is set and the path names a
symbolic link. O_CREAT | O_EXCL keeps failing with EEXIST, and a
trailing slash still resolves through the link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelRmdir, and rmdir through it, checked the path with
std::filesystem::is_directory and is_empty, which follow a symbolic
link, and then removed the path itself. So rmdir on a link to an empty
directory returned 0 and deleted the link, while the target stayed.
FreeBSD looks up the last component of rmdir's path without following
it (sys/kern/vfs_syscalls.c, kern_rmdirat: NDINIT_ATRIGHTS with
LOCKPARENT | LOCKLEAF and no FOLLOW) and then fails with ENOTDIR for
anything that is not a directory: "if (vp->v_type != VDIR) { error =
ENOTDIR; goto out; }". A link, whether it points to a directory or
nowhere, is a VLNK there.
rmdir now returns ENOTDIR for a symbolic link and leaves it alone, as
unlink already looks at the link itself through symlink_status. A
trailing slash still resolves through the link, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pthread_once and scePthreadOnce marked the control as running, called
the initializer and marked the control done only after it returned. A
thread that left the initializer through pthread_exit or a
cancellation point never came back, so the control stayed in progress
and every later call on it waited forever.
FreeBSD libthr runs the initializer under a cleanup handler
(lib/libthr/thread/thr_once.c, _pthread_once:
"THR_CLEANUP_PUSH(curthread, once_cancel_handler, once_control);
init_routine(); THR_CLEANUP_POP(curthread, 0);"). once_cancel_handler
stores ONCE_NEVER_DONE and wakes the waiters, and thread exit and
cancellation run the cleanup handlers. POSIX requires this for
cancellation: "if init_routine is a cancellation point and is canceled,
the effect on once_control shall be as if pthread_once() was never
called."
Both functions now push such a handler around the initializer, so the
control returns to its initial state and the next caller, or one
already waiting, runs the initializer. scePthreadOnce waiters now retry
when the control goes back to its initial state, and an exception from
its initializer resets the control as it already did for pthread_once.
CleanupHandler moves to Cancel.hpp so that Once.cpp can use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShaderCacheDirectory located the executable through /proc/self/exe,
which macOS does not have, so it returned an empty path there and
the shader and pipeline disk caches were silently disabled unless
ANYPS5_SHADER_CACHE_DIR was set. Use _NSGetExecutablePath and
resolve the path with std::filesystem::canonical on macOS. Linux
and Windows are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Route semantic self-loops through a dedicated empty back-edge block. This prevents block merging from folding the continue target into the loop header when memory instructions emit nested selections. Add a synthetic loop-store regression that fails SPIR-V validation after optimization on main.
libSceFont and libSceNpEntitlementAccess located the executable
through /proc/self/exe, which macOS does not have, so finding the
system fonts and the entitlements file threw there. Use
_NSGetExecutablePath and resolve the path with
std::filesystem::canonical on macOS. Linux and Windows are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anyps5-input.ini accepts LeftStickDeadzone and RightStickDeadzone (whole
percent, 0 to 90), a radial deadzone on the controller sticks whose
remaining travel is rescaled to the full range, and MouseSensitivity
(above 0, at most 10), which replaces the fixed factor of 1 for mouse
look. The defaults keep the current mapping exactly.
The 8_8 SWAP_ALT blend test now also draws with a vertex shader that
stores a dword to a buffer (buffer_store_dword through user data 4-7)
and a separate alpha blend, and requires the draw to be rejected for
its shaders writing memory. With the former pixel-only check the draw
went through and the test fails.
parseJsonValue recursed without a limit, so a param.json with tens of
thousands of nested arrays overflowed the stack. Reject nesting beyond 512
levels, the limit libSceJson2 uses.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
sceUserServiceGetAgeLevel reported 0. On a PS5 (firmware 13.60) the
value is the age of the user's parental restriction (0, 7, 12 and 15
measured) and 0xFFFFFFFF without one, so 0 is a user restricted to age
0. A user who is not logged in returns 0x80960009 (not logged in) with or
without an output, before the null output check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The libraries are built with -fno-asynchronous-unwind-tables and unwind
through their own DWARF tables. MinGW-w64 GCC 11.4 and 13.2 answer that
flag by switching to SjLj exceptions. Configuration succeeded and, with
13.2, the build stopped at the libc.prx link with undefined references
to __gxx_personality_sj0 and _Unwind_SjLj_Register, which does not name
the cause. Configuration now checks the exception model the flag selects
and stops with the supported toolchain's location.
symlink, added in #3491 while this branch was open, still resolved its
new path with ResolvePath_nid_no_patch, which now throws for an invalid
"..". Use ResolvePathError_nid_no_patch as link does, so
"missing/../x" returns ENOENT and "file/../x" ENOTDIR.
Reported by @Silverash-i8 on #3485.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guest paths were normalized lexically before they reached the host, so
"missing/../file" and "file/../x" dropped the component before "..":
stat and open succeeded on the file, unlink deleted it and rmdir removed
a directory, where FreeBSD's lookup() resolves ".." with VOP_LOOKUP in
the directory reached so far and fails with ENOENT for a missing
component and ENOTDIR for a file. POSIX path resolution (XBD 4.13) says
the same.
".." is still collapsed lexically when the path before it is an existing
directory, so case-insensitive matching and aliases behave as before.
When it is not, the rest of the path is passed to the host unchanged and
the host resolves it. Linux then reports ENOENT or ENOTDIR as FreeBSD
does; Windows collapses ".." itself, so its results are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYhZu5HokHMo9mfi8QU83m
GuestError passed host error values up to 34 through and reported every
other value as EIO, so readlink failed with EIO for a name that is too
long or a symbolic link loop, and a host EAGAIN would have been reported
as the guest's EDEADLK. The values whose numbering differs are now
mapped to FreeBSD's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
FreeBSD returns the difference of the lowercased bytes where the strings
differ, including where one of them ends. The guest wrappers returned the raw
difference of the terminator and the unfolded byte, so strcasecmp("ab", "A")
gave 66 instead of 98.
FreeBSD's soreceive leaves the sender unset for a stream socket, so recvfrom
returns an address length of 0 (kern_recvit). The guest call formatted the empty
host address as a 28-byte AF_INET6 sockaddr and reported length 28. Report
length 0 when the host returns no AF_INET or AF_INET6 sender, as recvmsg does.
A thread CPU clock whose thread has finished made clock_gettime fail with errno EINVAL, but the sce wrappers passed the raw -1 through to the title. They now return the encoded errno like the other sce kernel calls.
FreeBSD's kern_clock_nanosleep writes the remaining time only when the sleep is cut short. A completed sleep zeroed rmtp, so nanosleep(&ts, &ts) in a polling loop slept zero time from the second call on.
Check.duplicates reported a duplicate export for a hit in another
library unless that single grep line ended with a semicolon, so a
prototype wrapped over two lines was reported as a duplicate
definition. The hit file is now read at the head commit and the
existing function_body decides whether the hit is a definition.
The git grep pattern also used the GNU escapes \b and \s, which the
BSD regex library on macOS does not support, so the check never
matched there. The pattern now uses POSIX classes instead.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
A FLAT_SHADE input without pass-through that a pixel shader reads
through v_interp_mov_f32 P10 became an explicit per-vertex input and
read vertex 1 minus vertex 0. With the last provoking vertex the
pipeline then threw ("last provoking vertex with explicit per-vertex
flat interpolation is unsupported"), which stopped all three
ps5-opengl titles in the catalog.
ps5-opengl reads every flat input through P10 when GL's last-vertex
convention is in effect, and its driver passes the GL 4.6 CTS on a PS5
(#1413). Such a read now returns the provoking vertex's value, as P0
does, and only a P20 read keeps a flat input per vertex. The lane was
not measured on the console itself, so FragmentParameter records it
and a pipeline using the first provoking vertex throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exports() only checked whether the matching line ended with a semicolon, so a
prototype wrapped over several lines was counted as an implemented export and
two PRs adding the same forward declaration were flagged as overlapping. It now
joins the following added lines up to the first semicolon or brace and treats
the match as a declaration when the semicolon comes first.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
When squarify gave a group a sliver narrower than 2px, treemap passed cells a zero or negative width, so the row count became complex or divided by zero and render crashed. cells now returns no cells for rectangles without area. A regression test renders a 300 and 1 item split.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
`setuid`, `setreuid` and `setgroups` are absent imports of XPSemu
(PPSA97358) and PS5MC (PPSA99420) on `main`. The guest process is modelled
as root with ids 0, as `seteuid` and `setegid` already assume: `setuid`
accepts 0 and `setreuid` 0 or -1 per id and change nothing, any other id
throws. `setgroups` keeps the supplementary group list as FreeBSD's
`kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the
effective group as the only entry, a first entry other than 0 throws) and
the new `getgroups` reports it. `guest_credentials` covers the accepted
ids, the default list, a set list, an empty list and the EINVAL/EFAULT
cases.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit d12b062074dcf64bff694800d77f7261adf6f59e)
Document why the Driver singleton must initialize the guest write tracker before its static construction completes on Windows.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Initialize the guest write tracker during Driver construction so its function-local static is destroyed after Driver on process exit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
close() of a descriptor that is not open calls _close, which raises the
UCRT invalid-parameter handler before returning EBADF. In a relinked title
that handler is libc's crash reporter, so an ordinary close of a closed
descriptor printed a FATAL report and every thread's stack. The call now
runs with the thread-local no-op handler, as dup and dup2 already do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The abort() and UCRT invalid-parameter reports capture the stack with
RtlCaptureStackBackTrace, which stops at the first frame without unwind
data (guest code, or our libraries built without asynchronous unwind
tables), so they often end two or three frames in, before any caller.
The fault report already scans the stack for code addresses; that scan
is now a helper the backtrace reports use as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two more agc_driver_relative_indexing rows read a register of the window
the program never wrote, directly and through v_movrels_b32, and expect 0,
which the zeroed window array gives like an unwritten SSA register did;
the TechnicalDebt entry says the console's value is undefined.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A pixel wave starts with only its covered pixels in EXEC, so a shader that
takes an implicit LOD has to run s_wqm first or the helper lanes of a partly
covered quad hold no coordinate. The test's pixel shader did not, which goes
unnoticed while its one triangle covers every quad whole. llvmpipe clips that
triangle at twice the viewport into three, and in the quads on the two inner
edges the helpers' zero coordinate gave the last mip instead of the expected
one.
The shader now starts with s_wqm_b64 exec, exec, as a compiled pixel shader
that samples with an implicit LOD does.
M0 is unsigned, so an index at or past the window reads v0 and drops its
write, v_swaprel_b32 writes neither side when either is out of range, and
the window array starts zeroed like unwritten SSA registers; the window
load counts as a lane source so a wave64 program's second half keeps its
own registers. agc_driver_relative_indexing runs in wave32 and wave64 and
pins the rows measured on gfx1035 in the review of #3856, and the
TechnicalDebt entry states the measured behaviour, the window's end at the
last named register and the sizes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The f16 unary ops, v_frexp_mant_f16, v_frexp_exp_i16_f16, the norm
conversions and v_cvt_f32_f16 read denormal halves as they are and
rounded denormal results as they are, whatever the denormal field of
FLOAT_MODE says. On RDNA2 a clear bit 0 flushes a denormal half input
to its signed zero and a clear bit 1 flushes a denormal half result.
One shared rule now drives all of them, and v_ldexp_f16 uses it for its
input too.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PPSA28416 calls it on its existing save at boot (after sceSaveDataDirNameSearch finds it) and aborted on the missing implementation. The call takes one parameter block: user id at +0, the directory name pointer at +8. Host saves have no size limit, so the conversion completes at once: an existing save returns OK, a missing one NOT_FOUND, an invalid name PARAMETER. The title polls sceSaveDataGetEventResult once afterwards and continues on 'no event'.
Goat Simulator 3 (PPSA04159) imports sce::Json::Object::find(String const&)
const (ehN1Biw3tw8), Object::iterator::operator->() const (EnesV4gXDtE) and
String::compare(char const*) const (Yfw63FVJgtU); without them the Windows
entry stub stops before the game starts with "unresolved ELF import
ehN1Biw3tw8".
find returns an iterator to the first pair whose key equals the argument,
or end(), using the same key match as Object::operator[]. operator->
returns the pair like operator*. compare orders the string against a C
string as std::string::compare does; a null argument throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceVideoOutSubmitChangeBufferAttribute2 threw when a buffer of the group
had a flip pending, and Marvel's Wolverine makes that call with flips in
the queue. A flip now takes its buffer group when it is reserved, in
submission order, so flips submitted before the change keep the
attributes their buffers were rendered with and later flips use the
new ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA19546 (Cookie Clicker) and PPSA34959 (The King is Watching) list
libSceXml.prx among their needed libraries but import nothing from it.
AnyPS5 builds no such library, so on Linux the dynamic loader stops both
before they run:
./eboot.elf: error while loading shared libraries: libSceXml.prx:
cannot open shared object file: No such file or directory
On Windows the start-up stub has skipped a missing library that nothing
imports from since #3129; the Linux loader resolves DT_NEEDED itself
and cannot skip one. libSceXml now exists with no exports, like
libSceAgcVsh and libSceAudio, so it loads on both. A title that imports
from it still stops at the missing import.
Both titles now load and stop at their next missing import:
setSpecialFloatFormatType (PPSA19546, #3575) and sceHttpsFreeCaList
(PPSA34959, #3497).
Extend the decode test to mul:4, div:2, -|v2|, -|v2| div:2, a p2
coordinate with neg and a non-zero attribute channel, a clamped p2, and
the mov form with clamp and mul:2. All 18 accepted words are the
assembler output (llvm-mc -mcpu=gfx1010).
The search returned saves in host directory order and ignored the condition's key and order. A title that asks for the newest save first (modification time, descending) got whichever directory the host listed first: the alphabetically first on NTFS, an arbitrary one on Linux. Sort by directory name, user parameter or modification time, reverse for a descending order, and reject unknown keys and orders with the parameter error, as shadPS4 does.
PPSA06888 stops on main at start-up because its bundled
Media/Plugins/SceRudp.prx imports sceRudpEnableInternalIOThread2,
sceRudpGetOption and sceRudpGetRemoteInfo, which no library exports.
The signatures are unknown, so they are declared without parameters and
throw. The title does not call them during start-up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add thirteen offsets measured repeatedly on PS5 firmware 13.42, keeping DST names ahead of their shorter prefixes. Cover case folding, prefix matching and a second valid date.
Written with AI assistance; validated against real hardware and the guest RTC tests.
One queue for APR and AMM buffers kept an AMM buffer behind an APR buffer
waiting on an address that the AMM buffer writes, so neither ran.
Marvel's Wolverine does this: an APR buffer waits on a counter that the
WriteAddress commands of later AMM buffers advance, and its loading
stopped. APR and AMM buffers now keep their order within their own queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetModuleInfoFromAddr has a Windows path since 77ef75c1d, so
_sceLibcInternalForceTlsDestructor finds each dso's module there too.
The test takes the other module's address with GetProcAddress where
Linux uses dlsym(RTLD_NEXT), and the TechnicalDebt entry no longer says
the call throws on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rect-list draw whose count is not a multiple of 3 threw "incomplete rect-list
primitive". Rect lists are drawn as patch lists of 3 control points, and Vulkan
generates floor(vertexCount/3) patches, so the draw now records as is and the
trailing 1 or 2 vertices are left out. Palworld (PPSA23126) draws 4 vertices x 9
instances as a rect list when character creation opens (#3832).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePadOpen returned the standard pad's handle for a special port
(type 2) as well, and scePadGetHandle found it there. Both ports then
read the one input queue, so a press queued between two frames could be
drained by the special port's read and never reach the standard port.
PPSA16827 (Harmony's Odyssey) opens both ports for the initial user and
reads both every frame, also polling controller information and device
class data for the special one.
Special ports now return DEVICE_NOT_CONNECTED (0x80920007) from
scePadOpen, as scePadOpenExt already does because the host has no
special controller, and scePadGetHandle has no handle for them.
TechnicalDebt's scePadOpenExt entry now covers both calls.
guest_pad opens a special port before and after the standard one and
expects DEVICE_NOT_CONNECTED, and DEVICE_NO_HANDLE from scePadGetHandle
for the special port while the standard handle stays valid. It fails on
main.
PPSA16827 over 130 s of its title screen: 6812 scePadRead, 3408
scePadGetControllerInformation and 3406 scePadDeviceClassParseData calls
before, two reads of the same queue per frame; 2702 reads, one
information call and no parse calls after.
Elden Ring Nightreign (PPSA25380) imports the function, which no library
exported, so the title failed to load. The option is ORed into the buffer
attribute and a null attribute throws. Options other than 0 and 8 still
throw when the buffers are registered.
`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`
and `v_swaprel_b32` lowered to a compare-and-select chain over every
register from the base to the program's last allocated register, so a
program addressing 154 registers 104 times turned 3176 ISA words into
649,515 SPIR-V words that Intel's compiler did not finish. The registers
from the lowest relative base up now live in a function-scope array per
invocation (`LoadVectorWindow`/`StoreVectorWindow`), so an indexed access
is one load or store; plain accesses to those registers go through the
same array, DPP-modified relative writes keep their `DppUpdateU32` merge,
an index past the window reads 0 and drops its write, and programs
without relative addressing are unchanged. The same program recompiles
to 392,333 words with 4,992 selects instead of 32,383.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mingw's fprintf writes an unbuffered stream a character at a time and
holds the stream's lock for the whole line. The APS5_LOG_* lines that
carry a RecompileRequest ("Shader ... could not be prepared after
registration", 15-20 KB each) took one WriteFile per character, and every
other thread writing to stderr waited behind them, guest calls that log
included. Demon's Souls (PPSA01342 v1.005, Windows) stops on a black
screen after two frames on main: a BPE job worker waits for the stderr
lock in sceSaveDataDirNameSearch's trace while the shader preparation
pool prints its dumps, so GameCoreLoop never finishes a frame.
The log macros now format the line into memory and write it with one
fwrite, as Driver::reportSkip does since 8cf2f6580. The UCRT writes an
unbuffered stream's fwrite as one even-length block and the last byte,
so a line takes at most two writes on Windows and one on Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetOperationMode returned 0 for both values, which were
unknown. A payload on a PS5 (firmware 13.60) reads mode 2 and submode 0;
nothing past the two ints is written and the result register holds no
status, so the function now returns nothing. PPSA12544 logs both values
and ignores the result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hollow Knight Silksong (PPSA12544) imports libSceAgc NID vieBRwlh1Lw
(#699). Its name is unknown: it is not in aerolib, and the
sceAgcUpdateInterpolantMapping label other projects give it hashes to
SbuY2jN+axQ.
OpenAGC (src/game_compat.c at 3c7e807, sceAgcUnknownVieBRwlh1Lw) reads
it from FW 11.60 as the dbOlWdppb4o transform with the same (regs, gs,
ps) arguments, without the identity tail: it writes one register per
pixel shader input, none for a pixel shader without inputs, and rejects
a null pixel shader. It now shares the dbOlWdppb4o path and throws for
the rejected arguments.
For is_f16 1 and 3 inputs whose default_value and default_value_hi
differ, OpenAGC takes DEFAULT_VAL from default_value_hi, while KytyPS5,
sharpemu and dbOlWdppb4o here take it from default_value; these inputs
throw. On every other input the dbOlWdppb4o values equal OpenAGC's.
agc_interpolant_mapping runs the dbOlWdppb4o vectors and OpenAGC's
enhanced-mapping vectors through it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
agc_driver_f32_denormal_flush fails on RADV RENOIR (Mesa 26.2.4):
lane 20 no float mode v_fma_f32 v14, v4, v5, v6 is 0x00800000, expected 0x00ffffff
The driver lowers Fma to a multiply and an add: the compiled shader has
v_mad_f32 and no v_fma_f32. The product 0x1fffffff * 0x1fffffff is below
2^-126 and is flushed before 0x00800000 is added. Lane 20 is the only
row with a tiny product and a non-zero addend. v_fma_f32 and v_fmac_f32
fail on it in all three float modes; every other check passes.
When both return that sum, the two values are not checked. FmaRounding
already leaves out its fused forms on a device that splits fma.
sceKernelGetModuleInfoFromAddr found the size of the image's .eh_frame
on every call by walking all of its CIE and FDE records, checking each
against every PT_LOAD header. The guest libc's unwinder calls it for
each frame it unwinds, so a title that unwinds often walked its whole
.eh_frame hundreds of times per second.
The tables read from a .eh_frame_hdr are now kept per header address
and reused while the image stays loaded. The cache is dropped whenever
dl_iterate_phdr reports that an object was unloaded (dlpi_subs), so an
address reused by another image is read again.
guest_module_info queries a module twice and expects the same tables,
then unloads it, loads it again and expects tables of the same size at
the same offset in the new image.
PPSA16827 (Harmony's Odyssey), title screen, over 500 calls per second
counted under gdb, which slows the title: the process used 9269 CPU
samples per 10 s, 53.7% of them in this walk, and 3306 with this change,
3.2% in the lookup. The title still runs at 27 FPS; its frame rate is
limited elsewhere.
Rows that run the same kernel at the same wave size compile the same
pipeline, so each part now takes whole groups of them, and the test is
split into fourteen parts. Each shader is compiled once per CI run
instead of in up to three parts at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA30601 (Resonance: A Plague Tale Legacy) fails to load without these imports that no library exports. Their signatures are unknown, so they are declared without parameters and throw until they are implemented. Adds a TechnicalDebt entry.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On main asin(2.0) returns 0x7ff8000000000000 and sets errno to 33;
FreeBSD msun (lib/msun/src/e_asin.c) returns 0xfff8000000000000 and
never sets errno. asin now handles |x| > 1 itself with quiet
comparisons, so a NaN input still raises no exception. For |x| <= 1
and for NaN the host function is still called.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Surround Sound Studio (PPSA99051), catalog build on main a11c8f7f with
the flat-interpolation fix, stops 16 s in, before its first frame
(orishamir, #3581 comment 6109839314):
sceAudioOutSysGetHdmiMonitorInfo not implemented
The call takes (type, out, size); out must be non-null, size exactly
0x180 and type 1 (HDMI) or 0x10 (sainsaji/PS5-Audio-Passthrough-Research
c3be9c2a, docs/FINDINGS.md section 3 and AGENTS.md:25, firmware 12.70).
Titles read the monitor name at +0x0B, the descriptor count at +0x94 and
8-byte descriptors from +0x98 (same, FINDINGS.md hardware log item 1 and
sample-app/src/platform/ps5/bitstream_out.cpp:92-128).
Audio reaches the host through SDL as PCM and there is no EDID, so type 1
with a 0x180-byte buffer reports a sink without a name or descriptors:
the buffer is zeroed and 0 is returned. Surround Sound Studio's
Passthrough page then lists no formats; EVO Player's code treats an empty
list like a negative return. A null buffer returns 0x80260004, as the
other AudioOut calls do. Type 0x10, other types and other sizes throw:
their results and error codes are not known.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v_cvt_i16_f16 with mul:2, mul:4, div:2 or clamp in the VOP3 encoding
stops the recompiler with "VOP3 source modifiers are not implemented".
On gfx1036 (Radeon 610M) the result is identical with and without
mul:2, mul:4, div:2 and clamp combined with mul:2, for all 66 measured
inputs; clamp on its own is not a measured case.
The decoder now accepts omod and clamp on v_cvt_i16_f16 and drops them,
as measured. Clamp without an output multiplier is accepted by the same
rule: clamp with mul:2 gives the same hardware result as mul:2 alone.
Matches 528 of 528 rows in two float modes, 0 differing.
v_cvt_u16_f16 is not measured and stays refused.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Thread stacks are host-private memory; the call threw instead of setting the native protection.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PPSA07411 (Resident Evil 4) fails to load without these imports that no library exports. Their signatures are unknown, so they are declared without parameters and throw until they are implemented. Adds one test and a TechnicalDebt entry.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
#2072 taught the SRT evaluator SLessThan32, but IsRuntimeUniformOp still
left it out, so the runtime validator rejected any descriptor dword computed
through a signed comparison before the evaluator could compute it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
munmap and sceKernelMunmap over a range in which no page is mapped
failed: munmap returned EINVAL and sceKernelMunmap let the registry's
exception escape. Both now return 0. The console returns 0 for
sceKernelMunmap of a range it just unmapped and of a never-mapped
address, and EINVAL only for a zero length, as measured by prosper's
console oracle (kernelx.golden.tsv, kx_fmem_unmap_again,
kx_fmem_munmap_unmapped).
The other half of #3196, a range with an unmapped page in the middle, is
already fixed on main by 04ed61957; the test covers both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, real thread stacks that exist before the guest arena is
reserved can sit near the arena top, so the AMM range correctly lands
9-60 GiB below it and the 1 GiB bound failed about 1 run in 10. The test
now requires that no free 64 GiB window exists above the AMM range.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Broken Sword Reforged (PPSA21480) calls scePadSetVibrationMode(handle, 3) after
scePadOpen and treats a failure as fatal (ps5_controls.cpp:19 "Sony call failed
with code 80920001"). Modes up to 3 are accepted, 4 and above stay invalid.
Co-Authored-By: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com>
scePthreadRwlockUnlock called unlock_shared on the host mutex whenever the
caller was not the writer, which is undefined behaviour for an unlocked rwlock
or one write-locked by another thread. Track the reader count and return
EPERM like FreeBSD libthr when the caller does not own the write lock and no
read lock is held.
Dying Light 2 (PPSA02262) imports sceKernelAioSetParam (9WK-vhNXimw) from
its eboot. In #1794 tv7 reported on main b5f5362d3 + #2129 (2026-10-10):
FAIL: unresolved ELF import -q-vbO859Tw from libSceVideoOut.prx
with "12 other imports are still unresolved on `main`. In the eboot,
`-q-vbO859Tw`, `GTfJ64qFCPw`, `vwtVpHQKelI` (no known names),
`sceKernelAioSetParam` and `sceNpWebApi2GetMemoryPoolStats`." The import
is observed; its position in the loader queue after the nameless NIDs is
predicted.
The function fills one SceKernelAioSchedulingParam (five 4-byte fields,
20 bytes): Orbital cc408367 tools/ida/db_types.json:1103, SharpProspero
5dfdb4b0 Interop/Kernel/KernelAio.cs ("Sets every field of param at
once") and ampr_emu cfa85df3 ps5/include/sce/_fs.h agree on
(param, schedulingWindowSize, delayedCountLimit, enableSplit, splitSize,
splitChunkSize). No source gives an error code, so a null struct and
values outside SharpProspero's maximums (window and delayed count 0..128,
split flag 0 or 1, sizes up to 0x1000000) throw. The engine does not use
the settings, as sceKernelAioInitializeImpl ignores its struct; this is
recorded in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit e45a2b950afe594212db2764529ccae32f84be8c)
sceKernelAvailableFlexibleMemorySize and the flexible maps counted only
explicit flexible mappings, so the full 448 MiB was reported while the
title's executable and modules were loaded. A title that derives its code
and data size from the budget minus the available size got an unsigned
underflow.
The segments of the main executable and of every .guest.prx module that
the title's ELF gives permissions now count, rounded to 16 KiB pages, as
shadPS4 maps every non-system module segment as flexible memory. The
relinker's own blocks and the host system libraries do not count. The
charge ends when a module is unloaded.
Refs #2883
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
v_cvt_pknorm_i16_f16 and v_cvt_pknorm_u16_f16 with |x|, -x or op_sel in
the VOP3 encoding stop the recompiler with "VOP3 source modifiers are
not implemented", although the RDNA2 hardware executes them.
These two opcodes now use the two-source f16 modifier path of
v_pack_b32_f16: op_sel picks the high half of either source, then abs
and neg apply. Other op_sel bits, clamp and omod are still refused.
Matches 15632 of 15632 rows measured on gfx1036 (Radeon 610M) in two
float modes, 0 differing.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Carry the additional GuestFilesystem checks from issue #3303 after its implementation landed on main. The declaration already exists on main, so keep one prototype.
Use an SRW lock and native Windows condition variable for epoll waits, preserving the shared handle lifecycle. This avoids the scheduler-tick overshoot measured with std::condition_variable on Windows.
Suggested-by: winstrex
Deathloop (PPSA01670) imports sceHttpAbortWaitRequest; Planet Coaster
(PPSA01736) and Palworld import sceHttpWaitRequest.
Epoll handles are kept in a registry with an abort flag, a destroyed flag
and a condition variable. A wait with a negative timeout blocks until
sceHttpAbortWaitRequest, sceHttpDestroyEpoll or the sceHttpTerm of the
last context wakes it; a bounded wait still returns 0 events. An abort
stays set and makes every later wait return SCE_HTTP_ERROR_ABORTED
(0x80431080); a wait woken by destroy or term returns 0 events.
sceHttpDestroyEpoll no longer frees a handle a waiter is blocked on.
sceHttpInit registers its context, and the epoll calls and sceHttpTerm
return BEFORE_INIT without a live context and INVALID_ID for an unknown
context or handle.
Behaviour and error codes: shadPS4 src/core/libraries/network/http.cpp
and http_error.h at a8a70da595da2a96e0410309472ba4a7eb558144.
Signature (void* eh): Orbital tools/ida/db_types.json at
b321fcdbd700cc02da1cb1ad25412781d5ec73c6 and SharpProspero
src/SharpProspero/Interop/Net/Http.cs at
60977b29103452b9681ae10c7a9011d44965bf16.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 766997df3f8329fd6e8e7aac2b5d83daf88c8292)
On macOS, GuestWriteWatchCollectFresh reported nothing and returned true, so
MarkWritten took pages no collection had armed yet for clean, and they stayed
writable without being tracked. GuestWriteWatchCollectArmed was a full collect,
which also took those pages.
Pages now carry a fresh bit from registration until a collection takes them, as
the Linux watch keeps its fresh ranges: CollectFresh reports and arms only fresh
pages, CollectArmed skips them, and Collect takes both. Protection changes, host
writes and faults keep the bit.
The write_watch fresh and armed tests check both collections.
The call was an unimplemented stub. It now writes syllable state 0 and
returns OK for every character outside the Thai block, where a PS4
firmware 11.00 libSceFont measures 1, 3 or 4; Thai characters throw as
not implemented. A null character or result returns
SCE_FONT_ERROR_INVALID_PARAMETER, and a null character writes 0 to a
non-null result, as measured. The signature follows SharpProspero.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
asinf returned 0xffc00000 (errno 33) for 2.0 (0x40000000), where FreeBSD
lib/msun/src/math.h returns 0xffc00000 (errno 0).
It now returns the FreeBSD result for that case. A guest test covers it
and fails on main.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
The second argument is an out pointer to the writing's letter step, as
KytyPS5 and the SharpProspero bindings declare it. It was filled as a
letter step struct, which wrote 64 bytes over the caller's 8-byte
pointer. The letter step now lives in the writing and the call stores
its address, or null when there is no current step.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
FreeBSD removes or renames a file that is still open; the descriptor stays usable. _wopen opens without FILE_SHARE_DELETE, so sceKernelUnlink returned EACCES and rename failed while the guest held a descriptor. sceKernelOpen now opens through CreateFileW with delete sharing and keeps the flag, mode and error mapping of _wopen.
(cherry picked from commit 2f5ada5c86a427505804f22def209e054b3571e4)
The AMM ranges were reserved at the last 64 GiB of the guest arena and
the reservation threw when that window was not free. On Windows the
stacks of the threads that exist before the arena is reserved (the main
thread and the loader's workers) are placed at random addresses and are
left in the arena as host regions; when one fell in the last 64 GiB, the
first AMM use threw "fixed guest mapping ... overlaps a host region in
the guest arena". Reserve the highest free 64 GiB window instead: the
last 64 GiB when it is free, and the window just below an occupied part
of it otherwise.
The decoder throws "VOP3 source modifiers are not implemented" for
VOP3-encoded float compares (v_cmp_*_f16/f32/f64) that set the clamp
bit, so shaders using them fail to compile.
On RDNA2 hardware the clamp bit does not change the result of a float
compare: the measured results with and without it are the same. The
decoder now accepts the bit on float compares; clamp on other carry-out
and scalar-destination opcodes still throws.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
The assembler encodes the unused third source of the VOP3-encoded
interpolation forms as the inline constant 0 (src2 = 0x80, bit 25 of the
second word), which the decoder refused. Accept that encoding and refuse
any other third source. The decode test now uses the assembler words for
every accepted form and keeps the refusals one field away from a valid
word.
Both open the same SDL capture port as sceAudioInOpen, so Input,
GetSilentState and Close work on their handles. HqOpen accepts only a
128-sample grain at 48 kHz (SharpProspero, KytyPS5); AsyncOpen returns
INVALID_SIZE above 384 samples (SharpProspero). Values the sources
disagree on throw. Format 0x10 still throws: only KytyPS5 accepts it.
Imported by FMOD-based titles, The Smurfs Dreams (PPSA21607) and Astro
Bot (PPSA21564); Ghost of Yotei (PPSA26344) calls sceAudioInOpen.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 16bfdf928d0a2da0b4473db2633a2e270538cbf5)
Root ignores the file mode, so truncating a file with only the owner
read bit succeeds and guest_truncate aborted when the suite ran as root,
as it does in containers. The read-only cases now run only for other
users and the test says on stderr when it skips them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
sceHttp2DeleteRequest erased the request's completion queue, but
sceHttp2DeleteTemplate and sceHttp2Term removed descendant request
handles through ReleaseOwnedHandles without clearing their completion
queues. A request that completed asynchronously before its template or
context was deleted kept a stale completion that sceHttp2WaitAsync could
still consume.
ReleaseOwnedHandles now returns every transitively removed handle id, and
DeleteTemplate and Term erase the completion queue of each removed
request. CompleteAsync validates that the request is live while holding
the handle lock and inserts the completion under it, so a send that races
a deletion either returns the invalid-id error without queuing or is
erased by the deletion; a completion is no longer re-created after a
deletion returns. The only lock nesting stays handles before completions,
and no mutex is held while the user event is triggered.
GuestHttp2 covers a queued completion before sceHttp2DeleteTemplate and
before sceHttp2Term: the completion is no longer consumable, a late send
returns the invalid-id error without queuing, and the removed handles are
rejected.
Ubuntu's GCC links with --as-needed, so a test that calls nothing in
libc.prx directly dropped it from DT_NEEDED and satisfied the prx
libraries' __cxa_* and __gxx_personality_v0 references from the static
libstdc++ instead. Those copies interpose libc.prx's, so exceptions
thrown inside the prx libraries were searched by libgcc and resumed by
the guest unwinder, which aborted guest_kernel_stack_protect. libc now
propagates to every consumer of a prx with --no-as-needed on Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
An SDL controller sets the L2/R2 bit whenever its trigger axis is above
zero, and BuildData then reported 255 for any set bit, so a partly
pressed trigger read as fully pressed. Report the analog value whenever
there is one; 255 remains for an injected press and for a bit without
an analog value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodeState bound the depth surface whenever DB_DEPTH_CONTROL enabled a
depth, stencil or depth bounds test and either plane was valid. A stencil
test beside a depth-only surface (DB_STENCIL_INFO format invalid, depth
test off) tests nothing, but the surface was still attached and the
render extent was clamped to it. A draw into a 1920x1080 target with a
960x540 depth surface left bound then covered only the top-left quarter.
A test whose plane is present but invalid no longer binds the surface.
Such a draw renders without a depth attachment, as it already does when
both planes are invalid. DrawRejection follows the same rule.
PPSA03490 (Demon Slayer) imports sceRudpGetMaxSegmentSize, sceRudpProcessEvents, sceRudpNetFlush and sceRudpNetReceived, which no library exports. The first three answer by the library's initialised state; sceRudpNetReceived is declared without an implementation because its signature is unknown. Adds one test and TechnicalDebt entries.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On the AMD Windows driver 32.0.13058.8001 (Radeon 860M), a NaN compared
less than +infinity, or +infinity greater than a NaN, gives 1 when the
infinity reaches the shader as a constant, as in
agc_driver_f32_compare_constant's register operand case (#3536). The
same driver returns 1 for a NaN in an ordered not-equal (#825), which is
presumably what its compiler turns these compares into. FPOrdLessThan32
and FPOrdGreaterThan32 now AND their result with the integer NaN check
FPOrdNotEqual32 already uses, which gives the same result for every
input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1cfbde5d69fe3b5c0aa482d4ff3551f0e87c3915)
SceErrorFromErrno in the libkernel file functions passed host errno
values 1-34 through and turned every other value into EIO. Linux numbers
EAGAIN 11, which is FreeBSD's EDEADLK, so a read from an empty
non-blocking pipe returned SCE_KERNEL_ERROR_EDEADLK, and ENAMETOOLONG,
ELOOP, ENOTEMPTY, EDQUOT and EOVERFLOW, whose host values differ from
FreeBSD's, all became EIO.
Map those host values to the guest's FreeBSD numbers, as
SceIoErrorFromErrno in Open.cpp already does for EAGAIN, EDEADLK,
EDQUOT and EOVERFLOW. The guest constants the callers pass are all at
most 34 and never 11, or GUEST_ENOTEMPTY, so they are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
Clang's PS4 and PS5 targets set the default new alignment and the suitable
alignment to 32 bytes, so PS5 code may store to a fresh mspace block with
32-byte-aligned AVX instructions, as the guest heap already allows since
#1621. The mspace allocator aligned blocks to 16 bytes only. Every mspace
allocation is now aligned to at least 32 bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV (Mesa 26.2.4, GFX12) updates the user SGPR count and draw-id flag
it uses for a mesh dispatch only when the bound mesh shader has the
grid-size/draw-id slot. A mesh shader reading neither NumWorkgroups nor
DrawIndex keeps the previous vertex pipeline's values: the dispatch
opens a 3-register SET_SH_REG, writes one value, and the CP parses the
following DISPATCH_MESH_DIRECT as register data. The gfx ring then
times out and the GPU is reset with VRAM lost.
Kena: Bridge of Spirits (PPSA01802) hits this on its layered volume
draws, which interleave with vertex draws in one command buffer.
Confirmed with RADV_DEBUG=hang (the dispatch header shows up as
SPI_SHADER_USER_DATA_GS_4) and the CP header history in the amdgpu
devcoredump.
Every mesh shader now loads NumWorkgroups.x and sets zero vertices
when it is 0, which no dispatch produces, so RADV gives it the
grid-size slot and writes as many values as it announces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover 16 paths with long leaves, exact-fit and overlong directory results, repeated separators, input preservation and guest errno. The regression reports 12 failures on unchanged main and passes with the accompanying fix.
AI-assisted: yes
Find the directory component before checking the result buffer limit, matching FreeBSD 11.4. Preserve input and guest errno on success; overlong directory results still report ENAMETOOLONG.
AI-assisted: yes
ValueClear constructs a Value, then calls the Value(const Object&)
constructor on the same object. The constructor allocates a new node and
overwrites the pointer, so the first node (56 bytes) is never freed.
LeakSanitizer reports it; destroy the value first.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
PPSA15246 (Teardown) imports both from the module-named libSceCesCs-module and
stops at the loader without them. Cp1251 joins Cp1252 in sceCesSbcToUtf8 and
sceCesUtf8ToSbc; the context initializer succeeds without storing anything
because its layout is unknown (listed in TechnicalDebt.md).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A pixel input whose SPI_PS_INPUT_CNTL offset names a parameter the
previous stage does not export failed validation ("graphics interfaces
disagree"), so the draw threw. No export writes that parameter slot for
the draw. radeonsi programs a pixel input without a matching vertex
output as OFFSET 0x20 with DEFAULT_VAL 0, so that it reads (0, 0, 0, 0)
(SI_PS_INPUT_CNTL_UNUSED in si_shader.h, applied in si_shader.c), and
rect-list draws already pass zero for such inputs.
Before the pixel stage of a draw is prepared, inputs that read a
parameter the previous stage's program does not export now read zero:
OFFSET bit 5 is set, DEFAULT_VAL and FLAT_SHADE are cleared, and an FP16
input also reads zero in its high half (USE_DEFAULT_ATTR1 set,
DEFAULT_VAL_ATTR1 cleared). Inputs that already read a default value
are unchanged.
Fixes#2975.
fmod set errno (33) for 1.0, 0.0. FreeBSD msun never sets errno and
reports errors through floating-point exceptions only, so errno is now
saved and restored around the host call.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Seed each iteration-selection update from its first eligible successor
instead of rebuilding and intersecting the universe of block IDs.
Reuse the existing in-place intersection and sorted-insertion helpers
while preserving the original empty-successor and fixed-point behavior.
Keep a cache-neutral switch for same-binary output and timing comparisons.
Run the existing structurizer and control-flow fixtures in a separate
process with this optimization disabled as well as in their default mode.
Publish Windows cache entries with the POSIX rename operation so an
open delete-sharing reader keeps its original file after replacement.
Close the rename handle before returning and retain the existing
fallback only when the extended operation is unsupported.
Exercise old and new reader contents, deletion lifetime and temporary
file cleanup in the registered disk-cache regression on both platforms.
The resolved-dispatch map was a nontrivial thread_local object.
MinGW can run its destructor after emutls frees the backing storage,
causing an access violation when a queue worker exits.
Use the existing ThreadOwned helper to destroy the heap-owned map
through its pthread key while keeping a trivial TLS pointer.
Reject FATAL output in both draw-cache test modes so a background
shutdown failure cannot pass only because the process returns zero.
Cover 20 clock IDs, preserved guest errno, non-null unsupported clocks and the SCE null-output fault. The regression fails against main and passes with the accompanying fix.
AI-assisted: yes
winpthreads initializes a statically initialized rwlock on its first
lock and fails a concurrent first lock with EINVAL, which
std::shared_mutex ignores, so two threads can hold the lock at once.
The source, binding plan and specialized module caches take their
first locks from several threads, so their mutexes now take and
release the lock in their constructor, as guest rwlocks do.
The GuestWriteWatch mutex the technical debt entry also named is in
the Linux-only watch, so it is not affected.
The guest fgets handed the size straight to the host. With a size of 0
glibc and the Windows runtime return NULL without setting errno or the
stream error indicator, and a negative size goes to the Windows
invalid-parameter path. FreeBSD's fgets checks n <= 0 before reading:
it sets __SERR on the stream, sets errno to EINVAL and returns NULL,
leaving the buffer and the file position untouched.
fgets_nid_postfix now does the same before calling the host. The
FileStream flag it uses was only set for encoding errors so far and is
renamed from SetEncodingError/encodingError to SetError/guestError; it
already stays set until clearerr like __SERR.
guest_standard_streams checks sizes 0, -1 and INT_MIN (NULL, errno 22,
error indicator set and cleared by clearerr, buffer and position
untouched) and that a following read still returns the line; it fails
without the change.
Fixes#3084
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A color view of slice N of a 2D array (CB_COLOR_VIEW SLICE_START, with
CB_COLOR_ATTRIB3 MIP0_DEPTH as the last slice) was rendered as a 2D
surface at the slice's address. The swizzle equations of the XOR tile
modes take the slice index (the detiler's slice bits), so the texels of
slice N of an array are not where a 2D surface at that address puts
them: a title that renders into array slices and then samples the array
read them scrambled.
The decoded target now keeps the array's address, its last slice and the
view's slice. A resident target that is a slice of an array attaches
that layer of the array's storage image, so the texels follow the
array's layout; storage images of 2D arrays are color attachments for
this, and an attachment view selects a layer. A slice whose array has
DCC, CMASK or an attachment proxy has no resident array image, and a
non-resident slice of an XOR-tiled array now throws instead of being
written in the 2D layout.
In #3338, Ratchet & Clank: Rift Apart (PPSA01474) hits a 36 GB / 5 s
memcpy stall at frame ~380 right after intro logos because the GPU
worker thread is blocked copying multi-gigabyte storage buffers on every
draw snapshot miss (specifically a 3.01 GiB streaming range
0x118f800000+0xbc200000 and a 232 MiB range 0x1171258040+0xe7fffc0
across 130,000 draws).
ShaderResources::PrepareDrawBindings now caps both per-allocation and
cumulative draw input buffer snapshot copies at 64 MiB by default,
overridable via APS5_DRAW_SNAPSHOT_MAX_MIB. Inputs exceeding the threshold
skip snapshotting and bind directly in place, preventing multi-gigabyte
memory copies while preserving standard small uniforms and draw arguments.
Budget allocation prioritises earlier descriptor bindings in deterministic
pipeline layout order.
Adds drawSnapshotCapTests in core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
verifying individual and cumulative capping, cross-platform cache behavior on
watched and compared hosts, and direct observation of subsequent CPU writes
on in-place bound ranges exceeding the cap. Exercised via --draw-snapshot-cap-only
registered as agc_draw_snapshot_cap in libSceAgcDriver/CMakeLists.txt.
PS5MC (PPSA99420) stops at start-up on wcstok once sendfile is in. It
follows FreeBSD's three-argument wcstok over the guest's 16-bit wchar_t:
leading delimiters are skipped, the token ends at the next delimiter,
which is overwritten with 0, and *last is set to null when the string
runs out.
sceKernelAprSubmitCommandBuffer ran the whole buffer on the submitting
thread, so a WaitOnAddress or WaitOnCounter that the submitter itself
satisfies after the submit returned never returned. A buffer now runs on
the submitting thread until such a wait; the rest of it, and every APR or
AMM buffer submitted after it, runs in order on one APR thread.
sceKernelAprWaitCommandBuffer and sceAmprAmmWaitCommandBufferCompletion
wait for that completion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wu3nuU87RJwjkkmxqft3sD
sceNpTrophy2GetTrophyInfo and sceNpTrophy2GetGroupInfo rejected a call
unless both outputs were given, although both already fill each output
only when it is not null. PPSA18391 (Clickteam Fusion) queries trophies
with a null details. Only a call with neither output is rejected now, as
in sceNpTrophy2GetGameInfo.
The bundled SDL builds its PipeWire, PulseAudio and ALSA drivers only
when their development files are found, and otherwise keeps just the
silent dummy and disk drivers without an error. The Linux CI and
release jobs install none of them, so the v0.1.1 Linux libraries
(libSceAudioOut, libSceAgcDriver, libfmod) contain no audible driver.
Fail configuration on Linux when SDL found none of the three, install
the Debian development packages in the Linux build and release jobs,
and list the packages in the build instructions.
PPSA04540 (Rise of the Ronin) imports it. The characters the callback
returns are parsed as by the (text, size) overload.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CB_COLOR_INFO format 17 with UNORM and the standard component swap renders
into VK_FORMAT_A1R5G5B5_UNORM_PACK16, whose attachment and blend support
Vulkan requires. AMD keeps X in the low bits and Vulkan keeps R in the
high bits, so the exports are mapped with X and Z exchanged and the stored
bits follow the guest layout. PPSA06699 renders into this format.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Defer registered NGG workgroup preparation until routing is known. Share the routing decision with static ABI resolution and draws. Gate the wave64 fixture on supported mesh subgroups; retain device-independent routing coverage.
PS5MC (PPSA99420) stops at start-up on sendfile. It follows FreeBSD 11:
headers, the file range and trailers go to a connected stream socket, the
file offset does not move, a non-blocking socket fails with EAGAIN after
a partial send, and *sbytes reports what was sent. The checks and the
point where *sbytes is written follow kern_sendfile.c.
The guest formatter threw on %n$ and *m$, which FreeBSD's vfprintf and
glibc accept. It now reads the arguments a numbered format names into a
table first, with the types their conversions give them, and follows
FreeBSD's rules for unnumbered conversions and unnamed arguments.
FreeBSD's vfprintf takes ' as the thousands-grouping flag. The Windows
formatter threw on it as an unsupported conversion. The guest's setlocale
only accepts the C locale, which has no thousands separator, so the flag
changes nothing: it is dropped before the host's printf sees it, and the
host's locale cannot add separators.
C and FreeBSD's vfprintf print a single 0 when # meets octal zero with a
precision of 0. The Windows formatter passed the conversion to the host's
printf, which printed nothing; it now asks for one digit instead.
EVO Player (PPSA99039), Jelly5 (PPSA99505), Emby5 (PPSA99515) and
Nuvio PS5 (PPSA99997) stop on main 0f863c10 with
what(): sceAgcCreatePrimState: invalid user configuration register offsets
All four build their shader headers by hand (EVO-PLAYER-PS5
evo_agc_shader_header.c, ported from ps5-xash3d-halflife) and store the
ge_user_vgpr_en special at 0x25c (GE_USER_VGPR1, value 0) instead of
0x262 (GE_USER_VGPR_EN), as analysed in #3457. They run on the console.
OpenAGC's reconstruction of the FW 5.50 sceAgcCreatePrimState
(src/game_compat.c at 3c7e8072) copies the four special register pairs
verbatim without checking their offsets; KytyPS5's offset checks
(src/libs/agc.cpp at 16a7fe68) are EXIT_NOT_IMPLEMENTED guards. The
special is now copied at the offset the header stores, not moved to
0x262; the driver reads neither register. GE_CNTL, VGT_SHADER_STAGES_EN
and a used VGT_GS_OUT_PRIM_TYPE are still required at their own
offsets because the driver reads them there, and the error now names
the special and the offset found.
With the change all four titles get past sceAgcCreatePrimState and stop
in the shader recompiler (GetImageResource dword 3 is not a valid
runtime value), as #3457 reported for EVO Player.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit aeaa5080d9595305aa73e74d966f989c88b79839)
FreeBSD's fsync and fdatasync succeed on a descriptor opened read-only and on a directory. _commit needs write access, so both returned EBADF there. A read-only descriptor is now flushed through a write handle from ReOpenFile and a directory through a handle opened for it, as sceKernelSync already does.
A merged (NGG geometry path) program's user data starts with eight
hidden words, so the vertex buffer and attribute table offsets of its
AGC header, which count from the first user SGPR, were read eight words
too early and found zeros. Killzone: Liberation (PPSA11142) stopped on
`AGC graphics: null vertex buffer table address` at the first frame of
its PSP game. The decoder now adds the hidden word count to the table
reads; the registers reported to the recompiler are unchanged, as it
already maps them from the first user SGPR. `agc_driver_graphics` gains
a merged and a plain layout case.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Porpoise PPSA99764 stops after pthread_attr_* (#3494) with the
predicted `FAIL: unresolved ELF import h05OHOMZNMw from
libSceLibcInternal.prx` (idx 311, call site 0x8EC37); RetroArch
PPSA99169 imports it at idx 448 (call site 0xA4A27). h05OHOMZNMw is
ftrylockfile by tools/nid_names.py and aerolib.csv (zecoxao/sce_symbols
2883963a); ps5-payload-dev/sdk f5d40f02 declares
`int ftrylockfile(FILE *)` in include/freebsd/stdio.h.
ftrylockfile tries the same host CRT stream lock that flockfile,
funlockfile and every locking host stdio call on the stream take, so
no second lock exists beside the one stdio uses. It returns 0 when the
lock is acquired or already owned by the caller (recursive, released by
as many funlockfile calls) and -1 when another thread holds it, as
FreeBSD 8c639945 lib/libc/stdio/_flock_stub.c:89-104 does. Linux calls
glibc ftrylockfile. UCRT has no try variant of _lock_file, so Windows
calls TryEnterCriticalSection on the CRITICAL_SECTION that _lock_file
enters (__crt_stdio_stream_data::_lock, ucrt/inc/corecrt_internal_stdio.h,
same layout in Windows SDK 10.0.19041, 10.0.22621 and 10.0.26100). A
null or closed stream throws. The UCRT layout dependency is recorded in
TechnicalDebt.
guest_standard_streams covers trylock on an unlocked stream, re-entry
by the owner, failure from another thread while held (also after one of
two unlocks and while another thread holds flockfile), stdio calls
from another thread blocking on the trylock-held lock, release order,
and null and closed streams.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PPSA04540 (Rise of the Ronin) imports both and now loads past them;
calling either throws until NGS2 produces reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA04540 (Rise of the Ronin) imports it. Like sceRazorCpuPushMarkerStatic
it does nothing without a capture and returns 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lane set contained in X is contained in WQM(X). Containment is proven
through BitwiseAnd and through ballots evaluated in the uniform entry
blocks, where every invocation is active. Under a whole quad mode mask the
masked lanes form whole quads, so implicit-LOD pixel samples, which only
read their own quad, do not observe them.
This removes the selects that kept the packed pixel ancillary input alive
after its fields were extracted in GTA V (PPSA04264) pixel shaders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
Keep the AlxcBTW #24 fallback as a separate guarded attempt before enabling shared-exit and nested-continuation repairs. Add the reduced padded shader reported by orishamir on #2523 as an end-to-end regression; the abstract graph does not reproduce the same failure.
The third structurize pass, which routes loop exits that leave the loop
before they meet, runs only when the plain and the privatized pass
throw, and its result was used without a check. Merged with #2471, it
turns some of those budget failures into graphs that spirv-val
rejects. In this one, selection 12 of the result lies in the selection
at 7 and branches to 7's merge block 13:
{{1}, {2, 3}, {4}, {5}, {6, 7}, {}, {8, 9}, {10, 11}, {1}, {12, 4},
{5}, {13}, {8, 14}, {15, 16}, {7}, {13}, {4}}
block 12 exits the selection headed by block 12, but not via a
structured exit
hasStructuredConstructs checks the third pass's result against the
structured control-flow rules of the SPIR-V specification (2.11), with
dominance and post-dominance taken over branch, merge and continue
edges. Back edges come only from a loop's continue block. A conditional
without a merge breaks or continues its innermost loop. A header
strictly dominates its merge, and a loop header its continue block. A
construct contains the merge of every header inside it and is entered
only through its header. It is left only to its own merge or to the
innermost loop's merge or continue block, and a continue construct only
to its loop's header or merge. When the result fails, the second pass's
error is reported, as when the third pass throws. The first two passes
are unchanged.
Differential fuzz with the structured-program generator and random
reducible graphs. Oracle: exact post-dominators,
requireStructuredBranches, merges dominated by their headers, the
original's executions, and spirv-val (SPIRV-Tools v2026.4).
- Against 9fa7d06a: the same outcome and output on every graph. That
covers 100,000 random graphs at 0 words and with one block at 1000,
and 30,000 programs at 16 and at 1000 words per block, each also with
one block at 1000.
- Merged with #2471, the same 30,000 programs: the third pass had made
41, 42 and 45 of them invalid (16 words, 16 with one block at 1000,
1000 words). These end in #2471's own error again, and nothing else
changes. The 1, 1 and 18 graphs that d59a9167 merged with #2471
already structures invalidly come from the first two passes and stay
as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A loop whose exit paths leave the loop construct before they meet got
the nearest common post-dominator of its exits as merge, and
splitSharedMergeBlocks never separated it. An exit continues the
enclosing loop, so its path to that merge runs through the outer
header again, or an exit enters a block that a branch before the loop
also reaches. splitOneLoopMerge gave the loop a forwarding merge block
each round, the other path still bypassed it, and the post-dominator
stayed the same block until "CFG shared merge splitting exceeded
budget". These shapes from #2642 fail that way on main, on d59a9167,
with #2471 and with both:
{{1}, {2}, {3, 4}, {2, 5}, {}, {1}}
{{1}, {2, 3}, {4, 5}, {}, {3, 6}, {1}, {2}}
{{1, 3}, {2, 4}, {3, 1}, {4}, {}}
{{1, 2}, {3, 2}, {4}, {4, 1}, {}}
{{1, 2}, {4}, {3, 1}, {4, 2}, {}}
When the plain and the privatized pass both fail, Structurize now runs
a third pass. It routes such a loop's exits through one junction with
routeLoopExits, as loops whose exits have no common post-dominator are
routed already, and gives an outer loop whose only latch lies in an
inner loop its own continue block. In that pass a loop exit conditional
that is neither a direct break or continue nor has an exit tail of its
own is structured as a selection, and an exit tail that the other arm
reaches is not used as one. Without that, graphs the routing unblocks
keep a conditional without merge (the rule from #2762) or a selection
entered past its header. Graphs that the first two passes structure
are unchanged, and when the third pass fails too, the second pass's
error is reported.
Differential fuzz against main, reducible graphs with 5 to 9 blocks,
each with 0 estimated words per block and again with one block at 1000
words (the privatizeSharedReturns path). Oracle: exact post-dominators,
requireStructuredBranches, merges dominated by their headers, the same
executions as the original graph, and spirv-val (SPIRV-Tools v2026.4,
Vulkan 1.3) on a module emitted from the structured graph.
- 200,000 random graphs. main structures 178,743 (0 words) and 178,750
(1000 words); the output is identical on all of them. Of main's
14,256 and 14,311 budget failures 13,527 and 13,596 now structure,
and 2,576 and 2,565 of its refusals. No new output is invalid. The
invalid outputs are main's 530, plus at 1000 words one graph that
d59a9167 already turns from a budget failure into a back edge to a
block that is not a loop header.
- 24,000 structured programs from orishamir/anyps5-cfgfuzz's
generator (if/else, loops, break, continue, return). main structures
21,093 and 21,022; the output is identical. 619 of 2,448 and 634 of
2,429 budget failures now structure. No new output is invalid; the
invalid outputs are main's 92.
agc_structurizer has the five shapes; requireOriginalBranches now
follows route variables. agc_shader_control_flow has two RDNA programs
of the first two shapes. The non-GPU tests and the 253 agc_driver GPU
tests pass on Windows (RTX 5070 Laptop).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A selection in a loop whose arms both reach their nearest common
post-dominator within the iteration got that post-dominator as its
merge. When an arm can also continue the loop, the post-dominator
follows the back edge: the continuing path re-enters the loop header
and reaches the outer selection's merge without passing the block where
the arms meet. In the loop {1}, {2, 4}, {3, 4}, {4, 5}, {1, 6}, {1}, {}
from #2642 (latches 4 and 5) the selections at 1 and 2 both merged at 4.
splitSharedMergeBlocks gave 2 a forwarding merge block, recomputed 4 for
it because 3 continues through 5, and repeated until "CFG shared merge
splitting exceeded budget: originalBlocks=9 currentBlocks=45
splitBudget=36".
When that post-dominator is inside the loop construct, findSelectionMerge
now takes the arms' nearest common post-dominator over the paths of the
iteration that reach it, so paths that leave through the continue block
or the loop's merge no longer move the merge past the arms' join. When
no arm leaves early the result is the same block as before. The loop
structures with two forwarding blocks and no clones or route variables;
so do three latches, the selections on the false side, and an if-else
whose one arm continues.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lseek on a non-directory descriptor no longer takes the directory table lock when no directory is open, and a listing is read under the descriptor's own lock. SEEK_CUR with offset 0 only reports the position, and an overflowing SEEK_CUR offset reports EOVERFLOW.
With f16 rounding toward -infinity, v_fma_mixlo_f16 and v_fma_mixhi_f16
gave +0 for an exact cancellation such as 1.0 * 1.0 + -1.0, because the
f64 sum is computed to nearest. RDNA2 gives -0 there, as IEEE 754 does
for an exact zero sum rounded toward -infinity, measured on gfx1036 in
#2631. An exact zero sum now gives -0 in that mode unless both the
product and the addend are +0. A nonzero result that rounds to zero
keeps its sign, so a tiny positive sum still gives +0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The f16 forms of v_fma_mix threw for any FLOAT_MODE with an f16 rounding
other than to nearest even. ps5-opengl's shaders run with FLOAT_MODE
0xcc, f16 rounding toward zero with f16 denormals kept, so PPSA99005
stopped at its first draw (#2631).
The exact product-sum, already computed in f64 with round to odd, is now
rounded once in the mode's direction: scaled by the f16 step at its
exponent, truncated, floored or ceiled, and scaled back. A result beyond
65504 becomes infinity or 65504 as IEEE 754 gives for the direction. On
RDNA2 (gfx1036) -1.5 * 2^-24 + 2^-126 gives 0x8002 with round toward
-infinity, and this gives the same. Flushed f16 denormals still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up to the 8_8 SWAP_ALT blending. The second channel of
such a target holds the guest's alpha. Vulkan applies the green blend
constant to it for CONSTANT_COLOR and ONE_MINUS_CONSTANT_COLOR, where the
guest's alpha equation uses the alpha constant. Decoding now throws for
these factors on a SWAP_ALT 8_8 target, unless the equation is MIN or
MAX, which ignore factors.
The second pass of a split draw runs every stage again, not only the
pixel shader, so a vertex or geometry stage that writes memory would
write twice. The memory-write check (now ShadersWriteMemory) covers all
the draw's stages.
A shader preparation transaction took the global writers lock when it
was constructed and kept it to its end. ResolveShaderAbi, reached from
sceAgcCreateInterpolantMapping and sceAgcLinkShaders, compiled its
variants inside such a transaction, so every game thread creating or
linking shaders waited behind one compile: Uncharted's stalled frames
spent about half a second each in shader registration.
The root transaction now takes the lock on its first Read, Edit or
Hold. ResolveShaderAbi prepares the variants before reading the
prepared state when its ABI was not committed yet. Registry
publication holds the lock as before: PublishRegisteredShader holds
it, and RegisterShader holds it before the driver mutex, so the lock
order is unchanged. Commit takes no lock: a transaction with changes
already holds it.
Propagation: Paradise Hotel (PPSA15878) does not load on main: its
executable imports 15 PS VR2 functions that no library exports, the
first reported being sceVrTracker2GetPlayAreaOrientedBoundingBox
(snYs7Nf-RKk). Declare the nine libSceHmd2 reprojection and gaze
exports, three libSceVrTracker2 exports and three scePadVrController
trigger effect exports, without parameters, as throwing stubs. Names
come from the aerolib NID list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on a PS5 (firmware 13.60): sceSysmoduleIsLoaded,
sceSysmoduleLoadModule and sceSysmoduleUnloadModule with id 0, and the
Internal and InternalWithArg calls with id 0 or 0x80000000, return
0x805A1000 and leave the WithArg result untouched. They threw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The empty-path utimes check restores the working directory's write time
afterwards and requires the restore to succeed. With MinGW,
std::filesystem::last_write_time fails with EACCES when setting a
directory's time (a file's works), so guest_filesystem aborts there on
Windows. The restore stays, only its result is no longer checked.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
inet_pton and inet_ntop no longer call the host resolver library, whose
accepted forms and output differ from the guest's (leading zeros,
zone ids, IPv4-embedded printing).
(cherry picked from commit 058d808ae7c43042d0ffd5fad76062a2df16b871)
Two catalog titles stop on the absent libSceLibcInternal export reallocf
(YMZO9ChZb0E) on main 0f863c10:
- PPSA97510 PSXS5 2.2.0
- PPSA98510 SwanStationPS5 1.1.3
FAIL: unresolved ELF import YMZO9ChZb0E from libSceLibcInternal.prx
reallocf follows FreeBSD 11 lib/libc/stdlib/reallocf.c (freebsd-src
stable/11 5ed7eb0d97ba4436218e810f61bd059acba984c2) on top of the guest
realloc: it reallocates through the application heap, and when realloc
fails (the application allocator returns null or the default heap
rejects the size) it frees the block through the same allocator, sets
ENOMEM and returns null. Size 0 frees and returns null as realloc does
here, so the block is not freed twice. Other heap failures throw, as
for realloc. The NID matches tools/nid_names.py and aerolib.
The application heap tests cover grow, shrink, a null pointer, size 0
and failure with both the replacement and the default allocator.
Co-Authored-By: screamm <noreply@github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Killzone: Liberation (PPSA11142) writes its frame fence with
`sceAgcDcbWriteData(dst 6, cache policy 3, write confirm)`, which the
SPRX-confirmed encoding turns into WRITE_DATA with DST_SEL 3 and a label
address in the title's memory that RELEASE_MEM and WAIT_REG_MEM then
use. The validator rejected DST_SEL 3 as the GDS; it now writes guest
memory like DST_SEL 5, resolves as a label, and the title runs past its
first frame. TechnicalDebt records where the semantics come from.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 9c53b7f6e8ce2ca81d8f0fa551d12632acfeb60d)
Titles that call Open/Speak after a failed initialize should get the same unavailable-engine error instead of a throw.
Co-authored-by: Cursor <cursoragent@cursor.com>
Deathloop (PPSA01670), Minecraft Legends (PPSA05510) and GTA III
Definitive Edition import both from libSceAmpr. They take a pointer to
the handle slot and act on the conditions created by the libc _Cnd_init:
_Cnd_signal wakes one waiter through a libc helper, _Cnd_init_with_name
is _Cnd_init with the name ignored. _Thrd_id still throws.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Document the level-triggered user event semantics of sceKernelWaitEqueue in TechnicalDebt.
(cherry picked from commit db79bee1e597effff34c72493ed893d4e750b95d)
A user event added with sceKernelAddUserEvent stays triggered until it is
deleted. The collection loop kept re-emitting it until the caller's event
buffer was full, so a wait with num > 1 returned the same event num times.
Like kevent, each event is now reported at most once per call; distinct
queued completions of one event are still delivered in order.
(cherry picked from commit db28ebbb7f078cb593cf6b7bb34b8db49dd94630)
The half-wave scan rules for 32-lane hosts made lane 31 of a matched
scan a subgroup reduction and threw on a device without subgroup
arithmetic, where the read used to build as a shuffle of the scan. Such
a device now keeps the shuffle for lane 31; lane 63 still reads the
identity, which needs no arithmetic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wave64 vertex program with the culling scan of #2783 (lane < s3's
primitive count, row_shr bound_ctrl:1 OR scan, v_permlanex16_b32,
v_readlane_b32 of lanes 31 and 63) compares the wave OR with the ballot
of its keys and collapses its triangle when they differ. On a 32-lane
device every pixel of the grid must be covered.
Before the s3 change the program does not build on 32 lanes; with a
shuffle from lane 31 instead of the subgroup reduction no pixel is
covered, as the device leaves lanes of each vertex warp idle. Other
subgroup widths skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wave64 vertex program runs at one guest lane per invocation, so on a
32-lane host subgroup its lanes 32-63 have no invocation. The vertex
entry still set s3 = 64 | 64 << 8, telling the program that its wave
holds 64 vertices and 64 primitives. An NGG culling program then keys a
full-wave OR on lane < s3's primitive count, lanes 32-63 hold real cull
results, and its v_readlane_b32 of lane 63 cannot be built (#2783).
The entry now sets s3 = 32 | 32 << 8 for a wave64 program on a 32-lane
host, the wave the host runs; the console launches waves with fewer
vertices and primitives. The host subgroup width reaches the vertex
input the way it reaches the compute and mesh inputs
(BuildShaderStageInputInfo from HostSubgroupSize), and the source,
prepared-shader and disk cache keys already carry it for every stage.
Programs that do not read s3 are unchanged; wave32 programs and other
host widths keep the full wave.
The keys are then 0 in lanes 32-63, so the read is sound. On a 32-lane
host, when the existing half-wave scan rules fail, the scan may also:
- run under s_mov_b64 exec, -1 (EXEC constant true);
- use bound_ctrl:1 row_shr steps when 0, the fill, is the identity (OR,
add, unsigned max), also into another register, whose old value never
shows since such a step writes every lane;
- take keys shown to be 0 in lanes 32-63: built from 0 through selects
under a lane id or v_mbcnt of -1 below at most 32, v_cmp_ne_u32 against
0, ORs, DPP and permlanes, in stages whose entry EXEC is every lane.
Lane 63 is then the identity and lane 31 the subgroup reduction of the
host's invocations. A shuffle from lane 31 is not enough: an RTX 5070
runs the vertex warps of a non-indexed triangle list with 30 of their 32
invocations. A v_readlane_b32 outside the host subgroup of another value
that is 0 in lanes 32-63 reads 0. Only reads that used to throw change,
besides lane 31 of such scans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XPSemu (PPSA97358) stops next at sigsetjmp (aRo9AhFUXcM) from libkernel,
per the body of #3323 "feat(libkernel): implement cpuset_setaffinity for
the calling thread or process": "XPSemu: with this PR, the next stop is
`sigsetjmp` (`aRo9AhFUXcM`)". RetroArch and PS5MC list it under libkernel
in the absent-import tables of #1578; aerolib names aRo9AhFUXcM sigsetjmp.
sigsetjmp is FreeBSD's amd64 sigsetjmp (lib/libc/amd64/gen/sigsetjmp.S,
release/11.4.0 8c639945), the counterpart of the merged siglongjmp (#2935)
and its sigjmp_buf layout (sys/x86/include/setjmp.h, _JBLEN 12): it stores
savemask at offset 88 and, when it is nonzero, saves the current mask at 72
through _sigprocmask(SIG_BLOCK, NULL, oset); then it records the return
address, rbx, rsp, rbp, r12-r15 and the x87 control word, and returns 0.
guest_signal_sets now jumps through the export instead of its copy of
FreeBSD's sigsetjmp, and checks that savemask 0 leaves the mask slot
untouched, a nonzero savemask records the current mask and its own value,
and the callee-saved registers come back after siglongjmp.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 8a609a5a1f189dce107a871e7da0fd55c746921f)
The macOS image initializer called a guest module's DT_INIT with no
arguments, so a module started with sceKernelLoadStartModule never saw
its args and argp, and its result was dropped. The Linux writer already
calls DT_INIT through libkernel's pending argument handoff. On the
macOS port, the PSN plugin of PPSA12544 refuses to start without its
argument block ("PSN is an old version that cannot be used by the
current player runtime") and the title then crashes.
A guest module with DT_INIT now gets two slots bound to
__aps5_get_pending_module_args_nid_no_patch and
__aps5_set_module_init_result_nid_no_patch, and its initializer calls
DT_INIT with the args and argp that sceKernelLoadStartModule records,
as on Linux, then hands the result back. The TechnicalDebt sentence
about the missing arguments on macOS is removed.
guest_module_args also relinks its fixture with --macos and checks the
two binds; on x86-64 macOS it loads the module and checks that DT_INIT
receives the arguments and that its result is returned.
AI-assisted: yes
Stremio Plus (PPSA74126) stops at start-up on _ioctl, which its statically
linked tc* functions call. FreeBSD's _ioctl is the same system call as
ioctl, so it forwards to ioctl.
Implement arc4random (UISQ-dQzw28) in libScePosixForWebKit on top of the
existing arc4random_buf, so both draw from the same thread-local host
std::random_device. Like FreeBSD 11.4 lib/libc/gen/arc4random.c
(8c639945) it returns a uniformly distributed 32-bit value and has no
failure result.
Cloud Play (PPSA99600) imports it: the import audit in #3249 on main
ccf9ff90 lists `UISQ-dQzw28` `arc4random` among nine absent imports
("no PR or issue found"). The ps5-payload-dev SDK stubs (f5d40f02,
sce_stubs/libScePosixForWebKit.c) place arc4random, arc4random_buf and
fnmatch in libScePosixForWebKit; arc4random_uniform is in neither that
set nor libSceLibcInternal, so it is not added.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 95da68017f6817abec785864e682530e9b135a2b)
Register copied the protection it had recorded for pages it already
watched over the protection the kernel had just reported. A guest that
reserves address space, which registers it as PROT_NONE, then maps
direct memory over it read-write and registers it again kept the stale
PROT_NONE. Collect only write-protects pages with PROT_WRITE, so writes
to that memory were reported once and never again. With PPSA12544 on
the macOS port, the title menu text blinked and glyphs went missing.
Register now keeps a recorded protection only where its own write
protection explains what the kernel reports, and takes the kernel's
protection otherwise. A protected page that was mapped again also loses
its protected mark, so erase no longer restores the old protection over
the new mapping. The kernel is read under the lock, so a concurrent
Collect cannot protect a page between the read and the merge.
Test write_watch_mapped: a reservation, a mapping over it and a second
registration keep reporting writes, and a protected page reserved again
stays PROT_NONE.
AI-assisted: yes
There is no system trophy UI, so the call returns success without showing a
list, like the other silent NpTrophy2 entry points; recorded in
docs/dev/TechnicalDebt.md. PPSA15552 (Dead Cells) calls it and
aborted on the NotImplemented exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dqgm9nUKygsXzr6poJsHJ
The pixel program packs its color for the export format, and the test now draws with FP16, UNORM16 and SNORM16 exports: an alpha of 0.75 keeps the pixel, 0.25 drops it, and so does -0.5 with SNORM16. Passes on an RTX 3050, a Radeon 660M and lavapipe.
Formats 4, 5, 6 and 9 (FP16, UNORM16, SNORM16 and 32-bit float ABGR) decode with alpha-to-coverage; formats 1, 2 and 3, which carry no floating-point alpha, still throw.
A dispatch whose group count exceeds maxComputeWorkGroupCount aborted the
process; a software device (lavapipe) reports the spec minimum 65535, and
Ghost of Yotei (PPSA26344) dispatches 129199x1x1 in its presentation path.
The count is now recorded as consecutive vkCmdDispatchBase chunks of at
most the limit per dimension, each continuing the grid at its base offset
(a plain vkCmdDispatch would restart group ids at 0 and re-execute the
first chunk's workgroups), with a barrier between chunks so a later chunk
sees an earlier one's writes. Compute pipelines are created with
VK_PIPELINE_CREATE_DISPATCH_BASE_BIT, which a nonzero base requires.
A split on NVIDIA throws instead of dispatching: measured on an RTX 5070
Ti, the driver drops the first workgroups of a vkCmdDispatchBase dispatch
with a nonzero base (reproduced with a standalone Vulkan program outside
this repository), so its 65535 Y/Z limits cannot be split through. A split
of a dispatch whose LDS lives in device memory also throws: its buffer
slot base is computed from gl_NumWorkGroups, which inside a chunk is the
chunk's count, so workgroups of different chunks would alias (a 1D X split
only works because the base degenerates to the group id).
Fixes#3212
The Linux desktop entry was written for every non-Windows output, macOS
included since --macos landed. It is now written only for Linux output,
and its paths go through Io::NativePath/Utf8Path like the rest of the
relinker. An end-to-end test relinks for Linux, macOS and Windows.
Co-Authored-By: Claude <noreply@anthropic.com>
GuestMemory.cpp carried its own procmap_query fallback and a ProcMapsQueryFd that opened /proc/self/maps once, probed PROCMAP_QUERY and honoured APS5_NO_PROCMAP_QUERY, the same as HostMapping::QueryFd. It now includes HostMapping.hpp and calls HostMapping::QueryFd.
MarkDirty stores uncompressed keys at an image's first write and marks
its uploaded keys uncompressed. The write-back stored them again,
whenever the key bytes did not read uncompressed. By then the title may
have reused the metadata memory: RIDE 4 (PPSA01599) frees its 1920x1088
render targets after the legal screen and Unreal Engine's small-block
allocator takes the 64 KiB key block that followed one of them. The
deferred write-back then filled the allocator's free lists with 0xff
and the title crashed popping one.
The write-back now stores keys only when the image's uploaded keys are
not uncompressed already, that is when no store at the first write or
an earlier write-back made them so since the keys were last read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA08427 (Killing Floor 3) stops on main at start-up because its bundled
pros.sdk.Prospero.prx imports it:
pros.sdk.Prospero.prx.guest.prx: undefined symbol: y7JD1gfwyfA
The signature is unknown, so it is declared without parameters and
throws, like the other entitlement flag queries the title does not reach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
note_bda_write loaded the chunk's page mask and set the page's bit after the probe loop, through a function variable holding the slot. NVIDIA 610.57.04 (Linux) never finishes a dispatch that takes that path: agc_driver_bda_device and agc_driver_flat_apertures_0, _2, _3, _4, _5 lose the device. The same load and conditional OpAtomicOr now run in the loop body, on the iteration that finds or claims the slot.
Dungeons (PPSA16064) calls select on descriptor 0 and stopped with
"select on descriptor 0 (not a libSceNet socket) not implemented".
Descriptor 0 is now never readable and descriptors 1 and 2 are always
writable, as for a console title with no input. The assumption is in
TechnicalDebt next to the termios entry for the same descriptors.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
The abort only said "select on descriptors that are not libSceNet
sockets", which leaves no way to tell from a title's log what it waited
on. It now names the descriptor and says whether libSceNet was loaded.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
select passed every descriptor it was given to the libSceNet socket poller
and aborted on any it did not know. A title that selects on its
/dev/urandom descriptor (Dungeons, PPSA16064, aborts right after opening
it) stopped with "select on descriptors that are not libSceNet sockets".
On FreeBSD the random device is always readable and writable, so these
descriptors are now answered without the poller, and a ready one makes
the socket poll return at once.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
sceAgcDebugRaiseException (NID T6xuVw0KUJo) now logs the exception id
with APS5_LOG_OUT and returns 0, as libkernel's
sceKernelDebugRaiseException does.
Palworld PPSA23126 (#2598) and 007 First Light PPSA11386 (#2523) import
it; neither is shown calling it.
OpenAGC 3c7e8072 (src/game_compat.c) returns 0 and says the function is
a no-op on retail hardware; the ud2 trap it describes for development
hardware is not reproduced. KytyPS5 6f0cddd4 (src/libs/agc.cpp) takes a
uint32 exception_id, logs it and returns 0. OpenAGC declares no
arguments, but a body that only traps reads none, so the uint32 is kept
and the conflict is recorded in TechnicalDebt. The aerolib name is from
zecoxao/sce_symbols 2883963a; ps5-payload-dev/sdk f5d40f02 has only an
untyped stub.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
image_sample_c_lz on gfx1035 returns 0 for 8-bit sRGB, so the unorm clamp was the wrong result. Bilinear cube comparison without DISABLE_CUBE_WRAP keeps throwing because that filter blends across faces.
Co-authored-by: Cursor <cursoragent@cursor.com>
On an RTX 4060 (Ada, driver 617.42) the optimized link of a pipeline built
from libraries loses the device (VK_ERROR_DEVICE_LOST) as soon as it is
bound, which fails agc_driver_pixel_valid_mask in 16 of 20 runs. With
APS5_NO_PIPELINE_LTO=1 it passes 20 of 20. Reporters see the same loss on an
Ampere card with a depth attachment; two Blackwell cards do not lose it.
A device whose driver is VK_DRIVER_ID_NVIDIA_PROPRIETARY keeps the
fast-linked pipeline and no longer queues the link-time-optimized relink.
Other drivers are unchanged.
Refs #2866
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GuestMemory.cpp reads PROCMAP_QUERY_VMA_READABLE and PROCMAP_QUERY_VMA_WRITABLE from the query result. The fallback for kernel headers without PROCMAP_QUERY now defines them too, so GuestMemory can use this header instead of its own copy.
The icon, title and output path are checked before the ELF is written,
so a malformed icon0.png or param.json leaves no output behind. Paths
with control characters are refused instead of producing an invalid
entry.
Co-Authored-By: Claude <noreply@anthropic.com>
ELF has no icon resource, so when sce_sys/icon0.png sits beside the input
the relinker copies it next to the output and writes <output>.desktop
(freedesktop Desktop Entry) with that icon. Name is the param.json title
without trademark signs or line breaks, or the output name without
param.json. Exec is quoted per the spec and the entry is marked executable
so file managers launch it. A malformed icon0.png or param.json fails the
relink; without icon0.png nothing extra is written.
Co-Authored-By: Claude <noreply@anthropic.com>
sceAgcFuseShaderHalves returns a fused hull as a copy of its back half
(type 7) that is not registered: type 3, no user data, and shader
registers copied to scratch memory with the checksum, RSRC1/RSRC2 and
the LS program address rewritten. sceAgcCreatePrimState accepts only that
type-3 header, and graphics ABI resolution refused it as a replaced
shader header.
SameHeader now also takes a type-3 header as the registered type-7
header for the same code when every other field is equal and its shader
registers differ only in the fused ones. Graphics ABI resolution then
applies the bound header's shader registers over the registered state.
PPSA26344 fuses its hull halves during its loading screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One 4-page direct mapping loses its second page to munmap, then pages
1-2 are mapped fixed. The range starts in the hole; before the fix the
unmap of the overlap threw "guest protection or unmap range has a
hole". The pages around it keep their data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dungeons (PPSA16064) calls select on descriptor 0 and stopped with
"select on descriptor 0 (not a libSceNet socket) not implemented".
Descriptor 0 is now never readable and descriptors 1 and 2 are always
writable, as for a console title with no input. The assumption is in
TechnicalDebt next to the termios entry for the same descriptors.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
The abort only said "select on descriptors that are not libSceNet
sockets", which leaves no way to tell from a title's log what it waited
on. It now names the descriptor and says whether libSceNet was loaded.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
select passed every descriptor it was given to the libSceNet socket poller
and aborted on any it did not know. A title that selects on its
/dev/urandom descriptor (Dungeons, PPSA16064, aborts right after opening
it) stopped with "select on descriptors that are not libSceNet sockets".
On FreeBSD the random device is always readable and writable, so these
descriptors are now answered without the poller, and a ready one makes
the socket poll return at once.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
Subnautica Below Zero stops while compiling a menu shader that compares an sRGB color texture or a bilinear cube face. sRGB uses the unorm reference clamp, and a bilinear cube sample stays on the face it hits.
Written with AI assistance.
GuestAllocationsUnmap took the piece containing the cursor from the
allocation's original extent. After part of an allocation was unmapped,
a later unmap or fixed map starting in that hole still matched the
allocation, and replaceRange threw on the hole. Match against the
registered range itself and end each piece at its end. RIDE 4
(PPSA01599) maps fixed over a partly unmapped direct memory range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A child process runs from a root whose app0/sce_sys/playgo-chunk.dat
declares 3 chunks: ids 0-2 are local, id 3 is rejected with
SCE_PLAYGO_ERROR_BAD_CHUNK_ID and scePlayGoGetChunkId counts 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without playgo-chunkdefs.xml every chunk id was accepted, so a title
that probes chunk ids until SCE_PLAYGO_ERROR_BAD_CHUNK_ID never stops.
RIDE 4 (PPSA01599, Unreal Engine 4) grows its chunk list until it runs
out of memory. The package's chunk table header holds the chunk count
at offset 0x0A; ids below it are valid. Every id is still accepted only
when neither file exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD fchdir(2) changes the working directory to the directory referenced by a descriptor. The guest imports fchdir from libkernel_sys (PPSA99764, issue #1578) and the module did not export it.
The descriptor's directory is resolved from the path recorded when the directory was opened on Windows, and from the /proc/self/fd link target on Linux. The guest working directory is then set through the libc routine shared with chdir. A descriptor that is not open returns EBADF and one that is not a directory returns ENOTDIR.
AI-assisted: yes
(cherry picked from commit 76329cbb08781e13d757c3ff955acf71f8bd506f)
The system-dependency rule was case-sensitive, so FIND_PACKAGE(SDL2) passed,
and its trailing word boundary failed when whitespace followed the opening
parenthesis. find_path, find_program and find_file were not covered at all.
The pattern is now case-insensitive, anchors each command name, and covers
the find_* family.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
`truncate` (`ayrtszI7GBg`) is an absent import of PS5MC (PPSA99420),
XPSemu (PPSA97358) and Porpoise (PPSA99764) on `main`. It resolves
through `sceKernelTruncate`, which now returns `EISDIR` for a directory
and the host errno for other failures instead of `EIO`, as FreeBSD 11
`kern_truncate` does. The guest-relative test `guest_truncate` covers
shrinking, growing with zero fill, a negative length, a missing path,
an empty path, a null path, a directory and a read-only file.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hypot(inf, NaN) returned a NaN on main. C Annex F returns +inf when
either operand is infinite, even if the other is a NaN, and FreeBSD
msun (lib/msun/src/e_hypot.c) does so for a signalling NaN too. The
infinite case is now checked before std::hypot; other inputs do not
change.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
#698 taught the reader, the module builder and the Windows writer to
resolve relocations against SHN_ABS symbols and to leave them out of the
exports, but the macOS writer still threw "Unsupported guest export
section" for them. PPSA12544 bundles modules with such an export
(RKDbTdN8TO0), so --macos stopped before writing anything.
The macOS writer now resolves a relocation against an absolute symbol to
its value plus the addend, without a rebase, and skips absolute symbols
when exporting, as the Windows writer does. guest_absolute_symbols covers
the macOS output too.
AI-assisted: yes
C17 7.21.6.2p16 returns EOF only if input fails before the first conversion completes, and a suppressed conversion completes. FreeBSD counts it in nconversions (vfscanf.c, releng/11.4, lines 799-805). The host scanf returned EOF there, so sscanf("12", "%*d%d", &v) reported EOF instead of 0. Each suppressed conversion now records its end with a %n and a result of EOF becomes 0 when one was recorded.
Git appends a tab after file names containing spaces in the +++ header, so the
added-lines key kept the tab and its suffix no longer matched. Comment,
extension, title and silent-stub rules then skipped such files. The tab is
now stripped, with a test covering core/a b.cpp.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
QueueState held the 48 KiB constant RAM inline, so every QueueState
cleared 48 KiB when constructed and copied it when copied.
ResolveShaderAbi and ResolveGraphicsStagesAbi construct one on each call
(sceAgcLinkShaders, sceAgcCreatePrimState, interpolant mapping),
resolveGroupAhead copies the live state, and a queue reset builds a new
one, although few command buffers write the constant RAM.
The constant RAM is now a vector allocated when WRITE_CONST_RAM first
writes it. ConstantRam returns a shared all-zero view until then, so
DUMP_CONST_RAM of unwritten words still stores zeros, and the bounds use
QueueState::ConstantRamWords. The store view ResolveStore returns is
copied by the recorder within the packet, as before.
testMemory dumps unwritten constant RAM and checks for zeros, and checks
that a copied queue state keeps the words written before the copy.
A QueueState is 336 bytes instead of 49464; constructing one takes 0.3
to 0.8 us instead of 1.5 to 2.1 us, and copying one 0.4 to 0.6 us
instead of 1.9 to 3.8 us (GCC 13 -O3). On the title screen of PPSA21402
(Balatro) the clears from the two resolvers took 30 to 129 of 3459 to
7838 CPU samples per 10 s.
The PR template's untouched "Depends on: <!-- #PR, or none -->" line
matched the checklist regex, so prose such as "Depends on #1400" was never
consulted and the declared dependency was lost. HTML comments are now
stripped and the prose form is used when the checklist line is blank. An
explicit "Depends on: none" still takes precedence.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
PSXS5, SwanStationPS5 and Nuvio stop at start-up on setcontext on main,
and EVO Player and ProsperoStore do on Windows. The three are imported
by the statically linked OpenSSL's ASYNC code.
The three use FreeBSD 11's amd64 ucontext_t (mcontext at 0x10, mc_len
800, uc_link at 0x330, 0x370 bytes), which the titles are built
against: EVO Player's ASYNC_is_capable captures into a 0x370-byte stack
buffer, and async_fibre_makecontext sets uc_link at 0x330 and uc_stack
at 0x338. getcontext saves every register at its call in assembly and
stores the context as FreeBSD's sys_getcontext does: rax and rdx as 0,
the carry flag clear, the FXSAVE image and the signal mask, and nothing
from uc_link on. setcontext checks mc_len and mc_flags as set_mcontext
does, installs the signal mask, masks MXCSR with the host's MXCSR_MASK
and returns through iretq, so every general register and the flags are
restored and nothing is written below the target stack pointer.
makecontext follows FreeBSD 11's amd64 libc.
Windows checks guard-page growth and exception unwinding against the
stack bounds in the TEB. setcontext and longjmp now set them to the
stack they switch to, which makecontext registers and pins writable as
libSceFiber does for fiber stacks. OpenSSL enters a context with
setcontext and then switches with _setjmp and _longjmp.
Iterating a Registers advances with nextPresent, which recomputes the
word, mask and next bit from the index on every step, about 3.5 ns per
register. BuildRegisteredAbiKey walks all of a state's registers, about
300 with the context, and ResolveGraphicsStagesAbi walks each stage's
registered state to merge it, on every sceAgcLinkShaders,
sceAgcCreatePrimState and interpolant mapping call.
Registers::ForEach visits the set registers in ascending order straight
from the presence words, and both paths use it. The key and the merged
state are unchanged.
testRegisterFile checks that ForEach visits what iteration visits, in
order, and skips an erased register.
Building the key for 297 registers takes 200 ns instead of 1030 ns (GCC
13 -O3). On the title screen of PPSA21402 (Balatro) the two functions
took 0.18 to 0.29 CPU samples per sample of the title's own code, and
0.06 to 0.12 with this change.
stubgen emitted TechnicalDebt links as repo-relative paths, but
TechnicalDebt.md lives in docs/dev and the conventions checker resolves
links relative to it, so pasted lines failed CI. The derived default path
now carries a ../../ prefix; an explicit --td-path is used as given.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
sleep_for can return up to a scheduler tick early against the
performance counter the guest clock reads, so the first reading could be
just under 50 ms on a loaded machine.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Titles that import it now load; calling it throws until push event
handles have operations that can be aborted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f50cb08c988736d645f0cc1c329f911f732fa935)
(cherry picked from commit ae3227dbfa2c972b36242056df5971d858ac65c3)
(cherry picked from commit 3df66a6a5dd9f0cd73daaee1342c22742c1915e4)
CONTRIBUTING.md told authors to write `Depends on #N` in the
description, but the pr-template rule in tools/check_conventions.py
only accepts the checklist line `Depends on: #N` or `Depends on: none`
from the pull request template. A pull request that followed the
document failed the Conventions job with "fill Depends on with #N or
none". The sentence now shows the checklist form.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
A T# picked per lane with SelectU32 and made uniform with
v_readfirstlane, as in a waterfall loop, left a descriptor web without
Phis that the web walk could not split. CollectDescriptorPhis now steps
through ReadFirstLane, and a Phi-free tuple containing SelectU32 or
ReadFirstLane is split on the shared condition (SelectU32) or wrapped in
a matching ReadFirstLane. The new nodes are placed after the last
original node of the tuple in its block and inserted only when the web
is split; otherwise they are invalidated like the web's Phis.
PPSA26344 compute shader 0x800037f200 selects its T# per lane between
SRT reads or zero and reads the first lane of the result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
s_buffer_load returns zero for dwords outside the V#'s range. The SRT
evaluator refused such a read instead, so evaluating a descriptor failed
as soon as one arm of a split T# read through a null or short V#, even
when that arm is never taken. The evaluator now returns zero for those
reads, as the hardware does.
PPSA26344 compute shader 0x800037f200 reads one arm's T# through a null
V# and stopped at "SrtWalker::EvaluateRuntimeSources failed to evaluate
runtime sources".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
source_stubs pooled done NIDs across all libraries, so a throwing stub
in libSceShare.native counted as implemented when libSceShare had the
same function. Stubs are now keyed by (library.prx, nid) and audit marks
an import stub only when every provider of it is a stub.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
One PR diff containing a byte that is not valid UTF-8 made git() and
merge() raise, which aborted the hourly run and dropped the overlap
comment on every PR. Both subprocess calls now pass errors="replace",
matching check_conventions.py. A test commits a Latin-1 byte and checks
the export is still found.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The pull request check only matched unchecked items written with a dash,
so a checklist rewritten with * or + bullets passed with every box empty.
GitHub renders all three bullet styles as task items. The pattern now
accepts any of them, and new tests cover each bullet style.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
The window was read from APS5_HOST_IMPORT_WINDOW_MIB on the device.
It is now always 8 MiB, the value the previous commit was measured
with; SetHostImportWindow stays for the tests. TechnicalDebt records
the window as a choice.
one span. A title that grows a heap 16 KiB at a time then re-imports the
whole chain on every growth: Ratchet & Clank: Rift Apart re-imported about
1.5 TiB while loading its first scene, and NVIDIA refused grown spans with
VK_ERROR_OUT_OF_DEVICE_MEMORY, which leaves those ranges copied per draw.
Ranges are now joined only when a chain covers a whole aligned window (8 MiB
by default, APS5_HOST_IMPORT_WINDOW_MIB on the device); until the window is
complete each range is imported on its own, and a span never crosses a
window edge. Growth therefore re-imports a range once more at most when its
window completes, instead of the chain on every growth. Joining any part of
a window was tried first and still re-imported 2 GB per second in Ratchet,
as each growth re-imported the window's partial span. A single range larger
than the window is imported whole. SetHostImportWindow(0) keeps the
unbounded spans, which is what the existing tests use.
The new importWindowTests (agc_driver_graphics) checks that a full window is
one span, that a partly filled window imports its range alone, that a view
crossing a window edge gets no import, and that completing one window leaves
the other window's import in place; with the window ignored it fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux skip check only recognised subgroup, "the device has no",
"the device reports" and "no display", so tests that skip with
"the device lacks ...", "does not import", "imports no", "flushes",
"binds fewer", "no usable Vulkan device" or "host imports are
unavailable" failed the job. The regex now covers those messages, and
unrelated reasons such as "flaky" still fail. A unit test covers the
messages and an end-to-end run.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
With git's default core.quotePath=true, paths with non-ASCII characters
were printed quoted and octal-escaped. The checker stored the quoted
string as the path and then failed in git show with a traceback. git()
now passes core.quotePath=false so raw UTF-8 paths are used. A test
covers a diff containing a non-ASCII file name.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9UU71SP1UKGR51Sowbgmq
Authors with no merged pull requests can have 1 open, with at least 1
merged 20, and from 20 merged there is no limit. Reopening a pull
request runs the check again.
Co-Authored-By: Claude <noreply@anthropic.com>
Titles that import it now load; calling it throws like the other
coredump queries, since host crashes are not turned into guest dumps.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e0e5fd9ffbbaffdc065d1471ce1c91b144af5f98)
(cherry picked from commit 2ac0c88fdbb9e380b6eb5a37d5f98d557940f131)
(cherry picked from commit 1279010b1bc123d3b87664ede15ec6ab2ced806a)
DecodeShaderStages accepts LS, HS and an NGG passthrough ES, reads the
3-bit VGT_TF_PARAM fields, refuses its other bits but DISTRIBUTION_MODE,
and needs HOS levels that keep [1, 63]; 0x286-0x287 join the draw key.
Local and domain programs are prepared with their stage group: its
headers are merged over one set of defaults, and a local program left
unbound by the hull header is prepared at its first draw.
The local program ends at its jump into the hull half.
TessellationLowering lowers LDS and ring addresses that are affine in
the control point and relative index to tessellation interfaces with
strides from the code, and refuses accesses outside the patch, per-patch
writes from other control points and a modified primitive export. Hull
lanes read their own saved exec bit, also of host-subgroup-masked
copies; other masks are refused, gl_InvocationID loads as a scalar and
factors clamp to 63.
A descriptor dword built by S_BREV_B32, V_BFREV_B32 or S_BREV_B64 from a
constant stayed a runtime value, so the resource could not be resolved
and the shader failed to prepare. The constant folder now reverses the
bits of an immediate the way the hardware does (D[31-i] = S0[i]), like
the existing BitCount32 fold. ConstantBitTest covers the fold.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Waits on eventCondition go through libkernel's TimedWait::Condition (precise
timed waits on Windows) instead of std::condition_variable::wait_for, as
requested in review.
libkernel.prx does not export TimedWait::*, so libSceVideoOut.prx had an
unresolved import and titles stopped at load. Compile TimedWait.cpp into
libSceVideoOut, and use NotifyAll for the notify added on main.
The flip queue's condition variable went through winpthreads, whose
timed waits end on GetTickCount64 steps: the present thread's 10 ms poll
for SDL events and the pad, taken while no flip is queued, ended 11 to
24 ms later on Windows, and WaitForFlipRoom's 60 s bound had the same
granularity. Use TimedWait::Condition, which waits on an event and a
high-resolution timer, so the poll ends at 10 ms and a submitted flip
still wakes the present thread at once.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Use unlocked POSIX EOF/error queries after unlocked byte operations so status synchronization cannot reacquire the stream lock. Keep the verified UCRT status calls on Windows, and cover byte, status, error, and held-lock behavior.
Co-authored-by: MarcZX100 <marczxrr@gmail.com>
agc_driver_dcc_keys_first_write only exercises the import window when the
device's own host import resolves the write protection of the imported
pages, as the NVIDIA driver does on Linux. CI's devices do not, so the
comparison that #2984 adds for BlockMaybeWritten units is not run there.
RunImportWindow opens an import window itself over a pending storage
image with GuestMemory::ImportWatched and stores the byte already there,
which leaves the same stamp without depending on the driver: the image
must stay pending and unchanged. A store of a new byte inside a window
must still be seen. Devices whose imports are compared print that the
window is not tested, as the recorder's (w) test does. Linux only: the
Windows path compares imports.
TechnicalDebt records that an identical-byte CPU store inside a window
cannot be told apart from the import, and that images tracked by layer
still upload again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
range is write-watched. Since #2984, Refresh turns a BlockMaybeWritten
unit into BlockUnchanged only through compareUntracked, which needs that
copy. Where a host import leaves its range watched and making it stamps
the imported pages (the probe's writtenAtImport, 4 of 4 on NVIDIA's
Linux driver), the import window stamps the surface, so a pending
fast-cleared image counted as changed again and
agc_driver_dcc_keys_first_write failed on main (#3179).
HostImports now keeps whether making each kind of import stamped the
probe's pages, and WatchedImportsStampPages reports whether an import
that leaves its range watched can stamp it. The clear path skips the
copy only when no such import can, as oneandonlydean proposed in #3179.
Devices whose probe saw no pages written at the import, devices without
host imports and Windows (whose alias imports don't disarm the guest
views) keep #2699's saving.
An unprobed watch (APS5_WRITE_WATCH_IMPORTS=watch) keeps the copy.
The fast-clear check in agc_driver_dcc_keys_first_write now expects the
copy exactly when the range is unwatched or such a stamp is possible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetProcessTime and sceKernelGetProcessTimeCounter measured from
a start captured on their first call, so the first reading was about 0
however long the process had run, and exactly 0 when both reads fell in
one tick of the host clock (100 ns on Windows). Capture the start when
libkernel loads.
Titles that ship their own libc.prx or Unity's PS5Util.prx import
_umtx_op (04AjkP0jO9U) for atomic waits, call_once and futex-style
waits. Implement UMTX_OP_WAIT, UMTX_OP_WAKE, UMTX_OP_WAIT_UINT,
UMTX_OP_WAIT_UINT_PRIVATE and UMTX_OP_WAKE_PRIVATE as FreeBSD 11's
kern_umtx.c does, on the sceKernelSyncOnAddressWait waiter queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
Two defects found in review of the Linux delivery:
A raise right after scePthreadCreate reached the new thread before
RunThread set currentThread, so scePthreadSelf() in the handler adopted
a second handle, and could deadlock on glibc's thread lock when the
signal interrupted pthread_getattr_np. The creator now blocks all
signals around the std::thread construction, so the child starts with
them blocked, and RunThread restores the creator's mask once the thread
is registered, as libthr does. A raise in that window stays pending and
is delivered just before the entry runs.
A handler that set reserved MXCSR bits made rt_sigreturn fail with
SIGSEGV. The MXCSR the handler leaves is now masked with the frame's
MXCSR_MASK (0xffbf when it reads 0), as FreeBSD's fpusetregs does, on
both platforms; Windows also keeps CONTEXT.MxCsr equal to
FltSave.MxCsr.
guest_raise_exception raises right after each of 50 thread creations and
checks scePthreadSelf() in the handler, and its context round trip sets
a reserved MXCSR bit and FZ and checks that only FZ is kept.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwJx7AHrSUaMGSpEGhL1Kb
sceKernelRaiseException threw on Linux, so a title whose garbage
collector stops threads with it (Unity's PS5Util raises SIGUSR1 on each
thread and reads mc_rsp from the handler's context) could not run there.
The raise now sends the host SIGUSR1 to the target thread with
pthread_kill. The host handler, installed on first use with SA_SIGINFO
and SA_RESTART, builds the FreeBSD-layout ucontext from the interrupted
registers and the FXSAVE area, calls the guest handler on the target's
own stack and writes the registers back, so the handler's changes reach
the interrupted code, as they do on Windows. errno is left as the
handler leaves it, as libthr does.
A raise on another thread takes the target's join lock with the host
signal blocked and returns ESRCH once the thread has finished, so a
detached thread's pthread_t is never signalled after it exits. The
pthread_t is recorded when the thread is created or adopted. Handlers
are now lock-free atomics, as the host handler reads them.
guest_raise_exception now runs on every platform. It also checks that
r13 and xmm8 reach the handler and that r12 and xmm8 written by the
handler reach the interrupted code.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwJx7AHrSUaMGSpEGhL1Kb
Comparison sampling of a color texture required the view's X channel to select red. A title runs image_sample_c_lz on a 1x1 R8 unorm placeholder whose dst_sel is constant 0 on all four channels.
On hardware the compare result is placed in every channel and each returned channel then applies its own dst_sel: 0 gives 0, 1 gives 1, R/G/B/A give the compare result. Return each dmask register from its own select, and throw only for the reserved selects 2 and 3.
On Windows every unwind step called VirtualQuery to find the frame's
module, and a module with a plain .eh_frame (libstdc++, libgcc, libwinpthread)
was searched by decoding each FDE in turn. A C++ exception crossing those
frames cost about a millisecond. Look the module up with RtlPcToFileHeader
first, falling back to VirtualQuery for images the loader does not know,
and binary-search a per-module index of FDE start addresses built on first
use.
A 1D sample or load of a 2D T# had no runtime image mode after the mode table landed, so it was still refused at materialization. Sampled 1D accesses now also accept a 2D view; the address completion in the emitter reads the missing row as 0.
Measured on hardware: a position component the instruction omits reads as 0, like an explicit 0, and the next VGPR is ignored. Missing array layers and derivative components are 0 too. Cube coordinates still need all components.
Multi-level views, and views starting above mip 0, are bound as their base
level only when every use of the image is image_sample_lz through an
unnormalized S# without minimum LOD or LOD bias, the case measured exact on
RDNA2. Selected-LOD, implicit-LOD and offset uses keep throwing.
The recompiler records selected-LOD uses (SamplerUseNonZeroLod) and proves
the case per image (imageUnnormalizedBaseLevel); the shader disk cache
format goes to 27.
Co-Authored-By: Claude <noreply@anthropic.com>
Vulkan only accepts unnormalized samplers on single-level views, while the
guest binds full mip chains and samples their base level. Bind a lazily
created one-level view of the descriptor's base mip instead of refusing
mipmapped views and views that start above mip 0.
Forwards _sceUltWaitingQueueResourcePoolCreate and _sceUltSemaphoreCreate to the existing calls, like the other underscore Ult exports. Declares sceNetShowIfconfig, sceNpWebApi2GetMemoryPoolStats, Json::Value::setNullAccessCallBack, kqueue, kevent and one pad call of unknown name without an implementation.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
normF32 took the error of p = |x| * scale from fma(|x|, scale, -p).
Vulkan only guarantees GLSL Fma on f32 the precision of a multiply
followed by an add, and lavapipe in Mesa 25.2.8 splits it, so the error
was 0 there and products that round to k + 0.5 were resolved by
roundEven: v_cvt_pknorm_u16_f32 of 0x37c000c0 gave 0x00020002 instead of
0x00010001.
scale + 1 is a power of two, so A = |x| * (scale + 1) is exact and the
product is A - |x|. Since A >= |x|, p = A - |x| and the error
(A - p) - |x| are computed with correctly rounded subtractions only
(Fast2Sum), which every driver gives exactly.
Map red and blue exports, write masks and constant-color blend factors together. Reject conflicting simultaneous target constants.
Semantics: Mesa 26.2.2 src/amd/common/ac_formats.c ac_translate_colorswap; console equivalence remains unverified. Graphics tests passed. Written with AI assistance.
v_min_f16, v_max_f16, v_min3_f16, v_max3_f16, v_med3_f16, v_pk_min_f16
and v_pk_max_f16 converted their sources to f32, ran the f32 min, max
or med3 with its NaN and signed-zero rules, and converted the result
back to f16 before applying the f16 NaN rules on the bits. The result
is always one of the sources, or for two zeros their sign bits
combined, so it is now chosen on the f16 bits: an integer comparison of
the bits with the sign folded into an ordering key, the same AND/OR
rule for two zeros, then the existing NaN, IEEE_MODE, denormal flush
and clamp rules.
The integer comparison doesn't depend on how the driver converts f16
denormals to f32. Comparing the f32 conversions instead fails on an
Intel UHD, whose UnpackHalf2x16 makes f16 denormals zero when the
module declares no f16 DenormPreserve, as these instructions no longer
convert to f16.
New test agc_driver_float16_min_max_bits checks the seven instructions
(VOP2 and VOP3 forms with abs, neg, op_sel and clamp; packed op_sel,
op_sel_hi and neg) on random and special inputs against a model of
those rules, in seven float modes including the f16 input denormal
flush. Current main gives the same results as the model on an RTX
4060, an Intel UHD and lavapipe.
Capture errno before the ENOTDIR lookup, translate EINVAL from a name
under a regular file to ENOTDIR, check the parent only once and strip a
trailing separator before taking it, and split the dense rmdir return.
On Windows mkdir_nid_postfix returned ENOENT and sceKernelMkdir and
sceKernelRmdir returned ENOENT or EIO when a regular file was used as a
directory in the path. FreeBSD mkdir(2) and rmdir(2) return ENOTDIR for
a path component that is not a directory.
Allocate cached sets without individual-free pools. Reset a pool after its
last owner releases it. Keep stable pool indices, skip full pools, and
terminate if reset fails. No GPU wait is added.
This avoids RADV 26.2.2's affected individual-free allocation path.
Validation: Fedora 44 Release graphics tests pass. The reset-failure
regression fails on the previous behavior and passes on this change.
llvmpipe and RX 6600 pass the 3,301-set lifecycle test with core and
synchronization validation. Windows was not tested.
AI-assisted: yes.
FreeBSD's kernel_sigaction rejects SIGKILL and SIGSTOP only for a handler other than SIG_DFL, so signal(9, SIG_DFL) and signal(17, SIG_DFL) succeed. Drop the SIG_ERR rejection, which has no kernel basis and which sigaction does not apply.
Add an optional input helper for test runs without desktop input injection. Preserve physical input and release injected buttons automatically.
Validation: guest_pad tests and Python helper CLI smoke checks passed. Written with AI assistance.
signal() returned SIG_ERR with EINVAL for any signal without a host
equivalent, such as SIGHUP, SIGPIPE and SIGUSR1, while sigaction()
accepted them. FreeBSD's signal() is sigaction() with SA_RESTART and fails
only for numbers outside 1..128 and for SIGKILL and SIGSTOP.
Drops the stale pending-dependency note (#553#683#684#685 are merged), adds the
package source and the Linux, extended-gameplay, save/load and audio results
reported in the thread, and replaces the cold-start note with the measured cause.
The UCRT diagnostic note is removed: no report on current main.
Co-Authored-By: Claude <noreply@anthropic.com>
The Windows formatter passed %p to the host CRT, which prints 16
zero-padded digits without a prefix. FreeBSD prints 0x followed by the
lowercase hex digits (0x0 for a null pointer), as if by %#lx.
v_interp_p1_f32_e64, v_interp_p2_f32_e64 and v_interp_mov_f32_e64
(VOP3Interp opcodes 0x200, 0x201 and 0x202) reach the decoder as VOP3
and were refused as unimplemented VOP3 opcodes. Decode them into the
existing VInterpP1F32, VInterpP2F32 and VInterpMovF32 opcodes with the
VOP3Interp operand layout, so the interpolation translation already
used by the VINTRP encodings handles them.
Encodings and operand layout follow LLVM's gfx10 MC tests for
VOP3Interp_Real_gfx10 (D69348). The interpolation parameter is the low
six bits of src0, the channel bits 6:7, the coordinate (or the
interpolation parameter of the mov form) is src1, and clamp and omod
reach the destination. op_sel, the packed-half selector, a third source
and abs/neg on operands that do not exist throw.
LLD built for a MinGW host runs the COFF linker when ld.lld gets no -m option,
so it rejects the AMDGPU object with "unknown file type" and the
hardware_oracle test fails on Windows with WinLibs LLVM in PATH. Passing
-m elf64_amdgpu selects the ELF linker on every host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v_cvt_pknorm_i16/u16_f32 rounded |x| * 32767 or 65535 to nearest even
with a 64-bit integer product, variable 64-bit shifts and a sticky bit.
In f32, p = |x| * scale is the f32 nearest to the exact product, so
roundEven(p) is the answer unless p is exactly k + 0.5, where the sign of
the exact error fma(|x|, scale, -p) decides. Lanes that select a
constant (NaN, |x| >= 1) multiply 0 instead.
v_cvt_pk_u8_f32 truncated with the saturating conversion and rounded the
fraction by hand; it is now roundEven(x) between the 0 and 255 clamps,
NaN giving 0, read without the f32 denormal flush.
New test agc_driver_f32_pack_rounding: 563,232 inputs around every
rounding tie, checked against the exact product in double.
A program with one GLC or DLC buffer load or store decorated the whole
guest buffer array Coherent, so its other loads were coherent too. On
NVIDIA that keeps them out of the L1 cache: one GLC store made the
re-reads of a 4 KiB window about 2.7 times slower.
RDNA keeps those loads incoherent. A load without GLC or DLC can hit the
per-CU L0 and per-SA L1 caches, which are not coherent between
work-groups, and code that needs another work-group's data reads it
with GLC/DLC or invalidates the caches first (LLVM AMDGPU memory model,
GFX10: an acquiring load is followed by buffer_gl1_inv; buffer_gl0_inv).
The program metadata now records whether a program invalidates a cache
(buffer_gl0_inv, buffer_gl1_inv, s_gl1_inv, s_dcache_inv). Without one,
a program with GLC/DLC accesses gets a second array on the same binding,
buffers_coherent, decorated Coherent, and both arrays are Aliased as
buffers_u64 already is. Loads without GLC or DLC read buffers; GLC/DLC
loads, stores and atomics use buffers_coherent. A program that
invalidates a cache keeps the whole array Coherent.
Write initial SaveDataParam metadata before replacing the memory blob so metadata failure cannot leave a prematurely grown blob. Handle blob write failure with metadata rollback, and allow retries to recover slots with missing metadata. Expand tests with nonzero data, blob failure, and fresh readback.
Report SAVE_DATA_ERROR_INTERNAL when setupSaveDataMemory2 fails to persist initial SaveDataParam metadata, and restore previous state so retrying setup can initialize cleanly. Closes#1667.
One thread per title, for every platform:search this category for the title ID first. If a thread exists, reply there, also when your OS or GPU differs from the one it reports.
Name the discussion `Game name (title ID)`, for example `DOOM Shareware (PPSA99666)`, without the platform.
Bug reports and implementation work belong in issues and pull requests; link them here as the blocker.
Per-game progress is also tracked on the [blocker map](https://alxcbtw.github.io/anyps5-roadmap/), built from public GitHub data. Only share material you have the right to distribute.
- type:checkboxes
id:searched
attributes:
label:Existing thread
options:
- label:I searched this category for the title ID and no thread exists for this title on any platform.
required:true
- type:input
id:title
attributes:
label:Title
description:Name and title ID from `sce_sys/param.json`.
placeholder:DOOM Shareware (PPSA99666)
validations:
required:true
- type:markdown
attributes:
value:|
### First report
The fields below describe one run. Other systems and content versions are added to the thread as replies.
- type:input
id:version
attributes:
label:Content version tested
description:The content version from `sce_sys/param.json`.
placeholder:01.001.001
validations:
required:true
- type:input
id:commit
attributes:
label:AnyPS5 commit
description:The `main` commit that was built, or the release that was used, and any pull requests merged on top.
validations:
required:true
- type:input
id:system
attributes:
label:System tested
description:OS, GPU and driver version, and the compiler if you built it yourself.
description:A slow scene or a change in speed, measured with a performance trace so that reports can be compared.
title:"perf: "
body:
- type:markdown
attributes:
value:|
Capture the scene with `APS5_TRACE_FRAMES=<start>:<count>` (presents; `APS5_TRACE_SECONDS=<start>:<length>` also works), as described under [Performance trace](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/BUILD.md#performance-trace). It writes `aps5-trace-<commit>-<pid>.json.gz` and appends one line to `aps5-summary.jsonl`.
- Run the scene once and restart the title, so the shader and pipeline caches are warm.
- Use the same scene, window, resolution and settings for every capture you compare.
- Set no other `APS5_TRACE_*` or `APS5_PROFILE_*` switch and do not redirect the log; they cost time.
- Capture the base and the change on the same machine in the same session.
A trace holds timings, scope and thread names and counts, no game data. The summary line names the GPU, driver and OS.
- type:input
id:scene
attributes:
label:Title and scene
description:Title ID and what the window covers (menu, level, position, cutscene).
placeholder:PPSA01325, main menu after the intro
validations:
required:true
- type:textarea
id:observed
attributes:
label:What is slow or what changed
description:What you see (frame time, stutter, a phase that grew) and, for a comparison, the two commits.
validations:
required:true
- type:textarea
id:summary
attributes:
label:Summary lines
description:The line of aps5-summary.jsonl for each capture. It holds the commit, the APS5_* switches, GPU, driver, OS, resolution and frame times.
render:json
validations:
required:true
- type:textarea
id:trace
attributes:
label:Traces
description:Attach each aps5-trace-<commit>-<pid>.json.gz (drag it into this field). They open in ui.perfetto.dev.
validations:
required:true
- type:textarea
id:digest
attributes:
label:Digest
description:The output of `python3 tools/trace_digest.py <trace>` (with `--stacks <file>.collapsed.txt` if you sampled the worker).
- type:textarea
id:stacks
attributes:
label:Worker stacks
description:Optional. The collapsed-stack file of APS5_SAMPLE_WORKER (<file stem>.collapsed.txt), attached. It opens in speedscope.app.
- type:checkboxes
id:protocol
attributes:
label:Capture
options:
- label:The caches were warm (the scene ran once and the title was restarted)
required:true
- label:Noother APS5_TRACE_* or APS5_PROFILE_* switch was set and the log was not redirected
required:true
- label:Captures compared here come from the same machine, scene and settings
<!-- OS, title or homebrew, what was checked. "Not tested" is a valid answer. -->
<!-- OS, title or homebrew, what was checked. "Not tested" is a valid answer. A performance change: the summary lines of main and of this PR from the same machine and scene, with the traces attached, captured as the Performance report issue form describes. -->
### Checklist
<!-- Rules behind each item: https://github.com/boykopovar/AnyPS5/blob/main/CONTRIBUTING.md -->
- [ ] Based on current `main`; no other open PR implements the same functions
- [ ] One topic per PR; follow-ups go in a new PR
- [ ] No comments in code except [technical debt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/CONVENTIONS.md)
- [ ] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed in [TechnicalDebt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/TechnicalDebt.md#silent-stubs)
- [ ] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed under Silent stubs in their library's [technical debt](https://github.com/boykopovar/AnyPS5/tree/main/docs/dev/debt) file
- [ ] No notes, investigation `.md` files or images added to the repository (attach them to this PR)
- [ ] New third-party code is a submodule built from source
- [ ] Generic behaviour, not specific to one title
- [ ] Depends on: <!-- #PR, or none -->
- [ ] Unblocks: <!-- title ID and Game bring-up discussion, e.g. PPSA01325 #3232, or none -->
- [ ] GPUs tested: <!-- for AGC, shader recompiler or VideoOut changes: vendor, model, driver and OS of each GPU the GPU tests ran on; otherwise n/a -->
const body = 'Thank you for your interest in the project! We are sorry, but we cannot accept more PRs while we work through the existing backlog. This PR has been automatically closed and labeled "intake paused". This is not a judgment of the quality of your changes.';
const body = 'Thank you for your contribution! Authors with no pull requests merged into `main` can have 1 open pull request at a time, authors with at least 1 merged can have 20, and from 20 merged there is no limit. This PR has been automatically closed and labeled "open PR limit" because that limit was reached. This is not a judgment of the quality of your changes: reopen it once one of your open pull requests is merged or closed.';
stale-pr-message:'This pull request has conflicted with `main` and had no activity for 7 days. Rebase it on current `main` so it can be reviewed and merged. It will not be closed automatically.'
option(BUILD_TESTING"Build and register relinker tests"OFF)
option(ANYPS5_RELINKER_ONLY"Build the relinker without system libraries or third-party dependencies"OFF)
option(ANYPS5_BUILD_WASM"Build the browser relinker"OFF)
if(ANYPS5_BUILD_WASM)
if(NOTEMSCRIPTEN)
message(FATAL_ERROR"ANYPS5_BUILD_WASM requires emcmake and Emscripten")
endif()
if(BUILD_TESTING)
message(FATAL_ERROR"Use the native build for C++ tests and the browser tests for WASM")
endif()
set(ANYPS5_RELINKER_CLIOFFCACHEBOOL"Build the native relinker command line"FORCE)
add_subdirectory(core/relinker)
return()
endif()
if(BUILD_TESTING)
enable_testing()
if(MINGW)
@ -24,7 +37,9 @@ if(BUILD_TESTING)
endif()
option(ANYPS5_ENABLE_SPIRV_TOOLS"Enable SPIRV-Tools validation and optimization"OFF)
option(ANYPS5_ENABLE_ASAN"Build Linux targets with AddressSanitizer and use an ASan-compatible guest arena range"OFF)
option(APS5_ENABLE_TIMING_LOG"Write frame timings asynchronously to frame-timing.log"OFF)
option(APS5_ENABLE_TRACY"Build the Tracy profiler client and report performance timer scopes as Tracy zones"OFF)
option(ANYPS5_USE_SYSTEM_DEPS"Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the bundled submodules"OFF)
- Follow the [coding conventions](docs/dev/CONVENTIONS.md): naming, no comments except [technical debt](docs/dev/TechnicalDebt.md), Conventional Commits.
- Every function either does exactly what it is supposed to or throws. Unimplemented exports call `NotImplemented_nid_no_patch(__func__)` (see [libSceAudioIn](core/libs/prx/libSceAudioIn/Export.cpp)).
- Pull requests that add or change shader instruction semantics say where they come from: measured on hardware (which GPU and what was checked, e.g. with the [hardware oracle](docs/dev/HW_ORACLE.md)) or the exact source (ISA section, LLVM, ACO or Mesa file). Reviewers check the semantics on hardware. Cases the source doesn't settle throw, and behaviour not verified on hardware is recorded in [technical debt](docs/dev/TechnicalDebt.md).
- A silent stub is allowed only when it unblocks a title and only affects the UI; add it to [silent stubs](docs/dev/TechnicalDebt.md#silent-stubs).
- A silent stub is allowed only when it unblocks a title and only affects the UI; add it under Silent stubs in its library's [technical debt](docs/dev/debt/) file.
- Missing imports fail at startup with a clear error; don't replace them with fallbacks that keep running.
- Implement the general behaviour of a function, not what one title happens to need.
- Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries (NpCppWebApi over NpWebApi2). The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx).
@ -37,9 +37,9 @@ For pull requests, full Linux and Windows builds and tests run only when a colla
gh workflow run build.yml --repo boykopovar/AnyPS5 --ref main --field pr=1234
```
This manual run also builds and tests the relinker on Linux, Windows and macOS, regardless of the author's previous contributions or the changed files. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit in `main`; full builds never run automatically for pull requests. All build jobs build the selected pull request merged into the current `main` (GitHub's merge commit), so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds.
This manual run also builds and tests the relinker on Linux, Windows and macOS, regardless of the author's previous contributions or the changed files. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit in `main` and it changes a file the relinker-only build uses; full builds never run automatically for pull requests. Every job that runs automatically for pull requests runs only when the pull request changes its files: a `paths` filter on the `pull_request` trigger, or `needs: eligibility` with an `if` on the files output it sets in `build.yml`. The Conventions check fails a workflow change that adds a job without one. All build jobs build the selected pull request merged into the current `main` (GitHub's merge commit), so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds.
The Conventions check runs automatically for every contributor, including first-time contributors. It uses the base branch checker and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [TechnicalDebt](docs/dev/TechnicalDebt.md), and only UTF-8 text files. Run it locally before pushing:
The Conventions check runs automatically for every contributor, including first-time contributors. It uses the base branch checker and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [technical debt](docs/dev/TechnicalDebt.md), the index or a file in `docs/dev/debt/`, and only UTF-8 text files. Run it locally before pushing:
The [pull request template](.github/pull_request_template.md) is the checklist for these rules.
- One branch per topic, based on current `main`. Follow-up work goes in a new pull request, not into an open one.
- Open pull requests per author are limited by how many of theirs are merged into `main`: 1 with none merged, 20 with at least 1, no limit from 20. A pull request opened or reopened over the limit is closed automatically; reopen it once another one is merged or closed.
- Before starting, check that no open pull request already implements the same functions.
- Keep the branch up to date with `main` and resolve conflicts yourself; rebasing and force-pushing is fine.
- If a pull request needs another one first, say so in the description (`Depends on #N`).
- A pull request that conflicts with `main` and has no activity for 7 days is labeled `stale` with a reminder. It is not closed.
- If a pull request needs another one first, say so on the checklist line (`Depends on: #N`; `none` otherwise).
- If a pull request fixes a stop or a wrong result seen on a title, name it on the checklist line with the title ID and its [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussion (`Unblocks: PPSA01325 #3232`; `none` otherwise). Reviewers review these first.
- Fixes a title needs go to `main` as pull requests. A Game bring-up discussion lists the open pull requests and local changes the title still needs; update the list when one is merged, closed or replaced.
- The PR overlap comment lists open pull requests that change the same lines or exports; agree with their authors which one goes first and declare it with `Depends on`.
- Run the tests before opening the pull request and describe what was tested (OS, title or homebrew).
- A pull request that claims a change in speed shows the summary lines of `main` and of the change, from the same machine and scene, and attaches the traces, captured as the [Performance report](.github/ISSUE_TEMPLATE/performance.yml) issue form describes. A slow scene is reported with the same form.
- Changes to the AGC driver, the shader recompiler or VideoOut name each GPU the GPU tests ran on (vendor, model, driver, OS) on the `GPUs tested` checklist line. CI runs these tests only on lavapipe, and hardware drivers fail where lavapipe and other vendors pass, so a reviewer can ask for a run on another vendor before merging.
- Investigation notes, reports, screenshots and logs go in the pull request, not in the repository. Images for documentation go in the [gist](https://gist.github.com/boykopovar/0e53f2e1426f29ecd41e3b51540b8a90) comments.
- Say whether the change was written with AI assistance. The author of the pull request is responsible for every line of it.
## Documentation
Text in the repository is dry, strict and concise. Record known gaps, unknown NIDs and signatures in [technical debt](docs/dev/TechnicalDebt.md) instead of new files.
Text in the repository is dry, strict and concise. Record known gaps, unknown NIDs and signatures in [technical debt](docs/dev/TechnicalDebt.md) instead of new files. An entry goes in `docs/dev/debt/<name>.md`, named after the first `core/` path it links (`<name>` for `core/libs/prx/<name>/...`, otherwise for `core/<name>/...`), under the same `###` sections as the index; entries that link neither stay in the index. A pull request whose `TechnicalDebt.md` change conflicts after the split rebases and runs `python3 tools/debt_split.py resolve` during the stopped rebase, which moves its added, edited and removed entries to their files; `python3 tools/debt_split.py split` moves entries left in the index.
Includes a [relinker](core/relinker) that converts executable to the target system's native format and implementations of [system prx libraries](core/libs/prx) suitable for dynamic linking. No emulation or separate runtime process.
[Usage](docs/user/USAGE.md), [Build instructions](docs/dev/BUILD.md), [Architecture](docs/dev/ARCHITECTURE.md), [Technical debt of the project](docs/dev/TechnicalDebt.md), [code style conventions](docs/dev/CONVENTIONS.md), [contributing](CONTRIBUTING.md)
<sub>* System libraries: percentage of the functions known to the project so far (declared in [core/libs/prx](core/libs/prx)), not of every PS5 system function. The total grows as more functions are declared.</sub>
[List of verified games](docs/user/COMPATIBILITY.md)
Dreaming Sarah (2D platformer) runs at a stable 60 fps on a GTX 1050 Ti / i5-7500 3.4GHz.
Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates.
The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V (validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`).
## Compatibility
See the [game compatibility list](docs/user/COMPATIBILITY.md) for tested games and known issues.
Per-game progress (stage reached, open blockers, libraries and GPU coverage) is tracked on the [blocker map](https://alxcbtw.github.io/anyps5-roadmap/), a community project built from public GitHub data ([source](https://github.com/AlxcBTW/anyps5-roadmap)). Test reports go in the [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussions, one thread per title.
message(FATAL_ERROR"${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} selects SjLj exceptions with -fno-asynchronous-unwind-tables; the libraries need DWARF unwinding. Use the MinGW-w64 toolchain listed in docs/dev/BUILD.md.")
Agc::Command::Require(semaphoreMemory==nullptr,__func__,"the AMM semaphore memory is already registered; the console's error is unknown");
Agc::Command::Require(memory!=nullptr&&sizeInBytes!=0,__func__,"null or empty AMM semaphore memory; the console's error is unknown");
Agc::Command::Require(((address|sizeInBytes)&(semaphoreMemoryAlignment-1u))==0,__func__,"AMM semaphore memory or size is not 16 KiB aligned; the console's error is unknown");
Agc::Command::Require(sizeInBytes-1u<=std::numeric_limits<std::uintptr_t>::max()-address,__func__,"AMM semaphore memory wraps the address space");
auto*labels=static_cast<std::uint8_t*>(memory);
Agc::Command::Require(hasOnlyZeroLabelTails(labels,sizeInBytes),__func__,"AMM semaphore labels hold data past their first 8 bytes; whether registration clears it is unknown");
Agc::Command::Require(semaphoreMemory!=nullptr,__func__,"no AMM semaphore memory is registered; the console's error is unknown");
Agc::Command::Require(index<semaphoreBytes/semaphoreLabelBytes,__func__,"the semaphore label lies outside the AMM semaphore memory; the console's error is unknown");
Agc::Command::Require(label!=nullptr,__func__,"null semaphore label output; the console's error is unknown");
std::snprintf(message,sizeof(message),"sceAgcCreatePrimState: %s special register at offset 0x%x, expected 0x%x",name,static_cast<unsigned>(reg.offset),static_cast<unsigned>(expected));
check(sceAgcDcbSetWorkloadsActive(&storage.buffer,vector.streamId,ids,vector.workloadCount)==nullptr,"DCB workloads active returned a packet for an unregistered stream");
check(sceAgcDcbSetWorkloadComplete(&storage.buffer,vector.streamId,vector.workloadId)==nullptr,"DCB workload complete returned a packet for an unregistered stream");
check(sceAgcDcbSetWorkloadStreamInactive(&storage.buffer,vector.streamId)==nullptr,"DCB workload stream inactive returned a packet for an unregistered stream");
check(sceAgcAcbSetWorkloadsActive(&storage.buffer,vector.streamId,ids,vector.workloadCount)==nullptr,"ACB workloads active returned a packet for an unregistered stream");
check(sceAgcAcbSetWorkloadComplete(&storage.buffer,vector.streamId,vector.workloadId)==nullptr,"ACB workload complete returned a packet for an unregistered stream");
check(sceAgcAcbSetWorkloadStreamInactive(&storage.buffer,vector.streamId)==nullptr,"ACB workload stream inactive returned a packet for an unregistered stream");
check(storage.words==before&&storage.buffer.cursor_up==storage.words.data(),"a workload call for an unregistered stream wrote to the buffer");
"a workload call for an unregistered stream on an exhausted buffer returned a packet");
check(exhausted.words==before&&exhausted.buffer.cursor_up==exhausted.words.data(),"a workload call for an unregistered stream modified an exhausted buffer");
set(ANYPS5_AGC_EXECUTION_TESTS""CACHESTRING"tests/execution sources to build, by file name without extension; the others build a stub that reports a skip. Empty builds all")
elseif(name!="fifo")throwstd::runtime_error("Vulkan presentation: ANYPS5_PRESENT_MODE must be fifo, fifo_latest_ready, mailbox or immediate");
}
if(std::find(supported.begin(),supported.end(),mode)==supported.end())throwstd::runtime_error("Vulkan presentation: the surface does not support the requested present mode");
RequireCapture(count>=5&&spirv[cursor+1]==5u&&spirv[cursor+2]!=0&&spirv[cursor+2]<spirv[3]&&spirv[cursor+3]==0x6e69616du&&spirv[cursor+4]==0,"only a compute main entry point is supported");
RequireCapture(!mainEntry,"multiple entry points are unsupported");
mainEntry=true;
}
cursor+=count;
}
RequireCapture(mainEntry,"compute main entry point missing");
RequireCapture((binding.flags&VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT)==0||binding.binding==highest,"variable descriptor count must use the highest binding");
}
}
RequireCapture(totalBindings<=4096,"too many total descriptor bindings");
RequireCapture(pushConstants.size()<=1,"too many compute push constant ranges");
RequireCapture(!std::filesystem::is_symlink(std::filesystem::symlink_status(directory)),"capture directory is a symbolic link");
if(std::filesystem::exists(recordDirectory)){
RequireCapture(std::filesystem::is_directory(recordDirectory)&&!std::filesystem::is_symlink(std::filesystem::symlink_status(recordDirectory))&&!std::filesystem::is_symlink(std::filesystem::symlink_status(path)),"existing capture path is not a regular capture");
RequireCapture(ReadBytes(path)==bytes,"existing capture differs or is incomplete");
AgcDriver::ProfilePrint_nid_no_patch("[present] %llu presents over 10 s (%zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per present: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n",static_cast<unsignedlonglong>(presents),inFlight,inFlight!=0?"inflight_wait":"render_fence_wait",percentile(0.5),percentile(0.9),waits.empty()?0.0:waits.back(),waits.size(),(counts.gpuBusyMs-previous.gpuBusyMs)*perPresent,(counts.gpuGapMs-previous.gpuGapMs)*perPresent,static_cast<unsignedlonglong>(counts.gpuUnread-previous.gpuUnread),static_cast<double>(serial-windowSerial)*perFlip,static_cast<double>(flipBatchesUnsignaled.load()-previousUnsignaled)*perFlip,static_cast<double>(counts.batchesAheadOfBlit-previous.batchesAheadOfBlit)*perPresent,static_cast<double>(counts.batchesAfterFlip-previous.batchesAfterFlip)*perPresent,(counts.lastSubmitAfterFlipMs-previous.lastSubmitAfterFlipMs)*perPresent,(counts.blitSubmitAfterFlipMs-previous.blitSubmitAfterFlipMs)*perPresent,static_cast<unsignedlonglong>(counts.readsOverwritten-previous.readsOverwritten),static_cast<unsignedlonglong>(counts.readsChecked-previous.readsChecked));
AgcDriver::ProfilePrint_nid_no_patch("[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n",static_cast<unsignedlonglong>(presents),static_cast<unsignedlonglong>(dropped),inFlight,inFlight!=0?"inflight_wait":"render_fence_wait",percentile(0.5),percentile(0.9),waits.empty()?0.0:waits.back(),waits.size(),(counts.gpuBusyMs-previous.gpuBusyMs)*perFrame,(counts.gpuGapMs-previous.gpuGapMs)*perFrame,static_cast<unsignedlonglong>(counts.gpuUnread-previous.gpuUnread),static_cast<double>(serial-windowSerial)*perFlip,static_cast<double>(flipBatchesUnsignaled.load()-previousUnsignaled)*perFlip,static_cast<double>(counts.batchesAheadOfBlit-previous.batchesAheadOfBlit)*perPresent,static_cast<double>(counts.batchesAfterFlip-previous.batchesAfterFlip)*perPresent,(counts.lastSubmitAfterFlipMs-previous.lastSubmitAfterFlipMs)*perPresent,(counts.blitSubmitAfterFlipMs-previous.blitSubmitAfterFlipMs)*perPresent,static_cast<unsignedlonglong>(counts.readsOverwritten-previous.readsOverwritten),static_cast<unsignedlonglong>(counts.readsChecked-previous.readsChecked));
APS5_LOG_OUT_DEBUG("Shader 0x%llx is drawn into a layered target; preparing its layered variant at draw",static_cast<unsignedlonglong>(snapshot.codeAddress));
return;
}
if(!snapshot.header.empty()){
if(!snapshot.prepared->deferred)APS5_LOG_ERR("Shader 0x%llx stage %u has no artifact prepared at registration for the state of this draw or dispatch; preparing it at use",static_cast<unsignedlonglong>(request.shader.codeAddress),static_cast<std::uint32_t>(request.shader.stage));
if(std::getenv("APS5_TRACE_PREPARATION_WAIT")!=nullptr)std::fprintf(stderr,"[shader-prepare] demanded queued task at position %zu of %zu\n",static_cast<std::size_t>(std::distance(tasks.begin(),found)),tasks.size());