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.
Add standard quiet NaN generators nanf (DZU+K1wozGI) and nan (zck+6bVj5pA) to libc.
Resolves startup failure in VLC for PS5 (PPSA85300) where execution halts at nanf on Linux once strsep is available.
PPSA21703's Unity PSN plugin imports sceNpWebApi2GetMemoryPoolStats
(Xweb+naPZ8Y), so the title did not start.
sceNpWebApi2Initialize now records the pool size of each context and
sceNpWebApi2Terminate forgets it. The statistics report that size with
no peak or current use, as sceHttp2GetMemoryPoolStats does, since nothing
is allocated from the pool. A null output or an unknown context throws.
The signature and layout follow the Http2 statistics and are recorded in
TechnicalDebt.
The library gets its own CMakeLists.txt, equivalent to the generic rule,
to register its test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The typed descriptor heaps write a null descriptor for a null guest
image, and the device therefore required VK_EXT_robustness2's
nullDescriptor. MoltenVK has the extension but not the feature, so on
macOS no Vulkan device opened and every GPU test was skipped. The
feature is now enabled when the device has it. Without it, the device
creates a placeholder image per typed image heap: 1x1, cleared to zero,
of the heap's dimension, sample count, numeric class and access, and a
null guest image binds the placeholder of its heap. A heap whose
placeholder the device cannot create throws when a null image reaches
it. TechnicalDebt.md lists where a placeholder differs from a null
descriptor.
agc_placeholder_images checks every image resource the recompiler maps
to a heap against that heap's placeholder, so the two cannot drift
apart, and agc_shader_device_profile now expects a device without
nullDescriptor to be accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NVIDIA's Vulkan compiler (driver 616.92) aborts while building a pipeline
whose OpFUnordLessThanEqual or OpFUnordGreaterThanEqual compares against
+infinity. The negated inverse ordered relation gives the same result for
every input, NaN included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace per-draw Vulkan descriptor copies with a single write from retained buffer and image metadata. Keep per-draw guest snapshots, sets and resource ownership unchanged. Baseline RX 6600 profiling attributed 28% of graphics-worker cycles to descriptor copies.
Validation: mixed descriptor and snapshot regression passes on RADV RX 6600 and llvmpipe with Vulkan validation; graphics and guest-memory tests pass. Written with AI assistance.
The entry said the result is flushed before or after the scale. The
lowering flushes the exact scaled value below the minimum normal before
rounding. Measured on RDNA2 (gfx1036) with round to nearest over every
half magnitude 0x0000..0x0fff of both signs, scaled by 2^-1 and 2^0,
IEEE 0 and 1, denorm16 0..3: no mismatch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v_ldexp_f16 ignored the FLOAT_MODE denorm16 flush bits on main. Flush a
denormal half input when bit 0 is clear. When bit 1 is clear, flush the
result when its exact value is below the minimum normal half, decided
on the f32 product before the half conversion rounds it: 0x07ff scaled
by 2^-1 is zero on RDNA2, not the minimum normal. NaN handling and
destination high-half preservation stay the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Report the thread's affinity mask as a 32-byte FreeBSD cpuset_t, with
FreeBSD 11's errors: ERANGE for any other set size, ESRCH for a null
thread and EFAULT for a null set.
EVO-PLAYER (PPSA99039 v0.11.0) imports it (Jb2uGFMr688) and calls it
once with a 32-byte set on pthread_self().
A fill was taken to cover the surface when it spanned ceil(w/8)*ceil(h/8)*4 bytes, a linear HTILE layout. In
the pipe-aligned SW_64KB_Z_X layout a surface's HTILE words lie across a 32 KiB metadata block per 1024x512
pixels (AMD PAL gfx10 addrlib: a 16x16 surface has a valid word at offset 260), so a fill of the linear size
cleared tiles it never wrote. HtileMetadataBytes gives that extent; the fast clear tests fill it (the compute
fixture now dispatches one group per KiB with TGID_X).
Reported with a PAL reproducer by @Amoenus.
Co-Authored-By: Amoenus <5391396+Amoenus@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`Recorder::Active()` is process-wide. With two devices (agc_driver_depth_fast_clear_submit draws on its
own device; its compute-fill DCB makes the driver create a second one, whose recorder becomes the
active one) a surface's fast clear was recorded into the other device's command buffer, unordered with
the draw: the compute-fill case failed about once in 60-90 runs. A surface now records into the active
recorder only when that recorder is its own device's, and submits on its own device otherwise.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rebased onto main as one commit (the six commits of #374). A uniform fill over a depth surface's whole
HTILE (DMA_DATA, the fill kernels or a CPU store) clears the depth and stencil aspects its ZMask and
SMem fields say, to DB_DEPTH_CLEAR / DB_STENCIL_CLEAR, when the surface is next bound or sampled.
Main's RADV clear_htile_mask recognition (#2229) now goes through the same pending clear: an HTILE clear
marks the matching surfaces' depth aspect, and one for a surface not created yet is applied when it is
first bound. Main's HTILE address decode and this one are merged into one (TILE_SURFACE_ENABLE, a
nonzero DB_HTILE_DATA_BASE and a depth format).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A system SDL2 that does not define SDL_JOYSTICK_HIDAPI (sdl2-compat over
SDL3 ships no such macro, and neither does a build with the driver
disabled) fails pad_sdl and makes the disk audio driver open the port
with a different channel count, so ANYPS5_USE_SYSTEM_DEPS=ON now rejects
it with a named error, like the VK_KHR_maintenance8 check. Ubuntu's real
SDL2 defines the macro through its SDL_config.h indirection and is
unaffected.
SPIRV-Tools only exports SPIRV-Tools-static when built with
SPIRV_TOOLS_BUILD_STATIC=ON; distributions that ship it shared expose
SPIRV-Tools instead, so a shared target is aliased to the static name.
Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and
SPIRV-Tools from the system instead of the bundled submodules when the
option is ON. It is OFF by default, so the bundled build is unchanged.
Every package is required: a missing one, or Vulkan-Headers older than
1.4.310 (the agc driver uses VK_KHR_maintenance8), fails the configure
with an error and nothing falls back to the submodule. The system
packages are exposed under the same target names the bundled path uses,
so the rest of the CMake is untouched.
SDL2 is linked from the system as a shared library because the prx
libraries are shared objects and distribution static SDL2 is usually not
PIC. ffmpeg-core, libjpeg-turbo, stb, VulkanMemoryAllocator and LibAtrac9
stay bundled.
An apostrophe in a block comment paired with a later character literal on
the same line when literals were blanked before comments, exposing a brace
and ending the body early.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceSharePlayGetCurrentConnectionInfoA now writes a 72-byte
SceSharePlayConnectionInfoA that reports no session: status DORMANT,
mode 0, empty online IDs, account IDs 0 and both user IDs invalid (-1).
A null pointer returns SCE_SHARE_PLAY_ERROR_INVALID_ARGS (0x810E0001)
instead of throwing. KytyPS5 and fpPS4 agree on the layout and the error
code; KytyPS5 sets the user IDs to -1, fpPS4 leaves them 0.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
Relinks the ELF modules of extra directories under the module parent directory. Modules the executable or another module needs start with the executable; the others are initialized when the title loads them with sceKernelLoadStartModule, with its start arguments (Windows: __aps5_guest_initialize export; Linux: left out of DT_NEEDED).
Squashed onto main by the reviewer (history had a merge commit).
Explicit-LOD offset samples through FORCE_UNNORMALIZED samplers now add
sign-extended texel offsets to f32 coordinates before clamping, using
OpFAdd with NoContraction and a sampler specialization selector.
Without native nonconstant-offset support, retain only runtime image
modes valid for unnormalized sampling and reject normalized samplers.
Preserve constant-swizzle handling, indirect-image rejection and main's
flat-volume and flat-line compatibility rules when selecting those modes.
Serialize image compatibility and folding flags and the sampler folding
flag in cache format 34, with roundtrip, preparation and execution coverage.
Retain main's request format 15 and runtime ABI 11.
MinGW-w64's UCRT has no aligned_alloc, so the Windows build of
guest_job_manager_tests fails since #3170. Use the aligned operator new
and delete instead, which are standard C++17 on every host.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Measured on a PS5 (firmware 13.60):
- sceSslGetMemoryPoolStats returns 0x8095F007 for a null output, before
the id check, and 0x8095F006 for an unknown or terminated context,
zeroing the 32-byte output.
- sceHttp2GetMemoryPoolStats returns 0x817B1225 for a null output,
before the id check, and 0x817B1100 for an unknown or terminated
context, zeroing the 32-byte output.
- sceNetGetMemoryPoolStats checks the pool before the output, so an
unknown pool with a null output returns EBADF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5 dumps carry no playgo-chunkdefs.xml (only sce_sys/playgo-scenario.json, which
lists no chunks), so the valid set fell back to chunk 0 and every query for another
chunk returned SCE_PLAYGO_ERROR_BAD_CHUNK_ID. A title that asks for all of its chunks
in one scePlayGoGetLocus call then never sees them as installed and waits forever.
Without definitions the chunk table is unknown and a dump is fully installed, so any
chunk id is accepted and reported as local. With playgo-chunkdefs.xml present,
validation is unchanged. scePlayGoGetChunkId still reports the known set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Coherent (GLC/DLC) global loads were emitted as Volatile loads through
physical pointers. Volatile does not make another workgroup's stores
visible within the same dispatch, so a workgroup spinning on a flag
written by another one (decoupled look-back scans, as in RADV's
acceleration-structure build) never saw it on NVIDIA. Aligned coherent
dword reads now use OpAtomicLoad at device scope; the byte path keeps
the old reads for unaligned addresses.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VLC (PPSA85300) imports daemon (Wn6I3wVATUE) and fails to load because
no library exports it. Forking into the background is not supported,
so it throws if called. VLC 3.0 calls daemon only when its daemon
option is set, and the title starts libVLC with --ignore-config and
without --daemon.
A job's inputs were joined into one vector before decoding, copying every
input buffer at its stated size. PPSA36082's FMOD submits Opus run jobs
whose one input buffer states 0xFFFFFE74 bytes (a remaining size that
went below zero) ahead of a single packet, so each such job copied about
4 GiB and sceAjmBatchStart took about a second; the mixer missed its
deadline and the title pushed silence. A job with one input buffer now
decodes from that buffer in place, reading only the packets it decodes;
jobs with several input buffers are joined as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the retry on <name>.prx after libs/<name>.prx fails. The test now
runs from its own directory holding libs/libSceFiber.prx, copied from
the libSceFiber output, so the single path resolves in the test too.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A stack base need not be page aligned (it is not on Linux), so rounding the
range down first could leave the stack and miss the stack path. The range the
guest asked for is now checked against the guest stack, and the rounded
pages are protected, as FreeBSD rounds the address down.
A thread that exits with part of its stack protected left those pages to the
next thread that reused the stack. Protected stack pages are recorded, and a
thread's stack is made readable and writable again when it is unregistered at
exit.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows a guest thread runs on its host thread's stack, which is not a
registered guest allocation, so mprotect on any of its pages failed with
EINVAL. A guest that guards the bottom of its own stack (Dolphin's JIT
read-protects 64 KiB there to catch stack overflows) got an error where the
PS5 changes the protection.
A range that lies inside a live guest thread stack, with no GPU access
requested, now takes the native protection directly. Ranges that cross the
stack's bounds are still rejected.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Link libc, configure Windows unwinding for the test so its runtime_error
checks are caught, and run it from libSceFiber's output directory so
openHostModule finds the unpatched libSceFiber.prx next to the modules the
test already loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9MzdVqDf6wHUKzNymQSze
Return a host module handle for a loaded sysmodule id so callers can
sceKernelDlsym after sceSysmoduleLoadModule. Unknown or null inputs
throw like neighbouring internal load paths; unloaded ids return
0x805A1001. Unblocks Prospero Win past NID D8cuU4d72xM at load.
Co-Authored-By: Composer <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
#3158 imports a run of address-adjacent registered ranges as one span.
On Windows a shared mapping in the span needs a read-write alias, and
MapAlias refuses one unless the range is a single contiguous run of
views of one section. Ranges that are adjacent in guest address can be
separate views, so the refusal escaped from importAllocation and
agc_driver_dcc_keys_first_write failed on main with "read-write alias
of shared guest memory ...: the range is not one contiguous run of
views of a section".
A span whose alias is refused is now remembered, and its ranges are
imported one at a time as before spans; a single range that is refused
still throws.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
A shader store through the BDA table records its 4 KiB page in the fault
buffer, and the driver marks those pages written after the use. The page
number was truncated to 32 bits, so a store above 16 TiB marked a page at
another address. And the driver scanned all 4096 slots after every use,
even when nothing was stored.
Slots now hold a 128 KiB chunk (its number plus one, 32 bits for any
address below 512 TiB) and a mask of its 4 KiB pages. A shader that
claims a slot appends it to a list, with a count. The driver reads the
count, decodes only the claimed slots, marks each run of pages once and
clears only those slots. A writable BDA range past 512 TiB is refused
when the table is built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1454, a libSceLibcInternal import that no native library exports
on Windows falls back to the title's own libc.prx. The loader shim that
ps5-native-app-boilerplate builds into homebrew titles exports 1874
functions at three addresses whose code is `xor eax, eax; ret` or
`pxor xmm0, xmm0; xor eax, eax; ret`. Such an import then binds to a
function that returns 0, and the title starts instead of stopping with
`unresolved ELF import`.
The Windows guest module writer records the exported functions whose
code is one of these two sequences, and the entry stub leaves the guest
libc.prx out of the search for those names. A guest export with code of
its own still provides a missing libSceLibcInternal function, and native
libraries keep their priority.
A draw or dispatch whose shaders have guarded descriptor slots (or a rect
list) but no BDA table gets a fault buffer, and with it NeedsCompletion(),
which kept its resources out of the resource cache, and NeverReusable
skipped the cache lookup for it. In PPSA36082 nearly every draw in the hub
is such a build, so the cache served none of them.
Such an object has no table to revalidate, no stores through BDA and no
copied writes, so it is now reusable. Each use of a reused object checks
the fault record when its batch completes (CheckFaults) instead of
running the once-only write-back; draw recipes over such a template
register the same check, and dispatch recipes still skip templates with
completion work. NeverReusable now refuses what noteReusable refuses: a
BDA table, or a fault buffer with APS5_LOOP_GUARD set (its fault resets
the buffer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extend the independently Windows-tested WAIT_REG_MEM candidate 056534a9 with a focused shutdown regression. Keep labels unsatisfied on graphics and two compute queues beyond the configured diagnostic, then require prompt shutdown without executing the commands or pending submissions behind those waits.
The production cancellation path already calls CheckFailure/checkStopping and catches ProcessShutdown in the worker; no runtime behavior is changed. This test does not explicitly open a Vulkan device.
Validation: Linux/GCC 13.3, C++20, -O2 -Wall -Wextra -Werror object compilation against the production headers; conventions and whitespace pass. CMake configuration, runtime linking and execution, and native Windows execution were not performed in this environment. Runtime verification remains pending. Depends on the persistent-wait candidate for #2832/#2985; maintainer policy approval is pending.
AI-assisted: yes.
With the f32 denormal field of FLOAT_MODE set to keep, v_ldexp_f32
flushed subnormal inputs and results to zero. Keep subnormal inputs and
round a result that lands in the subnormal range in the f32 round mode
of FLOAT_MODE. An overflow follows the round mode as well, with or
without denormals kept: the largest finite value instead of infinity
when the mode rounds that sign toward zero. This matches RDNA2
(gfx1036) on 2163 rows in each round mode. Denormal modes 1 and 2 now
throw.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Unsubscribe an event queue from the output mode events that
sceVideoOutAddOutputModeEvent registers, the same way the flip and
vblank events are removed. Deleting a registration that does not exist
succeeds, and a null queue or an invalid handle throws.
Co-Authored-By: Gemini 3.8 Flash <noreply@google.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The bundled libc.prx of PPSA21703 calls sceKernelGetModuleInfoFromAddr
with a 0x1A8-byte stack buffer and never sets st_size, so the field holds
whatever was on the stack (983040 in one run) and the call threw. The
console cannot require the field on input. The full size is still
written back. The flags check is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vkQuake's NGG culling vertex shaders load only the vertex ID
(SPI_SHADER_PGM_RSRC2_GS ES_VGPR_COMP_CNT 0), and DecodeShaderStages still
required 3 for a program routed to the mesh path. The merged ES inputs keep
their positions whatever the count, and the mesh entry loads all of them,
so the count is now checked only for real geometry programs, as the GS
VGPR count already was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply the three-case test correction offered by Scramblrrrr in 36b26122a5458028f76fe7c7710333fcfca80f6d on issue #2832. Preserve all current-main test additions outside those three functions. Credit the supplied implementation to its contributor.
Independent Windows Vulkan results and the timeout-return negative control are reported in issue #2832. Local validation: exact three-function source match, preservation of unrelated Submit.cpp content, conventions and whitespace. Native Windows/GPU execution was not performed here. Shutdown cancellation and persistent-wait policy remain unresolved.
AI-assisted: yes.
Co-authored-by: Scramblrrrr <70121301+Scramblrrrr@users.noreply.github.com>
Retain the original WAIT_REG_MEM fix from #2985 and resolve its test-registration conflict with main d7cfa728. Preserve both upstream test blocks and the original commit ancestry.
Local conventions and whitespace checks pass. Windows/GPU results cited in issue #2832 belong to their reporting contributors. No local GPU execution.
The original fix predates llroundf, which was added in #2933. Use the same guest range and exception behavior for the fourth wrapper and include it in the edge-case checks.
FreeBSD amd64 converts the rounded value with a plain cast, so NaN, infinities and values outside the 64-bit range give 0x8000000000000000 with FE_INVALID raised. The Windows host llround returns 0 for them.
(cherry picked from commit 0ee2741534bdfcc9d818b1d93f2eb793a68f111e)
FreeBSD's memset_s returns EINVAL for every constraint violation and writes nothing when smax or n exceeds RSIZE_MAX. The guest returned ERANGE when n exceeded smax and wrote up to min(n, smax) bytes even when smax or n exceeded RSIZE_MAX.
(cherry picked from commit bf08837cf929e40f1b646df82e140a33076d5587)
Following the ImageRead case, every image operation was checked against
the two lists the masked-lane walk uses: accesses guarded by a trailing exec
operand, and lane-local values.
- ImageWrite carries its exec last like StoreBuffer, and EmitWriteOp stores
only inside an if on it (bindless tables and FMASK refuse the store). It
joins the exec-guarded list.
- A gather with an LOD of its own (gather4_l, gather4_lz) or outside a pixel
shader reads only its lane's address, like the explicit-LOD samples the
walk already follows. A pixel gather without one still keeps the select,
since on the GPU it takes its LOD from the quad.
- ImageQueryDimensions returns the size of its lane's level of the image and
is lane-local.
Left out on purpose:
- ImageQueryLod and implicit-LOD samples read their quad.
- ImageBvhIntersectRay has a trailing exec operand, but its emitter ignores
it and records faults in every lane.
- Image, buffer, shared and address atomics are guarded by their exec in the
emitter too, but the test from 60c65521 pins them as keeping the select,
and no refused shader has needed them so far.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ImageRead carries a trailing exec argument like the buffer loads, but it
was in neither the read-only-where-active list nor the lane-local list, so a
masked select feeding its address was kept.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Kena: Bridge of Spirits (PPSA01802) refuses ten pixel shaders with
packed pixel ancillary input has an unsupported live use (used by SelectU32 ? ?)
at any visit limit. All ten have 30 to 33 phis on loop back edges (reported
on Linux with an RX 9070 XT).
Two rules stopped the proof at a loop, and the first one fires before the
second is ever asked:
- The walk from the select gave up at the first use inside a loop. A value
derived from the select reaches a loop phi as soon as its register is
written inside the loop, because the masked write becomes
Select(loopExec, new, phi). A loop that leaves exec alone needs nothing
more than this.
- An exec that a loop narrows and carries around its back edge is a phi
inside the loop, and such phis counted as not implying the write's exec.
The walk now follows uses into loops. A phi inside a loop implies the
write's exec when all of its incoming values do, where the phi itself counts
as implying it while its back-edge values are checked. This is sound because
each rule of the proof takes a lane bit of one value from a value computed
earlier: a phi's value on each iteration is the incoming value computed
before the edge was taken, a set lane of an And is set in both operands, a
set lane of a ballot is a lane whose predicate held. If any value in the
proof had a lane set where the write's exec is clear, the earliest such
value would take that lane from an earlier value of the proof that is set
there too, which contradicts it being the earliest. Two conditions keep this
true:
- The write's exec must be computed once, outside every loop. Otherwise a
phi can carry a ballot of it from an earlier iteration, which is not the
value the select used. Masks computed inside a loop keep the old rules.
- A result may only be kept if the assumptions it used held. When a value
that was assumed turns out not to be implied, every result recorded since
it was assumed is dropped. Results that say "not implied" are kept; they
only make the proof refuse more.
A lane that is turned back on inside the loop makes the back-edge exec wider
than the write's exec, so the phi is not implied and a read under it keeps
the select. A lane that is turned back on but never reads the derived value
does not.
New tests cover a loop that carries the write with exec saved, narrowed and
restored (read inside and after the loop), a loop that leaves exec alone,
and the helper-only ancillary select feeding such loops. The refusing tests
each fail when the matching check is removed: a revived lane that reads the
write inside the loop (ignoring back-edge values), a result proven under an
assumption that later fails (keeping it), and a write mask computed inside
an earlier loop (allowing such masks). Two existing tests expected a loop to
keep the select although every read is under the write's own exec; they now
expect it to go, next to new counterparts with a wider exec.
On the Taxi Chaos 2 requests, compute shaders with a loop inside a branch now
lose the branch's masked writes: 0x194000b400 removes 207 selects instead of
36, 0x19002bd200 111 instead of 28, and seven more gain 5 to 43. In
0x16400cf700 the 26 new removals are writes under the branch condition
tid < count; the loop's exec starts as a ballot of that condition, only loses
lanes at the latch, and every store and atomic inside the loop runs under it.
No proof on these requests had to drop a result.
Walks now go on into loops, so the pass takes longer on loop shaders, but it
stays below main. Time in MaskedSelectEliminator per prepare (median of 3,
the pass runs twice per prepare), 54 Taxi Chaos 2 requests, one run:
shader previous commit this commit main 5edae353
0x1ac0413000 334 ms 349 ms 424 ms
0x19002bd200 38 ms 103 ms 112 ms
0x19c016aa00 9 ms 74 ms 16 ms
0x194000b400 13 ms 31 ms 32 ms
all 54 547 ms 805 ms 977 ms
0x19c016aa00 removes nothing more; its walks only end later inside its
loops. The 144 requests of the NVIDIA dump go from 1283 to 1589 ms and gain
removals in the copies of the same shaders. 197 of the 198 requests prepare,
as before.
Related: #2548
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LEGO Horizon Adventures (PPSA14632) still refuses a pixel shader with
packed pixel ancillary input has an unsupported live use (used by SelectU32 ? ?)
although it has the form the previous commit handles: the only whole-word
reader is SelectU32(IEqual32(HelperInvocation, 0), BitFieldUExtract(x, 0, 11),
ancillary), the exec words are Ballot(not helper) & Ballot(true), and there
are no image ops, stores or loop phis. About 10,250 IR values derive from the
word, so the walk from that select stops at its fixed limit of 4096 visited
values and the select is kept. With the limit raised to 8192 the shader
prepares (reported on Linux with an RX 9070 XT).
A walk may now take max(4096, 2 * instructions) steps. A step is a value
visited for the first time or an implication evaluated for the first time.
The walk only visits values derived from the select, at most one per
instruction, so a walk is only cut short when the implications it evaluates
cost as much as the program itself. On the 198 Taxi Chaos 2 requests no walk
needs more than 0.79 * instructions steps. One walk stays linear in the
program, O(instructions + operands): each value is expanded once, and with
the per-walk memo each implication is evaluated once.
The walk is also cheaper, so that the larger limit costs nothing:
- visited values are marked in a table indexed by value id instead of a hash
set, and the table, the work list and the memo are reused across walks;
- blocks outside loops are looked up in a table indexed by block id instead
of a hash set. This lookup ran once per use and was most of the pass's time.
Time in MaskedSelectEliminator per prepare (median of 3, the pass runs twice
per prepare), 54 Taxi Chaos 2 requests:
shader before 40a8a5a7 this commit main 5edae353
0x1ac0413000 689 ms 285 ms 401 ms
0x19002bd200 102 ms 34 ms 100 ms
0x18c07bf000 191 ms 52 ms (refused)
all 54 1294 ms 484 ms 919 ms
0x1ac0413000 and its three copies in the NVIDIA dump now remove 659 selects
instead of 625: their walks no longer stop at 4096. No other request changes
what it removes. 197 of the 198 requests prepare before and after; the other
one fails the same way in both (v_readlane_b32 of lane 63 on a 32-lane host
subgroup).
Related: #2548
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Taxi Chaos 2 (PPSA30279) pixel shader 0x18c07bf000 is refused when it is
registered, and the title stops:
packed pixel ancillary input has an unsupported live use (used by SelectU32 ? ?)
The shader reads the ancillary VGPR (v2) once, with v_bfe_u32 v3, v2, 16, 11,
which #1904 lowers to Layer. The next write to v2, v_rcp_f32 v2, s33, runs
before any kill under the initial exec and becomes
SelectU32(IEqual32(HelperInvocation, 0), rcp, ancillary). The raw word
therefore survives only in helper lanes. The shader has no s_wqm, no DPP,
readlane or swizzle, no stores, and only *_lz samples and gathers. Every
helper-lane value that comes from v2 is dropped by a later exec select, a
compare that is ANDed with exec before its ballot, or an export whose exec
excludes helper lanes.
MaskedSelectEliminator exists to remove selects like this one, but it could
not prove the case. The kill paths save and restore exec, so the later execs
are phis at block merges, and the AND chains the kills build go deeper than
its eight-level limit.
When it checks whether a later exec implies the write's exec, it now:
- accepts a phi when the phi and each incoming value lie outside loops and
every incoming value implies that exec;
- pairs low and high exec words merged by phis in one block, predecessor by
predecessor;
- memoises results for each select and follows up to 64 levels. A cycle
counts as not implied.
Once the select is gone, nothing reads the ancillary word, so no bit of it
is produced. A select whose false lanes can still be observed is kept and
the shader is still refused. That covers a data test that leaves the word in
non-helper lanes that later export, an export under the full wave, and an
unmasked ballot.
Related: #2548
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the dynamic symbol count is absent from the SCE or SysV hash metadata, derive it from the GNU hash buckets and chain. Keep all table reads within mapped file-backed ranges.
Carried from issue #3224, commit 1a10126089882a2306318f18d19d05b04a764d6c; original author preserved.
The fp16 overflow shader translates to 64-bit float instructions, so a device without shaderFloat64 cannot run this test. Skip before recompiling on unsupported devices.
Carried from issue #3230, commit 922d4015e05c34f616912e1913854007a5d689bf; original author preserved.
Resolve each argument after --nid against the parser's option strings the
way argparse does, so --nid=B, abbreviations such as --j and --d=, and the
-- marker end the NID list instead of being taken as NIDs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SharedCodeHeaders draws on the initial context, whose PA_CL_CLIP_CNTL
selects the [-1, 1] clip space with viewport Z scale 1 and offset 0, so
the draw asks for a viewport depth range of [-1, 1]. Devices without
VK_EXT_depth_range_unrestricted, such as Intel GPUs on Windows, reject
that and the test fails before it reaches registration. Set
DX_CLIP_SPACE_DEF as the other draw tests do.
(cherry picked from commit 3c3bcf8f80cceaba4aa1f8742c32251fcc143e6b)
A 2D surface and a one-slice 2D array over the same memory are separate
storage images, since the storage key holds the dimension. pendingAlias
only took a pending image of the same dimension, so a refresh of one
view wrote the other view's results back and uploaded them again.
Sonic Origins Plus (PPSA05324) switches between the two views of the
same surfaces about 50 times a frame in its opening animation.
pendingAlias now counts a 2D surface and a one-slice 2D array as the
same dimension. The rest of the shape test (size, array range, mips,
tiling, element size, image layers and depth, tracked units) and the
conditions of the device copy (uncompressed keys, a host import, the
surface's write stamps) are unchanged.
agc_driver_storage_one_slice_alias stores through the 2D view twice and
loads through the array view after each store. It checks the texels
read through the array, that the 2D image is still pending after the
array image's refresh (where the imported block is write-watched), and
the guest memory after the final flush. Without the change the pending
check fails on lavapipe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NIDs use the base64 alphabet with '-', so some start with a dash, such as
-nvxBWa0iDs (gethostname). argparse read such a value after --nid as an
option and nid_names.py stopped with "unrecognized arguments". Every
value after --nid up to the next option of the tool is now passed as
--nid=<value>, and --nid collects them with append.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DT_SCE_STRTAB, DT_SCE_SYMTAB, DT_SCE_RELA and DT_SCE_JMPREL are offsets
into the PT_SCE_DYNLIBDATA segment, as the code analysis and the guest
image reader already read them, but the pipeline read them as file
offsets. That only works when the segment starts at file offset 0, as in
the test fixtures. A PS4 executable (CUSA00900) keeps the segment at
0x53e9b40, so the relinker read its relocations from the code and failed
with "Dynamic string offset is outside DT_STRSZ". Without the segment the
values are still read as file offsets.
(cherry picked from commit 5dbf89a57e096f2891ae786f44c3f57935af2285)
Shaders with f64 instructions now declare DenormPreserve for 64-bit floats
when the device reports shaderDenormPreserveFloat64. Without it Mesa ANV
flushed the subnormal result of v_fma_f64 to zero.
(cherry picked from commit 7df6c709de994cea0019707d45b6e7b96698fad1)
The buffer fill and copy fast paths returned before the partial last group
was applied, so a PARTIAL_TG_EN dispatch of the fill kernel filled whole
groups, and a zero last group did not throw. The partial sizes are now
decoded before the fast paths, which are skipped when the last group is
partial; the dispatch then runs the shader with the partial thread counts.
PartialGroupThreads validates all three axes before an empty dispatch
returns, and throws when (groups - 1) * full + last does not fit in 32 bits
instead of wrapping. Validate rejects PARTIAL_TG_EN combined with
USE_THREAD_DIMENSIONS (initiator 0x63).
agc_driver_pattern_buffer_fill now runs the fill kernel with a 16-of-64 last
group through the driver and checks that 80 dwords are written.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV sets the draw and dispatch indirect base with SET_BASE to the
argument buffer address plus the command's offset, which Vulkan requires
only to be a multiple of 4; vkQuake's base ends in 0x14. The validator
required 8-byte alignment. The records are read as 32-bit words, so a
4-byte-aligned base is now accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV emits 0 as the start-instance location of DRAW_INDIRECT and
DRAW_INDEX_INDIRECT when the vertex shader does not read the base instance,
and as the draw-index location of the multi forms when draw IDs are off.
The validator accepted only 0x280 as "no register". Location 0 is now
accepted and decoded as 0x280; vkQuake's draws use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV clears ORDER_MODE in the dispatch initiator for a dispatch that
needs its waves launched in order, and the validator required it set.
In-order launch is the stricter guarantee and the driver does not reorder
waves either way, so a direct or indirect dispatch now needs only
COMPUTE_SHADER_EN; vkQuake's map load issues one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV writes every COPY_DATA memory destination with DST_SEL 5
(COPY_DATA_DST_MEM in Mesa's sid.h), the current form of the deprecated
GRBM-synchronized DST_SEL 1 that was already accepted. vkQuake's timestamp
writes use it. It is now validated, resolved and executed as memory like 1
and 2; GDS and performance-counter destinations are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where the GPU has no LOAD_CONTEXT_REG_INDEX, RADV loads an image's clear
values into DB_STENCIL_CLEAR/DB_DEPTH_CLEAR and CB_COLORn_CLEAR_WORD0/1
with COPY_DATA from memory to a register (DST_SEL 0), one or two dwords.
The validator rejected every register destination.
A COPY_DATA from memory to one or two context registers is now accepted.
It is not a memory store, so the packet drains like other COPY_DATA the
driver cannot record on the GPU, and executing it reads the source then and
writes the registers. Register, GDS, immediate and clock sources, and
destinations outside the context registers, are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV sets PARTIAL_TG_EN in the dispatch initiator for an unaligned direct
dispatch, with each axis's last-group size in the upper half of
COMPUTE_NUM_THREAD_X/Y/Z, and TUNNEL_ENABLE on every GFX10 dispatch. The
validator rejected PARTIAL_TG_EN, and DecodeComputeStageInfo read the
whole register, so a partial size would have widened the workgroup.
A direct dispatch with PARTIAL_TG_EN now runs (groups - 1) * full + last
threads on each axis through the dispatch-size limit USE_THREAD_DIMENSIONS
already uses, or plain groups when every last group is complete; a zero
or oversized last group throws. The workgroup size is NUM_THREAD_FULL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The builder threw on a null indirect base, while the PM4 executor accepts
SET_BASE with address 0 (it only requires the alignment and address bits) and
an indirect draw that later reads the base is checked there. Titles clear the
base with 0, as with sceAgcDcbSetIndexBuffer, which already accepts 0 to
unbind. A non-null base is still checked for 8-byte alignment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A recorded draw counted as reading and writing any guest memory when its
resources needed completion work, which a fault buffer alone (guarded
descriptor slots, rect lists) also needs. Such a draw reaches no guest
memory through it: the buffer is the draw's own, written only on a fault.
Its pass hazards now come from its known ranges and images like any other
draw; address-based draws, draws through the BDA table and draws with
copied writes still count as touching any guest memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port FreeBSD 11 lib/libc/stdlib/random.c (freebsd-src 4ab2e064): the
additive feedback generator with the default 128-byte TYPE_3 table,
good_rand seeding, state sizes rounded down to 8, 32, 64, 128 or 256
bytes, setstate returning the previous state, and initstate returning
null below 8 bytes without setting errno. Calls are serialised by one
lock; a null state throws.
The import audit of the homebrew catalog on main 262c0316 lists them as
absent libSceLibcInternal imports:
- random: PPSA74126 Stremio Plus, PPSA85300 VLC, PPSA99169 RetroArch,
PPSA99177 PS5VR, PPSA99420 PS5MC, PPSA99505 Jelly5, PPSA99515 Emby5,
PPSA99810 PSBox Cloud Gaming
- srandom: PPSA85300, PPSA99169, PPSA99420, PPSA99810
- initstate, setstate: PPSA99177, PPSA99420, PPSA99505, PPSA99515
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An 8_8 target with SWAP_ALT keeps the x export in its first channel and
the alpha export in its second, and the color block blends that second
channel with the alpha equation. Blending into such a target threw.
With one equation for color and alpha the draw blends as before. With a
separate alpha equation the pipeline draws twice: the first pass writes
the first channel with the color equation, the second writes the second
channel with the alpha factors and operation moved into its color
equation. The split is refused, by throwing, for draws that write depth
or stencil, use per-face depth bias, or whose pixel shader writes memory,
since those would repeat; factors of the destination alpha throw since
the target keeps it in its second channel. Pipeline libraries are not
used for split pipelines.
A plain image_sample that says DIM 2D on a 1D T# takes its texel and its LOD
from the first coordinate alone, measured on RDNA2 (gfx1036) with the
hardware oracle. Astro's Playroom draws with such a pair.
The expected rows were measured with Vulkan alpha-to-coverage on a Radeon 660M (gfx1035) under RADV_DEBUG=noatocdithering, which programs DB_ALPHA_TO_MASK 0xaa01: an alpha of at least one half, 2 and +inf keep the pixel; below one half, -0, negatives, -inf and NaN drop it.
Covers code, stack and an anonymous page, and an unmapped address. On Linux the test runs again with APS5_NO_PROCMAP_QUERY so the line scan stays covered.
DB_ALPHA_TO_MASK 0xaa01, the enable bit with the four offsets at 2 and no OFFSET_ROUND, reaches the pixel stage, which keeps a pixel when its MRT0 alpha is at least one half. Dithered or rounded settings, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha, still throw. God of War Sons of Sparta (PPSA28997) stopped on it a few minutes into play.
It carried its own copy of the /proc/self/maps line scan and is called on the same Unity stack walks as sceKernelGetModuleInfoForUnwind. It now uses the shared HostMapping lookup.
A pixel shader whose stage info asks for alpha-to-coverage declares SampleMask, starts with every sample covered and, at its MRT0 export, clears the mask when the alpha is below one half or NaN, running with late fragment tests. The shader keeps running, as a zero coverage mask does on the GPU. The flag is part of the static shader identity and of the request serialization, now version 16.
On Linux every call opened /proc/self/maps and scanned it line by line with sscanf. Unity titles call it on each stack walk, and with about 900 mappings God of War Sons of Sparta (PPSA28997) spent most of its main thread there. The covering mapping is now asked of the kernel with PROCMAP_QUERY (Linux 6.11), on one descriptor opened once, as GuestMemory does; older kernels and APS5_NO_PROCMAP_QUERY keep the line scan.
AnyPS5's libc heap registered every block with GPU access, so every
address-based build imported or mirrored each malloc block the title had
made. The console's libc.prx maps its heap with protection 3 (CPU read
and write, no GPU bits), so the GPU never sees it there either.
Heap blocks are now registered without GPU access; reallocating keeps the
block's access. The AvPlayer fallback for a title that passes no texture
allocator stands in for that allocator, which hands out memory the GPU
reads, so it allocates through a new GuestHeapAlignForGpu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title calls our exports on stacks it sized for the console. With lazy
binding the first call through a PLT slot runs the dynamic linker's symbol
lookup on that stack: on the 2 KiB fiber context Astro's Playroom uses it
wrote below the context into the neighbouring heap chunks.
The import probe only checked whether a GPU read of an imported range
reports its pages as written. lavapipe runs command buffers on CPU
threads, so its writes into an import go through the page tables and
are collected as CPU stores. In agc_unit_shadow the whole publish's
copy into the import is then collected as a CPU store over unit 3, and
the next partial publish drops the slab's newer bytes as stale: "the
partial publish did not copy exactly the partly covered unit".
The probe now also fills the scratch range on the GPU and collects
again. When the read or the fill reports pages as written, host and
dma-buf imports are compared. lavapipe reports 0 of 4 pages after the
read and 4 of 4 after the fill; NVIDIA reports 0 after the fill.
The probe change is the first commit of #1046.
The title id now comes from GetAppTitleId, so param.json has one parser and
its validation (a missing file or titleId throws). Another pid returns EPERM
and a null info returns 0 without writing, as shadPS4 does. app_id stays 1,
the running title's id in libSceSystemService, which is inferred, not
measured.
guest_app_info runs the call without param.json in a child process and
checks that the field between app_id and the title id is left alone.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetAppInfo fills the app id and the title id of the calling
process: app id 1, the running title's id in libSceSystemService, and the
titleId of /app0/sce_sys/param.json at offset 0x10, up to 9 characters and a
terminator. Only those fields are written, since callers declare the
structure with different sizes. Another pid returns ESRCH and a null info
EFAULT.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AMM ranges were reserved address-less on first use, so they took the
lowest free address of the arena, directly after the title's last mapping,
where the title's next hinted maps go. They are now reserved at a fixed
place, the last 64 GiB of the guest arena, which maps without MAP_FIXED and
the hints derived from them do not reach; a reservation that cannot be made
there throws.
Fixes#3146
Split at the first delimiter like FreeBSD's strsep: empty fields are
returned, and the cursor becomes null after the last token.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Share rand's generator step with rand_r, which advances the caller's
state instead of the global one, as FreeBSD's rand and rand_r share
do_rand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on a PS5 (firmware 13.60):
- sceRudpInit rejects a null pool or a size of 0 or less with
INVALID_ARGUMENT, and a pool under 0xC58 bytes or not 8-byte aligned
with MEMORY (0x80770007).
- sceRudpGetStatus rejects a null pointer or a byte count of 0 or above
0xF8 with INVALID_ARGUMENT and reports the live context count at 0x64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export sinh, cosh, coshf, asinh, asinhf, acosh, acoshf and atanh,
forwarding to the C++ standard library like tanh and tanhf.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InitialContextRegisters built the clear-state context registers on every
call: about 200 emplace calls from its list and loops, with the register
vectors growing several times on the way to offset 0x3b8. Each
QueueState starts from it, so it ran for every CLEAR_STATE, and
ResolveShaderAbi and ResolveGraphicsStagesAbi construct a QueueState on
each call, which sceAgcLinkShaders, sceAgcCreatePrimState and
sceAgcCreateInterpolantMapping make while a title draws.
The registers are now built once into a table that is never destroyed,
so a queue cleared while the process exits still finds it, and each call
returns a copy of the table.
A call takes 71 ns instead of 746 ns, and constructing a QueueState 885
ns instead of 1378 ns (200000 calls each, GCC 13 -O2). On the title
screen of PPSA21402 (Balatro) the two functions took 44 to 47 of about
2300 CPU samples per 10 s and no longer appear in the profile.
sceNgs2ParseWaveformFile read everything from the offset to the end of
the file into a zero-filled buffer and parsed that, although parsing
uses only the RIFF chunk headers and the fmt, fact and smpl payloads.
A waveform at an offset in a sound bank cost a read of the rest of the
bank on each call.
ReadChunks now takes the bytes through an accessor. Waveform data passes
its buffer; a waveform file seeks to and reads the headers and those
payloads alone, with the same checks against the length from the offset
to the end of the file. The format dispatch both use moves to
ParseWaveform.
ngs2_parse adds a bank of two waveforms in one file, the second with a
chunk after its samples, and parses each at its offset.
PPSA21402 (Balatro) parses its five 33 MB music tracks at start: one
call took 8.3 to 26.7 ms and allocated 33 MB, and now takes 0.01 to
0.04 ms with the same result.
An unregistered graphics program is read raw only while the shader registry
is empty; with shaders registered it throws, as before. A draw that uses a
raw program is noted, and RegisterShader throws once that has happened,
since a title that registers shaders after drawing unregistered programs is
not understood. The draw cache revalidates a raw program by reading it raw.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The POSIX alias takes (attr, int), returns EINVAL (22) for a null or
uninitialized attribute like the other pthread_attr setters, and
stores the value through scePthreadAttrSetsolosched, returning its
result in errno form. The signature and the stored int follow the SCE
twin in KytyPS5 and sharpemu; PS5PCEM implements the alias the same
way. The value is not applied to scheduling, recorded in TechnicalDebt.
Returnal (PPSA01285 v03.000.005) stops on it after eager binding
(#2872, #1155):
what(): pthread_attr_setsolosched_np_nid_postfix not implemented
LEGO Horizon Adventures (PPSA14632, #734) and Palworld (PPSA23126,
#2598) import it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The register draw key identifies an unregistered program only by its
address, and a key hit reuses the entry's decode without reading the
program again. A different program uploaded to the same address with
the same registers would run the old compiled shader. A hit now checks
the decode's unregistered programs against guest memory and drops the
entry when one changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the upstream CMASK implementation and remove the import requirement from image clears. Propagate storage-image creation failures. Test repeated clears, copied write-back, and failure without changing guest data.
AI-assisted: yes.
guest_filesystem creates symbolic links for the Windows lstat checks.
Without administrator rights or Developer Mode, CreateSymbolicLinkW
fails with ERROR_PRIVILEGE_NOT_HELD even with
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE, and the test aborted there
(reported as a stale guest errno). Skip those checks with a note in that
case; any other failure to create the first link still fails the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2839 decides f32 product tininess after rounding, in all four f32
round modes, from the exponents and a UMulHi and IMul of the
significands, and builds the flushed value from them. To nearest even,
the mode of shaders without a known float mode and of the 0xc0 most
titles use, one multiply decides it: one factor times 2^64, which is
exact below 2^64, times the other factor rounds the exact product once
to 24 bits in the normal range, and 0 < |that| < 2^-62 is tininess
after rounding. A product that rounds up to 2^-126 is then not tiny and
keeps the emitted product, which is 2^-126 there, so nothing has to be
built from the significands.
The flush of v_mul_f32 v5, v6, v7 takes 12 IR instructions instead of
34, counted as the instructions the flush select needs beyond the
product. vector_constant_factor_tininess pins 12 to nearest and 36 for
the directed path of the same instruction, and checks that the
directed modes and v_mad_legacy_f32 (before rounding) keep #2839's
check. Its round-to-nearest branches are removed as unreachable.
The f32 denormals entry says which rounding the check inherits: the
multiply is a plain OpFMul like the product, with no f32 rounding mode
requested, so it relies on the driver rounding f32 to nearest even, as
the products do. F32ConstantFactorTininess names the row a check before
rounding flushes, 0x3f7ffffe * 0x00800001, which keeps the emitted
0x00800000.
Against #2839 to nearest even, a model of both checks agrees on
1,074,977,546 operand pairs (every 24-bit significand at both rounding
thresholds, random pairs, specials) with each other and with an exact
reference. On an RTX 5070 the outputs of #2839's own tininess and
mixed-source shaders are bit-identical with either lowering over
165,298 and 164,008 generated rows in all six float modes tested.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wait on an equeue that holds socket events now blocks in poll/WSAPoll on
those sockets plus a per-thread loopback wake socket, instead of rechecking
every millisecond. Adding, triggering or deleting an event and closing the
equeue wake the blocked waiters. Closing a socket removes its read and write
events, as on FreeBSD. A read event also reports EV_EOF once the peer has
shut down its sending side.
sceKernelAddReadEvent/DeleteReadEvent and sceKernelAddWriteEvent/
DeleteWriteEvent register EVFILT_READ and EVFILT_WRITE events on guest
sockets. The equeue polls the host socket for readiness on each wait
and reports the readable byte count or the send buffer size in data,
with EV_EOF on hang-up or error.
pthread_cond_wait and pthread_cond_timedwait, and their sce counterparts, threw a
host exception when the mutex was unlocked, locked by another thread, or not yet
initialized, and dereferenced the adaptive static initializer. FreeBSD returns
EPERM for these (EINVAL for a destroyed mutex) before touching the condition.
(cherry picked from commit a78bb8a855ca3b277f128c6a9e52ff3949b9cb80)
The elected lane's inner spin loop never ended on Mesa ANV with the LDS in
a storage buffer. It now tries once and the outer loop elects again; the
update is unchanged.
(cherry picked from commit e894f9092326a849ebe1ce46ff0ed3b25fbb7e9b)
sceVideoOutVrrPegToFixedRate and sceVideoOutVrrUnpegFromFixedRate were
NotImplemented without arguments. Both take the output handle; the peg
also takes two further arguments whose meaning is unknown. VRR is not
modelled and the output always runs at a fixed rate, so both validate
the handle and return 0; non-zero peg arguments throw. Recorded in
TechnicalDebt.
Uncharted: Legacy of Thieves Collection (PPSA05684) stops on main with
"sceVideoOutVrrUnpegFromFixedRate not implemented"; it passes its
output handle to both (and zeros to the peg) and ignores the results.
Since #2133, a 3D image_sample_lz or image_load bound to a 2D-array T#
decodes as 2D and matches the new 2D mode, so it reads layer 0 for any
r and returns zero for z != 0. adriadam10 measured on RDNA2 (gfx1035)
that dim 3D on a 2D-array T# behaves exactly like dim 2D-array, with r
or z as the layer. Decode a 2D-array T# bound to a 3D access as 2D-array
and add a 2D-array mode next to the 2D one.
The same measurement covers image_load_mip on both T# types and
image_get_resinfo, which returns (w, h, 1, levels) on a 2D T# and
(w, h, layers, levels) on a 2D-array T#. Allow those accesses and return
1 as the third resinfo component through the 2D mode. image_get_lod is
not measured and keeps throwing.
Pipeline destroyed its shader modules right after LinkPipelineFromLibraries returned, while the pre-rasterization and fragment shader library parts built from them stay cached and are relinked later with VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT. On AMD's Windows driver (RX 6700 XT, Adrenalin 25.10.45.05) that link faults inside amdvlk64.dll at varying offsets, reading freed heap memory, in 22 agc tests on a cold driver cache; all of them pass with APS5_NO_PIPELINE_LTO=1. Running the optimized link synchronously or without a pipeline cache did not help, keeping the modules alive did.
When the optimized link is enabled, the library store takes over the modules of each newly built shader part and destroys them in ClearPipelineLibraries after the pending links have finished. Modules of a pipeline whose parts were all cached are destroyed as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Key 0x10 (GFX9_DCC_CLEAR_SINGLE in Mesa) marks a 256-byte block whose texels all equal its first texel. A CB DCC decompress pass over uniform comp-to-single keys now writes that texel over its block and stores uncompressed keys; a fast-clear eliminate over them throws.
When another readable allocation starts right after the buffer's allocation,
the bound range still ends at the first allocation: the bytes past it are
outside the buffer the descriptor's records describe, and binding into the
neighbour would send the range back to a copy on every draw.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV rounds a strided buffer descriptor's record count up, so stride * records
can run up to one stride past the end of the buffer's allocation. The guest
buffer region then no longer fits inside the allocation's host import and is
copied on the CPU, whole, for every draw that binds it.
When a strided descriptor ends less than one stride past the end of the
readable registered allocation that contains its base, the range is bound up
to the allocation's end. RetroArch's Dolphin core binds a 48 MiB stream buffer
with stride 20 (records 0x266667, 12 bytes past the allocation) on every draw.
With this change the buffer is bound in place from its import.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window offset register is already required to be zero, so the enable bit has no effect on a draw that gets this far.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Background registration preparations (#2583) copy the device's
SpirvTarget, whose capability and extension spans point into the
device's shader profile. Driver::stop reset the device without waiting
for the detached pool, so a preparation still queued or running read
freed extension names and agc_driver crashed at exit on RADV.
Driver::stop now closes the pool after joining the queue workers: it
stops queueing and waits until no task is queued or running. A
preparation submitted after that runs on the caller's thread, which
holds its own reference to the device, like the existing fallback for
a pool without threads.
Fixes#2891
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v_log_f16 is lowered through the same f32 log2, so the entry names both
and states the f16 effect measured over every f16 input (one NVIDIA
result moves onto the hardware's bits, none on RADV). The series is
chosen with OpSelect, so its cost applies to every v_log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
The driver's Log2 has an absolute error that does not shrink with the
result, so near 1.0 the relative error explodes: on an RTX 3080, v_log_f32
of 1 - 2^-24 is 23,947,836 ULP off. Measured on RDNA2 (gfx1036), the
hardware's log2 is within 1 ULP of the correctly rounded value, and an
eight-term series in x - 1 is within 2 ULP of both for |x - 1| <= 1/8.
The lowering keeps the driver's Log2 and takes the series only inside that
band when the two differ by more than 4 ULP, so RADV's native result stays
bit-exact and NVIDIA's is within 6 ULP inside the band.
agc_driver_log_near_one pins 64 inputs per type from the oracle with
per-lane tolerances.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
Enable the existing input-only f32 flush policy for division scaling.
RDNA2 measurements pin all three sources and signed zeros; keep the
scaling rules unchanged and retain their separate technical debt.
Co-Authored-By: Codex gpt-6.1-sol <noreply@openai.com>
Resolves a guest path the way FreeBSD 11's realpath does: a relative
path starts from the guest working directory, "." and empty components
are skipped, ".." drops the last component and never climbs above "/",
every other component must exist, and one that is not a directory with
anything after it is ENOTDIR. Backslashes separate components as in the
rest of the path layer. The result is the guest path; the host is
consulted only for existence and type, so host symlinks are followed but
not expanded, and ".." is lexical, as in the rest of the path layer. A
null buffer is PATH_MAX bytes from the application heap, a null path is
EINVAL, an empty one ENOENT, and 1024 bytes or more in the input or the
result is ENAMETOOLONG. On failure the caller's buffer holds the path
that failed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
Keep each registered header and its prepared shader state instead of
replacing every registration for the same code address. Resolve explicit
ABIs by header and programs by type, preserving re-registration order.
Co-Authored-By: GPT-6.1-Sol <noreply@openai.com>
The detached alarm thread waits on a condition variable that was a
namespace-scope static. At exit its destructor ran with the thread still
waiting, and glibc's pthread_cond_destroy blocks then, so a process that
called alarm never exited on Linux. The mutex, condition variable, deadline
and started flag now live in one structure allocated once and never
destroyed.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host import covered exactly one registered range, so a buffer crossing
two ranges had no import and was copied on the CPU at every use, under
the GPU mutex. Titles that map a GPU heap piece by piece (Uncharted maps
1 MiB direct-memory pieces) hit this for every buffer larger than a
piece.
An import now covers the run of address-adjacent registered ranges that
are readable with the same write and GPU access. Runs never overlap, so
imports do not either; an import is retired when any range of its run
changes, and a refused run is remembered by its base. Callers already
address imports relative to their base.
Their signatures are inferred from SharpProspero only, so the line belongs
with the other unverified signatures and not at the end of Silent stubs.
Co-Authored-By: Claude <noreply@anthropic.com>
sceFontSetScriptLanguage, sceFontGetScriptLanguage,
sceFontSetTypographicDesign and sceFontGetTypographicDesign threw. With
the signatures SharpProspero declares, a font handle now keeps the
language set for each script and the feature set for each typographic
design, under the handle's lock, and returns them, with 0 for one never
set. They do not change shaping or rendering yet; TechnicalDebt records
the assumptions.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
(cherry picked from commit c4dcbe4403dc635fde2a823dec311a939816cefc)
RetroArch (PPSA99169) stops at alarm on main: `FAIL: unresolved ELF import DQXJraCc1rA from libSceLibcInternal.prx`.
alarm(seconds) arms one process-wide real-time alarm and returns the seconds left on the previous one, rounded up like FreeBSD's alarm.c (any partial second counts); alarm(0) cancels it. When it expires, SIGALRM's disposition as recorded by sigaction or signal applies: ignored, nothing happens; default, the process terminates as on FreeBSD (exit status 128 + 14); a guest handler throws, because asynchronous delivery to a guest handler is not implemented.
The timer lives in libkernel next to the signal dispositions (GuestAlarm_nid_no_patch); libSceLibcInternal, which the titles import alarm from, forwards to it.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The converted thread pointer was set to the TLS block plus the size rounded to max(p_align, 16), while the guest reads its first variable at tp minus the size rounded to p_align. For a PT_TLS with memsz 0x21 and p_align 8 (Resident Evil 4) that is 0x30 against 0x28, so the first variable was read from the second slot. The thread pointer now uses the size rounded to p_align; the block layout is unchanged. WIP: the test for this is still missing.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A PS4 executable (CUSA00900) keeps 37 MiB of read-only data in its
executable segment after the code. The syscall scan and the call-site
resolution decoded the whole segment linearly, reached a float table and
failed with "Unsupported 0F 79 opcode without SSE4A prefix". In
executables with a PT_SCE_DYNLIBDATA segment they now stop at the end of
the PLT that follows the last function of the unwind table. The PLT is
found by searching forward from that function for its first entry, which
pushes the GOT entry at PLTGOT + 8 and jumps through PLTGOT + 16, since
code without unwind entries can sit in between (680 bytes of start-up
code in an OpenOrbis-built sample). When the executable has PLT
relocations and no such entry follows, the relinker fails instead of
guessing. Executables without the segment are scanned as before.
(cherry picked from commit a21e8fe4c0e6c03189d559e992c0f037c076700d)
libSceCdlgG2P and libSceVrSetupDialog threw from every export. They now
run the common-dialog state machine libSceLoginDialog uses: Initialize,
Open, the status calls, GetResult, Close and Terminate follow it, the
dialog finishes at Open without showing anything, and GetResult reports
that the user cancelled, writing only the leading int32 result. The
signatures are assumed from the other common dialogs, as TechnicalDebt
records. 13 exports move from stubs to implemented.
Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a
(cherry picked from commit 07992adf80bae9b2d7da4b751cd21c040fa1c5b8)
A flat_atomic_* whose address lands in the LDS aperture updated the dword without the lock, so it could lose updates against a ds_add_u64 or a 64-bit flat atomic on the same dwords. It takes the lock like the 32-bit ds atomics when the program has a 64-bit LDS atomic; programs without one emit the same SPIR-V as before.
On RADV the race between ds_add_u64 under the workgroup lock and a
concurrent ds_add_u32 only shows when the lanes get their operands from
per-lane buffer loads: with v_mov_b32 constants or loads from one address
no increment is lost there. The new case has the split-lane kernel's
parity split and loads each lane's (0xffffffff, 0, 1, 0) row from the input
buffer at tid * 16 with buffer_load_dwordx4, so the input buffer now holds
256 rows. With SpirvMemoryInstructions.cpp reverted it fails on RADV and
on the RTX 3080; with the change it passes on both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
Even lanes run ds_add_u64 and odd lanes run ds_add_u32 on the slot's high
dword, one pass per dispatch, as in the reported kernel, and the dispatch
is repeated 32 times from one compiled shader.
With ANYPS5_ENABLE_SPIRV_TOOLS=ON the scalar loop around the ds_add_u64 lock loop made
SPIRV-Tools' block merge pass merge the loop header with its multi-block continue
target, and validation after optimization rejected the module. The 16 iterations are
now emitted inline; the expected sums and the divergent exec masks are unchanged.
The hardware serialises 32-bit and 64-bit LDS atomics on one address; the lock that makes the 64-bit forms atomic did not order 32-bit atomics, which lost increments between the two halves of a 64-bit update (#2297).
GuestMemory::WriteChanged stores only the bytes the GPU changed, so that CPU writes to the bytes
around them survive. It found them a byte at a time inside every changed 256-byte block and
stored each run with memcpy. Where changed and unchanged bytes interleave (the low bytes of
floats), every run is a few bytes long, and that loop took 43% of the GPU submission thread in
Disney Epic Mickey: Rebrushed's first level (0.9-1.0 ms per MiB).
Each 16-byte chunk is now compared with SSE2. A chunk with no change is skipped, a chunk that
changed entirely is stored whole, and a mixed one is stored with MASKMOVDQU, which writes only the
bytes selected by the mask. An SFENCE after the loop orders those non-temporal stores before the
stores that follow. Bytes the GPU did not change are still never written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134RWCyiyydJBbfKz8g9Bwe
An import whose module matches no needed library has no library name,
and the Windows startup stub counted it against every library, so a
missing library with nothing imported from it was still never skipped.
Every such import in the executables of Record of Lodoss War, Resident
Evil 4, PRAGMATA and Returnal declares a module that exists under
another file name (libSceJson for libSceJson2.prx, for example), and
none declares libSceXml or Party.
The relinker now keeps the declared module names of those imports, for
the executable and each guest module, and the stub counts a host
library as imported from when its name up to the first dot matches one
of them, ignoring case. A module declared as libGuest still keeps a
missing libGuest.xyz required, as guest_module_identity checks.
Imports without a module ID declare nothing and count against no
library. The rule and the title data are from xXPBSXx.
The Windows startup stub stopped at the first DT_NEEDED library it could
not load, even when no import is searched in that library. Titles list
libraries they import nothing from (Road of Death needs libSceXml.prx,
Resident Evil 4 needs Party.prx), so they stopped with GetLastError 126
before running. A host library that no import's search list contains is
now skipped with a message when it is not found; its handle stays null
and is never read. Missing libraries that something imports from, other
load errors and guest modules still stop the start.
AddWaveformBlocks rejects a null data pointer before reaching the silence
path, so TestSilenceBlock aborted and its dataless rejection passed for the
wrong reason.
PPSA11386 registers 3840x2160 tiled display buffers in pixel format
0xC001000622000000, R16G16B16A16 float in KytyPS5 and SharpProspero
(0xC001000600000000 is the blue-first variant), and stops on "VideoOut:
unsupported display pixel format". The buffer is eight bytes per texel; its
tiled layout is assumed to be the 8-byte SW_64KB_R_X equation applied to
full coordinates, as addrlib applies it. The values are taken as linear
light and encoded with the sRGB curve for the 8-bit output. The encoding
compares the raw half-float bits against two tables of 255 thresholds
(color and alpha) that the CPU decode and the color transfer shader share,
so both give the same bytes on any driver; negative and NaN values give 0.
Guest-memory detile, the resident image path and the CPU decode all handle
both formats; DCC metadata on a float buffer throws. Neither the layout nor
the transfer is verified against console frames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
A waveform block that declares samples with no data threw as not
fitting them. PPSA04867 adds such a block with flags 0x11 (data_size 0,
num_samples 16385) and terminates the process on the throw, followed by
a fully specified block under the same flags, which takes the ordinary
path. Accept flag 0x10, named SILENCE here: a dataless block under it
renders num_samples frames of silence and finishes like a complete
block, reporting its bytes through decoded_data_size. A dataless block
without the flag keeps throwing. No public reference names the flag;
the assumption is recorded in TechnicalDebt.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The CFG used by unused-filter=1 followed only direct branches, so code reached
through lea, relative-relocated pointers or jump tables was unreachable and its
imports were dropped. It now roots RIP-relative lea targets, relative relocation
targets and relative jump tables, and counts any RIP-relative reference to a GOT
slot as an access. Address-taken targets are walked after the direct control
flow, and a target that does not decode as an instruction is not code.
Comments are reserved for technical debt (docs/dev/TechnicalDebt.md), so
the ones the previous commit added to the recorder, the snapshot keep and
the tests go.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZHkb8kUwnHAuBBbHywvJf
A ShaderResources from the resource caches is the same object for every
draw or dispatch that uses it, and Recorder::ReusableDrawSnapshot hands
the next draw the same snapshot buffer, so passing their bytes with each
Keep counted one allocation once per use and submitted the batch before
it held the budget. Recorder::KeepBytes counts an owner's bytes once per
batch, and the dispatch, draw and snapshot keeps use it.
The recorded kept bytes test runs a dispatch and a draw that copy a
guest buffer and two draws that snapshot one on the device, and fails
when any of the three sites stops counting or a reused snapshot counts
twice. The BdaResources check pins the copied region it sets up.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZHkb8kUwnHAuBBbHywvJf
The recorder bounds the bytes a batch keeps alive (KeptBytesBudget) and
waits for older batches past twice that. A recorded dispatch or draw kept
its ShaderResources, and a draw its snapshot buffers, with a size of 0, so
the host-visible copies of guest buffers they own never counted. A title
that binds a 124 MiB writable guest buffer in every draw then held about
ninety copies, 11 GB, until the GPU caught up, and aborted with
VK_ERROR_OUT_OF_HOST_MEMORY from vkAllocateMemory.
GuestBufferMemory reports the bytes of its copied regions, and the
dispatch and draw keeps and the draw snapshots pass their bytes to
Recorder::Keep, so the existing bound applies to them.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The lookback classifies preceding instructions at their decoded opcode
offset, the same place the forbidden-instruction check looks since #1935,
so a legacy-prefixed mov rax, imm or mov r10, rcx is recognized. The REX
byte stays part of the patterns, so mov r8 and mov r8d cannot masquerade
as rax loads. Anything unrecognized still stops the inference.
Adds prefixed fixtures to both test suites: a prefixed constant stub that
must report syscall number 9, and a prefixed clobbered stub that must
report no number.
AI-assisted: yes. The submitting author is responsible for every changed line.
The syscall check names only the code offset, so a title that links a FreeBSD
libc (the ps5-payload-dev SDK statically links one into every homebrew binary)
stops with no indication of which call it reached. Those stubs load the number
with a constant before the instruction (mov rax, 9 for link), so decode the
nearest constant load into rax and add it to the message:
Forbidden syscall instruction at code offset 0xa: syscall number 9
The number is reported only when it can be established. The constant load must
be the instruction directly before the syscall, or one mov r10, rcx (the SDK's
first-argument move, which does not write rax) in front of it; any other
instruction stops the inference instead of returning a stale value, so
mov rax, 9; add rax, 1; syscall reports no number rather than 9. Only the three
invocation opcodes report a number: sysret is a return and keeps the current
message, as does a stub that loads rax from a register, like the SDK's generic
syscall() wrapper. The check still throws; only the text changes.
Tested: relinker-only build, full ctest on Linux x86-64 (45/45). The C++
syscall_scanner test covers the opcode matrix and the review regression; the
Python test_syscall_number covers the same contract through the harness the
relinker lane already runs on Ubuntu, Windows and macOS, where the C++ fixture
helpers (sys/mman.h, posix_spawn) do not build.
AI-assisted: yes
The registry calls an observer on the mapping thread, after the registry
lock is released, with the ranges a kernel mapping gave GPU access:
direct and flexible maps with it, and fixed remaps with it over a
registered range (a reservation). The mutation looks them up while it
still holds the lock and hands them over weakly, with the registry
generation it ended at, so the observer does not keep a range alive.
The driver imports those ranges on its current device, so an
address-based build finds the import instead of pinning the memory on
the queue that first uses it. A range that gains GPU access through
sceKernelMprotect, and AnyPS5's own libc heap, are still imported on
first use.
The import at map does not reconcile the other imports with the
registry, so its cost does not grow with the number of imports. It skips
a range that is gone, and any range when the imports were reconciled
with a newer registry since; the next lookup imports those as before.
It installs the pin waiter as the other import paths do, so unmapping a
range imported at map retires its import. The live import bytes are kept as a
running total instead of being summed over every import. Once the import
table belongs to the current device the import takes only the table's
lock; the first import on a device (which runs the write-watch probe)
and a device that was just replaced go through the GPU lock. Clearing
the observer waits for calls already running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux every executable or guest-module import whose NID a guest
module exports was renamed to the guest-suffixed name, whatever module
the import declares. A title that bundles a module exporting system
NIDs then had those imports bound to it: the libc.prx loader shim of
ps5-native-app-boilerplate exports placeholder functions under the
libSceLibcInternal NIDs, so DOOM PPSA99666's snprintf, vsnprintf,
memcpy and malloc (declared from libSceLibcInternal) returned 0 and the
title never found its WAD (#1497).
An import now binds to a guest export only when the module it declares
is a guest module, as on Windows. Imports without import-module
metadata keep the previous behaviour, and a title's own libc.prx
imported from libc still binds to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ProsperoRadio (PPSA99001) imports six sceOpusCeltDec functions from
libSceOpusCeltDec and fails to load because no such module exists. It
opens this decoder only as a fallback when libSceOpusDec reports a
retryable error for a CELT packet, which libSceOpusDec here never
returns, so the module is exported without an implementation and each
function throws if it is called, as for sceSaveDataCancel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on RDNA2 (gfx1035) in review:
- the shared and private apertures are 4 GB windows (the gfx9+ layout of kfd_flat_memory.c), so a flat
address routes by its whole high dword, 0x80000000 or 0x70000000, not by its top 16 bits;
- a dword or short access whose last byte is past the end of LDS reads 0 or writes nothing as a whole;
multi-dword accesses are checked per dword.
The aperture test gets rows that straddle and pass the end of LDS; the TechnicalDebt line names the
public source of the layout and keeps only the PS5's base values as inferred.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main's StoreAddressU32x2..x4 (one BDA lookup per wide store, b95842597) bypassed the aperture routing:
a flat_store_dwordx2 into the shared aperture went to BDA at 0x8000000000000000 and LDS kept its old
value. The wide store now stores each component through the aperture like the 32-bit store, and keeps
the single BDA lookup outside the apertures.
The aperture test's V# word 3 is 0x31016fac (OOB_SELECT 3, raw), as in main's other execution tests:
with OOB_SELECT 0 its output buffer read as out of range after #639, which was the failure reported on
four drivers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sub-dword stores and unaligned dword accesses go byte-wise; 32- and 64-bit FLAT atomics
route to LDS (64-bit under the workgroup lock), scratch (read-modify-write) or BDA.
agc_driver_flat_apertures covers them in 121 rows, with mixed-address waves and wave64.
The aperture bases and the unmodelled operands 235-238 are listed as inferred.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A compute FLAT access whose address has the console's shared aperture in its high dword (0x8000xxxx)
reads or writes the workgroup's LDS at its low dword, and one in the private aperture (0x7000xxxx)
the lane's scratch; other addresses stay guest memory through BDA. GTA V's ray traversal keeps its
stack in LDS and spills it to scratch through such addresses; treated as guest memory the pushes were
lost and the traversal never ended (device lost). An execution test round-trips both apertures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v_cvt_f32_i32 and v_cvt_f32_u32 used the host Convert which always
rounds to nearest even. When FLOAT_MODE bits 1:0 are directed, build
the f32 bits from the integer magnitude so discarded low bits follow
round toward +inf, -inf or zero, as measured on RDNA2 (gfx1036).
Co-Authored-By: Composer <noreply@cursor.com>
Twenty-five of the ninety-seven errno messages differed from FreeBSD releng/11.4 lib/libc/gen/errlst.c (for example errno 0, 30, 47, 62, 70 and 84).
(cherry picked from commit ce14c9ad7dc639335ff72b60122bae2bd0fee302)
Directed FLOAT_MODE modes for v_cvt_f16_f32, v_cvt_f16_i16,
v_cvt_f16_u16 and v_ldexp_f16 were ignored, so results stayed
round-to-nearest-even. Hardware uses FLOAT_MODE bits 3:2 (round16);
the directed path runs only when f16 denormals are preserved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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.
The mesh stage was compiled at the driver's default subgroup width, while the translated mesh program lays wave64 lanes out on, and ballots over, a 32-lane host subgroup. Intel's Windows driver (Arrow Lake, 32.0.101.8991) reports subgroupSize 32 but prefers 16 for mesh shaders, and every agc_driver_mesh draw drew no triangle there. The mesh stage now gets VkPipelineShaderStageRequiredSubgroupSizeCreateInfo{32} when the program's hostSubgroupSize is 32 and the device lists the mesh stage in requiredSubgroupSizeStages, as compute pipelines already do.
Rosetta runs AVX2, FMA, BMI1/2, F16C and MOVBE, but, unlike an Intel
host, it has neither RDSEED, CLWB nor RDPID. --to-rosetta converts
everything --to-intel does and lowers these three as well:
- RDSEED becomes RDRAND (the /6 form of the same opcode), which also
returns a random value and sets CF.
- CLWB only writes a cache line back, so it becomes a NOP of the same
length.
- RDPID becomes a load of 0 through an optional stub: Rosetta's TSC_AUX
is a constant, not a processor index, so every thread reports
processor 0. A site with no movable instruction after it for the jump
is kept.
The flag implies --to-intel and conflicts with --windows. It is
independent of --macos: it only changes what the conversion produces, so
--macos alone leaves these instructions as they are, as on an Intel Mac,
and a title for Rosetta is relinked with --macos --to-rosetta. Intel
output is unchanged: the target is a parameter of the converter, Intel by
default.
Scalar f16 add/mul/sub ignore FLOAT_MODE denorm16 flush bits on main.
Flush inputs when bit 0 is clear and results when bit 1 is clear at
nearest-even. Multiply checks tininess after 11-bit rounding
(0x387ff000); add/sub use 0x38800000.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The relinker reads its arguments as narrow strings, so on a Windows
system whose ANSI code page cannot represent the directory it cannot
open the input, and the windows_working_directory case failed before
reaching the executable. Convert the case under an ASCII name and rename
the directory before starting the executable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The --windows-diagnostics walker looked up modules and directories with
GetModuleHandleA, LoadLibraryExA, GetFileAttributesA,
GetModuleFileNameA and GetSystemDirectoryA, so under a directory outside
the ANSI code page it failed with "dependency diagnostics Windows API
failed" instead of naming the missing dependency.
Keep the walker's paths in UTF-8 and convert them at the Windows API
calls, which now use the wide functions. The entry stub passes it, and
prints, its paths as UTF-8 instead of the ANSI code page. The dependency
diagnostics test now builds its fixtures under a non-ASCII directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows entry stub read its path with GetModuleFileNameA, which
turns every character the ANSI code page cannot represent into '?', and
then used it with SetCurrentDirectoryA and LoadLibraryExA. A converted
executable in such a directory (Japanese on a Western-locale system, an
emoji, a script without an ANSI code page) stopped at start-up with
ERROR_INVALID_NAME.
Read the path with GetModuleFileNameW and enter the directory and load
the libraries with SetCurrentDirectoryW and LoadLibraryExW, with library
paths stored as UTF-16. Messages and the --windows-diagnostics walker
still get the paths in the ANSI code page, as recorded in TechnicalDebt.
Fixes#2640
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep WAIT_REG_MEM diagnostic timeouts from completing unsatisfied waits.
Register the existing gated-dispatch capture test with a 1ms diagnostic
timeout and document the missing-producer limitation.
sceSystemServiceParamGetInt always reported English (United States) as
the console language, so a title that follows the console's language,
such as Jelly5 (PPSA99505) with its Arabic text, can only show English.
ANYPS5_LANGUAGE now selects a console language number from 0 to 30
(21 is Arabic). Unset or empty keeps English (United States); any other
value throws at the language query instead of being ignored.
(cherry picked from commit 9bcf196daa01b07a22be876a7f1143149503e054)
Submit the same vertex and pixel draw twice through the driver and
require the second one to hit the draw cache, with graphics pipeline
libraries and with APS5_NO_GPL=1. With pipeline libraries the pixel
stage is cached at its fixed push slot, so a lookup at the stacked
offset misses every draw on its layout. The driver exposes a copy of
its draw-cache counters for the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AI-assisted: yes. Record that gethostname exposes AnyPS5 rather than the native machine name. Console hostname configuration, sysctl integration and name resolution remain unverified and unimplemented. This one-line note is placed beside an unchanged libc compatibility entry so the branch merges cleanly into tested main 5edae3536.
AI-assisted: yes. Return AnyPS5 without reading the native machine name, hostname environment or network configuration. Preserve FreeBSD bounded-copy behavior, guest ENAMETOOLONG, null queries and success errno. The final tree passes a complete Windows Release build and guest_hostname/guest_errors repeated five times each on main 5edae3536. No new Linux or title test is claimed.
AI-assisted: yes. Verify AnyPS5 rather than the native PC hostname. Cover buffer capacities, guard bytes, maximum size_t capacity, null queries, success errno and thread-local isolation. The old implementation fails this regression; the final fixed tree passes the complete Windows build and five repeated runs each of guest_hostname and guest_errors on main 5edae3536. Linux and title tests of this new revision are pending.
AI-assisted: yes. Moves hostname test registration to avoid #2170 overlap. The full current-main build and focused Windows tests pass; implementation and test source are unchanged.
AI-assisted: yes. Add a deterministic backend that overwrites errno on success; verify restoration on success and retained failure errors. Extend native tests for current-time errno preservation. The new regression fails on original ca87abb and passes with this fix.
AI-assisted: yes. Restore guest errno only on successful utimes forwarding and retain delegated failure errors. Register the controlled-backend regression test. Windows library build and focused tests pass; Linux rerun pending.
PPSA11386 calls sceSharePlayInitialize(NULL, 0x1800) at start-up and stops
on any non-zero result, so the console accepts a null heap. The library
keeps no state and does not use the heap; a zero size still throws, as its
error is unknown. libSceSharePlay gets its own CMakeLists for the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An NGG passthrough program on the vertex path whose s_barrier only orders wave 0's GS_ALLOC_REQ produced a workgroup OpControlBarrier, which the Vertex execution model does not allow; SPIR-V validation rejected the module at the program's first draw. In a Vertex-stage program the barrier now emits nothing when the program has no LDS or GDS access and no memory write or atomic, so no wave can observe another. With any of those it throws.
A comparison sample with a 2D-array address on a plain 2D color T# takes the emulated comparison, whose runtime modes were only the 2D-array and cube ones. No mode matched, and materialization threw that the image descriptor was incompatible with the static runtime image interface. The emulated comparison now also gets a 2D mode, as the native modes of a 2D-array access already have; the slice is ignored.
Resize drained the device before reading the surface extent, so a drawable
size that never matches the surface, as with fractional scaling, drained it
on every present.
(cherry picked from commit b4aae246766a108f78cd9ad29d7c874ea036d481)
Export it next to remainderf, stepping to the adjacent float in the
direction of the second argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export it next to lroundf and llround, rounding halfway cases away
from zero into the 64-bit guest long long.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both parse like strtol and strtoll in base 10. The guest long is 64
bits wide on every host, so atol uses strtoll rather than the host
atol, whose long is 32 bits on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export the double version next to __isnan and __isfinite, returning
1 for either infinity like __isinff.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PokeMMO Prospero (PPSA98001) stops at siglongjmp (pebqbE5ws8s) from
libkernel. The title links its own FreeBSD sigsetjmp, so siglongjmp
follows FreeBSD's amd64 __siglongjmp and its sigjmp_buf layout: when the
save-mask flag at offset 88 is set it restores the mask saved at 72
through _sigprocmask, then reloads rbx, rsp, rbp, r12-r15 and the x87
control word and returns to the saved address with val, or 1 for 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ternary forms share minMaxF16 with v_min_f16 and v_max_f16 and follow
the same rule on gfx1036: a subnormal half input becomes a signed zero when
bit 0 of the f16 denormal field is clear, at IEEE=0 and IEEE=1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With graphics pipeline libraries a draw places the pixel shader's push
constants at the fixed fragment slot (Graphics::StagePushOffset) and the
draw cache records that offset, but lookupDraw still expected the stacked
offset after the vertex shader's push bytes. Every pixel stage then missed
as a layout mismatch, so no cached draw or draw recipe was reused and each
draw was captured and materialized again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's unlink ignores the file's own permission bits, so it succeeds
on a file the guest made read-only with chmod(0400). On Windows the guest
chmod sets the read-only attribute and _wunlink then failed with EACCES,
which sceKernelUnlink and unlink reported. When the path is a regular file
without write permission, make it writable before the single _wunlink and
restore the read-only attribute if the unlink fails, as remove() does
since #2342.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mutex attribute now keeps its protocol and priority ceiling the way
FreeBSD's libthr does: the ceiling can be set and read only under
PTHREAD_PRIO_PROTECT (EINVAL otherwise), and setting a protocol resets it
to the highest SCHED_RR priority. Mutexes still do not apply priority
protection; that is listed as a silent stub.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d10c024f8fdbcdfced0187550f272333264f07df)
A map without MAP_FIXED whose hint lies wholly inside a range still
reserved by sceKernelReserveVirtualRange is placed at the hint, as a fixed
map into the reservation would be. Hints outside reservations and ranges
that are partly mapped keep their hint behaviour. The reservation is
checked under the registry mutation, so it cannot be released and its
address reused between the check and the map (MarcZX100, #2687).
Fixes#2687
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2cY3SsTYnh4u4ZtEr2Adx
Unity reuses the memory of transient render targets: in FPV Simulator
(PPSA10698) an RGBA8 1920x1080 color texture is sampled at the address
that a D32S8 surface's stencil plane occupied earlier. The lookup found
the stencil plane and threw "sampling the stencil plane ... is not
implemented", which ended the presentation.
9d65003c made depth-plane views in a format that cannot hold the plane
fall through to the ordinary texture path. A stencil-plane view whose
texels are not one byte wide cannot hold the 8-bit stencil plane
either, so it now falls through the same way.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Registration prepares the variants a shader's registered state
predicts; a draw or dispatch whose state needs another one threw
"prepared shader artifact is missing for the requested static ABI".
Titles that link their own libSceAgc build that state themselves and
never call the driver's static ABI resolution, so their draws differ
from the registered prediction. The missing artifact is now prepared at use, logged, and kept
with the snapshot like the deferred and null pixel cases. SourceHandleFor
now checks that the request refers to the registered code, as
InvocationFor already did.
Uncharted: Legacy of Thieves Collection (PPSA05684) still misses on
main 197ffac9 (after the fixed push slots of #2661) for its own pixel
shaders: "prepared shader artifact is missing for the requested static
ABI: address=85899411712 stage=5 wave=64 pushOffset=128
pushCapacity=128".
sceVoiceSetMuteFlag was NotImplemented with an unknown signature and
sceVoiceGetMuteFlag was not exported. Signatures follow shadPS4's
voice.h: a port id and a bool, and for sceVoiceGetMuteFlag a port id
and a bool pointer. The flag is stored per port like the volume; an
unknown port throws and a null output pointer is an invalid argument.
Uncharted: Legacy of Thieves Collection (PPSA05684) imports
sceVoiceGetMuteFlag (Pc4z1QjForU), so on main it stops at startup with
"undefined symbol: Pc4z1QjForU".
CB_COLOR_INFO COMP_SWAP 1 (SWAP_ALT) on a two-channel format stores the
x and w exports in its two channels (Mesa si_translate_colorswap,
"X__Y"). COLOR_8_8 UNORM and SNORM targets with it bind as R8G8 with
export mapping 0xec; blending into them still throws, since Vulkan
would apply alpha factors to the wrong channel.
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped in its
first level with "unsupported color format 3 number type 0 component
swap 1".
The specialization pass turned OpCompositeConstruct of extracts from one
composite into OpVectorShuffle without checking the types. When the
source is a struct (the {uint, uint} pairs of extended multiplies and
phis of them), the result is invalid SPIR-V ("The type of Vector 1 must
be a vector type"), which the NVIDIA driver (615.71.09) crashes on in
vkCreateGraphicsPipelines instead of rejecting. The fold now requires
both the source and the result to be vector types; a test builds the
struct and vector cases by hand.
Uncharted: Legacy of Thieves Collection (PPSA05684) crashed in the
driver's SPIR-V compiler right after loading its first level.
sceAvPlayerAddSource queues an auto start for a player created with auto
start or without an event callback. When the title enabled its streams
and called sceAvPlayerStart before the controller thread ran it, the auto
start called Start again on the playing player, which stopped it, released
every video buffer, including the frame the title held, and started the
film over. guest_avplayer occasionally caught this as "video frame is not
a texture allocation"; right after the test's own start, 8 texture
buffers were released in 15 of 15 runs.
The auto start now starts the player only while it is still ready, and
checks that under the player lock together with the start.
TestAutoStartKeepsStartedPlayback starts a callback-less player itself and
checks that no texture buffer is released afterwards and that the frame
it holds stays allocated. It fails on every run without the fix.
The Linux patcher writes its own PT_PHDR and PT_INTERP but also copied
the input's. glibc takes the load bias from the last PT_PHDR, which was
the input's entry with the pre-relink address, so such an executable
crashed at start. Both copies also took program header slots, so an
input with PT_PHDR and PT_INTERP and no spare slots failed to relink.
The MinGW CRT delivers argv to main as ANSI code page bytes, and every narrow string became a filesystem path through the same code page, so a game in a directory with characters outside the code page failed to open (#2692). Read the command line as Unicode (wmain via -municode), keep paths as UTF-8 strings, convert at the filesystem boundaries through Io::NativePath and Io::Utf8Path, and run the --autorun output through CreateProcessW.
(cherry picked from commit 30a5181f8a71b177cf2dc03334e39998938b3472)
v_div_fmas_f32 always quieted source NaNs and let a NaN addend
hide the default NaN from an invalid product. Reuse the existing
bit selection helpers, measured on gfx1036 in four float modes.
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
RDNA2 min/max flush half inputs to signed zero when input
preservation is disabled, while output flushing alone keeps them.
Apply this to the measured binary forms and pin the hardware rows.
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
The background link-time-optimized link used the Pipeline's own layout, which ~Pipeline destroys right after marking the slot released, and ClearCachedPipelines destroys the cached pipelines before ClearPipelineLibraries waits for pending links. A link already past the released check then created its pipeline with a destroyed VkPipelineLayout. On Intel's Windows driver (Arrow Lake iGPU, 32.0.101.8991) agc_driver_vertex_fetch, agc_driver_pixel_interlock and agc_driver_mesh faulted inside igc-default64.dll every run, and agc_driver_prepared_exports and agc_driver_pixel_barycentric_emulation intermittently; all passed with APS5_NO_PIPELINE_LTO=1. The library store's layout, made from the same create info and used for the libraries, lives until ClearPipelineLibraries has waited for the pending links.
(cherry picked from commit 758eeb9fb255d88c41945b299dc3ec5ac1578331)
Validate timeout arguments and track live requests so unknown or deleted
ids return REQUEST_NOT_FOUND. This supplies the remaining absent load
import alongside the dialog Close export; timeouts follow the existing
offline network model and are not stored.
Co-Authored-By: GPT-6.1-sol <noreply@openai.com>
Eager binding fails when a caller imports sceG2PDialogClose.
Add the missing throwing stub alongside the existing G2P exports
and record the assumed Close signature in TechnicalDebt.
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
sceAjmBatchJobDecodeSplit queues a split run job with the stream
sideband, as sceAjmBatchJobDecode does for a single buffer pair.
sceAjmBatchJobSetResampleParametersEx with a zero ratio change per
sample sets the ratio as sceAjmBatchJobSetResampleParameters does; a
non-zero change throws, since its ramp is unknown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measure the mixed f16/f32 boundary on gfx1036 and pin both factor
slots, half selection, signs and zero f16 addends in the existing test.
Record this measured scope while keeping legacy mad/mac unchanged.
Co-Authored-By: Codex gpt-6.1-sol <noreply@openai.com>
With f32 denormals flushed, RDNA2 flushes a product only when its value
rounded to 24 bits in the current round mode is below 2^-126. The
tiny-product check tested the exact product, so products that round up
to 2^-126 became zero in v_mul_f32, v_mul_legacy_f32, v_mullit_f32 and
zero-addend f32 FMAs. The separately rounded v_mad_legacy_f32 and
v_mac_legacy_f32 keep the exact check, as they are not measured there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v_cvt_f16_u16 SDWA only accepted word and dword source selects. Silent
Hill Townfall (PPSA33286) converts the top byte into the low half of a register
and keeps the high half (src0_sel:BYTE_3 dst_sel:WORD_0
dst_unused:UNUSED_PRESERVE), so its shaders failed to recompile. The
generic SDWA source path already zero-extends byte selects, which is the
u16 value the conversion expects, so the rule now accepts every source
select, with whole and partial destinations.
agc_driver_sdwa_cvt_f16_u16 runs four byte selects with dword, WORD_0
and WORD_1 destinations on the GPU and checks the converted halves and
the preserved bits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d55dfe8b33c6ee418f40a92e49236f8679a6470b)
A guest path whose bytes the host cannot convert to a native path made
ResolvePath throw std::filesystem::filesystem_error, which aborted the
process. No file can have such a name on the host, so stat reports
ENOENT, as for any other missing file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VLC (PPSA85300) imports execvp (0NJDcQ2B8vE) from libkernel and fails
to load because no library exports it. Replacing the process image is
not supported, so it throws if called, like sceSystemServiceLoadExec
does for executable replacement. VLC itself cannot reach the call: its
fork and vfork fail with ENOSYS, so the child that would call execvp is
never created.
sceKernelSignalSema threw invalid_argument for a count of 0. Marvel's
Wolverine (PPSA03671) signals with a count computed as min(a, b) - c,
which can be 0, and ignores the result, so the throw ended the title a
few minutes in. A zero count now adds no tokens and returns SCE_OK, as
in shadPS4 and Kyty. A null semaphore and a negative count still throw.
guest_kernel_errors signals 0 on a full and on an empty semaphore and
checks that the count is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7191d34c4d79622ba71087f0da2b75eb81ad2c23)
The Windows TLS rewriter only patches FS-relative instructions that
CodeInstructionCollector decodes. Code that no symbol, unwind range or
root reaches is covered by a linear sweep that starts after int3 padding
and stops at the first instruction beginning with two zero bytes, so
zero-filled bytes are not read as code.
The .NET NativeAOT homebrew titles pad between functions with zero bytes
instead of int3 and have unwind entries for only part of their code. In
Prospero Vibrate (PPSA99920) the sweep ended at the padding after the
first function. Functions reached only through switch jump tables, which
the control-flow graph does not follow, or through pointers it cannot
resolve kept 105 of 670 FS loads unpatched: 84 `mov reg, fs:[0x28]`
stack-guard reads (65 into rax, 19 into rcx) and 21 `mov rax, fs:[0]`
thread-pointer reads. The title faulted reading address 0x28 within a
second of start.
The sweep now treats a zero run that follows a jump, call, return or
trap and ends at the next 16-byte boundary as alignment padding, and
continues at that boundary. A call counts because a function can end in
a call to a noreturn function. Zero fill after straight-line code, or a
whole zero 16-byte block, still ends the sweep as before.
All 670 FS loads in PPSA99920 are now patched, as are the 102 of 685 in
Prospero Explorer (PPSA99109) and the 103 of 718 in Prospero Multi Tools
(PPSA99110). New tls_function_coverage padding cases: loads behind zero
padding after a return, a call and a jump, with 13, 2, 1 and 5 zero
bytes, are patched; a load behind zero fill after a nop is left
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0306c677784b6c4272990ce65f6daf9fb2dbc3ca)
sceKernelMkdir checked that the parent of the resolved path exists, but
for "dir/" std::filesystem::path reports "dir" itself as the parent, so
the call failed with ENOENT and created nothing, while FreeBSD's mkdir
(and the POSIX mkdir export) accept it. Strip trailing separators first,
and report ENOTDIR when the parent exists but is not a directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5f74ddba8b346e6c552cf02554d47382708206f3)
The Linux loader takes the unnamed main image for guest code, as the
relinker's ET_DYN output is. Fedora's GCC does not default to PIE, so the
test executables were ET_EXEC and three loader tests failed.
PPSA11386 imports it; on main it is one of the title's three missing
imports, with sceSslGetCaList and sceSslFreeCaList, and the title does not
call it during its start-up. The signature is unknown, so it is declared
without parameters and throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA11386 imports sceSslGetCaList and sceSslFreeCaList and PPSA15508
imports sceSslUnloadCert; on main all three are missing and the loaders of
both titles stop on them. No root certificate bundle is shipped and no
loaded certificate is kept, so an SSL context holds no certificates:
sceSslGetCaList clears the list and returns NOT_FOUND, as sceSslGetCaCerts
does; sceSslFreeCaList accepts only that cleared list; sceSslUnloadCert has
nothing to drop and is assumed to return 0. A null list returns the
invalid-argument code that sceSslGetCaCerts and KytyPS5 return. An unknown
context and a list sceSslGetCaList did not return throw, as their codes are
not confirmed. Signatures and the SceSslCaList layout follow SharpProspero
and shadPS4.
guest_ssl_tests now links libc, as guest_http2_tests does, so an exception
thrown from libSceSsl unwinds with the same unwinder that resumes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MemoryIndexBelongsTo walked every instruction of the program and asked
BufferAccessOf, AddressOpcodeInfoOf and ImageOpcodeInfoOf about each one.
TryMakeTableImage calls it for each of a table handle's eight descriptor
dwords and once more for a material key, and PlanIndirectImages calls
TryMakeTableImage for every image instruction whose handle is not planned
yet, so each such instruction could cost up to nine walks of the whole
program. PlanIndirectImages now indexes the instructions that access
memory by their memory index once, before it plans, and
MemoryIndexBelongsTo looks the index up while that pass runs. The
planning pass adds, removes and changes no instruction, so the index
stays valid until the pass drops it at its end. Outside the pass, where
the function has no caller, it walks the program as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq
Add the guest socket descriptor guard to sceKernelFdatasync before any
native syscall, matching the existing sceKernelFchmod pattern. This
prevents high guest socket descriptors (e.g. 0x10000000) from reaching
the Windows CRT _commit, which would trigger the invalid-parameter
handler, and ensures the correct SCE error is returned on all platforms.
Refactor fdatasync_nid_postfix to delegate to sceKernelFdatasync via
PosixResult, removing the duplicated socket guard and native syscall.
Extend GuestFilesystem tests to cover:
- sceKernelFdatasync on a valid guest socket returns SCE EINVAL (0x80020016)
- sceKernelFdatasync on a closed guest socket returns SCE EBADF (0x80020009)
- sceKernelFdatasync on a high invalid descriptor (0x7fffffff) returns SCE EBADF
- closed-ordinary-descriptor tests now run on Windows (verified _commit on
a closed descriptor returns EBADF without provoking the CRT handler)
A recorded dispatch or draw whose use needs completion work goes into the
copied-writer lists (VulkanDevice's copiedWriters, DrawCopiedWriters).
Fills, buffer copies and indirect argument reads treat every write of a
listed object as a store the CPU makes when its batch is reaped, and wait
for that batch. Completion work is also needed for the BDA fault check
alone, and the written sub-ranges of GPU copies are copied back by the
GPU (RecordCopyBacks) before the listing. Such an object has no CPU
write-back (HasCopiedWrites is false), and its stores are ordered by the
barriers like direct writes, yet it was listed and waited for.
Both lists now take an object only when HasCopiedWrites holds after
MarkGpuWrites. The completion action still runs for the fault check.
agc_driver_copied_writer_copy records a dispatch that reads through
global (BDA) loads and stores through a bound buffer into registered
memory, then copies that output on the GPU (VulkanDevice::CopyBuffer)
before the batch ran. It checks that the copy is a transfer between the
host imports without a sync, and the copied bytes. Without the change the
copy waits for the dispatch's batch.
PPSA14632 (LEGO Horizon Adventures): every one of the DMA_DATA copy
waits counted over 75 s came from such writers. Queue 0's GPU-mutex hold
for copies drops from 3.5 s to 30 ms per 10 s, and presents rise from
about 102 to 109 per 10 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An upload of a storage image up to 1 MiB reads the surface's guest bytes
into a baseline (#1599), which a later write-back compares against for
blocks the CPU stamped. When a unit shadow, another image's pending
results or a recorded GPU store covers the surface, that read goes
through the flush hook: the shadow is published and the worker waits for
the GPU, although the upload itself runs on the GPU from the import.
The baseline is now taken only when nothing of that kind overlaps the
surface. Otherwise the image has none, and its write-back keeps the CPU's
bytes for stamped blocks, the rule before #1599. TechnicalDebt's entry
names the condition.
agc_storage_baseline_settled creates a storage image over a watched host
import with a recorded store pending over part of it and checks that the
upload did not submit the open batch; without the change the baseline
read syncs on that store. agc_unchanged_cpu_stamps (#1599) still passes.
PPSA14632 (LEGO Horizon Adventures) refreshes such images about 870
times per 10 s from queue 0 dispatches: the hook's recorded-store syncs
go from about 3400 waiting 4.0 s to about 630 waiting 0.3 s per 10 s,
and a direct dispatch's device call from 356 to 107 us.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build's sampled image elements often repeat the element just before them
in plan order (the same eight descriptor words). Each still took its own
stage-A record (decode, collect, DCC key read, cache entry) and its own
stage-B lookup.
The lookups run in plan order and nothing runs between two consecutive ones,
so a repeated element's lookup would find the state the previous lookup just
left: the entry it validated or made, current against the same collect. Such
an element now binds the previous element's texture (when the depth-compare
flag matches too, which is part of the cache key), and stage A gives it a
record of the decoded descriptor only. An element repeating an earlier but
not the immediately preceding one still takes its own lookup, as does any
element after a storage image lookup.
A resource build decodes every image element's eight descriptor words,
describes the surface (a mip layout with a vector of levels) and finds the
sampled texture's cache entry by hashing its key, once in stage A and again
in stage B. Most elements repeat a small set of descriptors from build to
build.
Each worker now keeps a small direct-mapped memo by descriptor words of the
decoded resource, its guest size and view component mapping (pure functions
of the words) and of the cache entry the words last found. The entry is
reused only while the sampled texture cache has erased no entry since it
was found (an erase counter bumped by eraseTexture), so the list iterator is
still valid and still holds the same key; anything else takes the hash
lookup.
Buffer and DeviceBuffer created their VkBuffer at the pool's size class, so
a robust read past the requested size stayed in bounds and returned the
bytes an earlier user of the pooled memory left there instead of an
out-of-range result. Each VkBuffer now has the requested size, created with
the pool's usage, over memory of its class (or its slab slot); one taken
from the pool for a different size is recreated over the same memory at
the same offset. agc_buffer_pool follows a pooled buffer by its memory, so
its class and reuse cases hold with the exact-size buffers.
Intersect copied fixed-point sets without allocating each intermediate.
Preserve sorted results and the existing analysis iteration order.
Compact oversized storage only when publishing a changed set.
Keep an allocating control switch outside the cache key.
Compare complete fixed points over empty intersections, loops and DAGs.
Register the same equivalence fixtures for the allocating control.
A DMA_DATA copy larger than the store limit runs as a transfer between
host imports. When the destination is not registered guest memory (the
GPU can neither read nor write it, so no import exists), the transfer
was refused and the packet drained the device: every batch recorded so
far was submitted and waited for, then the CPU copied.
Such a copy only has to see the source's results. When CopyBuffer
refused for want of an import (path 2) and nothing can still store over
the destination (VulkanDevice::StoresPendingOver: a recorded write, a
label, a copied write-back, a storage image's pending write-back or a
unit shadow), the packet now flushes storage results pending over the
source and waits for the batches that write it (Recorder::SyncThrough),
then copies on the CPU without a drain. Anything else still drains as
before. The [sync] line counts these copies.
agc_driver_tests copies a buffer the GPU just wrote into host memory,
once directly and once behind a label into the destination, and checks
the bytes and that the copy did not drain.
PPSA14632 (LEGO Horizon Adventures) copies 256 KiB of GPU results into
host memory this way, about 50 times per 10 s: device drains drop from
about 50 to none per 10 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestUnsyncedVideoKeepsUpWithAudio built its player without an event
callback, which enables auto-start (autoStartEnabled = init.auto_start ||
callback.event_callback == nullptr). The controller thread then enables the
streams and starts playback shortly after the source is added, racing the
test's own sceAvPlayerEnableStream. When the controller won,
Source::EnableStream saw started == true and the test failed with "enable
video failed".
The test now passes an Events object, as the other tests that drive the
player by hand do.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Jelly5 (PPSA99505) imports sceAudioOutExGetMonitorInfo (PhnNN0UEV6Y),
sceAudioOutExConfigureOutput (VcE+gXSwFXI), sceAudioOutExOpen
(6X6dp+07h4U) and sceAudioOutExClose (0TfjSulCV2A) from libSceAudioOut
and fails to load because no library exports them. It calls them only
for its optional HDMI bitstream output, and their signatures are
unconfirmed, so they are exported without an implementation and throw
if they are called, like sceSaveDataCancel.
Without MAP_FIXED the address is a hint, as for FreeBSD's mmap: the reservation goes at the hint when that range is free, else at the first free range above it. The hint was dropped, so every hinted reservation landed at the lowest free address.
RetroArch (PPSA99169) reserves its cores' direct-memory heap near 0x2000000000 outside the GPU window (0x200000000-0x300000000) and refuses a range inside it; with every reservation at 0x200400000 the heap was never made, and Dolphin's first aligned allocation returned null.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every draw into a resident target refreshes the target's storage image
(StorageTexture::Refresh): a pending-registry scan for an alias,
FlushPending over the surface, the collect, the key proof, a
whole-surface UnchangedSince, four vectors and a generation update over
every 64 KiB unit, nearly always finding the image current.
A refresh that finds the image current now keeps a proof: the image's
generation and uploaded keys, its key proof's generation and the pending
registry serial. The next refresh is answered from it when the image's
own state is still that, the surface and its key range are unstamped
since those generations (CollectWrites over both, then one
UnchangedSinceAll), and no other image has results pending over the
surface: the registry serial is unchanged, or the registry is scanned and
holds none (a newly pending image, a flush in progress). The proof is
taken only under GuestMemory::GpuMutex, without a pending alias, with
stable keys and when no other pending image overlaps the surface; any
full refresh drops it. A CPU write, a driver or GPU store, a key store,
another image's pending results or a different image therefore always
refresh in full, and the image's own pending results (a draw into it)
keep the proof.
agc_driver_recorder_tests refreshProofTests: an unchanged image and one
holding its own pending results are proved; a CPU write, a driver store
into the surface, a key store, another image's pending results, a new
image of the surface and a fast clear of the keys each refresh in full.
A geometry (NGG) strip draw on the mesh path assigned every primitive the next index positions without looking for the restart index: a triangle spanning a restart fetched vertex 0xFFFF and was drawn to wherever that read landed, and the strip parity never reset, so after an odd-length strip every triangle's winding flipped and face culling dropped it.
For an indexed strip draw with restart enabled and the restart index present, the draw now records each position's strip start (one past the last restart at or before it) and passes the table after the mesh arguments, flagged in draw word 3. The mesh prologue reads the start of its primitive's last vertex: a primitive whose start lies past its first vertex spans a restart and is made degenerate, and the others take their parity from their strip's start (or the draw's first index). The restart vertex itself fetches vertex 0. The table is built from the index bytes the draw already reads (its copy, or the guest memory an in-place import reads), next to #1968's restart-aware highest-index scan.
The new Mesh.cpp test draws quads as four-index strips each followed by a restart (16- and 32-bit indices, and an indirect draw starting mid-buffer) with front faces culled: every quad must render whole and the gaps between them stay clear. Without the table the first check fails ("a triangle crossed the restart after quad 0").
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2096 keeps a copy of a storage image's guest bytes so that Refresh can
compare memory the write watch does not cover. The host-import upload
path takes that copy only when the range is unwatched, but the DCC
fast-clear path took it on every upload. Reading the whole surface goes
through the flush hook, so each 64 KiB block of a surface that a unit
shadow covers recorded a publish and waited for the GPU.
The clear path now resolves the surface's host import first (the import
can unwatch the range, as a compared host-pointer import does) and takes
the copy only when the range is then unwatched, like the import path.
Write-watched memory, such as direct memory behind a dma-buf import,
skips it; a range that is unwatched later re-uploads instead of
comparing, as before #2096.
agc_driver_dcc_keys_first_write checks that a fast-cleared upload keeps
the copy exactly when its range is unwatched at that point.
PPSA14632 (LEGO Horizon Adventures) on Linux, RX 9070 XT: the flush hook
waited ~60,000 times per 10 s for these publishes (6.4 s), now ~3,500,
and presents rise from ~24 to ~100 per 10 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stale storage snapshot is revalidated by comparing every tracker block
stamped since it was taken. A title that binds buffers a dispatch writes
in place every frame (the whole declared range stamped, few pages really
changed) makes the graphics worker compare gigabytes per second.
Compares of 1 MiB or more are now split by block across a small pool of
helper threads (hardware threads / 8, from 1 to 4). The compare is memory
bound, so the worker's share roughly halves rather than dividing by the
thread count. A caller that finds the pool busy compares on its own
thread. Results and the patch-in-place rules are unchanged.
agc_driver_graphics_tests parallelCompareTests: per-span results on the
pool and serial paths, and two threads comparing at once.
A stale storage snapshot was compared whole with the guest bytes and, on
any difference, copied whole into a new buffer, although few of the
tracker blocks stamped since the copy usually hold different bytes.
Only the 64 KiB tracker blocks stamped since the snapshot's generation
are compared now. When some differ and the cache holds the only
reference (no recorded draw binds the buffer), those blocks are copied
into it in place; otherwise the range is copied again as before.
agc_driver_recorder_tests drawSnapshotPatchTests: changed bytes in
several blocks and a whole changed block patch the snapshot nothing else
holds, an unchanged one is reused, and one a recorded draw still holds is
copied again with its bytes untouched.
A written V# element bound in place in a host import makes every load a
PCIe round trip. A particle update that loads and stores a 10 MiB pool
(2048x64 threads over 131072 records of 80 bytes), above the 2 MiB written
window, took about 4.8 ms of GPU per frame.
An element the shader also reads (DescriptorBinding::bufferRead), written by
a direct dispatch whose threads times the V# stride cover at least half of
it, now takes device-local staging up to 16 MiB: that dispatch drops to
about 0.06 ms plus 0.9 ms of bulk copies. The coverage test keeps a kernel
that updates about 12k records of a 16 MiB pool in place, where staging all
of it would cost 1.3 ms of copies for a 0.08 ms kernel. Write-only
elements, indirect dispatches and raw (stride 0) buffers keep the 2 MiB
window.
A storage write-back copied the image into a linear buffer and retiled
that buffer; an upload detiled into a linear buffer and copied it into
the image. With storage image access without a format, the retile now
reads each texel from the image through an unsigned integer view of the
element size, and the detile writes it there, so the linear buffer, its
copy and one barrier per move go away. The texels move bit for bit as
before, through the same tiling equations and windows.
This covers the block-unit windows of write-backs and uploads and the
whole-surface direct upload, for 2D, array and cube images.
agc_driver_recorder_tests singlePassTests: a first upload, a write-back
of new texels in every mip and a second upload over the stored memory
leave the images and guest memory byte for byte equal to the copies
through a linear buffer, in plain and write-watched memory.
DescriptorBinding gains bufferRead next to bufferWritten and bufferAtomic,
filled from the resource tracker's per-buffer read flag and kept in the
shader disk cache (format 24).
A recorded draw continued the open render pass only after a draw that
wrote no memory, so every draw with a storage write ended the pass and
paid two barriers. A draw now continues the open pass when its accesses
have no hazard (PassHazards) against those of the draws since the last
barrier. Address-based draws, draws through buffer device addresses and
draws with copied writes count as reading and writing any guest memory.
A draw of other attachments with no hazard ends the pass without a
barrier and begins its own without one, and a hazard break records the
begin barrier alone, towards every graphics stage, instead of the pass
end barrier plus the begin barrier. A pass with a depth attachment or
attachment proxies, or one followed by work after the pass, keeps both
barriers. APS5_FULL_BARRIERS keeps every barrier, as for the other merges.
PassHazards keeps the guest ranges, images and attachments the draws
since the last barrier read and wrote, and answers whether a new draw has
a RAW, WAW or WAR hazard against them. Address-based reads and unknown
writes count as touching any guest memory; attachments of the current
pass are ordered by rasterization and only conflict with other passes or
with sampled and storage uses. Unit tests in agc_pass_hazards.
The Windows sceKernelPread stopped going through pread when it learned to
return errors, and with it lost the random device read, so a pread on
/dev/urandom reached ReadFile on a handle that is not a disk and failed
with ESPIPE. Read random devices first, as the Linux path does.
A pread into a read-only guest mapping now returns EFAULT instead of
throwing, matching Linux and the AIO read beside it, so the Windows
guest memory test checks for that return.
Read rtkit's MaxRealtimePriority and RTTimeUSecMax before touching the
limit, keep a hard limit that already satisfies rtkit, lower the soft
limit to at most half of the hard one, and log when the hard limit is
lowered. rtkit checks the hard limit, so it cannot be left alone when it
is above RTTimeUSecMax; the side effect is listed in TechnicalDebt.md.
Log the policy rtkit applied, SCHED_RR by default.
A draw's storage snapshot was copied again whenever the write tracker
reported a store over its range, although most such stores leave the
bytes as they were: in one title screen about 10k of 14k stale lookups per
10 s held the same bytes (3.5 GiB of copies).
A stale storage snapshot is now compared with the guest bytes, after the
caller's collect of the range, before it is dropped. When they are equal
it takes that collect's generation and is reused; any store after the
collect is stamped newer and fails the next check, as for a fresh copy. A
registry change still drops it without reading, and vertex and index
snapshots are dropped as before.
materializeRegisterClear read the target's DCC keys with CurrentDccKeys on
every draw into a resident target with metadata: a full AllKeysEqual scan
of the key range (52 KiB for a 2432x1368 RGBA8 target), about 440k scans
per 10 s and 7.2% of the queue 0 worker's CPU time in one level.
ProvedCurrentDccKeys answers CurrentDccKeys from a proof kept with the
resident image (DccRangeProof: the key range, the keys and the write
generation collected before the scan), the rule ProvedClearKeys already
applies to a storage image's own keys: the proof holds while the key range
is unstamped since that generation (CollectWrites first, then
UnchangedSince), and a scan becomes the proof only when it read the bytes
(not the pending store memo) and no recorded work still writes them. Every
key writer stamps the range, so a store, a pending result or another key
range always scans again. The proof is used under GuestMemory::GpuMutex
only, and not with APS5_NO_KEY_FAST_PATH.
agc_driver_recorder_tests: an unchanged range is proved, and a key store
stamp, a CPU write, another range or address, and a pending key fill each
force a scan; the fill's result is proved once its batch finished.
Every small host buffer (size classes below 1 MiB: draw and dispatch data,
copied regions, snapshots) had a VkDeviceMemory of its own. When the pool
missed, the worker paid a vkAllocateMemory and vkMapMemory, and the release
thread a vkUnmapMemory and vkFreeMemory, for each one. The frees also stall
the worker's vkQueueSubmit in the driver.
A host-visible buffer of a size class now gets its own VkBuffer bound into a
slot of a shared block (2 MiB, or 8 slots for the large classes) per memory
type, size class and device-address use, mapped once. Releasing it destroys
only the VkBuffer and returns the slot; a block is freed when it empties and
its slab already keeps an empty one. A slot is used only when the buffer's
alignment and the non-coherent atom divide its size class, so the slot
offset is valid for the bind and Invalidate covers exactly the slot. The
pool still retains released buffers by class as before; only what backs
them changed.
A sample whose T# is chosen in one if/else and whose S# is chosen in a later one has descriptor Phis in two blocks. The web walk required every Phi of the tuple to sit in one block, so it gave up and the plan threw "GetImageResource dword 0 is not a valid runtime value". The walk now splits at the Phis of the latest block in program order, and Phis of earlier blocks pass through each edge unchanged until the walk reaches their block.
The hold no longer polls every millisecond and has no switch to turn it
off. A held submission waits on the driver's condition variable until no
flip is ahead of it, or until queue 0 waits for memory that only the
title can store. The queue 0 worker reports the second case from its
memory and rewind waits, and the worker that reaches a flip wakes the
holders.
agc_driver_flip_tests: a flip held behind a rendering wait is released
when the worker goes on to wait for a label only the title stores, with
no further submission.
A draw whose stages bind a BDA table or a fault buffer builds resources
that need completion work, so noteReusable never keeps them and the shared
resource cache never holds an entry such a draw could hit. Building the
content key and probing the cache for each of them was pure cost. Those
draws now skip both (ShaderResources::NeverReusable) and count as
uncacheable.
A title that paces itself on pending flips assumes what hardware gives it:
the command processor consumes a submitted buffer at once, so a pending flip
is a frame whose commands have been read and that only waits for the display.
The queue worker is slower than that. With three flips pending it can still be
recording the oldest frame while the title rewrites that frame's slot of a
three-deep ring of per-frame memory for the frame after next, and the draws
are then recorded with the later frame's (or half-written) constants.
A submission on queue 0 that carries a flip now waits until the worker has
reached the flip of every earlier one. The wait ends early when queue 0 is
blocked on memory that no running or pending work writes, since only the
title can end that wait. APS5_NO_FLIP_HOLD=1 submits as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MPYE7P9ob12aAE1H4JtHF
Throw when /proc/self/maps cannot be read or parsed, and when the kernel
places a reservation chunk at another address (MAP_FIXED_NOREPLACE
treated as a hint). Remove the unused PageSize and keep the POSIX-only
headers in the non-Windows block.
An unmapped range inside the guest arena stays a PROT_NONE host
reservation, so host munlock succeeds on it. Check the guest mapping
state first, as msync does, and return ENOMEM for ranges the guest
has not mapped or reserved.
CheckUnmappedArenaRangeStaysReserved uses mmap, PROT_READ, PROT_WRITE,
MAP_FIXED_NOREPLACE and EEXIST directly, so the Windows build fails to compile it.
The probe exists to show that an unmapped arena range still rejects a hostile
MAP_FIXED_NOREPLACE mapping, which is Linux-specific behaviour.
Guard the function and its call site with __linux__. The other tests in the file
compile on both platforms.
The Linux implementation of GuestArenaReset released the range in addition to
resetting the memory, while the Windows implementation only resets the memory and
leaves accounting to the caller. Two call sites already release explicitly after a
reset, so every one of them released the same range twice: sceKernelMunmap through
ArenaForget, and GuestHeap::release through GuestArenaRelease.
The two releases acquire the arena lock separately. Contains is span-based, so the
second release still runs, and Release erases every overlapping entry. A thread that
allocates the range between the two calls therefore has its entry erased, and the arena
hands the same guest address range out twice.
Delete the release from the Linux reset so both platforms restore the documented
contract. Every existing caller already pairs the reset with an explicit release.
The arena constructor was Windows-only, so on Linux Available() was false: the guest heap fell back to the host allocator and direct memory landed wherever the kernel pleased. Titles that require a low virtual address rejected the mapping and re-issued the identical request, which surfaced as a start-up assert inside their own allocator.
Seed the window from the mappings that already exist instead of reserving it up front, because a PROT_NONE reservation of the whole range starves the kernel's own top-down mmap, which picks addresses just below the stack inside that range. Commit, reset and set-protection become portable so the arena owns its used ranges on both platforms, and fixed mappings are checked against the arena's used ranges rather than against EEXIST from a reservation.
The window-placement and no-overwrite checks in guest_memory were compiled only for Windows; they now run on Linux too, and the window check fails without this change.
Fixes#692
Since guest threads get exactly the stack size they request, host code
that a guest thread calls runs on that small stack too. sceAgcCreateShader
reaches Driver::RegisterShader, whose frame alone is about 50 KiB, so a
title whose job threads ask for 48 KiB overflows the stack (#2603).
Each guest thread now gets a fixed 1 MiB of host stack below the range it
asked for. The guest still sees the requested size and address range
through scePthreadAttrGet and sceKernelIsStack; only host calls made from
the thread use the extra room. Running the prepared-shader driver tests on
a limited main-thread stack needs between 128 and 192 KiB for shader
registration, Vulkan setup included, so 1 MiB leaves several times that.
Switching to a separate host stack at each HLE entry point would need a
trampoline on every export that may go deep, handling of stack-passed
arguments and unwinding across the switch, and a list of which exports
qualify. A margin below the guest range covers every host call with no
per-export list.
On Windows the committed size was the requested size, and the margin
below it depended on the executable's default stack reserve; requests at
or above that reserve had none. The commit now includes the same margin.
macOS does not run guest threads.
guest_pthread_stack now also calls host code that uses 256 KiB of stack
below each thread's guest range; without the margin it crashes on the
16 KiB thread.
Fixes#2603
scePthreadCancel and pthread_cancel threw, scePthreadTestcancel did
nothing and the cleanup handler exports did not exist.
A cancel request is now recorded on the thread and acted on at
scePthreadTestcancel, the condition variable waits, scePthreadSem and
sem waits, scePthreadJoin and the sleeps, and under their POSIX names.
A thread blocked in one of the waits is woken; a condition wait takes
its mutex back before the thread exits. The thread exits with
PTHREAD_CANCELED through scePthreadExit, which now runs the handlers
of __pthread_cleanup_push_imp and __pthread_cleanup_pop_imp.
scePthreadSetcancelstate and scePthreadSetcanceltype reject unknown
values, and pthread_setcanceltype is added.
Constants, the handler node layout and the order of events follow
FreeBSD libthr. A sleep is not interrupted, file and socket calls are
not cancellation points and the asynchronous type acts like the
deferred one except at the two set calls (TechnicalDebt). New test:
guest_pthread_cancel.
Astro's Playroom (PPSA01325) calls sceRudpActivate during start-up, where the throwing stub stopped it. The host has no reliable-UDP stack to activate, so the export returns success and does nothing, matching the OK-returning stubs shadPS4 keeps for the whole library. The guest_offline_imports test now checks the success path with and without sceRudpInit, and still checks that the sceRudpGetStatus sibling throws and writes nothing.
Leave the Windows identity fields at their stat values when
GetFileInformationByHandle fails on a handle whose basic information
could be read, instead of failing the whole call.
st_ino was always 0 and st_dev differed between stat (drive number) and
fstat (0), so the usual a.st_dev == b.st_dev && a.st_ino == b.st_ino
identity check gave wrong answers. Read the volume serial, the file index
and the link count from the file handle, which works the same for path and
descriptor lookups.
Fixes#2296
ProsperoStore (PPSA99000) imports kill from libkernel and stops at it on
current main. Its crash reporter resets a crash signal to SIG_DFL and
sends it again with kill(getpid(), signal).
kill follows FreeBSD's kern_kill and killpg1 for the only guest process:
EINVAL for signals outside 0 to 128, signal 0 only checks the target,
and the caller's own pid, 0 and minus its pid deliver through raise.
Any other pid, and -1, which skips the caller, report ESRCH.
The registry records whether a range was mapped or protected with GPU
access (0x10/0x20), and an address-based build leaves out ranges without
it, as the console's GPU page tables do. The main image is registered
without GPU access; the title's own mprotect with GPU bits adds it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hardware turns anisotropy off under ANISO_OVERRIDE when the T#'s BASE_LEVEL equals its LAST_LEVEL. The view's level count is taken after LAST_LEVEL is clamped to MAX_MIP, so a T# with BASE 0, LAST 3, MAX_MIP 0 counted as single-level here while the hardware keeps anisotropic filtering for it (measured by @adriadam10 on the hardware oracle). The pairing now reads BASE_LEVEL and LAST_LEVEL from word 3 (DescriptorSingleLevel).
The per-draw path gets a test, agc_driver_image_sample_aniso_override: one ANISO_OVERRIDE sampler shared by two images in a dispatch, with single-level, mipmapped, mixed, and LAST_LEVEL > MAX_MIP descriptors, checking which pairings run (with their samples) and which are refused as mixed. With the old view-based check the LAST_LEVEL > MAX_MIP pairing is refused.
applyAnisoOverride reads the APS5_NO_SAMPLER_CACHE switch through the same cached flag as addImageBinding instead of calling getenv per sampler per draw.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With ANISO_OVERRIDE the texture unit disables anisotropic filtering when the
sampled view has a single level. Each draw now records, per sampler, whether
its paired images are single-level or mipmapped. A sampler with the bit and an
anisotropic filter whose images are all single-level is bound as the same S#
with point or linear filtering in place of the anisotropic filters, taken from
the sampler cache. One sampler descriptor serves every image paired with it,
so a sampler paired with both kinds in one draw still throws. Samplers of
mipmapped images keep their anisotropy, as on the hardware.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mesa sets ANISO_OVERRIDE (S# word 2, bit 29) on every GFX10+ sampler
unless aniso_single_level is on; it turns anisotropic filtering off for
single-level views. Samplers carrying it were rejected. Accept and
ignore it: host samplers are built from the S# alone and shared between
images, so single-level views keep the sampler's anisotropy. The gap is
recorded in TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Route oversized compute LDS through a per-workgroup storage buffer.
Retain native shared memory for programs that fit the device limit.
Carry the spill stride through prepared shader results and dispatches.
Preserve the stride in the full-result codec and its round-trip fixture.
Keep the Linux RecompileResult size guard aligned with the added field.
Exercise interleaved workgroups and the device-limit boundary.
This topic contains only the LDS change and its reviewer-requested repair.
The base includes upstream's separately merged NaN result correction.
VOP2 0x06 measured the same as VOP3 0x140 on gfx1010 (RX 5700 XT) in
wave32 and wave64, IEEE 0 and 1 and f32 denormal modes 0 to 3: the
product is rounded before the add, f32 denormal sources, product and
result are flushed in every mode, and a zero factor gives a +0 product.
It now shares the v_mad_legacy_f32 translation with the accumulator as
the addend, and f32DenormalFlushFor returns 3 for it as well. A new
execution test pins the measured results of its VOP2 and VOP3 forms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest_interfaces test now follows guest_thread_callback_abi instead of guest_errors, and the TechnicalDebt entry follows the libc SSE4a trap emulation entry instead of ending the Functional list, where other open pull requests add theirs.
A formatted buffer load or store through a GPU-selected descriptor still
emitted the switch over every buffer format inline, at each access. A
Ghost of Yotei compute pass with ~300 such accesses compiled to 37M SPIR-V
words, and its pipeline took 40 s or more to create.
The switch is now emitted once per component count, direction, d16,
coherence, kind and stop mode, as a SPIR-V function that takes the
descriptor fields, the offsets and the access's PC. When faults stop the
invocation, the helper returns whether one did and the caller stops there.
BDA faults record the PC from BdaInstructionPc, the access's own PC or the
one a shared helper takes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The --deferred-undecodable mode registers shaders through a Vulkan
device but, unlike --deferred, did not check for one first. On the
Windows CI runner, which has no Vulkan driver, vkCreateInstance returns
VK_ERROR_INCOMPATIBLE_DRIVER and the test fails instead of skipping, so
the windows job on main has failed since e5b32bb4. It now returns
VulkanTestSkipped like the other modes.
CB_BLENDn_CONTROL factors 15-18 (BLEND_SRC1_COLOR, BLEND_INV_SRC1_COLOR,
BLEND_SRC1_ALPHA, BLEND_INV_SRC1_ALPHA) threw "unsupported blend
factor". They blend MRT0 with the pixel shader's MRT1 export as the
second source, as RADV and radeonsi program it.
DecodeState maps them to the VK_BLEND_FACTOR_SRC1_* factors and accepts
them only with pixel exports to slots 0 and 1, color writes to slot 0
alone, an MRT1 export with MRT0's export format and shader mask, an
ADD, SUBTRACT or REVERSE_SUBTRACT operation and an identity component
order. The blend is draw state, so the specialized module of the
registered pixel shader moves its MRT1 output to location 0 index 1,
which ValidateShaders requires; pipelines need dualSrcBlend, now enabled
when present. The request field dualSourceBlend is in the module key,
the result memo, the draw key and the serialized request (version 15).
Drop IFF_POINTOPOINT from an address without a peer and compute an IPv4 broadcast address the host omits from the netmask, since FreeBSD always provides the address when the flag is set. Check zero port, flowinfo and padding in the test.
Return -1 with the FreeBSD errno for a null list and for expected host and allocation failures, as getcwd does. On Windows, give an unspecified prefix length a host mask, leave PPP and tunnel adapters without broadcast or point-to-point flags since their peer address is not reported, fall back to if<index> for long NDIS names, and bound the adapter query retries. Compare every entry with the host's own list in the test.
List the host's IPv4 and IPv6 addresses in FreeBSD's struct ifaddrs layout, with FreeBSD sockaddrs and interface flags, from getifaddrs on Linux and GetAdaptersAddresses on Windows. The list is one allocation, so freeifaddrs is a single free.
Move the image unregistration out of the Module destructor into
dlclose_nid_postfix. It runs while the handle is still registered, so a
failure (the image stays leased by GPU work) propagates to the caller and
leaves the module loaded and usable. Images shared by another open
handle stay registered until the last handle closes.
guest_memory now checks that dlclose under a held lease throws and keeps
the module, its symbols and its protection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A .guest.prx image registered by sceKernelMprotect stayed in the guest
allocation registry after dlclose freed it, so its dead ranges blocked
later mappings at that address and could reach GPU builds.
The Windows module destructor now unregisters the image before
FreeLibrary. Unregistering waits for outstanding leases on the image like
unmap and protect do; if the wait fails the module stays loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, sceKernelMprotect accepted only the executable's image and threw
"Memory protection of a foreign image is not supported" for every other
image. Bundled title modules are relinked into DLLs named <module>.guest.prx,
so a title that reprotects its own module pages failed. Dying Light 2's
bundled libNRD does this at start-up (#1794).
DoMprotect now also accepts images whose file name ends in .guest.prx, and
registers their committed pages like the executable's. Other images are
still refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, sceKernelPread and sceKernelPwrite called the POSIX pread and
pwrite wrappers, which throw on every failure, so a bad descriptor, a
negative offset or a write to a read-only descriptor aborted the process
instead of returning an error as the Linux build does. ftruncate and
sceKernelFtruncate threw on any host failure on both platforms.
Give the Windows sceKernelPread and sceKernelPwrite the same checks and
SceErrorFromErrno returns as the Linux ones, and accept a zero-length
transfer with a null buffer. Report a positioned transfer on a handle
opened without the needed access as EBADF instead of EIO. Return EINVAL
from ftruncate for a negative length and for a descriptor opened without
write access, as FreeBSD does, and pass other host errors to the guest.
Fixes#2423
Generate guest PRX dependencies with adjacent and separated repeats, equal names at distinct string offsets, and invalid-name controls. Check acceptance for Linux and Windows conversion and first-seen dependency order in ELF outputs.
Depends on the production fix by Hannah Lyons (babyhuey), c1a6696872153b8e41608f4cc8ab9a0d36f1d206. Written with AI assistance.
sceFontGetCharGlyphMetrics and sceFontGetRenderCharGlyphMetrics set the
vertical bearing and advance to zero although the glyph is loaded with
FT_LOAD_VERTICAL_LAYOUT. The library's own text writing steps the pen by
that vertical advance, so every character of vertical text was placed on
the previous one.
The vertical metrics now follow the FreeType driver's metrics of a
rendered glyph: FreeType's vertical advance, and for a face without
vertical metrics a bearing of half the horizontal advance left of the
horizontal bearing and of half the advance left over by the glyph height.
guest_system_font checks every metric of a square glyph at 100 pixels.
(cherry picked from commit 04dd69f806379df8b52cfdeb2979f2595973a9b3)
(cherry picked from commit 11f6f9e954756ee8d4a3fc2be574ab3b57ecd7c5)
WindowsPeWriter stored every PE section with SizeOfRawData equal to its
virtual size, so the zero-filled tail of a PT_LOAD segment (p_memsz above
p_filesz, the .bss) was written to the executable byte for byte. A segment
with 256 MiB of .bss produced a 269 MB .exe, while the Linux output of the
same image is under 5 KB.
Each section is now stored up to its last non-zero byte, aligned to the file
alignment, and VirtualSize keeps the full size. The PE loader zero-fills the
part of a section beyond SizeOfRawData (Microsoft PE format, section table:
SizeOfRawData). A section with no non-zero byte gets no raw data and a zero
PointerToRawData.
windows_bss relinks an image with 64 MiB of .bss, checks the section table
and the mapped image, and on Windows runs the executable, which reads the
last word of the .bss.
A bundled module whose file starts with a SELF container magic
(4f 15 3d 1d or 54 14 f5 ee) was skipped like any other non-ELF file,
so the relink succeeded without it and the module was missing at run
time. The guest module scan now throws, naming the module, as
ElfReader already does for a SELF executable.
Fixes#2599
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Palworld (PPSA23126) imports sceSaveDataCancel (e6y3WMBYbiM) from
libSceSaveData and fails to load because no library exports it. Its
signature is unknown, so it is exported without an implementation and
throws if it is called, like sceSaveDataConvert.
A registered compute shader takes its wave size from its header's
specials (dispatch_modifier), and PrepareRegistered read them without a
null check. ps5-opengl registers compute headers without specials, since
it writes the specials pointer only for linked graphics stages, and
passes the wave size in the dispatch initiator instead. Registration
then threw "shader metadata is outside the registered header".
Such a shader is now deferred like the other shaders that can't be
prepared at registration, so it is prepared at its first dispatch with
the wave size from the initiator's CS_W32_EN.
On Windows, getargc and getargv reported only the executable path, read
with GetModuleFileNameA in the ANSI code page, and dropped every other
argument. Linux returns the whole command line from /proc/self/cmdline,
and the relinker's Windows entry stub already passes the guest entry all
arguments converted to UTF-8.
Parse GetCommandLineW with CommandLineToArgvW and convert each argument
to UTF-8 with WC_ERR_INVALID_CHARS, as the entry stub does, so getargv
matches the entry's argv and the Linux behaviour. The Linux path is
unchanged.
DecodeColorTileMode rejected 2D color targets with a nonzero MIP0_DEPTH, so
an array target could only be rendered into through a view of a surface that
declares one slice. radeonsi sets CB_COLOR_ATTRIB3.MIP0_DEPTH to the last layer
of an array target (si_state.c, ac_descriptors.c), and Mesa-built homebrew
emits such targets. The view's slice already moves the target by one slice
size; it is now also checked against MIP0_DEPTH.
On Windows libc exports __cxa_init_primary_exception, std::uncaught_exception
and std::uncaught_exceptions only as _nid_postfix symbols (GCC_GLOBAL_ALIAS
is empty there), so host modules bind libstdc++-6.dll's versions:
- std::make_exception_ptr, which std::promise's destructor uses for
broken_promise, allocated the exception with libc and initialized it with
libstdc++'s __cxa_init_primary_exception, whose header layout differs.
A test that makes one and rethrows it dies with an access violation.
- std::uncaught_exceptions read libstdc++'s thread state, which libc's
throws never touch, so it returned 0 while an exception unwound. The
AGC driver's deferred-label guard relies on it to tell a failed record
from a finished one.
The three are now exported natively, like __cxa_throw and the rest.
__cxa_init_primary_exception also sets the reference count to 0, as the
Itanium ABI does: the exception_ptr created next takes the only
reference, so a made exception is destroyed once instead of leaking.
On Windows std::_Exit ends in ExitProcess, which still runs every module's
DLL_PROCESS_DETACH and TLS callbacks after the other threads are gone. A
static destructor that joins a worker then waits forever: a title that calls
_Exit while libSceVideoOut's flip queue runs (ps5-opengl's retirement
fail-stop) stays alive in FlipQueue's destructor. _Exit and libkernel's _exit
now terminate the process with TerminateProcess, as _exit ends a POSIX
process: no atexit handlers, stream flushes or module destructors.
Guest samplers with DISABLE_CUBE_WRAP (S# word 0 bit 28) are created with
VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT when the device enables
VK_EXT_non_seamless_cube_map; RADV maps the flag to the same bit. The bit
only changes cube filtering, so without the extension the sampler is still
created and only a cube texture sampled through it throws.
The buffer pool's device tier (detiler scratch buffers and the staging
shadows of written guest buffers) retained at most 512 MiB by default.
A title whose working set of these buffers is larger allocates and
frees device memory continually: PPSA14632 (LEGO Horizon Adventures)
had 70761 misses and 69828 evictions per 10 s with 485 of 512 MiB
retained, each a vkAllocateMemory or vkFreeMemory.
The default budget is now an eighth of the largest device-local heap,
and at least the old 512 MiB, so a 16 GiB card retains up to 2 GiB and
an 8 GiB card 1 GiB. APS5_STAGING_POOL_MIB still sets it.
Only the constructor knows the device, so only the budget comes from the
heap. Whether the tier is in use at all (APS5_STAGING_POOL_MIB unset or
not 0) stays a static check, DeviceTierEnabled, which Capacity, Usage and
tierFor share, so the size classes and the tier stay consistent.
On PPSA14632 with 2 GiB the device tier has 1312 misses and 99
evictions per 10 s (1628 MiB retained), and presents rise from about 34
to about 39 per 10 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A select of exec and zero gives a mask that is the same for every lane. Reading
it back went through a lane number, which Metal does not have in a vertex
shader, so MoltenVK rejected the shader. The select now stores the lane bit and
marks the pair as a mask, a constant mask word reads as a constant, and the
constant folder removes the bit test on an all-zero or all-one word.
agc_scalar_select_mask runs the translated IR over a wave and pins the lane
values for scc set and clear, a source that differs per lane, the two wave64
halves differing, a destination that is also a source, and constant and
literal sources.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
waitpid now sits between exit and _exit, and its TechnicalDebt entry follows the packed horizontal gathers entry, so the open system and getuid pull requests and the thread cancellation test pull request no longer touch the same lines.
Unknown options are now checked for every pid, with 0x80 and 0x80000040 added, and so are valid combinations such as WNOHANG | WUNTRACED. Each call is repeated with a null status. The TechnicalDebt entry cites kern_wait, which adds WEXITED and WTRAPPED, and lists pdfork.
waitpid returns -1 with ECHILD, or EINVAL for an option FreeBSD does not know, and leaves the status unwritten: AnyPS5 runs the title as one process and exports nothing that creates a child. VLC PPSA85300 stops on main at this import.
FillClearColor refused half and B10G11R11 patterns with a denormal code,
since an implementation may flush them in a clear, so such fills over a
resident image took the CPU path: PPSA01325 fast-clears its 3840x2160
R16G16_SFLOAT target 0x515390000 with a denormal half every frame, a
33 MiB guest write and re-upload (about 85 ms).
ClearKeepsDenormals probes the device once per format: a 1x1 image
cleared with denormal codes (half 0x0001, 0x83ff, 0x0200, 0x8001;
11/10-bit 0x001) and read back. When the bits survive, FillClear takes
the refused pattern again with SmallFloat decoding denormals exactly.
NaNs, float32 denormals and the SNORM minimum stay refused.
agc_driver_recorder_tests clears an R16G16_SFLOAT resident image with a
normal, a denormal and a NaN pattern: the denormal is cleared exactly
when the probe says the device keeps it, and the NaN is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
A shader reported in review binds 40 sampled images of one class, more
than 32. The cap only bounds the compact heap arrays, so no layout or
ABI version changes. The test covers 40 images and a full class through
the constant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A compute program of PPSA26344 binds 29 sampled images of one class, more
than the 16 a typed sampled heap held, and failed with "shader image heap
capacity exceeded". Heap arrays are declared with their compact length,
so shaders that fitted before keep their layout and SPIR-V.
Bindless image tables keep 16 slots: they were sized by the same constant,
and each slot costs an image entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A strided vertex fetch whose index is at or past the descriptor's num_records threw and aborted the title. PPSA01325 draws its fullscreen composite as a 4-vertex strip over a 3-record buffer, so the fourth vertex past the records stopped presentation.
Hardware returns (0, 0, 0, 0) for such a fetch with OOB_SELECT 0 and 1, measured on RDNA2 gfx1036 with tools/hw-oracle in #2241. VertexBufferReadSize now spans the whole fetch, and the staging copy reads the record bytes then zero-fills from num_records * stride onward. Each such fetch gets its own copy, outside the shared copies of its sibling attributes, the host-import in-place path and the draw-snapshot cache. OOB_SELECT 2 and 3 keep throwing past num_records, since each needs its own rule. Recorded in docs/dev/TechnicalDebt.md.
eb06284c7 made a read-only 2D image_load through a 1D T# valid, so the
test no longer pins a refusal. The TechnicalDebt line now says storage
loads still throw.
#1629 makes sceAgcDcbResetQueue throw 'unsupported op bit 0x40' (and for
0x200/0x400). Dreaming Sarah and Astro Bot call it with op bit 0x40 at
start-up, so both abort before the first frame. Main with only #1629
reverted runs Dreaming Sarah normally again (59.9/59.9 fps, no skipped
draws, no flashes).
Measured on RDNA2 (gfx1035) in review: image_store with DIM 2D through a
1D T# writes texel x for every y, including y = 0xffffffff. The T# type
decides the addressing and DIM only how many address VGPRs are read. The
store no longer drops y != 0.
The test now pins the second store at y = 1 with dmask 0x5: texel x holds
its components 0 and 2, components 1 and 3 are 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A compute program of PPSA26344 writes a 1D storage image with image_store
DIM 2D, and no runtime image mode matched its T#. A write-only 2D storage
access now also gets a 1D mode. A store through it is dropped when y is not
0, as a one-texel-high image bounds it; this mirrors the measured 3D loads
through 2D T#s of #2023 and is listed in TechnicalDebt as unmeasured.
Loads, atomics and image_store_mip through such a pair still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flip packet names a buffer index; the buffer was looked up when the
command buffer was submitted, so a title that registers its buffers
after submitting the first frame failed with "flip buffer is not
registered". The buffer is now resolved when the GPU reaches the flip,
as the hardware does. A flip whose buffer is still unregistered then
presents black and completes (see TechnicalDebt).
Uncharted: Legacy of Thieves Collection (PPSA05684) submits its first flip before registering its buffers and stopped with "flip buffer is not registered".
AI-assisted: yes. Match the firmware 13.60 probe in PR #1798: names of 32 bytes or more return guest ENAMETOOLONG=63 and preserve the prior name. The complete final change passed a full Windows build and eight focused runtime tests.
AI-assisted: yes. Validated with the final library change on Windows; full build and eight focused runtime tests pass. Expected rename behavior comes from the public firmware 13.60 probe in PR #1798.
In SCE_AVPLAYER_AV_SYNC_MODE_NONE every decoded frame was handed out in
order, so a title that requests video slower than the film's frame rate
fell behind its audio (about 52 s by the end of the PPSA12544 intro).
While the title pulls audio, frames the audio clock has already passed are
now skipped; frames that are not late are handed out as before.
Fixes#1635
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On RDNA2 (gfx1036) ds_min/max/cmpst(_rtn)_f32 and _f64 flush both operands
when bit 0 of the type's denormal field is clear: field 2 flushes like field
0, field 1 keeps like field 3, in IEEE 0 and 1. The flush decision now tests
that bit. agc_driver_lds_float_atomic_modes adds FLOAT_MODE 0x50 (both fields
1) and 0xa0 (both fields 2), and the TechnicalDebt entry records the measured
fields.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
ds_min/max/cmpst_f32 and ds_min/max/cmpst_f64 with their _rtn forms follow the
rule measured on RDNA2 (gfx1036): both operands are flushed to a signed zero
when the type's denormal field flushes, a signalling-NaN data operand is stored
quieted, else a signalling-NaN old value, else a quiet NaN loses, else the
ordered compare decides, and the flushed value is written. The shared f32
path used the host's float compares, so its NaN and denormal results were the
driver's; the shared f64 path lacked the signalling-NaN rules the buffer path
has. Both now use AtomicFloatBits. The translation marks the shared float
atomics with the mode's flush in MemoryInfo.
The new agc_driver_lds_float_atomic_modes pins 32 operand pairs per type in
the titles' mode, with both fields kept and with both flushed. The 64-bit
test's model gets the signalling-NaN rule and one lane kind now feeds the
signalling NaN of its table into min/max.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
The f64 unary instructions, v_ldexp_f64, v_div_fixup_f64 and
v_cvt_f64_f32 always quieted a signaling NaN. The hardware does that
only with IEEE_MODE=1, as #1867 measured for the f32 and f16 forms;
with IEEE=0 the NaN comes out unchanged, measured on gfx1036.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ±6.1e-5 lanes take the driver's cosine of a tiny angle, where RADV
returns the hardware's 0x3f7ffffe, NVIDIA 0x3f7ffffd and Intel ANV
0x3f800000 (reported by Kikoarl on #2380), so the lanes tolerate 2 ULP
instead of 1.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
After #1979 the cycle is exact, but the driver's sine of the radians loses
tiny results to its absolute error, and near the zeros of sine at k + 0.5
and of cosine at k ± 0.25 the f32 radians drop the low bits of the cycle.
Measured on RDNA2 (gfx1036), the hardware returns the input times 6.2831826
for every input below 2^-12 of a turn and stays within 7 ULP of the f32 2π
times the distance at the half and quarter turns.
Within 2^-12 of a turn from a zero the lowering now returns the linear term
of the exact distance to that zero, with the hardware's constant at the
integers and the f32 2π elsewhere; everywhere else it keeps the driver's
sine or cosine of the radians. agc_driver_sin_cos_near_zero pins 32 inputs
per type from the oracle with per-lane tolerances.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
A context without a device, such as the graphics test's mock, leaves state.device equal to context.device, so refreshImports never loaded destroyBuffer and freeMemory and retiring an import called a null pointer.
The pin waiter now receives the mutation range, drains GPU work and retires every overlapping host import before the unmap callback releases the pages. Deferred import retirement keeps only the device handle and the Vulkan destroy functions, so the static import registry no longer owns a Context or its BufferPool past the device.
A compute shader in Astro Bot samples an image as 2D while the bound
T# is 1D, and the recompiler refused it. Offer a 1D runtime mode for
sampled images used only by image_sample_lz and mip-less image_load,
which reads the texel the first coordinate selects, as measured on
RDNA2 in #2237.
With FP16_OVFL=1 the hardware writes an f16 result that would round to
infinity as the largest finite value of that sign, unless a source
operand is infinite. v_div_fixup_f16 keeps the infinity it writes for a
zero denominator or an infinite numerator. Measured on RDNA2 (gfx1036)
at IEEE=0 and IEEE=1 with f16 denormals kept and flushed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A 64-thread compute kernel (69 dwords) stores pattern[min(i % s9, 3)],
the pattern in s4..s7, to dword i of a 4-byte-stride buffer for every
i below min(launched threads, s8, num_records), with the group id in s10.
PPSA01325 runs it every frame to clear DCC keys, HTILE and other
metadata. It ran as a shader; a direct dispatch with a period of 1, 2
or 4 dwords, whole 16-byte units and a 16-byte aligned destination now
goes through fillBuffer like the existing fill kernel (clear path,
FillBuffer, key memo). APS5_NO_FILL_HLE turns both off.
tests/execution/DwordPatternFill.cpp checks the matcher and runs the
kernel through Submit for matched and unmatched cases (periods 3 and 5,
partial counts, record clamps, a partial dispatch); with
APS5_NO_FILL_HLE=1 the same expectations hold for the translated kernel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
Both dominance analyses start from sorted unique sets and intersect them.
Insert the current block ID with lower_bound instead of sorting again.
Preserve every intermediate set and the existing ID-order iteration.
Keep the generic unsorted helper and all graph rewrite logic unchanged.
Compare both analyses against the original algorithms on six raw CFGs.
Cover finite loops, closed SCCs, unreachable islands and permuted IDs.
With SDL's disk driver no pad audio device is open, so AudioOut2MixPorts
runs with no pad output. A vibration port and a mono pad speaker port
are pushed together: only the speaker's data reaches the main mix, and
a vibration port alone leaves it silent.
Without a DualSense audio device every AudioOut2 port was mixed into the
main output, including PAD_VIBRATION ports. Their waveforms drive the
controller's actuators on the console and are never heard; mixed into
the speakers they made PPSA28997's combat about 20 dB louder than its
dialogue. Vibration ports are now left out in that case; pad speaker
ports still go to the main output.
The f16 min/max/min3/max3/med3 now pick NaN results on the bits and
quiet a signaling NaN only with IEEE=1. With IEEE=0, v_min_f64 and
v_max_f64 return the other operand for a NaN, not a quieted NaN. This
completes #1826 for these forms, as measured on RDNA2 (gfx1036).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With IEEE_MODE=1 the hardware quiets a signaling NaN that v_cubesc,
v_cubetc or v_cubema_f32 returns, keeping the sign the instruction gives
it; with IEEE=0 the bits pass through. Same rule as #1867, measured on
gfx1036 in both modes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Neither existed under the guest root, so opening them failed and titles
that then read from the descriptor crashed. Opening either for reading
now returns a descriptor whose reads, positioned reads and vector reads
fill the buffer from the host's random device, as
sceRandomGetRandomNumber does. Opening them for writing throws.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The executable is linked at a fixed base with a __PAGEZERO that fills the space below it, and
the guest assumes 16 KB pages. With MH_PIE set dyld slides the image by a multiple of 4 KB,
which breaks that assumption and ends in a SIGBUS (KERN_PROTECTION_FAILURE) in about half of
the launches under Rosetta 2. Clearing the flag keeps the image at its linked address.
test_macos_output.py now reads the header flags and checks that MH_PIE is clear.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Since #1178 the SPI_PS_INPUT_ENA/ADDR emplace calls did nothing, so the registered variant had no pixel inputs, and null pixel draws took the last pixel shader's user SGPR count and the bound targets' export mapping. Registration now overwrites those registers, the draw gives the null program no user SGPRs and an empty export mapping, and the at-draw preparation is left for wave32 only. Root cause found by @Raghav314.
A draw that rasterizes both faces with different front and back depth
bias was rejected, since Vulkan has one depth bias per draw and the
cull mode is pipeline state. Such a draw is now recorded twice in the
same pass: front faces with the back faces culled and the front bias,
then back faces with the front faces culled and the back bias. A face
whose offset is disabled gets no bias.
#2279 made agc_driver_f32_arithmetic_nan fail on NVIDIA (Windows):
v_fmamk_f32 returns a quiet NaN where the signalling one is expected
(lane 1 no float mode v_fmamk_f32 is 0x7fc12345, expected 0x7f812345),
5/5 runs on main and 5/5 passing with #2279 reverted. CI's Windows job
skips the AGC execution tests, so it did not show there.
v_fma_legacy was rewritten as a split multiply and add after #2279; it
keeps that and gets back the invalid-product argument it passed before.
Fixes#2581
scePthreadCreate on Linux started guest threads with std::thread, so every
thread got the host default stack (RLIMIT_STACK, usually 8 MiB) whatever the
title asked for. A thread that asks for more than that and uses it crashes
with SIGSEGV. Once the thread ran, scePthreadAttrGet also reported the host
size instead of the requested one.
Create the thread with pthread_create and a stack of the requested size plus
what glibc keeps at its top (static TLS, the thread control block and the
start_thread frame). That reserve is measured once with a probe thread.
The thread reports [top - reserve - size, top - reserve) as its stack, as
Windows reports [top - size, top), and the creator waits until the range is
set, as on Windows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With f32 denormals flushed, as in the 0xc0 float mode titles use, the
hardware flushes all three v_mullit_f32 sources and its product like
v_mul_f32, so a positive denormal limit selects -FLT_MAX and a tiny
product gives zero. The recompiler kept denormals in every mode.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With IEEE_MODE=1 the hardware quiets each signaling NaN half that
v_pack_b32_f16 packs, after neg and abs; with IEEE=0 it copies the bits.
The recompiler always copied them. Same rule as #1867, measured on
gfx1036 in both modes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The registered-shader decode ran before the deferral decision, so a single
instruction the decoder does not implement ended the title at registration
instead of deferring the shader to its first use.
Give the decoder's unsupported-instruction failures their own type
(UnsupportedInstructionError): the five family-scoped SOP opcode defaults and
the MIMG opcode lookup (which now names the opcode) raise it, and the decode in
PrepareRegistered defers the shader with that reason when it hits one. Any
other exception still throws, so the corrupt-code contract is unchanged.
PPSA26344 hits this: unsupported MIMG opcode 229 (0xE5), issue #1941. The
failing word is reproducible offline from the title's own shader store
(cache_ps5/all_shaders.xpps, code word 260), which is how the fixture was
checked before it was written.
Scope: this defers the scalar-family and MIMG opcode gaps. The vector (VOP*)
and memory (SMEM/MUBUF/MTBUF/FLAT/DS) opcode lookups, the modifier-level
"not supported" throws and the malformed/truncated throws still abort.
Widening the deferral to those is a policy call for the maintainers.
Stacked on #1836 (the deferral machinery); re-stacked on its current head so
this diff no longer carries #1836's earlier broad catch. Once #1836 lands only
the decode change remains.
Written with AI assistance.
A title that opens a main and an overlay port flips both, and every flip
was presented as a whole frame, so the window alternated between the two
buses: PPSA01736 shows its intro on the main bus and an empty overlay,
and the intro blinked black on every other frame. Overlay flips now
complete and signal their events without being shown. Compositing the
overlay over the main output is not implemented.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The guest filesystem root and the save data directory are taken from the
current directory, so an executable started from another directory does
not find its title files and keeps saves elsewhere. Make the executable
directory the current directory in the Windows entry stub before the
system libraries are loaded.
A pixel shader whose early discards branch to one small ending block
gave that block several conditional predecessors outside any loop, and
structurizing failed. When the regular pass throws, the structurizer
restarts from the original graph and first gives each conditional
predecessor its own copy of such a return block (within the clone word
floor and a budget of four clones per block).
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped at such a pixel shader with "conditional block 15 has no structured merge".
A typed storage-image heap held four elements, and a dynamic-mip storage
image takes four of them for its mip slots. A shader with a dynamic-mip
storage image and any other storage image of the same class needed five
and failed with "shader image heap capacity exceeded". The mip slots
per dynamic image stay four (StorageMipSlots); a storage heap now holds
sixteen elements (StorageHeapCapacity). Heap arrays are declared with
their compact length, so shaders that fitted before are unchanged.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Vulkan counts the framebuffer colour attachments against maxPerStageResources
for the fragment stage. The draw's attachment count now seeds the fragment
stage's resource total in ShaderResources, so a pixel shader whose buffers and
images fit the limit but whose pipeline exceeds it throws instead of creating
an invalid pipeline. The draw resource cache key carries the attachment count,
so a cached build is never reused by a draw with more attachments.
RADV clears depth by rewriting HTILE with its clear_htile_mask kernel
(ZMASK to 0) instead of writing depth memory. Recognize that kernel and
clear the matching host depth surface to DB_DEPTH_CLEAR when it is next
bound; the depth target now records its HTILE address.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The f16 forms of v_fma_mix computed an f32 FMA and converted it to f16,
so a result that lands on an f16 tie after the f32 rounding was rounded
twice. RDNA2 rounds the exact product-sum once. They now use the exact
f64 path of v_interp_p2_f16 and flush f32 sources like v_fma_mix_f32.
They follow the f16 round and denormal fields on hardware, so another
f16 mode throws, as packed f16 does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DEPTH_CLEAR_ENABLE (DB_RENDER_CONTROL bit 0, Mesa sid.h) makes the DB
write DB_DEPTH_CLEAR wherever the draw covers. Such a draw is now
rasterized as submitted with an always-passing depth write, no depth
bounds, no depth bias and the viewport depth range pinned to the clear
value, so it touches exactly the pixels the title's geometry covers.
Clearing a read-only or absent depth plane throws, at decode and in the
draw precheck. Depth and stencil clears combine; copy, resummarize and
decompress draws still throw.
Uncharted: Legacy of Thieves Collection (PPSA05684) clears with draws
that set DB_RENDER_CONTROL to 0x3 and stopped with "depth/stencil
clear, copy, resummarize or decompress draws (DB_RENDER_CONTROL) is
unsupported (register 0x0 = 0x00000003)".
libc implements the C++ exception runtime and already defines
exception_ptr's out-of-line members, so host modules that link libc
reference-count libc's exceptions. Code inside libstdc++-6.dll does not
see those definitions on Windows: std::__future_base::_Result_base's
destructor is one of them, and it releases the stored exception with
libstdc++'s own exception_ptr::_M_release. That decrements a count 0xa0
bytes before the object, while libc keeps it 0x80 bytes before, so the
decrement lands in the heap memory in front of libc's allocation and
the exception is never freed.
VideoOutDriver reports the presentation thread's start-up failure
through a std::promise, so video_out_flip_lifetime sometimes ended with
0xc0000374 when SDL's offscreen driver refused the window.
libc now defines the base destructor, with a _nid_postfix twin so that
the NID-patched libc keeps its name, and modules bind it there.
AMD's Windows driver advertises shaderSignedZeroInfNanPreserveFloat16,
yet OpFConvert from f32 to f16 returns +0 for -0 inputs and for negative
values that round to zero, so six f16 execution tests fail there since
the native conversion was added. Conversion never changes the sign, so
the native path now takes the sign bit from the f32 source, as the
emulated path already does.
Fixes#2483
The x86 decoder had no XOP (AMD 8F escape) support and read 8F E8.. as
POP r/m, losing instruction boundaries. Since #2238 roots code addresses
taken by RIP-relative LEA, code analysis reaches CPU-dispatched XOP
paths, such as the vpperm routines in Stremio Plus (PPSA74126), OpenNOW
(PPSA99082) and PPSA99420, and their Windows conversion failed with
"Code analysis: overlapping instruction boundaries". Decode 8F followed
by a map of 8 or more as a three-byte XOP prefix (8F can only be POP r/m
with ModRM.reg 0) with a ModRM operand, an 8-bit immediate in map 8 and
a 32-bit immediate in map 0Ah, in both the length and operand decoders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A directory opens and tellg reports an enormous size, so the relinker
tried to allocate it and failed with std::bad_alloc. Require a regular
file and a non-negative size before allocating the input buffer.
The unused-filter=1 entry point collector translated DT_INIT_ARRAY and
DT_FINI_ARRAY through PT_DYNAMIC and skipped arrays placed before it, which is
the usual linker layout, so constructors and destructors were not roots and
imports used only by them were dropped. Arrays are now translated through the
PT_LOAD that holds them, slot targets come from RELATIVE relocations when
present, and DT_PREINIT_ARRAY is collected as in the strict filter.
A zero, unaligned, overflowing or out-of-range length reached the physical pool, where a sub-page length silently freed a whole page and fragmented the block map, and an out-of-range length walked the page table past the backing store. Validate alignment and bounds up front and return EINVAL instead.
- DISPATCH_DIRECT and DISPATCH_INDIRECT with the SHADER_TYPE header bit,
which RADV sets on every dispatch, as other packets already may.
- FORCE_START_AT_000 in the dispatch initiator. The driver never offsets
dispatches by COMPUTE_START_X/Y/Z, so it already starts at zero.
- LOAD_CONTEXT_REG_INDEX, LOAD_SH_REG_INDEX and LOAD_UCONFIG_REG_INDEX in
their consecutive-register form (a register offset and a count, DATA_FORMAT
0), which RADV uses to load DB_STENCIL_CLEAR and DB_DEPTH_CLEAR from an
image's clear values. The offset-and-data form keeps its old checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV builds its own command streams. Its preamble and draws use packet
forms the validator refused:
- CONTEXT_CONTROL that only updates the load and shadow enables to zero,
which RADV emits when it does not shadow registers. It is a no-op.
- SET_UCONFIG_REG_INDEX of VGT_PRIMITIVE_TYPE with index 1, next to the
index-2 VGT_INDEX_TYPE write already accepted.
- WRITE_DATA to memory (DST_SEL 5) from the graphics queue.
- DMA_DATA with DST_SEL NOWHERE, RADV's L2 prefetch of shader code. It
writes nothing and resolves to an empty store.
- EVENT_WRITE PIPELINESTAT_START, PIPELINESTAT_STOP, SQ_NON_EVENT and
VGT_FLUSH. The statistics counters are not modelled, and the events that
would read them stay rejected.
- ACQUIRE_MEM over the full 48-bit range (COHER_SIZE_HI and COHER_BASE_HI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VOP3 0x140 is v_mad_legacy_f32 on gfx10.1. Measured on gfx1010 (RX 5700
XT) in wave32 and wave64, IEEE 0 and 1 and f32 denormal modes 0 to 3, it
rounds the product before the add, flushes f32 denormal sources, product
and result in every mode, and gives a +0 product for a zero factor.
The translation multiplies, applies the zero rule and flushTinyProduct,
adds, and keeps the nanResultF32 wrapper. f32DenormalFlushFor returns 3
for the instruction before reading FLOAT_MODE. A sum of two zeros takes
its sign on the bits, as lavapipe returned -0 for +0 + -0. The execution
test pins the measured results and compares NaN bits exactly. RDNA2
(gfx1031), where the encoding is v_fma_legacy_f32 and fused, is noted in
TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Importing guest memory collected the write watch over the whole import,
which write-protected every page of it, so pages nothing had collected yet
took a write fault on their first CPU write. The import now collects only
the parts a collect already protects (GuestWriteWatchCollectArmed). The
rest stays unprotected until something collects it, which reports it as
written; a GPU write recorded into such memory (MarkWritten) first collects
the never-collected parts of its 64 KiB blocks
(GuestWriteWatchCollectFresh), so that report is older than the GPU's
results and does not read as a CPU write over them.
Those pages are stamped as written and their CPU pages are noted, but the
block's CPU stamp is left alone, so a GPU label record or a pending storage
image elsewhere in the block does not see a CPU write. StoredOver and
UnchangedSinceCollected read the noted pages whenever a block has them, so
a store to such a page is still seen by a check over that page. The extra
generation step happens only when something was collected; a block whose
write protection fails is unwatched and the write returns no generation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The padded, exact-fit and invalid-conversion swprintf checks come from #1512.
Co-Authored-By: Noface07 <yuvrajsoni7@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULF8vCbJFyzxVsW3SfiWC5
flock_nid_postfix threw on every failure, so a LOCK_NB request on a lock
held through another descriptor ended the process. FreeBSD flock(2)
returns -1 with EWOULDBLOCK there. Return guest errno 35 when LOCK_NB is
set and the host reports the lock as held: ERROR_LOCK_VIOLATION on
Windows, EWOULDBLOCK on Linux. Other failures still throw.
A formatted buffer load or store whose format comes from a prepared
descriptor emitted the switch over every buffer format inline, at each
access. The switch is now emitted once per resource, component count and
direction as a SPIR-V function taking the index, offsets, record count and
storage buffer offset, and each access calls it. Specialization still folds
the switch to the descriptor's format. Accesses through GPU-selected
descriptors keep the inline switch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's __sflags reads the access character, then b, +, x, e and v, and
stops at the first other character without failing, so "rt" opens for
reading. On Windows fopen inserted b after the first character and passed
the rest on, so "rt" became "rbt", the CRT rejected it with EINVAL, and
fopen threw. vkQuake opens history.txt with "rt" at startup. freopen
accepted only a fixed list of modes.
Both now parse the mode like __sflags. An unknown access character, or x
on a read-only stream, returns NULL with EINVAL; x on an existing file
returns NULL with EEXIST; e and v are accepted and ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An anonymous private mapping with MAP_ALIGNED_SUPER (FreeBSD MAP_ALIGNED(1))
is placed on a 2 MiB superpage boundary. Yamagi Quake II requests it for its
hunk allocations of 2 MiB and more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SDL queries the SCHED_OTHER range before it sets a thread priority. It gets
the same 256 to 767 range as SCHED_FIFO and SCHED_RR, which
pthread_setschedparam applies regardless of policy. The console's answer is
unverified and recorded in TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A global_store_dwordx2/x3/x4 was split into one StoreAddressU32 per
dword, each with its own write-pointer lookup and its own 4-byte store.
Across a wave those are strided partial writes, which are very slow to
host memory: RADV's clear_htile_mask kernel (one dwordx4 load and store
per thread over 640 KiB) took ~9 ms. Multi-dword stores are now one
StoreAddressU32xN; the emitter probes the whole span once and, when it
is mapped writable and 4-byte aligned, stores it as one vector (16- or
8-byte aligned) or as consecutive dwords, noting each written page.
Anything else takes the per-dword path as before, so faults and
range-spanning stores behave as they did. APS5_BDA_DWORD_STORES=1
restores per-dword stores.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0xffff1000 is a type-3 NOP whose count field is 0x3fff, which the command processor reads as a single dword. The driver read it as a 16385-dword packet and rejected every sceAgcDcbResetQueue envelope. Pm4::FillerPacket now also matches exactly this header, so every packet walker steps over it as one dword.
Refs #1581
The case reached the compared path once host imports kept a guest-byte
snapshot, so it passed without the fix. It now runs over watched memory
without an import on every host, and over a watched host import on Linux
(the font atlas's path, with no valid snapshot), checks the image is on
that path, and runs as its own ctest test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A write-back keeps the CPU's bytes for a block stamped since the layer's
generation. Stamps can fall on bytes the CPU did not change (PPSA12544's
font atlas: the stamped pages still hold the allocator fill the image was
uploaded from), and the GPU results there were lost. Images up to 1 MiB
keep the guest bytes of their generation point; a stamped range that still
equals them stores the GPU results.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CFG/GOT filter's entry point collector checked header and symbol
positions as `offset + size > file size`, which wraps for offsets
near 2^64. A section header or symbol table offset there passed the
check, read bytes before the input buffer, and could reach an
in-bounds table through the wrapped address, adding entry points that
keep relocations alive. Check every position by subtraction instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CMASK that was neither all fast-cleared nor all expanded threw, so a
title that fast clears a target and then draws over part of it before the
eliminate could not run past it.
The CMASK is now decoded tile by tile: one nibble per 8x8 tile in
1024x512 metablocks of 4 KiB, addressed with the pipe-aligned
SW_64KB_Z_X CMASK equation for 16 pipes (addrlib
GFX10_CMASK_SW_PATTERN, Gfx10Lib::HwlComputeCmaskAddrFromCoord). Code 0
stores CB_COLOR_CLEAR_WORD into the tile's texels, 0xF keeps them; any
other code, a cleared tile of a DCC target and any other CMASK swizzle
mode throw. Edge tiles are included and the CMASK is then stored as
expanded.
For a resident target whose CMASK is host-imported the clear is a
compute pass recorded in stream order: it reads the nibbles from the
import, stores the clear into the cleared tiles through a UINT view of
the image and fills the CMASK expanded, so nothing waits for pending GPU
writes of the CMASK. Invalid codes it reads set a bit that the next
materialization throws on. Otherwise the CPU reads the CMASK and stores
the cleared tiles into guest memory, or fills the resident image when
every tile is cleared. A CMASK nothing has stored over since it was
expanded is skipped.
The decode requires CB_COLOR_ATTRIB3 FMASK_SW_MODE SW_64KB_Z_X with
CMASK_PIPE_ALIGNED, a 4 KiB aligned writable CMASK and texels of at most
64 bits, and CMASK_BASE_EXT is part of the draw key.
Tests: CMASK sizes and nibble addresses against addrlib; decode
rejections and draw key coverage; CPU eliminate of full, partial,
out-of-range-nibble and DCC decompress cases and invalid codes. The new
agc_driver_cmask_fast_clear GPU test draws over fast-cleared and partly
fast-cleared linear and SW_64KB_R_X targets, eliminates partial, edge
and checkerboard CMASKs through the compute pass and checks that an
invalid code it reads throws.
A wave64 vertex program reduces a float key across the wave with the same shape as the integer half-wave reductions: keys selected under the all-lanes EXEC with -inf (v_max_f32) or +inf (v_min_f32) outside the entry EXEC, four DPP row_shr steps, v_permlanex16_b32, then v_readlane_b32 of lanes 31 and 63, combined by one more v_max_f32. On a 32-lane host subgroup the read of lane 63 threw that it is outside the subgroup.
The matcher now also accepts FPMax32 and FPMin32 steps, with their BitCasts and the f32 denormal flush the float mode may add around operands and results; the flush leaves the -inf and +inf keys unchanged. When the entry EXEC proves that lanes 32-63 hold the constant key, every step there combines that constant with itself. The existing v_max_f32/v_min_f32 lowering returns the operand unchanged for equal operands (NaN included), so lane 63 is exactly that constant, and the read returns it. No subgroup float reduction is emitted. Lane 31, the lanes on a 64-lane subgroup, and the combining v_max_f32 keep their existing lowering. So NaN and signed-zero results are the ones the shader computes today.
On Linux, sceKernelSync called sync(2), which blocks until all
dirty pages are written back. Unrelated host I/O can stall guest's
sceKernelSync, causing the guest_kernel_sync test to stall for minutes.
Share the Windows implementation instead.
Fsync each recorded path and its changed parent directories.
Use st_mtim and coarse realtime mark on Linux in place of FILETIME.
Every draw copied its index range and its vertex ranges out of guest
memory into new host buffers (CopyDrawInput) before the GPU could read
them. A range that lies in a host import is now bound from the import
itself, at its offset in it, the way the console's input assembler reads
guest memory directly.
A range is bound in place only when the batch has no write over it that
could still land (a recorded or in-flight GPU write after the pending
results are flushed, a completion label, a queued label store, a copied
buffer a dispatch or draw writes back) and its offset in the import is a
multiple of what the reader needs: the index size, or the strictest
format alignment of the vertex attributes that share the range
(VertexCopyPlan::alignments; a copy starts its range at offset 0, so
only an in-place range can be misaligned). Otherwise it is copied as
before. The read is noted on the batch (Recorder::ReadKind::DrawInput),
so a CPU store into the range waits for the draw like the other in-place
readers, and the barrier validation sees it. Imports gain vertex and
index buffer usage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD sys_flock takes LOCK_UN over LOCK_EX over LOCK_SH, ignores other
bits and fails with EBADF when none of the three is set. The operation
reached the host unchanged, so malformed operations differed between
hosts: Linux rejects them with EINVAL and the Windows lock tracking threw.
Decode the operation before either host sees it.
flock mapped each call straight onto LockFileEx/UnlockFileEx, whose
byte-range locks stack per handle. LOCK_UN without a lock failed with
ERROR_NOT_LOCKED and aborted the process, a repeated LOCK_EX blocked
forever, converting between shared and exclusive failed, and a repeated
LOCK_SH needed as many unlocks.
Track the one lock each descriptor holds: a repeated mode and LOCK_UN
without a lock succeed, a conversion releases the old lock before taking
the new one, and close forgets the descriptor's lock.
Measured on RDNA2 (gfx1036) with tools/hw-oracle in wave32 and wave64:
s_call_b64 writes PC + 4 to the link pair and branches to
PC + 4 + signext(SIMM16) * 4, and s_setpc_b64 of the link returns;
s_swappc_b64 with SDST 125 branches and leaves a marker pair untouched, so
its decode as s_setpc_b64 matches, and with an SGPR destination the link is
PC + 4.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
On Windows the existence check accepts "file/", then _wstat64 rejects
it and FillFileStat throws, aborting the title. On Linux the host
reports ENOTDIR, which became ENOENT. A trailing slash on something
that is not a directory now returns ENOTDIR on both hosts, as on
FreeBSD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No job saved its ccache, so every build restored a day-old cache and
recompiled about half the tree (971/2038 hits). Main push builds now
save it, and they no longer cancel each other, so one finishes and
refreshes it. Pull request builds still only restore, so PR code never
writes the shared cache.
Format the address bytes in network order as dotted decimal into a
per-thread buffer, like FreeBSD's inet_ntoa returns its static one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's gettimeofday stores the time only when tp is not null, and
clock_getres the resolution only when res is not null; both succeed
otherwise. Both threw, which aborted a title probing a clock or only
asking for the time zone. Write into a local result instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 3D image access bound to a 2D T# matched no runtime image mode and
threw "image descriptor is incompatible with the static runtime image
interface" (Sonic Origins, #2023). CouchRiv measured RDNA2 (gfx1036)
with the hardware oracle: image_sample_lz with dim 3D ignores r and
reads the (u,v) texel, and image_load reads the (x,y) texel when z is
0 and returns zero otherwise. Add a 2D mode for 3D accesses whose only
uses are those two forms, and make the load return zero for z != 0.
Every other access keeps throwing.
Fixes#2023
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows runner has no Vulkan driver, so agc_pipeline_cache (#1331)
fails at device creation: 'AGC graphics: vkCreateInstance: Vulkan
result -9'. Skip with 77 like the other agc execution tests, unless
ANYPS5_REQUIRE_VULKAN is set, as on the Linux job.
video_out_flip_tests writes app0/sce_sys/param.json in the shared test
directory on every run, and every VideoOut window reads it for its
title. Under ctest --parallel one test read the file while another
rewrote it: 'unexpected end of param.json' in video_out_flip_controls.
Give all of them one RESOURCE_LOCK.
The unopened-handle checks call sceVideoOutIsOutputSupported before
sceVideoOutOpen, and that first call builds the VideoOut driver, whose
window creation throws without a Vulkan-capable SDL driver. The throw
left main before the skip, so the test aborted on the Linux runner:
'DisplayWindow: Vulkan support is either not configured in SDL or not
available in current SDL video driver (offscreen) or platform'.
Run those checks inside the same skip.
A jump slot whose r_offset is 0xFFFFFFFFFFFFFFF8 or higher makes
targetGotOrPltAddress + 8 wrap, so the upper lower_bound lands before
the lower one and the call-site loop walks past the end of the map
(SIGSEGV; ASan heap-buffer-overflow in ResolveCallSites).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guest_video_out_output_mode (#1496) opens VideoOut directly, which needs
a Vulkan-capable SDL window, and aborts on the CI runners that have none.
Skip with 77 like the other VideoOut window tests, unless
ANYPS5_REQUIRE_DISPLAY is set.
AJM's instance and batch tables and APR's file, directory and AMM
state were namespace-scope statics, destroyed by std::exit while the
title's threads keep calling the libraries. A decoder thread then read
a freed instance (an unknown codec), sceAjmBatchWait no longer found
its batch, and APR resolved file ids through a freed vector. The state
is now allocated once and never destroyed.
#1331 registered the agc_pipeline_cache ctest, which runs
agc_driver_recorder_tests, without removing that executable from
KNOWN_UNRUN, so check_test_registration fails on main:
'agc_driver_recorder_tests is run by ctest now; remove it from KNOWN_UNRUN'.
The list is empty now; keep it a set for the set operations below.
A port kept only the last PCM buffer set before a push and played it again
at every later push without a new one. PPSA12544 sets one grain of a ring
buffer per call on its object port, often two or three between pushes and
then none for a few, so all but the last of each burst were dropped and the
stale grain repeated: a buzz on loud sounds and after a sound stopped.
Buffers set between pushes are now queued and played in order, at most 16
queued. A push without a new buffer repeats the last one only for a port
that has always been given the same buffer (rewritten in place, as the
timing tests do); a port whose buffers move on plays silence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without VK_KHR_fragment_shader_barycentric the recompiler dropped
v_interp_p1_f32 and made v_interp_p2_f32 read the attribute Vulkan
interpolated, but never wrote the I/J VGPRs: a pixel shader that read
them for anything else got 0, and v_interp_mov_f32 of P10/P20 (or P0 of
an interpolated input) needed PerVertexKHR and failed pipeline
validation.
The fallback translation now loads the I/J VGPRs and marks v_interp_p1
and v_interp_p2 with witnesses. After the front-end simplification
(SSA, folding, DCE, read-lane and masked-select elimination),
HostInterpolationChecker requires every p2 to read its J, and its p1's
I, from a barycentric of the parameter's kind, every p1 result to reach
only the matching p2 (through a write masked by the p2's EXEC, whose
inactive lanes keep the old register), no parameter read as raw
vertices and no slot shared by different interpolations. It then
lowers the witnesses to the host-interpolated attribute as before,
reruns DCE and masked-select elimination, and accepts the program only
when no barycentric read remains, so reusing the I/J VGPRs after
interpolating keeps host interpolation and the previous SPIR-V. Any other
program is translated with the barycentric path and marked as emulated:
its barycentrics become vec3 inputs (NoPerspective for the linear
weights) and its per-vertex parameters flat vec4[3] inputs, placed in
the free locations by LayoutBarycentricEmulation. The driver then
inserts a generated geometry stage between the vertex and fragment
shaders that copies the position and parameters, writes the one-hot
weights of each vertex and the three vertices' parameter values, so the
rasterizer interpolates the same weights the extension provides.
The stage needs geometryShader and a vertex shader before it;
tessellation, mesh and rect-list pipelines, other primitive types and
interfaces beyond 32 locations or the device's geometry limits throw.
CompiledShaderArtifact carries the emulation (shader disk cache format 21).
Hardware snaps window corners to 1/256 pixel before interpolating
(measured by yahya308 on an RTX 4070 Laptop and a Radeon 780M in #1547);
lavapipe snaps only for coverage (lp_rast.h FIXED_ORDER 8), so the test
accepts either reference per pixel at the original 1e-4 margin.
Sources:
- v_interp_p1_f32/v_interp_p2_f32/v_interp_mov_f32 semantics (P0,
P10, P20, I/J): AMD RDNA2 ISA, "Parameter Interpolation".
- BaryCoordKHR/BaryCoordNoPerspKHR weights and PerVertexKHR vertex order:
SPV_KHR_fragment_shader_barycentric, VK_KHR_fragment_shader_barycentric.
- Geometry shader triangle input order and provoking vertex: Vulkan
specification, "Primitive Topologies" and "Geometry Shading".
- Interpolation decorations decided by the fragment input: Vulkan
specification, "Interpolation Decorations".
- lavapipe interpolation: Mesa src/gallium/drivers/llvmpipe/lp_rast.h
(FIXED_ORDER 8), lp_setup_tri.c (subpixel_snap for coverage only),
lp_state_setup.c (init_args, plane setup from float positions).
A PIXEL_PIPE_STAT_DUMP event (EVENT_WRITE 0x39, Recorder::DumpSamples)
ended the render pass the previous draw left open: the dump ended the
batch-long occlusion query, copied its result with
vkCmdCopyQueryPoolResults and ran the fold kernel, none of which Vulkan
allows inside a render pass. VulkanDevice::DumpSamplesOnGpu also flushed
the queued store run over the dumped bytes first, which ended the pass
and recorded the run's barriers as well. A title that clears each query
slot with WRITE_DATA and brackets every bounding draw with a begin and an
end dump gets a render pass, two copy barriers and a label run per draw.
Samples are now counted in segments. A segment is one slot of a
per-batch occlusion query pool (128 slots, reset outside any pass when the
batch's first pass needs one). It begins at the first draw recorded after
a pass begins or after a dump, inside that pass's subpass, and ends at the
next dump or right before vkCmdEndRenderPass, so each query begins and
ends in one subpass. A pass whose pool has no free slot left is not
continued, so a new pool can be reset. The draw path that records a whole
pass of its own prepares a slot before its pass and ends its segment
before ending it.
A dump ends the open segment and is queued in the batch's store run with
the number of segments before it; it no longer ends the pass and no
longer flushes the queued stores. The run is recorded where queued stores
already are: at Submit, or earlier when a later command reads or writes
the queued bytes. Its recording copies the results of the segments the
dumps need (one vkCmdCopyQueryPoolResults per contiguous slot range) and
the dump list, then records the queue in groups: a group's stores, then
one dispatch of the SampleDumps kernel, which adds each dump's segments to
the running total and stores the 16 counters in queue order. A store over
bytes a dump of the current group writes starts a new group, so every
byte ends with the value of its last write in program order
(Recorder::QueuedWriteGroups). At Submit, segments no dump consumed are
folded the same way once 64 are pending.
No reader sees a dump later than before: a CPU reader of the dumped bytes
syncs the batch (the dump still notes them as a pending write), and Submit
records the run before the batch ends; a later label or store is queued
after the dump in the same run; GPU work that reads or writes the queued
bytes flushes the run first, as for queued label stores. No reader sees it
earlier either: the dump is recorded after every draw whose samples it
counts, and its segments end at the dump. Without the GPU sample counter
(no buffer device addresses) the batch-long query is kept as before.
Tests (agc_driver_recorder_tests, GPU, run by hand): QueuedWriteGroups
keeps the program order's bytes for 20,000 random store and dump
sequences; a queued dump does not land before its run is recorded, a
reader of other bytes leaves it queued and a reader of its bytes records
it; clear, dump and store orders over shared bytes; two dumps inside one
render pass keep it open (the next draw continues it) and split its three
draws' samples exactly with precise queries; a reader flushing a dump
inside a pass ends the pass; 160 dumps in one pass past a pool's 128
slots; 70 passes without a dump folded at Submit.
Main renders depth and stencil tests without a depth target when DB_Z_INFO and DB_STENCIL_INFO both have FORMAT INVALID (48ebf3195e): with no plane, the DB has nothing to test, and every test passes. The depth bounds test (DB_DEPTH_CONTROL bit 3) reads the same absent depth plane, but such a draw still threw "depth bounds without a depth surface". A title does this when a level loads.
Depth bounds now follow the same rule when both planes are absent. With an absent depth plane beside a present stencil plane, the bounds test still throws ("depth bounds without a depth plane"), as before.
Every direct memory allocation had a memfd of its own, so each one the
GPU used became its own udmabuf and its own imported buffer object. An
imported buffer object cannot be always valid in the GPU VM: amdgpu
validates every one of them at every command submission. PPSA14632
(LEGO Horizon Adventures) imports about 7000 allocations, 5800 of them
64 KiB, and each vkQueueSubmit took about 2.3 ms (5 s per 10 s, held
under the GPU mutex). A standalone test shows the cost follows the
count of imported dma-buf buffer objects, not their size or the count
of ordinary allocations.
- libkernel: on Linux all direct memory lives in one sparse memfd of
DIRECT_MEMORY_SIZE, at file offset = physical address
(PhysicalBacking keeps the file and its offset; SharedBacking returns
that offset). A range allocated again is zeroed through a temporary
mapping, as the old per-allocation memfd was.
- Driver: an import of shared direct memory that lies inside one
64 MiB chunk of the file binds its own VkBuffer into that chunk's
memory, one udmabuf import per chunk (HostImport::chunk holds it, so
it lives until the last buffer bound to it retires). Ranges that
cross a chunk or do not meet the buffer alignment are imported alone
as before.
On PPSA14632 vkQueueSubmit drops from about 2400 us to 46 us on
average, the GPU idles 36 ms instead of 172 ms per present, and
presents go from about 40 to 80-97 per 10 s.
Released direct memory stays committed in the file (see
TechnicalDebt.md): a live chunk import pins its pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS has no counterpart to userfaultfd write-protect or
MEM_WRITE_WATCH, so GuestWriteWatch had no backend there and the driver
re-read every guest buffer on each use.
The macOS backend write-protects watched pages. The first write to a
page faults, the SIGBUS/SIGSEGV handler records the page and restores
its protection, and Collect reports the recorded pages and protects
them again. A page is protected before its record is cleared, under the
lock the handler takes, so a write that lands in between is recorded.
The lock hands out turns in order: an unfair one starved writers under
Rosetta.
The protection is real, which adds three entry points:
- GuestWriteWatchBeginHostWrite/EndHostWrite lift it around a system
call that writes guest memory, which would otherwise fail with
EFAULT. GuestArena::HostWrite calls them, as it calls the Windows
equivalent.
- GuestWriteWatchProtectionChanged tells the watch the protection given
to watched pages by mprotect or a new mapping.
- GuestWriteWatchProtection reports that protection, since a watched
page is read-only between a collection and the next write.
Register keeps the recorded protection of a page it already watches:
read from the kernel it would be the watch's own read-only protection,
and the page would never be watched again.
The tests build without the prx libraries and run natively and through
Rosetta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain an immutable decoded program and structured control flow while
preparing the registration variants for one shader.
Match owned code, stage and the complete SWAPPC inputs before reuse.
Translate and plan each variant separately using its current inputs.
Keep cache keys and persisted artifact layouts unchanged.
Provide a cache-neutral off switch for controlled comparisons.
Cover identity, lifetime, failed replacements and variant equivalence.
The Windows runner has no Vulkan driver, so the VideoOut window tests skip there for the same reason; check_skips.py
only accepted agc* skips in that job.
Main's Linux job fails since #1028 and #1714: guest_video_out_resolution_status aborts when SDL's offscreen
driver has no Vulkan window, and check_skips.py rejects the skips of guest_video_out_wqhd_detection and the
flip tests because "no display" is not among the device limitations it accepts.
The resolution status test now skips with code 77 like its wqhd sibling, the flip tests print the same skip
reason, and check_skips.py accepts "no display" as a device limitation. ANYPS5_REQUIRE_DISPLAY still turns
the skip into a failure.
AI-assisted: yes. Full current-main project build passed. Seven focused Windows tests pass; four shader tests also pass in full and none optimization modes with actual Vulkan execution required. Updated Linux and title runs remain pending.
AI-assisted: yes. Full current-main project build passed. Seven focused Windows tests pass; four shader tests also pass in full and none optimization modes with actual Vulkan execution required. Updated Linux and title runs remain pending.
gmtime, localtime, mktime, strftime and asctime took and returned the
host std::tm. The guest's FreeBSD tm has tm_gmtoff and tm_zone after the
nine calendar fields, so on Windows, where std::tm is 36 bytes, a title
reading those fields read past the thread-local result, and gmtime_s left
them unwritten. Windows gmtime_s also accepts only 1969-12-31 12:00 to
3001-01-19 21:59 UTC, where the guest libc converts any time.
Convert through a GuestTm with the guest layout on both hosts. Compute
the UTC fields from the day count, so every time whose year fits an int
converts, and on Windows take the local offset and zone name from the
CRT. gmtime reports the zone as UTC like the guest libc. localtime and
mktime keep the CRT's range on Windows; recorded in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
As requested in review: a failed sceKernelGetModuleInfoFromAddr no longer
reports an empty answer. Adds a failure test (symbol interposition) and
extends the metadata test (EH record walk, terminator, no-header DSO).
Co-authored-by: jweidenh <38249793+jweidenh@users.noreply.github.com>
Co-Authored-By: Claude <noreply@anthropic.com>
A title that ships its own libc.prx unwinds C++ exceptions with that
libc's libunwind, which asks sceKernelGetModuleInfoForUnwind where each
frame's eh_frame is. On Linux the call always reported no tables, so
no C++ exception thrown in guest code could be caught. For the main
executable and .guest.prx images, report the PT_GNU_EH_FRAME tables
that sceKernelGetModuleInfoFromAddr finds, as Windows does from
.ehmeta. Other host images still report none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
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>
CB_COLOR_INFO format 6 (COLOR_10_11_11) with NUMBER_UNORM threw in
DecodeColorFormat: Vulkan has only the B10G11R11 float format. Such a
target now renders into an R32_UINT attachment in the layout of
format-30 images (unorm11 R and G in bits 0-21, unorm10 B in bits
22-31), so later image loads of the surface read the same words.
The target format is draw state, so the packing is a specialization of
the registered pixel shader: every float MRT export also computes the
packed word (EmitFormatStoreComponent) behind specialization constant
ExportPackingBase + target, and the specialized module turns a packed
target's output into a uint4 that ValidateShaders expects. The new
request field targetExportPacking is in the module key, the result memo,
the draw key and the serialized request (version 14). Blending, partial
RGB writes, UNORM16/SNORM16/UINT16 exports, DCC and ROUND_MODE throw.
agc_driver_pending_exact_surface writes mip 0 of a 2-mip 256x256 surface and then a 1-mip surface at the same address. The chain's mip 0 lies past the 1-mip surface's bytes, so both images stay pending, the chain first. A lookup of the 1-mip size must return the 1-mip image, and with only the chain pending it must still return the chain. On main the first lookup returns the chain.
Winsock fails send, sendto and sendmsg on a socket shut down for writing
with WSAESHUTDOWN, which neither Windows error map handled, so libkernel
and libSceNet reported EIO. FreeBSD reports EPIPE there, and the Linux
maps already translate the host's EPIPE to 32. Map WSAESHUTDOWN to 32
in both.
agc_driver_graphics registers a D32 and a D16 surface on a mock device and looks up views at their addresses. Views of the surface's own extent in R32F or R16, and T#s that read the raw depth bits, sample the depth plane; a view of another extent, a 2D array view of another extent, an R11G11B10 view, an R32 uint view without a depth layout and an R32F view of a D16 surface return null and take the ordinary texture path. On main the first of these throws in DepthSurface::Sampled.
Measured on a PS5 (firmware 13.60) through dlopen of
libSceVideoOut.sprx on a handle the payload opened.
sceVideoOutInitializeOutputOptions writes 0xFF to byte 2 of the 64-byte
options and returns INVALID_ADDRESS for a null pointer.
sceVideoOutIsOutputSupported checks the handle, then the reserved
arguments, then the mode, then the options. It never answers 0: a mode
the display lacks returns UNSUPPORTED_OUTPUT_MODE, and a value that is
not a mode returns 0x8029001E. Options must keep the initialized words,
except word 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fill takes either info struct, so sceKernelGetModuleInfo reports the
name and segments sceKernelGetModuleInfoFromAddr reports for the same
image, on Linux and Windows.
A sampler with FORCE_DEGAMMA (S# word 0 bit 20) threw at decode, so
every dispatch or draw sampling through one was skipped.
DecodeSamplerResource now records the bit, but accepts it only for a
caller that pairs the sampler with its images. The direct sampler
binding does, and RequireDegammaFormat checks each sampled image
against its paired samplers: only already sRGB formats (8, 8_8 and
8_8_8_8 SRGB, BC1, BC2, BC3 and BC7 SRGB) pass, decoded once, as
shadPS4 (PS4) models it. Other textures and unpaired decodes still
throw; gfx10 behaviour is not measured.
On Windows, nanosleep and this_thread::sleep_for/sleep_until go
through winpthreads, which ends a wait on a 15.6 ms GetTickCount64
step. Blocking sceAudioOutOutput and sceAudioOut2ContextPush calls
returned in bursts about every 16 ms, a blocking sceAudio3dPortPush
ran at about 68 % of real time, and sceAudioInInput without a device
woke anywhere from 1 ms to 31 ms apart. They now sleep with
PreciseSleepUs or the new PreciseSleepUntil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sigprocmask keeps the first word of the title's set as it is, where
FreeBSD keeps signal n at bit n - 1 (_SIG_BIT), but Dispatch tested bit
n. A title that blocked SIGTERM with a FreeBSD-built set (bit 14) was
not protected, and a set with bit 15 blocked SIGTERM instead of SIGURG.
Dispatch now tests bit n - 1; the stored mask and the set returned by
sigprocmask are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The AMD Windows driver returns 1 for a NaN operand when any float form
of ordered not-equal feeds a ballot on a 32-wide subgroup, which is how
e32 compares are read in wave32. FPOrdNotEqual32 is now emitted as an
unordered not-equal with both operands checked for NaN on their bits,
which gives the same result for every input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When every dst_sel of a sampled view selects constant 0 or 1, a sample
or gather returns that constant whatever the texel, filter, LOD or
coordinates. Such placeholder views can have a dimension the static
runtime image interface has no mode for (a 3D view under a 2D sample,
a 1D view under a 2D gather), and a 3D one paired with an unnormalized
sampler fails the unnormalized proof.
A direct sampled image whose accesses are all non-compare samples or
gathers gets three constant runtime modes (float, uint, sint). The mode
is selected when all four dst_sel are 0 or 1, unless the view is FMASK,
depth bits, converted or sRGB-decoded. In it the emitter returns each
channel's constant (the dmask channel's, in all four texels, for a
gather) from the image's swizzle specialization constants, and the
image is left unbound. ProveUnnormalized skips the mode after the
run-time-selected check, so table roots still throw.
Untracked host imports may retain pending image results on the GPU after an unchanged refresh. Assert that behavior and verify that an explicit FlushPending makes the results visible in guest memory, while watched memory still checks refresh write-back.
Fixes#2155
Keep serialized lookup scratch under the existing pipeline cache mutex and use the standard byte-string hash for indexing. Preserve exact key comparison and private handling of unidentified shaders. Add a real-device cache lifetime fixture and warmed lookup benchmark.
Written with AI assistance.
The machine check compared against 'x86-64' (uname reports 'x86_64'), so the
native execution was always skipped. With it enabled the fixture itself was
wrong: the initialiser's rip-relative call used the wrong displacement, and
the pending block was set on a different copy of the host library than the
one the module binds to.
Co-Authored-By: Claude <noreply@anthropic.com>
The DT_RELA tags were still guarded on the input module's relocation table, while the
new accessor relocations were appended to a copy of it. A module with an initialiser and
no imports therefore kept the argument stub but lost both relocations that fill its GOT
slots, so the stub called through a null pointer at load time.
Guard on the extended table instead, and drop the unused start parameter from the
displacement helper. The test now converts a module whose DT_RELASZ is zero and asserts
that the output carries both accessor imports and a 48-byte relocation table.
Add a hand-built guest module with an explicit DT_INIT that forwards rdi and rsi to a
host recorder without touching them and returns a known value. The test sets a known
argument count and argument pointer, loads the converted module, and asserts that the
initialiser received both values and that the stub forwarded its return value.
The host recorder also provides the two accessor symbols, so the test resolves them
without loading libkernel. The converted module must import both accessors, which is
what distinguishes the new behaviour from the previous stub that zeroed the arguments.
sceKernelLoadStartModule discarded its arguments, and the relinker's Linux guest writer
synthesized an initialisation trampoline that also cleared the argument registers before
jumping to the module entry point, so a converted module always reached its own start-up
code with args = 0 and argp = NULL.
On PPSA28997 two of the title's own modules validate that parameter block as their first
act and dereference it unconditionally: SaveData faults on `mov (%rsi),%eax` with rsi = 0
beside its `cmp $0x10` size checks, and PSN faults the same way at PSN+0x3ee7 with rbx = 0.
The Windows branch delivers the block, so identical code reports its parameter-version
error and continues instead of faulting.
sceKernelLoadStartModule now records the pair before dlopen and clears it afterwards, and
the emitted DT_INIT stub reads it through two accessors exported by libkernel. The
initialiser is called rather than tail-jumped so its return value can be reported through
*res as the PS5 API specifies. The state is thread_local, which makes the handoff per-call:
ld.so runs a module's initialiser on the thread that called dlopen. DT_FINI keeps a plain
call-through, so unloading neither reads stale arguments nor reports a result.
The accessor names use the _nid_no_patch suffix so the NID resolver leaves them alone and a
guest import can name them exactly.
Retain host backing views for emulator writes and completion labels. Validate full spans, preserve write versions and GPU ordering, and keep the fallback for other mappings. Test remaps, aliases, access checks, partial failures, tracking, view lifetime, and completion ordering.
Written with AI assistance.
Keep logical sample and skip counts across byte-only continuation buffers, assemble split superframes, and allow callbacks to refill, pause or reset the stream. Preserve decoded audio while waiting for input and reject truncated closed streams.
Validated with seven NGS2 tests, an ASan/UBSan streaming test build, and PPSA03747 repeatedly supplying continuation buffers during playback. Flag semantics follow the pinned KytyPS5 source documented in TechnicalDebt.md.
Written with AI assistance.
The named VRR functions hash to LibwuIonIBw and kP2L8t3j-aM already.
Their explicit aliases therefore create second PE exports with the same
patched NIDs and prevent Windows builds from completing.
Keep the named function exports and remove their redundant aliases.
The NID patcher's duplicate-export validation remains unchanged.
Scan version stamps within contiguous leaves. On Linux, collect nearby
pages within queue epochs only after confirming full watched coverage.
Keep the requested range on Windows. Select the range before collection
and do not retry a failed walk.
Test sparse-leaf boundaries, write histories, partial unwatches and epochs.
Fedora 44 Release tests pass with default, disabled and shared caching.
Isolated upstream comparison reduces the saved collect replay by 11-12%.
Windows was not tested.
AI-assisted: yes.
EVO-PLAYER (PPSA99039) v0.11.0 stops at sigaction once its earlier
imports resolve: it installs a crash handler for SIGSEGV, SIGBUS and
SIGABRT and a quit handler for SIGTERM, SIGINT and SIGHUP at start-up.
sigaction records the handler, flags and mask of signals 1 to 128 and
reports them back, with FreeBSD's EINVAL cases. signal shares the table
and records SA_RESTART, as FreeBSD's libc does. Plain handlers on the
signals signal already delivers run as signal handlers do; SA_SIGINFO
handlers and other signals are recorded only, and raise of such a
signal throws.
Assisted by Claude Code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwFnKjMxmwGDWiuYYf1v4D
The entry stubs called the title's entry with rbp left as the process
started it (0 on Linux) or as a loop counter left it (0 on Windows). The
title's start code pushes rbp, so the frame-pointer chain of its main
thread ended in a null frame pointer. Unreal Engine walks that chain to
capture call stacks (for an ensure, for example) and reads the return
address at [rbp + 8], so the walk read address 8 and crashed.
Both stubs now push a zero frame record (saved rbp 0, return address 0)
and point rbp at it before the call, so frame-pointer walks stop at the
entry. The Linux stub pushes it below the aligned stack pointer, keeping
the 16-byte alignment at the call. The Windows stub writes it into free
slots of its own frame at rsp+0x40.
PPSA15657 crashed in its stack walk at the title screen in about half of
its runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134RWCyiyydJBbfKz8g9Bwe
On Linux, shared direct memory is imported through /dev/udmabuf (#943).
Many distributions create it as root:kvm 0660, so a user outside the
kvm group cannot open it. The ranges are then copied instead of
imported, and GPU stores through FLAT/GLOBAL addresses to them fail
with "BDA access failed": DOOM PPSA99666's compute presenter falls back
to its CPU scaler. The only trace was "failed at open /dev/udmabuf
(-1000072003)", a Vulkan result that hides errno.
Print the open error once, with the fix for EACCES, and document the
requirement in USAGE.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With OOB_SELECT 0 and stride 0 every access is out of range now that the
buffer bounds follow OOB_SELECT, so the shader never read or wrote.
Co-Authored-By: Claude <noreply@anthropic.com>
Expected values measured on RDNA2 (gfx1035) with the HW oracle at IEEE=0
and IEEE=1, f64 denormals kept.
Co-Authored-By: Claude <noreply@anthropic.com>
A subnormal operand sent v_fma_f64 to a separate multiply and add, and a
subnormal result was rounded twice: once to 53 bits in the scaled domain
and again by the power-of-two rescale. Both differ from IEEE 754
fusedMultiplyAdd in the last bit.
Subnormal operands are now widened by 2^64 and enter the exact path with
their exponent lowered by 64. The exact path keeps the sign of the residual
between the 53-bit result and the exact sum; when the result lands on a
midpoint of the subnormal grid it is moved one ulp towards the exact sum
before the single final rounding. Normal results are rebuilt by setting
the exponent, so only a subnormal result rounds. v_div_fmas_f64 shares the
path.
Each Windows AIO request is an overlapped ReadFile/WriteFile at the
request's offset, which on a synchronous handle also moves the
descriptor's file pointer; on the console AIO transfers leave it alone.
The requests now go through the positioned path sceKernelPread and
sceKernelPwrite use (NativePositioned in Stdio.cpp, exposed as
NativePositioned_nid_no_patch), which restores the pointer afterwards.
Negative offsets still fail with EINVAL before the transfer.
GuestAio sets the offset to 2 before a write batch and checks it is
still 2 after. TechnicalDebt records that a concurrent read or write on
the same descriptor can see the moved pointer in between, as for the
pread family.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
Install lavapipe on Linux release builds and set ANYPS5_REQUIRE_VULKAN and GALLIUM_OVERRIDE_CPU_CAPS for ctest so GPU execution tests run instead of skipping. Scope SSL_CERT_FILE to Windows winlibs download and CMake configure steps only so Linux FFmpeg fetch is not broken. Leave release ctest as a single strict run.
A use of the packed ancillary VGPR other than a constant field extract (the word passed through a
select before its sample number is extracted) reads the word rebuilt from the sample number (bit 8)
and the render target index (bit 16). Extracts of other bits straight from the word stay refused, and
the refusal now names the uses.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Use the current registered range after protection splits and partial unmaps, including FIND_NEXT lookups. Add a regression covering extents, protection and surviving contents.
AI-assisted: yes
Keeps both new TechnicalDebt entries: this branch's note that a raise
can still reenter host code it interrupted, and #2107's sigaltstack note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
read, write, _read and _write on a guest socket descriptor went to the
host, which has no such descriptor, so sceKernelRead and sceKernelWrite
threw. They now reach the socket as recv and send without flags, with
the socket layer's errno (EAGAIN 35, EPIPE 32, EBADF 9), a zero-length
read returns 0 as dofileread does, and the two sceKernel forms return
the kernel error for the same cases. The host's SIGPIPE on EPIPE, a
write above INT_MAX bytes, readv, writev and the positioned forms are
recorded in TechnicalDebt.
OpenSSL's QUIC notifier signals its socketpair with write and read.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The status layout is shadPS4's OrbisGameLiveStreamingStatus2 (72
bytes). Live streaming is not supported, so the call reports that no
stream is on air, with an invalid user id; a null status throws.
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped at sceGameLiveStreamingGetCurrentStatus2.
Two different vertex programs that compute the same position give
bit-identical depth on the GPU, which a depth pre-pass followed by an
equal-or-greater redraw relies on. Without Invariant, a Vulkan driver may
compile the same position arithmetic differently in each shader (for
example contracting into an FMA in only one), so the redraw fails the
depth test.
Decorate the gl_PerVertex Position member and the mesh shader's Position
output Invariant, and check both in a new recompiler test.
AI-assisted: yes
msync follows FreeBSD 11's kern_msync and vm_map_sync over the guest's
mappings as sceKernelVirtualQuery reports them: the address is rounded
down to the page, a range that wraps and MS_ASYNC with MS_INVALIDATE are
EINVAL, an unmapped start or page is ENOMEM, and length 0 checks the
mapping that holds the address. The guest has no file mappings, so a
successful call has nothing to write back. MS_INVALIDATE on a mapped
range throws, since FreeBSD's EBUSY for mlocked pages needs lock state
that is not tracked.
EVO-PLAYER PPSA99039 v0.11.0 imports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The IME panel cannot be open while sceImeOpen is unimplemented, so the
query returns SCE_IME_ERROR_NOT_OPENED and leaves the output untouched,
like sceImeSetCaret, sceImeSetText and sceImeSetTextGeometry. As with
sceImeSetCaret, the closed panel is reported before a null output.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The guest's unsigned long is 64 bits. _Stoul was declared with the
host's unsigned long and returned std::strtoul, which are 32 bits on
Windows (LLP64): "4294967296" clamped to 4294967295 with ERANGE and
"-1" gave 0xffffffff, so a guest std::stoul threw out_of_range on values
the console accepts. It now returns std::uint64_t from std::strtoull,
as strtoul_nid_postfix and _Stoull already do; on Linux nothing changes.
The binary-prefix check stays in front of the host call.
Reported in #2424 by AchrafReyani; the same change was proposed in #2442
by SurajKhanna, whose two guest_math cases are taken here. Folded in at
MarcZX100's suggestion on #1459.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
shadPS4 returns success for an object id that was never reserved and the invalid-object error only for a released one; this library returns the error for both.
Co-Authored-By: Claude <noreply@anthropic.com>
Both exports are imported by titles that link libSceAudio3d; without them the loader fails with an unresolved import before the title runs. Following the offline model, object attributes are validated but produce no audio and PortFlush drops the frames queued by sceAudio3dPortAdvance.
Palworld's eboot needs EOSSDK-PS5-Shipping.debug_prx while the dump ships
prx/eossdk-ps5-shipping.prx. On Windows, compare module stems without
ASCII case, and let a name that already resolves to a guest image (exact
or case-folded filename, or soname) win over a stem alias.
Reported and diagnosed by @Rohan03x in #1830.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pan follows the PS4 firmware 11.00 libSceNgs2.sprx: the speaker layout
from the init angles, then each param's angle and distance into a
volume matrix for the voice. Every param is checked before anything is
written, and angles too large to wrap throw. The pan functions are
exported from the existing Voice.cpp export block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wcstol, wcstoll, wcstoul and wcstoull parse the ASCII prefix of the
guest's UTF-16 text with std::strtoll and std::strtoull, which glibc 2.38
and later bind to the C23 __isoc23_* functions: wcstol(u"0b101", &end, 0)
returned 5 with the end pointer after the whole string. They now check
StopAtBinaryPrefix_nid_no_patch first, like the narrow functions, and the
end pointer maps back through ParseAsciiPrefix as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C17 has no binary prefix: with base 0 or 2, strtol("0b101", &end, base)
reads the subject "0", returns 0 and leaves end at the 'b'. With glibc
2.38 or later, g++ (which defines _GNU_SOURCE) binds std::strtoll,
strtoull, strtoul, strtoimax and strtoumax to the C23 __isoc23_*
functions, which accept 0b/0B: the Linux exports returned 5 and
consumed the whole string. The Windows UCRT already follows C17, so the
same guest call gave different results on the two hosts.
strtol, strtoul, strtoll, strtoull, _Stoull, strtoimax, strtoumax and
_Stoul now return 0 with the end pointer after the '0' when, after
white space and an optional sign, the text starts with 0b or 0B and a
binary digit, in base 0 or 2. Everything else still goes to the host
function. The scanf family's %i has the same glibc behaviour; it is
recorded in TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A port without a matrix sent source channel n to destination channel n, so a
mono voice reached only the left channel of a stereo mix. The PS4 firmware
11.00 libSceNgs2.sprx mixes such ports, and ports whose matrix levels were
never set, through a fixed channel map instead: mono reaches both stereo
channels at -3 dB and the centre of 5.1 and 7.1, stereo folds to mono at
-3 dB per side, and wider sources fold down with 0.5 and -3 dB gains. The map
was measured for every source width into 1, 2, 6 and 8 channels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA22520 routes its title music through port matrix 0 without setting the
matrix levels, so the music rendered silent. A matrix without levels now
mixes like a port without a matrix, source channel n to destination channel
n. The console's initial levels are unknown; the assumption is recorded in
TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title's executable can name a bundled module differently from the file it
ships: Dying Light 2 needs EOSSDK-PS5-Shipping.debug_prx and ships
EOSSDK-PS5-Shipping.prx. Needed names were matched by exact filename, so the
module was skipped without an error and start-up failed to load it. Renaming
the file does not help either: the title later loads the module at run time
by its shipped name (/app0//EOSSDK-PS5-Shipping.prx), and dlopen looks for
that name's .guest.prx (#1794).
When no file has the needed name, a module whose name differs only in its
.prx, .sprx or .debug_prx extension now satisfies it, in the module
directories or elsewhere under the input. The output keeps the shipped file
name, and the needed name is added as another name of that guest module.
Two such candidates for one needed name are ambiguous, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD vsyslog logs the complaint with LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID passed as the priority (0x23), not LOG_ERR.
Co-Authored-By: Claude <noreply@anthropic.com>
ps5-opengl (PPSA99005) and ProsperoRadio (PPSA99001) import syslog
from libSceLibcInternal. Messages go to stderr with the priority, and
the format follows FreeBSD's vsyslog: %m expansion, masking of unknown
priority bits, LOG_USER as the default facility, errno preserved.
Assisted by Claude Code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title's descriptors are taken as never being terminals: a file or
pipe gets ENOTTY with the termios untouched, a socket gets what its
protocol answers a termios ioctl on FreeBSD (EADDRNOTAVAIL for AF_INET,
EOPNOTSUPP otherwise), and a closed or invalid descriptor EBADF.
tcsetattr gives EINVAL for an action other than TCSANOW, TCSADRAIN or
TCSAFLUSH, with or without TCSASOFT, before anything else, and EFAULT
for a null termios before the descriptor is checked; with TCSASOFT a
null termios throws, where FreeBSD's libc faults copying it. What the
console gives a title as descriptors 0 to 2 is unknown, as recorded in
TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The guest image reader and the module builder already produce a
platform-neutral GuestImage; --macos adds a third writer next to the ELF
and PE ones. The executable and every bundled module become x86-64 Mach-O
files (modules as dylibs in app0/sce_module) that run under Rosetta on
Apple silicon.
FreeBSD code reaches its thread pointer through %fs, which macOS cannot
set, so the writer rewrites each %fs access into a stub that reads the
image's thread pointer from a pthread key and allocates the TLS block on
a thread's first access. Dynamic TLS goes through the image's descriptor
instead of __tls_get_addr. Imports bind by NID to the prx libraries
beside the output, exports and TLS imports use the dynamic symbol names,
and the image's metadata lives in __APS5DATA,__meta.
--macos conflicts with --windows. The macOS runtime that consumes the
output (TLS allocation, libraries built for macOS) comes in separate
changes.
LibwuIonIBw registers video out event id 16, which the host never triggers because its VRR state does not change; kP2L8t3j-aM returns 0 without effect.
--registry only described the imports of the executable, so a title whose
own modules import system functions (PSN.prx, libc.prx and others) could
not be audited completely. With --registry the relinker now also writes
<output-stem>.<module>.registry.json for every converted guest module.
An entry is written for each relocation against an undefined symbol that
no other bundled module exports, the same test the module builder uses to
decide that a dependency is satisfied by a sibling. Entries carry nid,
library and targetOffset; the symbol name no longer has the # suffix by the
time the module is read, so the NID is the bare 11 characters. The
executable's registry is unchanged.
The test converts two modules where one is provided by its sibling, for
Linux and Windows output, covers the DT_RELA and DT_JMPREL tables, checks
that the same module imports the sibling's function once the sibling is
absent, and that nothing is written without --registry. Registered in
CTest as guest_registry.
Check out the repo when labeling open PRs so actions/labeler reads .github/labeler.yml locally instead of fetching it from the API once per pull request.
Keeps this branch's CONTEXT_FULL | CONTEXT_SEGMENTS read, context-first
order and bounded retry in RaiseOn, and adds #1478's ymm save fields to
both deliveries. The test keeps both phase sets; the vectors phase runs
after the continuing, selfing and looping phases and counts their
deliveries. The TechnicalDebt entry about ymm upper halves is dropped,
since #1478 preserves them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GuestOfflineImports still expected sceRudpGetStatus to throw not-implemented;
the implementation now returns RUDP_ERROR_NOT_INITIALIZED before init and
zeroes the status buffer after it.
v_dot2_f32_f16 rounded a.x * b.x + a.y * b.y + c once, computed with f64
TwoSums and round-to-odd. GeForce GPUs run f64 at 1/32 or 1/64 of the
f32 rate, which made it cost about 44 v_fma_f32 on an RTX 4060.
The f16 products are exact in f32, and the correctly rounded sum of
three f32 values only needs f32: two TwoSums, a round-to-odd sum of
their errors and a final add (Boldo and Melquiond, "Emulation of FMA and
Correctly Rounded Sums: Proved Algorithms Using Rounding to Odd",
IEEE TC 57(4), 2008), with round-to-odd emulated by stepping an even
rounded sum one ulp toward its error. With finite inputs, c zero or at
least 2^-100 and a result between 2^-100 and 2^127, every value involved
is zero or a multiple of 2^-123, so no driver flush can reach it. Other
inputs, and zero results, take the f64 lowering in a branch.
New test agc_driver_f32_dot2_rounding checks random, cancelling and tie
cases against an exact fixed-point sum, and the NaN, infinity and zero
rules.
GitHub refreshes merge_commit_sha only when the pull request's branch
changes, so a manual build could test the pull request against a main
from days earlier. Each job now checks out main as it is when the build
starts and merges the pull request head into it.
Titles that link the release module libScePsml.prx failed to start
because no host library had that name; PPSA25344 links it. libScePsml
and libScePsml_debug are now built from the same source.
scePsmlMfsrGetDispatchMfsrPacket1000, which PPSA25344 imports, is
added without an implementation.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the 16-bit RoundingModeRTE, DenormPreserve and
SignedZeroInfNanPreserve guarantees, convert each value to the nearest
half with OpFConvert, convert the half back to f32 and, when it is
further from zero than the source, step its bits one toward zero. That is
the toward-zero half for every finite value, including subnormal results
and 65504 for values from 65520 up; infinities and the quiet NaN with
the high payload bits are kept. Other devices keep the integer
conversion.
New test agc_driver_f16_pack_toward_zero checks every half, its f32
neighbours and the midpoints to the next half, with both signs.
The integer emulation of the round-to-nearest-even float-to-half
conversion is exact but costly: a compute shader with about 3600
conversions never finished compiling on NVIDIA. When the device enables
shaderFloat16 and guarantees shaderRoundingModeRTEFloat16,
shaderDenormPreserveFloat16 and shaderSignedZeroInfNanPreserveFloat16,
it advertises Float16, RoundingModeRTE and DenormPreserve, and the
recompiler emits OpFConvert under the 16-bit RoundingModeRTE,
DenormPreserve and SignedZeroInfNanPreserve execution modes instead.
NaNs keep the emulated quiet NaN with the high payload bits. Devices
without these guarantees keep the emulation.
Graphics validation accepts the RoundingModeRTE, DenormPreserve and
SignedZeroInfNanPreserve execution modes for 16-, 32- and 64-bit floats,
so vertex and pixel shaders with a native conversion still draw.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The smallest mip levels of a tiled surface are packed together into one
block, the mip tail. A size limit decides which level is the first to
go into the tail.
For SW_64KB_Z_X, AnyPS5 used the same limit as the other 64 KiB modes.
In addrlib, that is only the case on Navi10. On every other GFX10 chip,
including the PS5's gfx1013, addrlib halves the limit for 8 and 16 bpp
Z_X surfaces: the width at both sizes, and also the height at 8 bpp.
The setting behind this is dsMipmapHtileFix (see HwlConvertChipFamily
and ComputeSurfaceInfoMacroTiled in gfx10addrlib.cpp).
With the larger limit, the tail started too early. For example, a
256x256 R8 surface with 9 levels started its tail at mip 1 instead of
mip 2, and took 131072 bytes instead of 196608. Every level was then
read and written back at the wrong offset.
GetMipTailLayout now halves the limits the same way. Only the level
where the tail starts changes; the tail slot positions stay the same.
This follows addrlib's setting for gfx1013. The console's own layout
has not been measured.
Fixes#1590
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.
When f32 denormals are flushed, v_cmp_*_f32 compares flush(a) with
flush(b). With one operand a constant, the flush of the other operand
can go into the compare: a denormal and its flushed zero compare the same
way with a NaN, an infinity or a normal constant, and comparing flush(x)
with a zero is comparing x with +-2^-126 (flush(x) < 0 is x <= -2^-126,
flush(x) == 0 is |x| < 2^-126, and so on, the unordered forms alike).
emitFloatCompare reads both operands unflushed, finds a constant operand
after its abs/neg modifiers, mirrors the predicate when the constant is
first, and emits one compare. Two registers, f16 compares and modes that
keep f32 denormals are unchanged. New test agc_driver_f32_compare_constant
checks all 12 predicates against flush(a) op flush(b).
The translation of v_cvt_i32/u32/flr_i32/rpi_i32_f32 and v_cvt_i16/u16_f16
already maps NaN to 0 and clamps the value into range before
ConvertS32F32/ConvertU32F32, and the backend truncated it again and
repeated the NaN and range selects on the result. Those IR ops have no
other producers, so the backend now emits one OpConvertFToS or
OpConvertFToU, and EmitF32ToU32 and EmitTruncF32Value, which had no
other users, are removed.
The translation drops its FPTrunc32, as the conversions round toward
zero, and the selects whose constant the conversion of the clamped value
already gives. v_cvt_i32_f32 and v_cvt_u32_f32 read their source without
the f32 denormal flush: a denormal truncates to 0 either way.
The guest process is assumed to run as root. Under FreeBSD's
sys_seteuid and sys_setegid, id 0 is then accepted without a privilege
check and changes no credential, so both return 0. Any other id would
change the effective credential, which is not modelled, and throws.
Part of #1578.
When the embedded vertex fetch adds the base vertex or start instance
user SGPR to the index VGPR, the driver folds that SGPR into the draw's
first vertex and first instance. v5/v8 were gl_VertexIndex and
gl_InstanceIndex, which already include it, so the translated add
counted it twice, on direct draws and CPU-path indirect records alike.
The entry of a vertex or local program now subtracts the folded SGPR's
user data word from the builtin, which gives the zero-based indices the
hardware has. A record drawn in place on the GPU keeps the CPU's word,
so the fetch analyzer counts the SGPR as shared unless the fetch add is
the only reader of v5/v8 and the SGPR (no earlier access or EXEC write,
no carry, clamp, second add or other write of the SGPR); such records
take the CPU path. The fold moves into Driver::FoldDrawOffsets,
ClassifyIndirectDraw becomes public for the test, and the env switches
that skipped the fold go.
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.
gh label create exits non-zero for a label that already exists, and the
step runs with bash -e, so the first existing unforced label stopped the
job. Skip labels that already exist, keeping hand-set colors.
Fixes#2320
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelAprSubmitCommandBuffer rejected a buffer object with EINVAL
unless sceAmprAprCommandBufferConstructor had tagged it. The tag is
AnyPS5's own: in the retail layout the type word only carries state
bits and the constructors zero the object, so a zero-filled object
bound with SetBuffer is a valid APR buffer.
CRI's file system in Sonic Origins (PPSA05324) lays out its
AprCommandBuffer objects in its own zeroed work memory and never calls
the constructors. Its first read submit failed with 0x80020016, and the
CRI file system stopped there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceNpEAAccessInitialize and sceNpEAAccessTerminate resolve the NIDs
7CxI50-xlCk and pMxXhNozUX8, which shadPS4 names in np_partner.cpp. The
stubs still throw; only the names and no-argument signatures change.
FreeBSD's unlink ignores the file's own permission bits, so remove()
succeeds on a file the guest made read-only with chmod(0400). On Windows
the guest chmod sets FILE_ATTRIBUTE_READONLY, and DeleteFileW then fails
with ERROR_ACCESS_DENIED, which remove() reported as EACCES. Clear the
attribute and retry, restoring it if the retry still fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use INFINITY and NAN from <cmath> instead of a new <limits> include, and
list the TechnicalDebt entry beside the other NGS2 voice entries, so the
change merges cleanly with the pan, channel map and reverb changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The template allocated a fixed 4 KiB of LDS, so kernels with immediate
offsets past it could not run. --lds (Python lds) sets the group segment
size in bytes, up to 64 KiB, with 4096 as the default.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The guest passes and expects UTF-8 names, but on Windows opendir and
readdir used MinGW's narrow dirent API, which works in the ANSI code
page: opendir failed for a directory whose name is outside the code page,
and readdir returned names as ANSI bytes, with "?" for characters the code
page lacks. Use _wopendir and _wreaddir there and convert each name to
UTF-8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reverb_size other than the default was accepted and ignored, and below
about 7.5 kHz an allpass length went negative and wrapped to a 16 GB buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WindowsMappings::Collect resets each part of the range as it walks it:
it marks shared views seen and resets private pages' write-watch bits.
When it then met a NOACCESS view or memory that is not committed
private memory, it returned false with the earlier parts already reset,
so their writes were lost to the caller's fallback. With reset, the
whole range is now checked first and nothing is reset when it fails.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parse legacy decimal, octal and hexadecimal forms before host lookup. Preserve FreeBSD shorthand, whitespace and 64-bit accumulation, clear the SDK resolver error on numeric success, and add regression coverage.
getcwd(NULL, size) returned memory from the guest heap directly, while
the caller frees it with free(), which goes to the application heap.
With a title's malloc replacement registered, that free reached the
replacement with a pointer it never returned. Allocate from the
application heap like strdup, and report ENOMEM when it returns null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the missing EVO-PLAYER import reported in #1578. Convert native host entries into the guest ABI with owned per-thread storage, and use the SDK network error location for failures.
Check numeric address bytes, guest hostent fields, per-thread result lifetime, and the SDK resolver error location. The check currently fails to link because the reported export is missing.
EmitClassifyF32Bits found a NaN from the exponent and mantissa fields
separately. The magnitude settles it: a NaN is above 0x7f800000 and a
zero is 0.
min and max ended with two NaN selects. An ordered compare is false when
either operand is a NaN, so the ordered pick already returns rhs for a
NaN lhs; only the select that returns lhs for a NaN rhs, also when both
are NaNs, is needed.
med3 is the minimum of the three when any operand is a NaN and the
median otherwise. The median is used only without NaNs, so it is now
built from compares and the zero-sign rule alone, and the minimum reuses
the operands' NaN tests instead of classifying intermediate values.
MIN_LOD_WARN, MIP_STATS_COUNTER_EN and MIP_STATS_COUNTER_ID are ignored by the texture decoder, but the cache
key held the raw T# words. A title that rotates the counter fields per use got a cache entry per variant of the
same texture, and the extra entries evicted live ones under the budget.
sceAgcCreatePrimState only packs registers, but it now also resolves the
graphics ABI ahead of time, and that resolve throws when the primitive
type is a patch list without HS_EN or a hull stage with another type.
No draw can use such a state, so leave it to draw time and return the
registers. Bit 2 of VGT_SHADER_STAGES_EN is HS_EN, not an NGG bit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A flip still queued or in presentation when the title closes its port is discarded, as on hardware, rather than stored as a driver failure. A closed port no longer terminates the presentation thread nor surfaces from Shutdown. The lifetime test now expects cancellation and the reopen mode is registered in ctest.
#2083 chooses the NaN result of the f32 fma family on the bits: the first
NaN source, quieted in IEEE mode, with the addend skipped when the
product is infinity times zero, and 0xffc00000 otherwise. The selection
and the infinity-times-zero test ran for every fma, but a NaN source
always gives a NaN result, so for any other result they return it
unchanged.
FPNanResultFma32 carries the result and the three sources, with the IEEE
mode as a flag. The SPIR-V emitter makes the selection, including the
infinity-times-zero test, in a branch taken only for a NaN result, and
the SRT evaluator computes it exactly. Add, sub and mul keep the inline
selects: with two sources a branch costs more than it saves.
winpthreads initializes a PTHREAD_RWLOCK_INITIALIZER lock on its first
use and fails a concurrent first lock with EINVAL. std::shared_timed_mutex
ignores that result in release builds, so two guest threads could both
hold a fresh guest rwlock.
Lock and unlock the mutex once when the guest rwlock is created, before
another thread can reach it, and record the remaining host exposure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FreeBSD's unlink(2) reports EPERM for a directory, but the host call
returns EACCES on Windows and EISDIR on Linux, so the guest saw a
different error on each host. Check for a directory, not following a
symbolic link, before unlinking.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelMunlock and the POSIX munlock unlock the page-rounded range with
FreeBSD's results: 0 for a mapped range whether or not it is locked, EINVAL
for a range that wraps around zero, ENOMEM for an unmapped range. On Linux
the host munlock gives them. On Windows a reserved placeholder counts as
mapped only while the guest reservation or protection registry covers it,
and a region VirtualUnlock refuses as not fully locked is unlocked page by
page, as VirtualUnlock is all-or-nothing. sceKernelMlock shares the range
rounding.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
The sampled texture cache let go of its least recently used entry even
when a build, a recorded batch or a cached resource set still held that
texture, which frees no memory and makes the next lookup create a
second copy. A frame that samples more textures than the cache holds
also missed on all of them every frame.
Eviction passes over an entry whose texture is held elsewhere, or a
view of a cached storage image, up to a quarter over the budget, and
rescans for released textures about once a second. While the least
recently used entry was used in this frame or the previous one, a
texture's first use in the frame is returned uncached and only a second
use evicts, so the next frame finds the cached textures again, even
after an early miss; a new set replaces the old one on its second
frame. APS5_PROFILE_DRAW prints the cache's counts every 10 s.
The device-local tier of BufferPool only reused a buffer of the exact
size and usage it was asked for. Partial storage-image detiles and
retiles ask for a new size and the other transfer direction almost
every time, so the tier sat at its budget and missed and evicted
dozens of buffers per frame, and the vkFreeMemory calls on the release
thread held up submits and allocations on the recording threads.
Device-tier requests of 1 MiB and more now round up to an eighth of
their octave, the tier's buffers are created for storage and both
transfer directions, and a request takes the smallest retained buffer
from its own class up to twice its size. Host tiers, the budget and
eviction are unchanged. agc_buffer_pool covers the classes, the fit
and its bound, reuse only after release, the budget and the unchanged
lists.
An address-based build whose heap mirror the device refused, or one past
APS5_HEAP_MIRROR_MIB, stopped the process inside acquireMirror. PPSA26344
hit it at 478 s with 6.8 GiB of heap mirrors beside the host imports.
- a mirror that would go past the budget first sweeps the mirrors whose
registered range is gone, before its allocation; if it still does not
fit, the build throws "past the budget"
- the mirror is allocated once: a refusal throws with the range, the
Vulkan result and the mirror totals, so the draw or dispatch fails like
any other allocation; nothing is registered for the refused range
- the BDA resource tests run with a 4 MiB mirror budget and a mock memory
limit: a mirror past the budget is made in the swept mirror's memory,
and a refused one fails after a single allocation attempt
Every BDA dword read site still emitted in the entry function the add
of its first dword address with the overflow fault, a probe_bda_pointer
call, the wide loads, and one read_bda_dword_bytes call per extracted
dword. NVIDIA's pipeline compile time grows with every call left in the
entry function, so large compute programs with a few hundred such reads
spend 47-77 s in vkCreateComputePipelines.
A read site is now one call to a DontInline read_bda_span function, in
a stop and a coherent variant, taking the address, the immediate offset,
the mask of extracted dwords and the pc, which does the same steps in
the same order. Fault reasons, addresses, pcs, Volatile, the stop
behaviour and the APS5_BDA_BYTE_READS path are unchanged.
An address-based build maps every readable registered range: through a
host import when one can be made, else through a heap mirror, a
host-visible copy of the range. A range mirrored while the import budget
was full kept its mirror after a later pass imported it, until the guest
freed the range. The pass then read the range through the import, and the
mirror only held host-visible memory and a second copy of the bytes.
The pass now releases the heap mirror of every range it serves through
an import. Recorded work that bound the mirror keeps it until it
completes. If the import goes away while the range stays registered, the
next pass makes a new mirror from the guest bytes. The [buffers] image
mirrors profile line counts the released mirrors.
A build served by the cached address space walked every region of the
space for its mirrors, sorted the heap mirrors, compared each heap
mirror's blocks with the write tracker under its own lock and swept the
whole mirror registry. In an offline replay with 37,000 heap mirrors
that took about 10 ms per build, every build.
The space now keeps its writable mirrors, its sorted heap mirrors and
their adjacent runs from when it is published. Each run is still
flushed and collected; a run with no block stamped since the generation
its last complete check collected skips the per-mirror compare, and that
generation is stored only once the build's blocks were copied. The
registry sweep is skipped while every live mirror's range is held by the
cached address space's lease and no mirror was added, replaced or
dropped since.
LoadLibraryEx with an unresolved import shows the loader's "Entry Point
Not Found" message box unless SEM_FAILCRITICALERRORS is set, so
patched_libraries waited on the dialog until ctest's timeout instead of
printing the failing libraries. Set it at start, as GuestHeapRounding does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The POSIX form of sceKernelMlock: 0 on success, -1 with errno (EINVAL,
ENOMEM, EPERM, EAGAIN) where the sce form returns the kernel error, as
munmap and mprotect do beside it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Functions referenced only through a REX.W RIP-relative LEA (constructors,
callbacks, thread entries called indirectly) were not reached by code
analysis, so their guest TLS accesses stayed unpatched on Windows.
Add LEA targets inside executable code as roots, both in the instruction
walk and when following the control-flow graph of unreached code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pr_overlap.py took every added line with APS5_VABI before a name and a
parenthesis as an export, so a declaration such as
extern "C" int* APS5_VABI __error_nid_postfix(); made each pull request
that adds it "implement the same function" as all the others. A line
that ends in a semicolon is a declaration and is skipped; every
definition's first line in the tree ends in a brace, a parenthesis or
a comma.
Registers pr_overlap_exports.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Releasing direct memory left the virtual ranges that mapped it registered,
so sceKernelVirtualQuery reported them as flexible memory at offset 0 and
titles walking a range with FIND_NEXT released physical page 0.
Collect every view of the released pages before freeing them and unmap
them through UnmapRegistered, as shadPS4's MemoryManager::Free does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nid_patcher turns the _nid_postfix names of a library into NIDs for ELF
and PE. A library built on macOS is a Mach-O dylib, whose exports are in
a dyld export trie and whose imports are in the chained fixups, so it
gets its own patcher.
- The export trie is read, every exported C name (without Mach-O's
leading underscore) is resolved to its NID, and the trie is rebuilt the
way ld64 lays it out: the offsets are uleb128, so the layout is repeated
until it is stable. A trie that no longer fits is appended to
__LINKEDIT, which has to end the file, and a signed image is refused.
- Weak definitions that are not marked _nid_postfix or _nid_no_patch keep
their names: dyld coalesces those across images by name.
- In the chained imports, names bound to a .prx dylib that are guest
facing (a _nid_postfix name or an sce prefix) are patched in place; a
name that would grow, or that shares storage with another import, is an
error. Images that use bind opcodes instead of chained fixups are
refused.
- ExportExclusions reads the exports of a reference Mach-O, so the
exclusion list works for libraries built on macOS as well.
The GUI missing-symbol and missing-module fixtures were started with
ShellExecuteExW. A process the shell starts gets the default error mode,
so the status the entry stub raises reaches Windows Error Reporting as an
APPCRASH. Collecting each report took about 2 s, sometimes over 10 s,
and under a parallel ctest run it could pass the 30 s wait, failing
windows_import_modules with "ShellExecuteExW child did not exit" while
WerFault still held the fixture's files.
The fixtures now start through CreateProcessW with STARTF_USESTDHANDLES
and no standard handles, so the stub's diagnostic still has nowhere to
write, and with the test's error mode, which keeps the report away.
The offscreen SDL driver cannot create a Vulkan window on Windows since
the VideoOut window uses SDL_WINDOW_VULKAN, so the tests that open a port
now exit 77 (skipped) like the other display tests, unless
ANYPS5_REQUIRE_DISPLAY is set. The tests also write app0/sce_sys/param.json,
which AppMetadata now requires.
presentLoop called AgcDriverShutdown outside its try block, so a stored
worker failure rethrown there terminated the process. Store it in
flipQueue->failure so VideoOutDriver::Shutdown rethrows it after the join.
Register the lifetime, decode and controls modes of video_out_flip_tests
in ctest.
MinGW windres passes -I paths to cc1 with backslash-escaped spaces and
parentheses, so compiling 3rdparty/freetype/src/base/ftver.rc fails when
the checkout or build path contains them. FreeType is linked as a static
library and the resource object is never pulled into an output, so it is
removed from the target's sources on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The built Windows libraries import libgcc_s_seh-1.dll, libstdc++-6.dll and
libwinpthread-1.dll, and the entry stub loads them without searching PATH.
The release archives ship the DLLs in libs/, but the runtime layout only
told self-builders to copy the .prx files, which ends in error 126.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rect-list control and evaluation shaders put their fault buffer one
past the highest vertex or fragment binding. Bindings are grouped per
stage (RuntimeAbi::BindingNumber), so that number could fall inside
another stage's group and collide with one of its bindings, and the
draw failed with a duplicate binding. The fault buffer now uses the
TessellationControl FaultBuffer binding.
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped at its first rect-list draw with "duplicate shader binding".
v_not_b32_sdwa takes the same dst_sel and dst_unused operands as the
other 32-bit VOP1 SDWA forms (RDNA2 ISA 6.3.1 "SDWA"; LLVM
VOP1Instructions.td gives V_NOT_B32 a VOP1_SDWA encoding with dst_sel
and dst_unused). The rule now allows byte and word destinations with
every unused mode, which the existing SDWA writeback implements; the
execution test checks WORD_0 with UNUSED_PRESERVE.
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped at a v_not_b32_sdwa with dst_sel:WORD_0 dst_unused:UNUSED_PRESERVE.
GFX10 image format 2 (8_SNORM, Mesa gfx10_format_table) resolves to
VK_FORMAT_R8_SNORM and is marked sampleable in the recompiler's format
table.
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped at a compute shader sampling an 8_SNORM image.
Debug markers are NOP payloads the command processor skips, so a pop
with no marker pushed (a title that pops a marker it pushed in an
earlier command buffer) has no effect on the GPU; it no longer throws.
Uncharted: Legacy of Thieves Collection (PPSA05684) pops one marker more than it pushes and stopped with "marker stack underflow".
A viewport whose X or Y scale is zero maps every vertex onto one line,
so triangle, strip, fan and rect-list draws through it cover no pixel.
Such draws, which titles use to skip work without rebuilding a command
buffer, get a valid 1x1 Vulkan viewport and an empty scissor instead of
throwing. Points, lines and other negative or non-finite scales still
throw.
Uncharted: Legacy of Thieves Collection (PPSA05684) stopped at such a draw with "unsupported viewport transform: scale=(0, -0, 1)".
libSceVdecsw is the software H.264 decoder interface PPSA05684 uses for
its movies. Decoder creation, compute queues, memory queries, decode
input and output, AVC picture info, reset and finalize are implemented
with the layouts of shadPS4's vdecsw.h, checked against the title's
movie decoder, and decode through libSceVideodec2. The H.264 stream the
Videodec2 test uses moves to a shared fixture, and a guest test decodes
it through Vdecsw.
Uncharted: Legacy of Thieves Collection (PPSA05684) plays its movies through libSceVdecsw and stopped at sceVdecswQueryComputeMemoryInfo.
The header-block PT_LOAD has the highest address but was the first
loadable entry in the table. Linux before 5.18 sizes the ET_DYN mapping
from the first and last PT_LOAD entries in table order, so the output
failed to exec with SIGSEGV. Write PT_PHDR and PT_INTERP first, sort the
kept PT_LOAD entries by address, and place the extra and header blocks
after them.
libstdc++ already defines NOMINMAX on MinGW, so the unguarded define
warned on every Windows build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The geometry pass adds the mesh capability and extension to the device
target but kept its SPIR-V version. On a device without mesh shaders
that is 1.3, and the mesh module was rejected as exceeding the target.
VulkanDevice targets 1.4 whenever mesh shading is enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The FMASK identity read selected a uvec4 with a scalar bool condition,
which needs SPIR-V 1.4. Devices without mesh shaders target 1.3, so
every image test that reaches the path failed validation with
ANYPS5_ENABLE_SPIRV_TOOLS (#2138), as in #574.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux job reports "100% tests passed out of 371" with 20 agc_driver execution tests skipped
(llvmpipe reports subgroup size 8, so the tests that need a wider wave exit 77), and the Windows
job reports "100% tests passed, 0 tests failed out of 368" with 143 skipped - the runner has no
Vulkan driver and ANYPS5_REQUIRE_VULKAN is not set there, so 143 of the 147 GPU execution tests
silently do not run. ctest counts a skip as a pass, so neither job notices when the set grows.
Add tools/check_skips.py. It reads the file ctest writes with --output-junit and fails when a test
skips for a reason that is not expected:
- linux: a skip is expected only when the test's own output reports that the device's subgroups
are too narrow for it, so adding a wave32 test does not need the rule edited;
- windows: any agc_driver test, since the runner has no driver for them, while a skip of anything
else fails.
Both jobs now pass --output-junit and run the check after ctest. The JUnit file needs no
redirection, so the console output stays visible and no pipe can hide a failing ctest.
Verified here with the CI command and the CI device (lavapipe, subgroup 8): the real run reports
20 skipped, all expected; a 21st wave32 test skipping for the same reason passes; a skip whose
reason is something else exits 1; a file with 143 agc_driver skips passes with --job windows and
one non-agc skip in it exits 1.
On a host without IPv6, socket(AF_INET6) fails with EAFNOSUPPORT, so
guest_sce_net aborted at its first IPv6 socket and never ran the TCP,
UDP, epoll, sendmsg/recvmsg, resolver and NetCtl checks after it.
When sceNetSocket reports guest EAFNOSUPPORT (47) for the IPv6 socket,
skip those socket checks, run the rest of the test, and exit with 77,
which ctest reports as skipped. Hosts with IPv6 run every check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
llvmpipe builds the high dword of the shader clock with a left shift by
32 (emit_clock in Mesa's lp_bld_nir_soa.c), so it is always 0 and
s_memtime and s_memrealtime wrap every 2^32 ns. A pass of
agc_driver_shader_clock whose two reads straddle a wrap failed with
"memtime: lane N went back from 4294921902 to 3823"; dispatching right
before the boundary fails every lane of that pass.
On llvmpipe compare the low dwords modulo 2^32, which stays correct if
Mesa fixes the shift. Other devices keep the 64-bit comparison.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
When a queue worker failed, it published the failure and woke the idle
waiters before failing the flips of the submission it was executing.
A thread returning from AgcDriverWaitIdle with that failure could still
see one of those flips neither completed nor failed, which made
agc_driver_flip fail with "failed flip was completed successfully" in
2 of 240 runs under four-way parallel load on lavapipe.
Fail the executing submission's flips first, as ReportFailure already
does for the pending submissions it drops before it notifies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ha6Tsy62AFUyp9a5Mf7vjA
PS5 titles pass a 0x230-byte SceAvPlayerInitDataEx: auto start is the
byte after debug_level at 0x74, nine 0x30-byte thread parameter blocks
fill 0x78-0x228 and the output video frame buffer count is at 0x228.
sceAvPlayerInitEx read both fields at their PS4 offsets: auto start at
0xa8 is the video decoder thread priority and the buffer count at 0xa4
lies in the audio decoder block. PPSA25063 (Poppy Playtime: Chapter 4)
sets every thread priority to 700, so its intro video started on its
own and stopped at once; it writes auto start 0 and starts playback
itself.
With this_size 0x230 or more both fields are read at the PS5 offsets;
smaller structures are read as before. The layout follows KytyPS5,
prosper, SharpProspero and sharpemu and is recorded in TechnicalDebt.
guest_avplayer builds a 0x230-byte structure with auto start 0 and
nonzero thread parameters, then with auto start 1, and checks when
playback starts and how many video buffers are allocated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An address-based build maps each imported registered range through its
host import. UploadPrepare merges a descriptor range or snapshot
overlapping such a range into the range's region, and the region kept
the import even when the merge took it past the import's end, as a V#
crossing into the next registered range does. Descriptor then bound the
import's buffer beyond its size, and the BDA table mapped the whole
region, the next range included, through the import's device address.
A merge that extends a region past its end now drops the import. The
region then takes the import lookup and, with no import covering it,
the copy path, as a range crossing an image mirror's end already does.
Like every copied range, it is read-only to pointer stores.
sceVideoOutAddPreVblankStartEvent threw "physical pre-vblank timing is
not implemented", so titles that pace on it stopped at startup. It now
registers the event like the vblank event does, and each emulated vblank
tick triggers the pre-vblank start events just before the vblank events,
with the pre-vblank count as data. The console's lead time is unknown.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A title that never writes SPI_PS_INPUT_CNTL_0 relies on its clear-state
value 0, but the register was absent from the initial context, so every
draw of a pixel shader with interpolants was rejected with "missing
register at DWORD 0x191". All 32 SPI_PS_INPUT_CNTL registers now start at
0, as in the GFX10 clear state, both initially and after CLEAR_STATE.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v_fma_f16, v_fmac_f16, v_fmamk_f16, v_fmaak_f16, v_pk_fma_f16 and
v_pk_fmac_f16 rounded an f32 fused multiply-add to f16, rounding twice.
The product of two f16 values is exact in f32, so the sum is now rounded
to odd in f32 with an exact error term and then rounded to nearest even
in f16, which rounds the exact result once.
computePostDominators iterates its sets to a fixed point, visiting the
blocks in id order. Post-dominance flows from a block's successors back to
it, and successors mostly have higher ids, so each pass moved the sets back
by about one block: a long program needed about as many passes as it has
blocks, each costing O(blocks^2). recomputeAnalyses runs it after every
split of splitSharedMergeBlocks, up to four times per block.
Visit the blocks in post-order of the CFG (successors first), then any the
entry does not reach. The sets start from every block and only shrink, so
the iteration still converges to the same greatest fixed point, now in a
few passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CB_COLOR_ATTRIB3.RESOURCE_TYPE 0 is a 1D surface (AddrResourceType
ADDR_RSRC_TEX_1D in addrlib). On GFX10, addrlib allows a 1D surface the
linear and render swizzle modes (Gfx10Rsrc1dSwModeMask) and picks its
swizzle pattern the same way as for a 2D surface (GetSwizzlePatternInfo
only tells 3D apart). So a 1D color target one row high is laid out
exactly like a 2D target one row tall, and is decoded as one.
A 1D target with a standard swizzle mode is rejected as invalid; one
with more than one row is not modeled and is rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With f32 denormals flushed, every f32 multiply and fma checks whether the
exact product is below 2^-126 (#1850): two exponent extracts, a 32-bit
significand multiply-high and a chain of compares and selects. When a
factor is a literal or inline constant, its exponent settles the check
during translation:
- a factor that is zero (after the input flush), infinite, NaN or at least
1 in magnitude never gives a tiny product, so the check is not emitted;
- a power-of-two factor never carries out of the significand product, so
the multiply-high is not emitted.
Constant sources are folded with their abs, neg and denormal flush, so the
factor reaches the check as a constant.
With f32 denormals flushed, every f32 result is flushed when it is
written and every f32 source when it is read, so a source computed by an
earlier flushing op is flushed twice. A flush returns any value that is
not a denormal unchanged, and the driver can't tell that the first flush
already removed them.
DenormalFlushEliminator replaces a flush whose source can't be a
denormal with the source: a flush, a normal or zero constant, a sign
change, absolute value, clamp or bit cast of such a value, an integer or
f16 converted to f32, and a select or phi of such values.
sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats and
sceNetGetMemoryPoolStats threw. PPSA16064 (Minecraft Dungeons II,
Unreal Engine) queries all three while its HTTP module starts, and
aborted there.
Each library now records the pool size given at sceHttp2Init,
sceSslInit or sceNetPoolCreate and reports it with a peak and current
use of 0: the offline Http2 and Ssl models and the host-socket Net
library allocate nothing from these pools. The layouts follow the PS4
SDK's *MemoryPoolStats structures. Net returns EBADF for an unknown
pool and EINVAL for a null output, like its other pool calls; Http2 and
Ssl throw for both, as their error codes are unknown. Terminating a
context or destroying a pool forgets its size.
sceHttp2SetResolveRetry, which the title calls next, accepts and
ignores its value like the other Http2 settings. The four Http2
WebSocket exports the title imports are added as unimplemented.
guest_http2, guest_ssl and guest_sce_net check the reported sizes;
guest_sce_net also checks the null-output and unknown-pool errors.
A program's entry EXEC is the ballot of !gl_HelperInvocation in a pixel program and the ballot of true in vertex, local and tessellation programs (emitEntryPrologue). The half-wave reduction matcher only accepted the pixel form, so a wave64 vertex program that scans keys selected under its EXEC and reads lane 63 threw that the lane is outside the 32-lane host subgroup.
EntryLaneWalk now also accepts a ballot of true as within the entry EXEC for those stages. Every invocation is live at their entry, and a later ballot of true holds only invocations that are still active. Pixel programs keep the !gl_HelperInvocation form, because a ballot of true there includes helper invocations. Compute and mesh programs keep their own form too, because their entry EXEC can bound the local invocation index.
A draw's guest buffer region inside a host import at an offset off
minStorageBufferOffsetAlignment was copied per build (a new buffer, an
input snapshot, a vkCmdCopyBuffer that ends the open render pass), and
DirectRegions refused such builds as templates.
Descriptor already binds any DWORD-aligned view from the aligned offset
below it and passes the remainder through the shader's memory offset
slot. A draw build that is not address-based and whose every guest buffer
element has such a slot (a push constant byte, or a shader data byte
inside its data buffer) now binds a region whose offset in its import is
DWORD aligned in place, like an aligned one: its read-only views are
snapshotted per draw by PrepareDrawBindings at address - adjustment, the
import serial keys the template, and Revalidate proves it per use.
Dispatches keep the copy (they read in-place inputs at execution, not at
record).
agc_driver_recorder_tests misalignedRegionTests: a misaligned read-only
draw region is read in place, the build is reusable, the push constant
carries the adjustment, and the per-draw snapshot starts at the aligned
offset below the view.
CachedPipeline keeps at most 256 pipelines. A level with a larger working
set thrashes the LRU: every miss creates a new VkPipeline (modules,
layout, render pass and a driver compile from the pipeline cache). The
bound is now 1024. A cached pipeline is a pure function of its key, so
the bound changes only memory and time.
The half-wave reduction read (v_readlane_b32 of lane 31 or 63 after a DPP row scan) is exact on a narrower host subgroup only when the scan's keys are the identity outside the entry EXEC. EntryLaneWalk::ZeroBit now also proves that through a constant false, a SelectU1 whose two values both prove it, and a bool Phi whose inputs all do. A mask written inside a divergent branch reaches the key select in that form, so such a scan threw that lane 63 is outside the 32-lane host subgroup.
A draw with DB_RENDER_CONTROL.RESUMMARIZE_ENABLE recomputes the HTILE summary of the bound depth surface from its depth values. HTILE is not modeled, so such a draw with depth and stencil tests off and no color writes changes no modeled state. It now passes the maintenance check and draws nothing, instead of throwing. Resummarize draws with depth, stencil or color work, and copy and decompress draws, still throw.
ctest loads the libraries from libs/unpatched, so an import that nid_patcher
renames out of another library's exports only shows when a title loads the
patched prx. patched_libraries loads each patched .prx with LoadLibraryEx on
Windows and dlopen(RTLD_NOW) on Linux, and depends on the libs target so the
patched files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unified and service entitlement info list requests and polls threw.
They now return SCE_NP_ERROR_SIGNED_OUT, like the consume requests in
the same library. Their signatures are unknown, so they stay declared
without parameters.
PPSA15657 calls the unified request during start-up and continues past
the error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134RWCyiyydJBbfKz8g9Bwe
Sackboy: A Big Adventure (PPSA01289) imports nine functions that no library
exports, so eager binding fails before it starts.
- libSceVoiceChat: sceVoiceChatRequestLeaveVoiceChatGroup and
sceVoiceChatRequestDeleteVoiceChatGroup, offline requests like the other
sceVoiceChatRequest* calls
- libSceNpSessionSignaling: sceNpSessionSignalingGetConnectionFromPeerAddress,
a throwing stub; the signature is unknown
- libSceCdlgG2P, a new library: the six sceG2PDialog* functions, throwing
stubs declared without parameters
TechnicalDebt records the assumed and unknown signatures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The param is void*, as RommPS declares it. The test also passes a null and an empty URI and a param that must stay untouched, and compares against 0x8002002D itself. The debt entries now say the error is returned for any arguments and that the param layout and the console's argument checks are unknown.
PPSA01289 (Sackboy: A Big Adventure) calls sceAudioOut2MasteringTerm when it
replaces its world, which threw. Like Init and SetParam it has no effect, as
there is no mastering stage. The title creates its next world's audio device
before it destroys the previous one, so Init runs twice before Term runs
twice; whether the console reports an error for either is unknown, so both
return 0 and the case is recorded in TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceSystemServiceLaunchWebBrowser fails with the kernel's EOPNOTSUPP: there is no web browser to open. RommPS PPSA76677 stops on main at this import and shows a QR code instead when the browser does not open.
sceAmprCommandBufferClearBuffer detaches a command buffer's memory and
returns it so the caller can free it. It returned 0. PPSA01289 (Sackboy: A
Big Adventure) frees the result after every streaming read, so each read
leaked its 16 KiB command buffer until the allocator ran out of memory
during the first world load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With depth bias enabled, RADV sets POLY_OFFSET_PARA_ENABLE in
PA_SU_SC_MODE_CNTL next to the front and back enables, whatever the
topology; vkQuake's draws carry 0x3a46. The bit applies the offset to
points and lines, so it changes nothing for triangles, and Vulkan applies
depth bias only to polygons. It is now accepted with depth bias on
triangle topologies; point and line draws with it are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A label in memory the device has no host import of was stored when its
batch was reaped, after later labels the GPU had already made visible.
A title that reused that memory saw its next command buffer overwritten
by the stale value (#1458). Wait for the recorded work and store it then.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A title that replaces the allocator through its libc parameters also
supplies malloc_stats_fast in that table, and PPSA28997 calls it. It is
now forwarded to the title's function. Without a replacement allocator
that provides it, the call throws.
Without TZ, glibc's localtime and mktime run tzset on every call and
re-check /etc/localtime each time. PPSA28997 calls them every frame
during its intro, which then spent most of its time there. TZ is set
to :/etc/localtime when unset, so the zone is read once.
With f32 denormals flushed, as in the 0xc0 float mode titles use, the
hardware flushes the denominator and numerator of v_div_fixup_f32 but
keeps a denormal quotient and result. The recompiler kept all three,
so a denormal divisor gave the quotient back instead of an infinity.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PrepareMultisampledStorage only inspects the SPIR-V that the recompiler
emits for multisampled storage writes and atomics, but it took its
target from the device, so on a device without
shaderStorageImageMultisample, such as MoltenVK, the emission failed
and agc_driver_prepared_shaders failed in all its variants. The target
now has the StorageImageMultisample capability, and the check that a
target without it is refused still removes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PixelProgramSkipped only treated SPI_SHADER_PGM_LO/HI_PS written as 0 as
a draw without a pixel program, while the draw decoder already reads an
unwritten address as 0 and binds the null pixel program for it. A
depth/stencil-only draw whose pixel program address was never written
was instead rejected by the precheck with "depth/stencil-only draws
without a pixel shader are not implemented".
An unwritten address is now unset in PixelProgramSkipped too, so such a
draw goes through the null pixel program like one with the address
written as 0, and one that writes color is rejected as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With IEEE=1, v_pk_min_f16 and v_pk_max_f16 return a quieted NaN for a
signaling NaN operand, and DX10_CLAMP=1 clamps it to +0. The clamped
form skipped the bit-level NaN selection and saturated the driver's
min/max, which returns the other operand, so it gave that operand.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sonic Origins (PPSA05324) imports sce::Json::Object::empty() const
(i2l3IYvQ9UE) and fails to load without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceSystemServiceKillApp now ends the title as guest exit() does, so driver calls that wait for the exit park instead of throwing ProcessShutdown, and a second exit waits for the first. The test now fails when the kill returns, exits with a status other than 0 or bypasses the coordinator, and checks why a rejected call throws.
Report an app ID for the running title from sceSystemServiceGetAppIdOfRunningBigApp, and shut the guest down and exit when sceSystemServiceKillApp receives that ID with the arguments -1, 0 and 0. SwanStationPS5 (PPSA98510) 1.0.6 closes itself this way and stops on both imports on main.
ReadRawComputeShader decodes a growing prefix of guest memory and reads
more when the decoder throws std::out_of_range, which the vector, scalar
and instruction-length checks throw for an instruction cut off by the end
of the span. The memory (SMEM, DS, MUBUF, MTBUF, FLAT), image and export
decoders threw std::runtime_error instead, so a two-word instruction
across a window boundary failed the read: vkQuake terminated on "truncated
FLAT instruction". These now throw std::out_of_range too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hardware findings give 0xF_F000_0000, which is 0xFF0000000, not 0xFF00000000. Check the ring across threads, at ps5-opengl's size of 0x1b000 and with a canary after the outputs, and say in the debt entry that nothing is written to the ring.
Record the tessellation factor ring that sceAgcDriverSetTFRing receives and report it from sceAgcDriverGetTFRing, with the console's default of 0xFF00000000 and 128 KiB before a title sets one. VITA5 (PPSA39410) and Homebrew UI Lab (PPSA99050) stop on this import once sceVideoOutGetResolutionStatus resolves.
v_sin_f32 and v_cos_f32 take their input in cycles, and the SPIR-V
backend reduced it with GLSL Fract before scaling by 2*pi. For x in
(-0.5, 0), fract(x) = 1 + x is rounded to a multiple of 2^-24, which
drops the low bits of x, and for |x| <= 2^-25 it rounds to 1.0, which
RADV's v_fract_f32 clamps to 0x3f7fffff. On RADV, v_sin_f32(-2^-30)
returned 0xb4c90fde (-3.7e-7) where RDNA3 returns 0xb1c90fd5 (-5.9e-9).
The cycle is now x - roundEven(x). It lies in [-0.5, 0.5] and is exact
for every finite x: it is x when roundEven(x) is 0, and Sterbenz's
lemma applies otherwise. Finite inputs of magnitude 2^23 or more still
reduce to +0, so the select that forced them to 0 is removed. Infinite
and NaN inputs are unaffected: the translation selects their results
on the input bits.
Measured on RDNA3 (gfx1101) with the hardware oracle, with the same
results in all 16 combinations of IEEE, DX10_CLAMP and the f32 and f16
denormal modes: over 544 inputs, v_sin_f32(x - v_rndne_f32(x)) returns
the bits of v_sin_f32(x) for 519 and is at most 11 ulp off, while
v_sin_f32(v_fract_f32(x)) matches 403 and is more than 16 ulp off for
80, all of them in (-0.5, 0). For v_cos_f32 the counts are 523 (at
most 7 ulp off) and 484 (6 more than 16 ulp off). On RADV the
recompiled shader returns the bits of the v_fract_f32 sequence before
this change and of the v_rndne_f32 sequence after it, for all 544.
v_sin_f16 and v_cos_f16 share the reduction. On RADV, with f16
denormals kept, v_sin_f16 now returns the RDNA3 bits for all 65536
inputs, where it was 1 ulp off for 6 small negative ones, and
v_cos_f16 matches for all of them before and after.
An image with several runtime modes is emitted as a switch whose default
and disabled modes end in OpUnreachable. In a loop latch that exit breaks
the continue construct, and spirv-val rejected the shader. Inside a
continue target those cases now join the merge with an undefined result.
Fixes#1921
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
clang assembled every body as wave32, so a --wave64 body that used vcc
or an SGPR pair as a condition failed with "operands are not valid for
this GPU or mode". Pass -mwavefrontsize64 on wave64 runs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v_ldexp_f16 read src1 with readU32, so a float inline constant gave
the low 16 bits of its f32 pattern: 0 for 0.5, 1.0, 2.0, 4.0 and
their negatives, which returned src0 unchanged, and 0xf983 for
1/(2*pi), which clamped to -64 and returned a signed zero. The
hardware treats src1 as an f16 operand, as LLVM's
LDEXP_F16_VOPProfile in VOP2Instructions.td records, so it receives
the f16 encoding: 0.5 is 0x3800, an exponent of 14336. Measured on
gfx1101, ldexp(1.0, 0.5) is +inf and ldexp(1.0, -0.5) is +0.
Read VOP3 src1 through readF16SourceBits, which maps the float inline
constants to their f16 bits and reads other operands as before.
Registers, integer inline constants and literals already matched the
sign-extended low 16 bits the hardware uses. SDWA src1 keeps its old
read, as gfx11 has no SDWA to measure it on.
With VCC set and a src2 exponent field of 128 or more (scale 2^64),
v_div_fmas_f32 recomputed an fma result with exponent field 0 as an
fma of the smaller multiplicand times 2^64, the other multiplicand and
src2 * 2^64. For |src2| >= 2^64, src2 * 2^64 overflows to infinity, so
an fma that cancels exactly returned -inf or +inf. The hardware
returns +0: measured on gfx1101 for (1.0, FLT_MAX, -FLT_MAX) and
(1.0, -2^100, 2^100) in all 16 combinations of IEEE mode, f32 and
f16/f64 denormal mode and DX10_CLAMP.
Only exact cancellations reach that path. With |src2| >= 2, a nonzero
sum below 2^-126 needs a product above 1, which is a multiple of
2^-47, while src2 is a multiple of 2^-22, so a nonzero sum is at least
2^-47 and the fma result is normal. The scaled result is then the fma
result times 2^64, +0 for a cancellation. The recomputation is now
done only for the 2^-64 scale, when the fma result overflows.
FmaRounding checks v_fma_f32, v_fmac_f32, v_fmaak_f32 and v_fmamk_f32
against the fused result on the GPU, but not v_fma_mix_f32. It now
also runs v_fma_mix_f32 v20, v4, v5, v6 with all sources f32 on its 64
rows and expects the fused value, in the group that is skipped when the
device splits fma.
fastTexture took the Textures() cache entry of a stage-A image record
without asking whether a depth surface is resident at the address. The
full lookup in cachedTexture returns DepthSurfaceTexture first; the fast
path skipped it, so once a guest-memory entry existed for a depth
target's descriptor, every later sampled read got that entry and saw
the guest memory under the target, which a depth target never writes.
The fast path now returns nothing while a depth surface is resident
there, and the lookup falls back to cachedTexture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
Use the decoded opcode offset when checking forbidden system calls.
Add synthetic regressions for prefixed forms, immediate decoys, and the skip option.
AI-assisted: Codex. The submitting author is responsible for every changed line.
EVO-PLAYER (PPSA99039) creates resident AVC, HEVC and VP9 decoders at
start-up, and stopped at "Videodec2: unsupported codec" on the HEVC one.
Codec types 974921 (HEVC) and 2382845 (VP9) now open FFmpeg's HEVC and
VP9 decoders. HEVC input goes through the same Annex B conversion as
H.264 and VP9 frames are passed through; output stays 8-bit NV12, and a
frame deeper than 8 bits throws. Picture info throws for HEVC and VP9,
whose layouts differ from the AVC one.
Assisted by Claude Code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwFnKjMxmwGDWiuYYf1v4D
With conservative rasterization disabled, RADV programs
PA_SC_CONSERVATIVE_RASTERIZATION_CNTL to NULL_SQUAD_AA_MASK_ENABLE alone,
where libSceAgc writes the two INNER_TO_NORMAL overrides (0x6000). A value
made of only those bits now decodes as disabled; overestimation (0x6001) keeps
its checks, and every other mode is still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV sets OUT_OF_ORDER_WATER_MARK to 7 on every GFX10 draw, also when
OUT_OF_ORDER_PRIMITIVE_ENABLE is off. The watermark only tunes out-of-order
rasterization, which stays rejected, so the field is accepted alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FORCE_DST_ALPHA_1 makes blending read a target's destination alpha as 1, as
for an RGBX surface; RADV sets it for formats without alpha. The target's
blend factors are rewritten to what they become with Ad = 1: DST_ALPHA is
ONE and ONE_MINUS_DST_ALPHA is ZERO, SRC_ALPHA_SATURATE is ZERO in the color
equation, and DST_COLOR and ONE_MINUS_DST_COLOR are ONE and ZERO in the
alpha equation. Other CB_COLOR_ATTRIB bits stay rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title built with -fstack-protector reads its guard from fs:0x28 even when
it has no thread-local data and its linker emits an empty PT_TLS. The
Windows TLS builder refused any guest TLS access in that case. The guard and
the thread pointer live in the thread control block, which the builder
already emits, so an empty PT_TLS now takes the normal path, where a
zero-sized TLS block still rejects every other displacement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePadSetVibrationMode rejected mode 2 (compatible, per SharpProspero's
ScePadVibrationMode). The PS5 SDL joystick driver requests it on every
open and closes the pad when it fails, so SDL titles lost their controller.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Capturing a shader evaluates every V#, T# and S# the program uses,
including those behind a branch the wave does not take. When such a
descriptor was loaded through a pointer read from memory that is null
or unmapped, the capture threw "guest memory is not readable", while
the console faults only if a wave executes that s_load. The descriptor
now reads as zero words, and the flat SRT walk records the s_load's
dwords as it does for flat reads, so their guarded ReadConst raises
the BDA fault where the wave runs the s_load. A zero V# is an absent
buffer and a zero T# binds a null descriptor, as for any zero word
read from memory, so nothing new enters a key. A zeroed descriptor
whose read no flat slot records still throws, as do branch conditions,
uniform fills, table bases and user-data pointers.
Capturing a shader threw "null or misaligned address" when the flat
SRT walk read through a pointer loaded from memory that is null or
unmapped, even when the program skips that s_load. Such a read now
yields zero and a record with its pc and address, as does a slot whose
address needs it. The registered artifact checks one flag word per
guarded slot at each ReadConst; the capture appends the flags and the
records to the flattened SRT, and a set flag raises an Unmapped BDA
fault through the fault buffer and stops the invocation as a BDA read
does. Nothing per capture enters a key: poisoned and clean captures
share one variant and specialization. Descriptors, branch conditions,
uniform fills, table bases and user-data pointers that need such a
value still throw, and poisoned results stay out of the dispatch and
draw caches. The shader disk cache format becomes 21.
The layout offsets the titles read are static_asserts, and guest_sce_net now
checks a canary past byte 384, distinct records, and an untouched buffer on the
EINVAL, RESOLVER_ENODNS and EBADF paths, as #1845's test did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS5VR (PPSA99177) stops at start-up on the unresolved import sT4nBQKUPqM;
Jelly5, Emby5 and Nuvio import it too. The call fills the 384-byte resolver
info with up to ten IPv4 records (32 bytes each, family at +16) and the record
counts at +320 and +324, from the same host lookup sceNetResolverStartNtoa
uses, now shared. Only flags 0 is implemented; other flags throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PICO-8 for PS5 (PPSA99808) stops at start-up on the unresolved import
a7ToDPsIQrc from libSceLibcInternal. The parser follows FreeBSD's inet_aton
(lib/libc/inet/inet_addr.c): one to four parts in decimal, octal or hex, the
last part filling the remaining bytes, input ending at a NUL or whitespace,
and a null address only validating.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mesh test was built only on request and never registered, so the
translation of NGG geometry programs to mesh shaders ran in no test
run. On an RTX 4060 it passes. Register it with the other AGC driver
tests.
Three cases would fail or mislead under ctest. Without a Vulkan device
the test failed, and the Windows job has none. A device without
VK_EXT_mesh_shader exited 0, so ctest reported a pass for a test that
drew nothing. On llvmpipe, with the Linux job's Mesa 25.2.8 and Ubuntu
24.04's LLVM 20.1.2, every draw that builds a mesh pipeline stops the
process with "LLVM ERROR: Cannot emit physreg copy instruction"; Mesa
26.2.4 with LLVM 23.1 segfaults in LLVM's code generation instead. The
SPIR-V of all 18 modules the test builds passes spirv-val. Mesa #14376
reports the same error for lavapipe mesh shaders that index shared
memory per invocation; that the cause is the same is not established.
The test now opens its device with OpenVulkanTestDevice and exits 77
without a device, without VK_EXT_mesh_shader and on llvmpipe, saying
why. ANYPS5_REQUIRE_VULKAN still turns a missing device into a
failure. The Linux job therefore skips the test; it runs on hosts with
a GPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Config 0x30 (the ambisonic layout, 4 to 16 channels) was rejected at instance init, so Demon's Souls asserted in
its audio player and crashed entering the tutorial level. Each channel is decoded as a mono stream and the
blocks are interleaved.
Cli::ParseArgs had no test coverage: its only sibling test
(AutorunExitTests) exercises the autorun launcher, not the parser.
Add cli_args_tests covering every option and its side effects, the
--rpath and --exclude-sce-module value consumption (including that the
following token is taken verbatim rather than re-parsed as a flag), the
unused-filter value and single-specification validation, the
--exclude-sce-module/--skip-sce-module conflict in both orders, the
order-independent --windows-diagnostics/--windows-gui requirement on
--windows, unknown-option and extra-positional rejection, and the usage
message when an input or output path is missing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PICO-8 for PS5 (PPSA99808) imports stpcpy from libSceLibcInternal, one
of the absent imports #1578 lists for it. POSIX defines it as strcpy
that returns a pointer to the terminating null of the copy.
RetroArch (PPSA99169) imports ldiv from libSceLibcInternal, one of the
absent imports #1578 lists for it. The guest long is 64-bit, so the
export takes std::int64_t and returns lldiv_t, which has the guest
ldiv_t layout; std::ldiv would truncate on Windows, where long is 32-bit.
The repo allows one implementation per export. Move libc's rename into
libc/src/Rename.cpp, compile that file into both libc and libkernel, and
drop the second copy from libkernel's Stdio.cpp. FilesystemError moves to a
header so the shared source can use it.
The test now checks that the symbol it resolves belongs to libkernel (module
handle on Windows, dladdr on Linux) so it fails when the export is removed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The guest VFS on Linux does not map absolute host paths, so the test failed
at the first rename there. Match GuestFilesystem.cpp and build the directory
relative to the working directory.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
guest_filesystem_tests links libkernel and libc, both of which define
rename_nid_postfix, so which one it binds to depends on the loader and the
suite passes either way. Resolve the symbol from libkernel.prx explicitly so
the export added for titles that ship their own libc.prx is actually tested.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Titles that ship their own libc in sce_module import rename from
libkernel (NID NN01qLRhiqU), where it was missing, so PPSA12544 failed
to start. It checks both paths like unlink and maps sceKernelRename's
result to errno.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Give guest exception what, non-deleting destructor and deleting destructor slots explicit APS5_VABI types and register guest adapters. Preserve native helpers, object layouts, native exception routing and existing malloc/free ownership.
Add mismatched and cleared RCX regressions for plain and message vtables, observing actual libc free calls to reject foreign or duplicate frees. Retain typed guest catch/rethrow and native exception coverage. All 27 targeted CTests pass three repetitions on Windows Release; Linux execution remains unvalidated.
Guest message throws registered native Windows callbacks even though Release invokes guest callbacks using System V. Register an APS5_VABI adapter in the standard and regex message throw paths and retain the native DestroyMessage helper and legacy callback storage type.
Add MinGW CTest regressions with distinct or zero RCX, shared and final-release messages, typed guest catches, rethrow and normal Release. All ten new cases plus Windows exception and thread callback regressions pass three repetitions. Linux execution remains unvalidated; guest vtables and host exception paths are unchanged.
Keeping lastFuseTime at its maximum never let it recover from a process time that wrapped on the first call (fixed separately in #1501), so tilt correction stopped and guest_pad failed. Queued states keep their own timestamp; the orientation is fused once at the time of the read, as scePadReadState does.
Measured on a PS5 (firmware 13.60) through dlopen of libSceAudioOut.sprx:
a MAIN (type 0) port accepts 1, 2, 8 or 12 channels (bits 8-11) with
sample type 0 or 1 (bits 0-6). Bit 7 without 8 channels returns
0x80268001, any other unsupported format returns 0x8026800E, and both
write UINT64_MAX to the output handle. Bits 12-31 do not affect the
result. Other port types keep their current validation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AF_UNIX pairs as FreeBSD 11 makes them: SOCK_STREAM, SOCK_DGRAM and
SOCK_SEQPACKET with protocol 0, SOCK_NONBLOCK applied to both ends and
SOCK_CLOEXEC accepted, EAFNOSUPPORT for an unknown family, EOPNOTSUPP
for AF_INET and AF_INET6, EPROTONOSUPPORT for type 0 or a protocol,
EPROTOTYPE for another type, EFAULT for a null output, and nothing
written on failure. The ends are guest sockets, so send, recv,
shutdown, fcntl, poll and close work on them; getsockname and
getpeername report the unnamed AF_UNIX address, recvfrom and recvmsg
no address for a stream and the unnamed one for a datagram. On Linux
the pair is the host's socketpair; on Windows a loopback TCP connection
or two connected UDP sockets, and SOCK_SEQPACKET throws. The remaining
differences from the console are recorded in TechnicalDebt.
OpenSSL's QUIC notifier in 15 catalog titles creates such a pair.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Returns the name of the directory under which a title's sandbox exposes
system files, which homebrew SDKs splice into paths such as
/<name>/common/cert/CA_LIST.cer and /<name>/common/lib/libc.sprx. The
name is fixed here; recorded in TechnicalDebt together with the fact
that nothing is mounted under it.
Registers guest_sandbox_random_word.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
addGap and buildEdges each called isZeroPadding(address, end) at every
instruction boundary. isZeroPadding scanned from the current address to
the region end, so the total work for a region with N instructions and a
trailing-zero suffix of length Z was O(N*Z), quadratic in the worst case.
Replace with trailingZeroStart, which scans backwards from the region end
once to find the start of the trailing-zero suffix. The loop then stops at
that position, eliminating the per-instruction zero check entirely.
A path whose T# or S# dwords hold a program-counter, EXEC or compare-mask
value is no longer folded onto the first descriptor: the web split gives up
and the read throws as on main. Cache the invariant-Phi resolution per web,
lower the state limit to 512 and test the program-counter, EXEC and
compare-mask rows, unwritten registers, and the arm and state limits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It returned the network error, which titles that poll their epoll treat
as fatal. No request is ever pending, since sending fails at once, so the
wait now times out with no events; an infinite wait throws.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sceSaveDataMount3 with SCE_SAVE_DATA_MOUNT_MODE_CREATE2 opens a save that
already exists, so its mount status is 0 rather than CREATED, as shadPS4's
savedata.cpp reports. A CREATE mount still reports CREATED.
With stride 0, OOB_SELECT 2 puts a fetch out of range only when NUM_RECORDS
is 0, and OOB_SELECT 3 only when the offset is not below NUM_RECORDS (GFX10
table in Mesa ac_descriptors.c). A one-record descriptor therefore returns
its whole element: Unreal's per-draw PrimitiveId stream is such a descriptor,
and refusing it skipped the draw. A wholly out-of-range fetch reads a zero
buffer.
PPSA13427 imports sceHttpSetAuthInfoCallback (jf4TB2nUO40), which no
library exported, so the relinked title failed to load. No request
reaches the network, so no server asks for authentication: the
callback is accepted and never called. The signature and callback
type follow shadPS4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
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.
Restore full 64-bit RCX before 32-bit ALU operation so CMP and other non-writing/carry instructions do not zero-extend and clear upper 32 bits. Add test coverage for 32-bit CMP, ADC, and SBB.
The storage image and sampled texture caches each let go of their least
recently used entries past a fixed 2 GiB, and a sampled view of a
storage image counted that image a second time.
The storage image cache keeps a quarter of the largest device-local
heap, at least the 2 GiB of before (TextureCacheBudget). The sampled
cache gets what VK_EXT_memory_budget leaves on that heap after the
memory in use outside sampled textures and cached storage images, the
storage cache's budget and an eighth of the heap budget, read at most
once a second and at least 2 GiB; without the extension it keeps the
storage cache's rule. A snapshot counts its image allocation, a texture
still bound after leaving the cache counts as sampled memory, and a
change of more than 10% is logged at most every 10 s. A nonzero
APS5_TEXTURE_CACHE_MIB sets both budgets; a view of a cached storage
image counts nothing and leaves the cache when its image does.
Hades (PPSA03355) stops at load on main with "undefined symbol:
9HzfhdtESio", the NID of pthread_getname_np, which it calls on
pthread_self() with a 32-byte buffer. Measured on a PS5 (firmware 13.60)
through dlopen of libkernel: the function takes (thread, buffer), copies
the name and its terminator, returns an empty name for an unnamed
thread, ESRCH for a null or joined thread and EFAULT for a null buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recompiler emits OpCapability Int64Atomics for 64-bit buffer atomics
in any stage, and the device enables shaderBufferInt64Atomics when it
has it, but graphics shader validation only accepted its fixed list of
capabilities, so every draw whose shaders use one was rejected with
"SPIR-V requires unsupported device capability 12". Compute dispatches
are not validated this way and already ran them.
The device records the feature (bufferInt64Atomics in the device state
and the graphics Context) and ValidateShaders accepts Int64Atomics when
it is enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS4 firmware 11.00 libSceNgs2.sprx stores matrix levels clamped to
-4..4, keeps NaN, and rejects a level count of 0. Levels were stored
unclamped, so a level of 8 played twice as loud as on the console, and a
count of 0 cleared the matrix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three homebrew titles from #1578 import it and stop at start-up without
it: ps5-opengl (PPSA99005), EVO-PLAYER-PS5 (PPSA99039) and
ProsperoRadio (PPSA99001).
It reports the port's resolution as the full and pane size and the
refresh rate ID as sceVideoOutGetOutputStatus does. The 48-byte layout
follows the declarations in ps5-opengl and EVO-PLAYER-PS5, which agree;
the screen size is 0 and the flags are 0. An invalid handle or a null
status throws, as in the other status getters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guest_errors sets errno to 13 and checks at the end that the strerror_r
calls left it alone, but a std::thread is created and joined in between.
Guest errno is the host's errno. Since glibc 2.42, pthread_create first
tries madvise(MADV_GUARD_INSTALL) for the new thread's stack guard.
Kernels before 6.13, including WSL2's 6.6, reject it with EINVAL, and
glibc falls back to mprotect with errno left at EINVAL. POSIX leaves
errno unspecified after a successful call, so the test aborts on Ubuntu
26.04 (glibc 2.43) under WSL2 without anything being wrong.
Check errno right after the strerror calls, and set it to 13 again after
the join, so every errno check covers only strerror and strerror_r.
DMA_DATA with DST_SEL 2 (V_411_NOWHERE in Mesa's src/amd/common/sid.h)
reads its source into L2 and stores nothing; libSceAgc emits it as an
L2 prefetch with the source address repeated as the destination. It
threw as a register or prefetch destination. A prefetch of a memory
source is now accepted and writes nothing: ResolveStore returns it as an
empty store, so the packet stays ordered without a drain,
DecodeMemoryCopy keeps rejecting it and Execute skips it. A prefetch of
a register, GDS or immediate source and a register destination still
throw.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sceUltFinalize cleared gUlthreads directly, dropping the last
shared_ptr to an UltUlthreadState while its worker thread still held
the raw pointer passed to scePthreadCreate. Finalize now returns
ULT_ERROR_BUSY when an ulthread is still registered, before any global
state is torn down, following the BUSY convention of the queue,
semaphore and queue data pool destroy paths. Joining the ulthread
remains the caller's responsibility, as sceUltUlthreadJoin already
keeps the state alive across scePthreadJoin and erases it afterwards.
SPI_SHADER_COL_FORMAT 7 (UINT16_ABGR) was rejected by DecodeState,
though the recompiler already handles it: a pixel shader's MRT output
under mode 7 is a uvec4, and a compressed export's 16-bit halves are
taken with OpBitFieldUExtract. The format is now accepted when the
target's CB_COLOR_INFO number type is UINT; into any other target it
still throws, as does SINT16_ABGR (8), whose halves would need sign
extension.
The target records the export (ColorTarget::uintExport). Shader
validation then requires a uint4 output at its location instead of a
float4 one, and blending into it is refused (Vulkan has no blending of
integer attachments).
PPSA14632 (LEGO Horizon Adventures) writes a 2848x1600 R8_UINT target
this way; those draws were dropped.
agc_driver_pixel_uint16_export draws compressed MRT0 exports under mode
7 into a linear 16_16_16_16 UINT target, one row per pair of packed
words (0x0000ffff/0xffff0000, 0x00010002/0xffffffff and four distinct
halves), and reads every 16-bit component back under the identity,
STD_REV and ALT_REV export mappings. agc_driver_graphics covers the
decode and the blend refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScreenTester5 (PPSA69420) stops at start-up on the unresolved sysctlbyname
import and also imports sysctl. hw.ncpu (CTL_HW.HW_NCPU) reports the same
processor count as sysconf(_SC_NPROCESSORS_ONLN). Other names and MIBs throw.
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.
When another live image overlaps the fill, a fill at a live image's dccAddress is a key fill
only while it stays within DccKeyCount (the pipe-aligned key count); with nothing overlapping
the length rule is unchanged. ClassifyFill, NoteKeysFill and ClearByKeysFill share the match.
agc_driver_fill_cover_keys pins Keys, Inside and Several for fills at and beside the keys.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Use Hacker's Delight 8-3 formula for signed multiplication high words:
hi = umulhi(a,b) - (a<0 ? b : 0) - (b<0 ? a : 0)
This avoids using OpSMulExtended entirely, which returns wrong high words
for constants -1, -2, and -32768 on Intel iGPUs. The workaround is exact
for all inputs and has no performance impact on other platforms.
Fixes#1246
LibcExit called std::exit, which ran the static destructors of the libraries
before the guest exit handlers. A guest handler that freed memory then locked
a destroyed std::mutex, which throws, and a noexcept guest destructor ended in
std::terminate. The guest handlers now run first, while the library objects
are alive.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add sigemptyset, sigfillset, sigaddset, sigdelset and sigismember with
FreeBSD 11 semantics (lib/libc/gen/sigsetops.c): a 128-bit set of four
32-bit words, signal n at bit n - 1, signals 1 to 128 valid. sigfillset
sets every bit. An invalid signal returns -1 with errno EINVAL and leaves
the set untouched; sigismember returns 1 or 0. A null set throws.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Recognize read-direction ALU forms (add, or, adc, sbb, and, sub, xor, cmp) that read from fs:[mem] where mem has a base or index register (such as 64 48 33 03, xor rax, fs:[rbx], in #217). The stub loads the TLS value through the computed address, performs the ALU operation, and preserves scratch registers and flags.
Extend test_tls_function_coverage with execution and stub coverage cases for register-addressed ALU operations, including rax, rcx, rbx and r13 destinations, wide and 32-bit forms, and verification of register and flag preservation.
Refs #217
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each direct memory backing keeps a memfd open, so a title such as
Dreaming Sarah (PPSA02929) holds about 1100 descriptors at startup.
With the systemd default soft limit of 1024, later opens fail with
EMFILE: memfd_create throws "create direct memory backing: Too many
open files", or the title's fopen returns NULL and it faults in fread.
libkernel now raises the RLIMIT_NOFILE soft limit to the hard limit
when it is loaded, before the title creates any descriptor, and throws
if the limit cannot be read or raised.
MoltenVK reports VK_KHR_fragment_shader_barycentric, but its SPIR-V to MSL step
rejects the PerVertexKHR decoration that the barycentric path uses, so every
pixel shader on that path fails to compile into a pipeline. Treat the feature
as unavailable when the driver is MoltenVK, so the no-barycentric path is used.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A raw 5.1 LC frame decodes as 6 channels with a maximum of 6 and of 8,
which is only reached by moving past the mono/stereo configuration. The
configuration search is listed under unknown function info, together with
a mono raw stream being reported as 2 channels.
Co-authored-by: Dextroid17 <217507631+Dextroid17@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For a raw M4AAC stream the maximum channel count was written into the
AudioSpecificConfig as the channel configuration. The maximum only bounds
the stream: The Smurfs Dreams creates its decoder with 8 channels at most
and sends stereo, so FFmpeg refused configuration 8 (reserved) and a
stereo stream under configuration 6 or 7 decodes no frame. The decoder
now starts from the mono/stereo configuration and on the first access
unit that it cannot decode moves on to the configurations with more
channels, up to the maximum, keeping the first one that decodes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceSysmoduleIsLoaded returned 0x80A90002 and the unload calls 0x80A90003
for a module that is not loaded. Neither value exists in the SDK: the
sysmodule facility is 0x5A and the error is 0x805A1001, which titles
compare against (GTA V PPSA04263: cmp eax,0x805a1001 after IsLoaded) to
decide whether to load and initialise the module themselves. Return that
value from all three places and cover it in guest_sysmodule.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the CLEAR_STATE approximation with the envelope and per-op-bit packet groups measured on PS5 firmware 13.60. Emit groups for op bits 0x001, 0x002, 0x004, 0x008, 0x010, 0x020, 0x080, 0x100 and 0x800 in ascending order; throw for 0x040, 0x200 and 0x400 whose packets the driver rejects. state 2 sets the envelope flag and state 3 sets the 0x004 and 0x800 group payload dword.
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 malloc or operator
new result with 32-byte-aligned AVX instructions. The guest heap aligned
blocks to 16 bytes, and the arena's 32-byte classes put every small block
16 bytes past a 32-byte boundary after its header, so such a store faulted.
The minimum alignment is now 32 bytes; the padding still assumes only the
16-byte alignment the raw blocks guarantee.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A suspend point flushes the labels queued since the last submission. It
recorded them only when a Vulkan device existed, and the device is created
lazily, so labels queued by a title that had not yet drawn were never
written. RADV ends each submission with a RELEASE_MEM to a marker it polls
from the CPU; when the end of that submission was folded into a following
suspend point, the marker stayed at 0 and RADV reported the device lost.
The labels are now recorded either way, which writes them from the CPU
without a device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The segment filter dropped every header in the PT_LOOS..PT_HIOS range,
which includes PT_GNU_EH_FRAME. The unwinders find the executable's
unwind tables through that header, so no C++ exception thrown in the
main executable could be caught on Linux.
The header is kept only when the output program header table still fits
in the input's table; otherwise it is dropped as before, with a warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
The retry that waits for a target to leave NtContinue, NtContinueEx or
libwinpthread-1.dll had no bound, so a target that stayed there made
sceKernelRaiseException spin forever. It now throws after 1 s. A new
guest_raise_exception phase keeps a thread inside NtContinue by
continuing a context that calls NtContinue again, and checks that the
raise throws and that the handler did not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The integer emulation of the round-to-nearest-even float-to-half
conversion is exact but costly: a compute shader with about 3600
conversions never finished compiling on NVIDIA. When the device enables
shaderFloat16 and guarantees shaderRoundingModeRTEFloat16,
shaderDenormPreserveFloat16 and shaderSignedZeroInfNanPreserveFloat16,
it advertises Float16, RoundingModeRTE and DenormPreserve, and the
recompiler emits OpFConvert under the 16-bit RoundingModeRTE,
DenormPreserve and SignedZeroInfNanPreserve execution modes instead.
NaNs keep the emulated quiet NaN with the high payload bits. Devices
without these guarantees keep the emulation.
Graphics validation accepts the RoundingModeRTE, DenormPreserve and
SignedZeroInfNanPreserve execution modes for 16-, 32- and 64-bit floats,
so vertex and pixel shaders with a native conversion still draw.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A draw that sets STENCIL_CLEAR_ENABLE was rejected as a whole, so its
color output was lost as well. The draw is now rasterized with every
covered sample's stencil replaced by DB_STENCIL_CLEAR, whatever the
stencil and depth tests are, and its color outputs are written as usual.
The depth/stencil state is decoded for such a draw even when
DB_DEPTH_CONTROL enables no test. Depth clear, copy, resummarize and
decompress draws are still rejected.
SleepUntil, PollSleepUntil and SleepNanosCoarse each wait on one
thread_local high-resolution timer. A sceKernelRaiseException handler
that runs as an APC inside a sleeping thread's wait and sleeps itself
re-armed that timer and consumed its signal, so the interrupted sleep
never returned. A nested TimerWait now waits on its own timer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetProcessTime and sceKernelGetProcessTimeCounter subtracted
GetStartNanos() from GetMonotonicNanos() in one expression. GCC reads the
clock first, so on the first call in a process the start was captured
after it and the result wrapped to about 2^64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
toHexString, contains, intersectSorted, addUnique, sortUnique, remapId,
remapIds and rebuildPredecessors were defined with identical bodies in
the anonymous namespaces of two of ControlFlowGraph.cpp, GraphBuilder.cpp
and Structurizer.cpp. They are now declared in ControlFlowHelpers.hpp,
which only these three files include, and defined in ControlFlowGraph.cpp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FindPending returned the first pending storage image at a texture's
address that is at least as long as the surface. When transient
surfaces alias (a 2-mip 1424x800 chain and, after it, a 1-mip
1424x800 surface at the same address, each with its own DCC keys),
a sampled lookup of the newer single-mip surface could be served by
the older chain: the chain's view was refreshed under its own keys,
which the title had re-filled with a fast-clear code, so whole blocks
read as the clear value instead of what the GPU had just written.
An image whose length equals the surface's now wins; a longer chain
still serves a descriptor of its first mips when no exact image is
pending.
Engines alias transient render targets, so a texture can sit at the
address of a depth surface after the memory has been handed to a color
image: a 384x384 R11G11B10 texture over a 384x384 D32S8 surface, say.
Sampling it threw "sampling the depth plane ... is not implemented"
and the draw was skipped.
A view with another extent, or a depth-plane view in a format that
cannot hold the plane (neither the plane's own R32F / R16 unorm nor a
T# that reads the raw depth bits), is now treated as memory reuse: the
lookup falls through to the ordinary texture path instead of the depth
surface. Stencil-plane views keep their current handling.
scePthreadMutexDestroy threw when the mutex was locked. FreeBSD libthr,
which the console's libkernel follows, returns EBUSY and leaves the
mutex usable; PPSA28997 destroys a locked mutex during its intro and
was aborted.
A condition wait on a recursive mutex locked more than once unlocked the
native mutex only once, so the thread that should signal blocked forever
on it. Unlock the remaining levels before the wait and lock them again
after it, as FreeBSD libthr does in _mutex_cv_unlock and _mutex_cv_lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
A thread that sceKernelRaiseException redirects resumes through
RtlRestoreContext with a CONTEXT that has no XSTATE, so the upper halves
of ymm0-ymm15 came back as the handler and the delivery path left them.
A handler or host routine that runs vzeroupper or any VEX instruction
(Zen 2 guest code, the CRT's memcpy) cleared the upper lanes of a thread
interrupted in the middle of AVX code. The entry stub now stores
ymm0-ymm15 next to the delivery when the CPU and the OS enable AVX, and
the exit loads them back right before RtlRestoreContext, which restores
only the low halves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
sceKernelGetCurrentCpu returned the raw host CPU index. Guest affinities
are bookkeeping only and sceKernelGetAvailableCpumask advertises CPUs 0
to 12, so on a host with more than 13 logical CPUs a title could get 13
or higher, and a thread pinned to one CPU could get another one.
The host index now selects a CPU of the calling thread's affinity
clipped to the available mask, so the result is always an advertised
CPU and a thread pinned to one CPU reports that CPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
On the console a title whose param.json declares downloadDataSize finds
/download0 mounted from the start. The host directory was only created
by sceAppContentDownloadDataGetAvailableSpaceKb, so a title that opens
files there directly got ENOENT. libkernel now creates it when it loads,
reading only the run directory's param.json, without resolving guest
paths, so the libc path root is still fixed at first use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the AMD Windows driver (Radeon RDNA2 iGPU, 32.0.21030.2001)
UnpackHalf2x16 of a byte-selected half converts the unmasked source
register: an SDWA BYTE_0 source of 0xfff0fff0 reads as f16 0xfff0
instead of 0x00f0, so agc_driver_sdwa_float_selectors and
agc_driver_sdwa_ldexp_f16 fail there. EmitF16BitsToF32 now decodes
sign, exponent and mantissa with integer operations, scaling
subnormals by 2^-24, which is exact for every half and leaves the
driver nothing to fold into the conversion.
MinGW's thread_local goes through emutls and winpthreads' pthread_getspecific, which holds a per-thread spinlock. A raise that redirected a thread holding it ran the handler on that same thread, and a handler reading any thread_local, scePthreadSelf for one, spun on the lock forever. A raise that finds the target inside libwinpthread-1.dll now resumes it and suspends it again, as one inside NtContinue does.
guest_raise_exception's new phase raises at a thread that calls scePthreadSelf in a loop and checks that the handler sees its own thread. It hung in every run without the retry. TechnicalDebt records that a handler can still reenter other host code the thread was interrupted in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetModuleInfoForUnwind and sceKernelGetModuleInfoFromAddr each
parsed .ehmeta and the eh_frame_hdr it names. Both now read them through
Module/EhFrame.hpp, so a bad table fails the same way in both: an
eh_frame_hdr version other than 1, an indirect or non-absptr/pcrel
encoding, a 64-bit record length or anything outside the image throws.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkiyF6p7oDAaTtVe8ci5zP
Recognize the exact typed pattern-fill kernel and route aligned, nonaliased uniform ranges through existing fill/coherence handling. Preserve control and descriptor bounds, partial dispatch counts, queued labels, and shader fallback for other cases.
Semantics checked on Radeon RX 6600: native RDNA wave32/wave64 typed R32_UINT loads/stores, execution masks, and descriptor bounds (16 cases). Real PM4 tests cover HLE and shader paths, imports and copies, indirect/partial dispatch and GPU-to-label ordering under Vulkan validation. Graphics and PM4 tests pass; Skyrim Helgen save visually checked. 64 MiB copied-buffer fill median reduced from 67.5 ms to 3.8 ms.
Written with AI assistance.
Read the module layout from the PE image that holds the address. A
relinked image reports its .elf sections as segments and the
eh_frame_hdr recorded in .ehmeta; any other image reports its sections
with memory access and its .ehfram section. The eh_frame_hdr and
eh_frame helpers are shared with the Linux path, and the guest module
info test now also runs on Windows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SuspendThread returns before the thread has stopped, and GetThreadContext waits until it has. waitCount was read right after SuspendThread, so a thread that was leaving its wait could still decrement it and drain its APCs with NtTestAlert before QueueUserAPC queued the delivery, which then waited for an alertable wait that never came. The context is now read first, so waitCount is read from a stopped thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new phase's vectored handler continues every exception the thread raises, so the thread spends much of its time in NtContinue, where a redirect is lost unless the raise retries. Without the retry the phase hangs in every run; with it the test passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux guest module writer emitted DT_HASH with a single bucket, so
every symbol lookup that reached a guest module walked its whole symbol
table. Guest modules come first in the global scope, so each host prx
lookup paid that walk at load and on every lazy PLT call. Build the
standard SysV table from the final symbol names with one bucket per
symbol, as the console's modules ship it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A raised exception redirects the target thread to a stub that runs the handler and returns through RtlRestoreContext. On Windows 10 (19045) RtlRestoreContext restores in user mode only when the context flags are exactly CONTEXT_FULL | CONTEXT_SEGMENTS (CONTEXT_XSTATE aside); for the CONTEXT_FULL | CONTEXT_FLOATING_POINT context it was given, it calls NtContinue. When the next raise suspended the thread inside that system call, NtContinue then replaced the redirected context with the one it was restoring, so the handler never ran although sceKernelRaiseException had returned 0. guest_raise_exception lost such a delivery to its busy thread in about one run in five and hung until the ctest timeout.
The context is now read with CONTEXT_SEGMENTS, so the handler returns through the user-mode restore, and a raise that finds the thread inside NtContinue or NtContinueEx (an APC or an exception handler returning) resumes it and suspends it again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each in-place substitution went through X64InstructionRewriter::Rewrite,
which copies the whole code segment and decodes every instruction in it
to adjust references. The converter only accepts equal-length
replacements, so the adjustment is always zero and the copy and scan are
wasted: conversion time grew by one segment scan per substitution. Copy
the replacement bytes into the segment instead.
The driver created a windowless Vulkan device at the first dispatch and
replaced it with a windowed one at the first flip. The replacement built a
second device and re-imported every host import, and the first flip paid
for it.
VideoOut now creates its window when it starts (the first VideoOut call),
at the default 1920x1080 output, and attaches it to the driver
(AgcDriverAttachWindow) before the call returns. The driver creates its
device with that window, so GPU work and presentation use one device.
Present no longer creates or replaces a device. A device created by GPU
work before VideoOut started is still replaced, now in AttachWindow.
A failure to create the window or the device is thrown from that first
VideoOut call instead of terminating the presentation thread.
Tests:
- video_out_flip_tests onedevice: GPU work, then a flip, leaves one device.
- pacing makes its first presentation span vblanks with GPU work queued
ahead of the flip; it relied on the device being created at the flip.
- unavailable expects the failure from sceVideoOutOpen.
- guest_video_out_wqhd_detection writes a param.json for the window title
and is skipped without a display or Vulkan device unless
ANYPS5_REQUIRE_DISPLAY is set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SplitDescriptorPhi (#414) handles a T# or S# that is a Phi of SRT reads in
one block. A descriptor picked inside a loop through a jump table reaches
the read as a chain of Phis rooted at the loop header, so the plan threw
"GetImageResource dword 0 is not a valid runtime value".
When the one-block split does not apply, walk the descriptor dwords
together through the Phi web, give each distinct SRT descriptor an arm,
build a U32 selector Phi web carrying the arm index, and select between one
copy of the read per arm. Paths whose dwords hold a program-counter, EXEC
or ballot value fold onto the first arm (listed in TechnicalDebt); any
other descriptor that cannot be recomputed still throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
note_bda_write ran a device-scope compare-exchange on every probe, also
when the slot already held the page. The table lives in host-visible
memory, so each BDA store paid an atomic across PCIe, and nearly every
store hits a page an earlier store noted. Load the slot first and only
compare-exchange when it does not hold the page. The slots probed, the
pages recorded and the overflow report are unchanged.
DOOM (PPSA99666) presents through a compute kernel that stores about two
million pixels through raw pointers per frame: 248 ms per dispatch
before, 8.1 ms after, on an RTX 5070 Ti.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The declaration, the state field and the definition of write_bda_bytes now sit next to EmitBdaStore and the other BDA write functions instead of next to the dword read functions, which #1267 extends at the same lines. No change to the emitted code.
note_bda_write unrolled its 8 compare-exchange probes, and drivers inline it at every BDA store and atomic. The probes now run in a loop that tries the same slots in the same order and stops at the same slot, so the recorded pages and the overflow flag are unchanged. With the byte store helper, the agc_driver_flat_store binaries on an RTX 3060 go from 138,240 to 75,648 bytes (wave32) and from 225,152 to 130,560 bytes (wave64), and the cold run from 2.2 s to 1.2 s.
Every 8- and 16-bit BDA store and the unaligned path of every 32-bit store emitted one get_bda_write_pointer lookup and one note_bda_write per byte at the store site, five write lookups per 32-bit store. They now call write_bda_bytes, which loops over the bytes and does, per byte and in order, the same lookup, the store when it is mapped and the write note, so faults are recorded exactly as before. On an RTX 3060 the agc_driver_flat_store binaries shrink from 496,256 to 138,240 bytes (wave32) and from 928,384 to 225,152 bytes (wave64), and the cold run from 19.7 s to 2.2 s.
Recorder::KeepDrawSnapshot keeps nothing larger than the pool's byte
budget, and the storage-buffer pool's budget was 256 MiB (the draw
input pool, for vertex and index data, has 1 GiB). A read-only storage
buffer a draw reads in place from an import is copied into a snapshot
per draw; one larger than 256 MiB was never kept, so every draw copied
it again even when the guest had not touched it.
PPSA14632 (LEGO Horizon Adventures) binds a 576 MiB direct-memory buffer
to many draws: each one memcpy'd all 576 MiB (about 100 ms), 176 times
in 60 s. With the budget at 1 GiB, as for draw inputs, the snapshot is
reused while the buffer is unchanged (394 reuses and 41 copies in 90 s),
and Graphics::Draw drops from about 7 ms to 1 ms per draw packet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePadRead reported only the current state, so input that changed and
changed back between two reads was lost. Published states are now kept in
a queue of up to 64 and drained oldest first; with none queued the current
state is returned as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceKernelGetProcessTime and sceKernelGetProcessTimeCounter subtracted
GetStartNanos() from GetMonotonicNanos() in one expression. GCC reads the
clock first, so on the first call in a process the start was captured
after it and the result wrapped to about 2^64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The display window now takes its icon from /app0/sce_sys/icon0.png when
the game has one, decoded with the existing PNG decoder and set through
SDL_SetWindowIcon. Linux executables have no icon resource, so the
window, taskbar and alt-tab showed a generic icon; on Windows the
embedded resource from #350 already covers this and the call only
reinforces it. Without icon0.png nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reverb was a Freeverb-style approximation. It now follows the PS4
firmware 11.00 libSceNgs2.sprx, read and checked against its output: the
input mixed to mono with the library's fixed gains, a 14400-sample buffer
with four reflection taps from the reflection pattern tables and a late
feed, sixteen damped comb filters with a pre-emphasis stage, a signed
selection of combs and four allpasses per output channel, a two-pole room
HF filter, and the library's parameter clamps, crossfades and setup
defaults. The test values are the PS4 library's own output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rack 0x2001 is created with its option layout and KytyPS5's defaults, takes
the setup (0x20010000) and I3DL2 (0x20010001) voice params and reports the
common voice state. The voice mixes its inputs and runs a Freeverb-style
approximation driven by the I3DL2 parameters: room and room HF, early
reflections and their delay, late reverb and its pre-delay, decay time and
HF ratio, and diffusion. Out-of-range params throw, param changes keep the
tail, a stop or kill clears it, and the tail ends once it decays below
1e-20. The console's algorithm is unknown; the approximation is recorded in
TechnicalDebt. The test context buffer pool grows to 64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 -->
if [ "$(git rev-parse FETCH_HEAD)" != "$PR_HEAD" ]; then echo "::error::The pull request has newer commits; start a new build"; exit 1; fi
if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit FETCH_HEAD; then echo "::error::The pull request does not merge cleanly into main; rebase it first"; exit 1; fi
if [ "$(git rev-parse FETCH_HEAD)" != "$PR_HEAD" ]; then echo "::error::The pull request has newer commits; start a new build"; exit 1; fi
if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit FETCH_HEAD; then echo "::error::The pull request does not merge cleanly into main; rebase it first"; exit 1; fi
END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' ctest.log
if [ "$(git rev-parse FETCH_HEAD)" != "$PR_HEAD" ]; then echo "::error::The pull request has newer commits; start a new build"; exit 1; fi
if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit FETCH_HEAD; then echo "::error::The pull request does not merge cleanly into main; rebase it first"; exit 1; fi
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,23 +37,104 @@ 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).
- Avoid non-standard extensions (`__attribute__`, etc.) where standard C++ is enough. Helper symbols that must not become NIDs use the `_nid_no_patch` or `_nid_no_patch_cut` suffix.
- The relinker uses only the C++20 standard library.
- Third-party code is added as a submodule under `3rdparty/` and built from source, not found on the system.
- Third-party code is added as a submodule under `3rdparty/` and built from source, not found on the system. With `ANYPS5_USE_SYSTEM_DEPS=ON` the packages packaged by the distributions (SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools) come from the system instead; that resolution lives in [cmake/SystemDeps.cmake](cmake/SystemDeps.cmake) and the default build never reads the system.
- Don't add tests that only check that a symbol is exported: a missing export already fails at startup.
- Each test finishes in 30 s on the CI runners, Vulkan tests included (Linux runs them on lavapipe), counting the first run with no shader cache. ctest runs in parallel there, so a slow test competes for the cores and times out on some runs and not others. Split or shrink a test that gets close instead of raising its timeout. CI reports every test over 30 s as a warning on the pull request.
Python 3 is optional; without it some relinker tests are not registered.
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:
Every push to `main`, including a merged pull request, automatically runs full Linux and Windows builds and tests and the relinker matrix on Linux, Windows and macOS, using the pushed commit without contributor checks.
For pull requests, full Linux and Windows builds and tests run only when a collaborator with write access selects Actions > Build > Run workflow on `main` and enters an open pull request number targeting `main`. A collaborator can start the same manual run from the command line, replacing `1234` with the pull request number:
```
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` 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 [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.
check_cxx_source_compiles("#include \"SDL_config.h\"\n#ifndef SDL_JOYSTICK_HIDAPI\n#error SDL_JOYSTICK_HIDAPI is not defined\n#endif\nint main() { return 0; }"ANYPS5_SDL2_HAS_HIDAPI)
unset(CMAKE_REQUIRED_INCLUDES)
if(NOTANYPS5_SDL2_HAS_HIDAPI)
message(FATAL_ERROR"ANYPS5_USE_SYSTEM_DEPS: the system SDL2 does not define SDL_JOYSTICK_HIDAPI, so a DualSense gets no light bar, touchpad or trigger effects and the disk audio driver does not preserve the requested format; sdl2-compat (SDL3 underneath) and SDL2 builds with HIDAPI disabled both fail the pad and audio tests; install a real SDL2 2.x built with HIDAPI")
endif()
find_package(FreetypeREQUIRED)
if(NOTTARGETFreetype::Freetype)
message(FATAL_ERROR"ANYPS5_USE_SYSTEM_DEPS: freetype was found but does not provide Freetype::Freetype")
message(FATAL_ERROR"ANYPS5_USE_SYSTEM_DEPS: the system Vulkan-Headers do not provide VkPhysicalDeviceMaintenance8FeaturesKHR (VK_KHR_maintenance8), which the agc driver uses; install Vulkan-Headers 1.4.310 or newer")
endif()
find_package(SPIRV-HeadersCONFIGREQUIRED)
if(NOTTARGETSPIRV-Headers::SPIRV-Headers)
message(FATAL_ERROR"ANYPS5_USE_SYSTEM_DEPS: SPIRV-Headers was found but does not provide SPIRV-Headers::SPIRV-Headers")
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.")
if(info.BindSize!=0||info.LazyBindSize!=0||info.WeakBindSize!=0)throwstd::runtime_error("Mach-O bind opcodes are not supported; link with chained fixups");
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");
check(bound[1]==1&&bound[2]==static_cast<std::uint32_t>(address)&&bound[3]==static_cast<std::uint32_t>(address>>32u),"indirect base address mismatch");
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");