Skip to content

sync(upstream): integrate Batch 39 render-pass reads, macOS libkernel, and argument-validation parity - #564

Merged
RINNECODER merged 41 commits into
mainfrom
team/upstream39
Oct 18, 2026
Merged

RINNECODER merged 41 commits into
mainfrom
team/upstream39

Conversation

@RINNECODER

Copy link
Copy Markdown
Owner

Summary

Upstream sync batch 39: 41 commits / 32 upstream PRs on top of main (29 from boykopovar/AnyPS5 main since
2026-10-17, 3 from open upstream PRs), plus one fork fix for pre-existing conflict markers in DepthSurface.cpp.

Reconciliation: the rebase merged the additive test blocks in libkernel/CMakeLists.txt (guest_pthread_frame_chain
from main, guest_pthread_attr_stack from boykopovar#7026). fix(agc): drop committed conflict markers in DepthSurface.cpp
removes an empty-vs-theirs conflict block an earlier sync left in the file; it keeps the storage view members the
block carried, which later code calls.

Validation

  • Conventions: tools/check_conventions.py --base main pass, 0 findings; git grep -nE '^(<<<<<<<|>>>>>>>)' empty.
  • Rosetta CTest (x86_64): 25/25 passed.
  • Native Metal CTest (arm64, guest-replay): 63/63 passed.
  • Replay gate under the AGC lock: agc-map bit-exact hash 51a268f2e73efb9c (stable over the timed repeats);
    agc-live-map 88 px against a main baseline dump (allowed 200, 0 timed repeats over); agc-menu 59 px (allowed
    200, 0 timed repeats over). The unstable-hash warning on the two flickering frames is the known compute-shader race.

Findings

Finding Severity Resolution
none at open time - -

BIGFERRY999 and others added 30 commits October 18, 2026 17:32
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 22c584a)
fcntl threw for F_GETLK with an F_UNLCK request on the hosts that use POSIX locks, as Linux's F_GETLK rejects that type. FreeBSD 11.4's kern_fcntl accepts it and lf_getlock reports the first overlapping lock that lf_blocks lets conflict: another owner's lock, one of the two being a write lock. For an F_UNLCK request that is exactly another owner's write lock, the same set an F_RDLCK request finds, so the host is asked with F_RDLCK and the reply is written back as for the other types.

(cherry picked from commit 41ec785)
The fcntl tests only ever held locks in the guest's own process, where FreeBSD's lf_blocks never reports a conflict, so the host lock path that answers F_GETLK was not exercised. A forked process now holds a read lock, a write lock and a write lock to the end of the file through the host. F_GETLK with F_UNLCK reports only the write locks, F_RDLCK the same and F_WRLCK the read lock, each with the owner's pid and SEEK_SET; F_SETLK over the read lock fails with EAGAIN, and nothing is reported once the owner exits.

(cherry picked from commit 9ece094)
MeasureStackReserve took the distance from the stack top to a local
variable. In an ANYPS5_ENABLE_ASAN build that local lives on
AddressSanitizer's fake stack (detect_stack_use_after_return is on by
default on Linux), away from the thread's stack, so the reserve could
come out negative. scePthreadCreate then asked the guest arena for a
stack of nearly 2^64 bytes and threw 'guest address space arena
exhausted'. The frame address is on the real stack in every build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e88b7de)
An ITIMER_REAL or alarm expiry while SIGALRM is blocked is marked on the
pending set of sigtimedwait instead of throwing, as kern_psignal leaves a
blocked signal pending. sigwait, sigwaitinfo and sigtimedwait take it,
repeated expiries merge into one pending signal, and unblocking it runs a
guest handler on the unblocking thread or, with the default disposition,
exits the process with status 142 as FreeBSD's default action does.

The expiry rechecks the mask after marking the signal, so an unblock that
races it does not leave SIGALRM pending while unblocked, and TakePending
clears the bit with the value it read so one signal is taken once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1bc029d)
scePthreadCondInit, and pthread_cond_init through it, threw when attr
pointed to a null attribute: a zeroed one that was never initialized or
one that scePthreadCondattrDestroy has set to null. FreeBSD 11.4 libthr
(thr_cond.c, cond_init) takes a null attribute as the default one and
creates a CLOCK_REALTIME condition variable. A PS5 (firmware 13.60)
takes a zeroed attribute as the default one in scePthreadMutexInit
(boykopovar#4158). Take it as the default here too, as scePthreadMutexInit and
scePthreadCreate already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 111d817)
… pthread locks

pthread_rwlock_timedrdlock, pthread_rwlock_timedwrlock and
pthread_mutex_timedlock threw for a null abstime. FreeBSD's libthr
passes a null abstime on to the untimed kernel lock, so these calls
then wait like pthread_rwlock_rdlock, pthread_rwlock_wrlock and
pthread_mutex_lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 676ee71)
FreeBSD's getrusage rejects an unknown `who` with EINVAL before it
copies anything out and returns EFAULT when the result can't be
written. The HLE threw std::invalid_argument for both, which ends the
title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 01c8ceb)
… null buffer

read and write already fail with EFAULT for a null buffer and a
non-zero length and treat a zero-length null buffer as empty. The
sceKernel entry points threw std::invalid_argument for both. They now
do the same as read and write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b437dc5)
… size calls

scePthreadAttrGetstack, Getstackaddr, Getguardsize, Setguardsize,
Setstack and Setstackaddr threw for a null attribute, a null pointer
argument or a stack below the 16 KiB minimum. FreeBSD's libthr returns
EINVAL for those, as the pthread_attr_* wrappers here and the other
scePthreadAttr calls already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 19b236a)
…ove SSIZE_MAX

FreeBSD's read, write, pread and pwrite reject a byte count above
IOSIZE_MAX with EINVAL before they look up the descriptor. write threw
std::invalid_argument for a negative count, and the others passed the
count on to the host, which reports EFAULT on Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 28acd09)
…tschedpolicy like libthr

FreeBSD's libthr rejects a null sched_param and, under SCHED_FIFO or
SCHED_RR, a priority outside sched_get_priority_min..max with ENOTSUP,
and a policy outside SCHED_FIFO..SCHED_RR with ENOTSUP. The wrappers
returned EINVAL for the null parameter and stored any priority or
policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b693ef2)
…tect for a zero length

mprotect already returns 0 for a zero length. The sceKernel entry
points passed it to DoMprotect, which throws for an empty range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit cbf006c)
…inter

clock_gettime threw invalid_argument when the result pointer was null,
before looking at the clock id. FreeBSD 11.4's sys_clock_gettime reads the
clock through kern_clock_gettime, which fails with EINVAL for an unknown
clock, and only then copies the result out, so a null pointer fails with
EFAULT.

Read the clock into a local value first, then fail with -1 and EFAULT when
the pointer is null. A clock that is not supported still throws before the
pointer is looked at. Test the null pointer for the supported clocks.

Written with AI assistance (Claude).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit c17c6a5)
kevent threw for NOTE_LOWAT. On a socket's EVFILT_READ it now sets the
low-water mark as FreeBSD 11's filt_soread does: the event fires once the
buffered bytes reach it, or at end of file or on a listening socket, and
a later change of the knote replaces it. EVFILT_WRITE takes a mark of 1,
where the host's writability stands in for a free byte; other write marks
and marks below 1 still throw. A pipe ignores the mark, as filt_piperead
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e1d4ccb)
…he PS5 does

On a PS5 (firmware 12.70), of the values 0, 1, 2 and -1,
pthread_spin_init, pthread_barrierattr_setpshared and
pthread_mutexattr_setpshared accept only PTHREAD_PROCESS_PRIVATE (0);
1, 2 and -1 return EINVAL and leave the attribute unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit db504f6)
…D does

66 E8, 66 E9 and 66 0F 80-8F without REX.W take a 16-bit displacement on
AMD processors, the console included. Every near relative branch with that
prefix truncates its target to 16 bits, so the decoder reports no branch
target for it and stubs refuse to move it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TFMtUC4L4wMS4eeEqaNSR
(cherry picked from commit 206aaa5)
…nches

A short TLS access followed by a near branch with an operand-size prefix
decided the branch was relative from its flow kind and opcode alone, then
re-encoded it to the fall-through address or wrote its rel32 over the prefix
and opcode. Such a branch now counts as one that cannot move.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TFMtUC4L4wMS4eeEqaNSR
(cherry picked from commit aed1ffb)
…-bit store first

A 256-bit store is 4 bytes, so its stub window takes a neighbouring
instruction to make room for the jump. The window took the following
instruction first even when it was a guest TLS access (an FS or GS prefix).
The macOS writer cannot rewrite a TLS access inside a stub and refuses the
module with "AMD-only instruction is also a guest TLS access".

The window now tries the following and the preceding instruction without a
segment prefix first, and falls back to the old order only when neither
works, so outputs that can keep the access in the stub do not change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 513b2e1)
…t store

Covers a thread-pointer read (FS and GS form) after the store with a movable
instruction before it, and the fallback to the following access when there is
no other neighbour.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit cf9e5a2)
DrawPassTotals returns, since the process started, how many recorded
draws began a render pass of their own or continued the previous draw's,
and how many flushed the queued DCC key stores or label stores before
themselves, for address-based draws and for the others. A draw is
address-based when its resources hold the allocation registry lease,
whether they were built for it or found in the draw cache.

The totals are kept with the [draws] line's (APS5_PROFILE_DRAW), so a
test or a harness can tell whether an address-based draw kept the open
pass without reading the 10-second report.

(cherry picked from commit 2d4512e)
…its render pass

recordDraw passed every address-based draw to the pass hazard tracker as
both an unknown reader and an unknown writer of guest memory. An unknown
writer conflicts with any earlier access in the pass, so no address-based
draw ever continued the previous draw's pass: each one ended the pass and
began a new one, which loads and stores the attachments again. At Resident
Evil Village's menu about half of all draws are address-based, and about
590 render passes begin for about 850 draws per frame.

A build whose programs only read through the BDA table now reports unknown
reads only. The tracker already orders unknown reads behind any recorded
range write in the pass and keeps later range writes behind them, so such
a draw continues a pass whose earlier draws wrote no guest ranges. Builds
with stores through the table, copied writes, or an address lease without
a table keep both flags. APS5_NO_ADDRESS_READ_PASSES=1 restores the old
behaviour.

Render targets reach guest memory only by explicit write-backs, so a read
through the table sees the same bytes whether or not the pass breaks.

Raghav314's boykopovar#6947 makes the same change inside its GPU descriptor heap,
with the read-only fact from boykopovar#6946's recompiler field; this one keeps it in
the driver, without a shader cache format change. The idea also comes from
fable-void's 5d57298 (wip/new-gpu-path), which orders address-based
builds by their known access ranges; this takes the read-only half without
his run-time pre-pass bounds.

(cherry picked from commit a939efe)
…der pass

agc_driver_address_passes draws a triangle twice onto a guest block, with pixel shaders that global_load_dword or global_store_dword through s[2:3], and reads DrawPassTotals for the address-based draws:
- --readers: two draws reading through the table share one render pass;
- --store-after-read: a draw storing through the table after one reading through it begins its own pass;
- --read-sees-store: a draw reading through the table what the previous draw stored sees the store, in a pass of its own.

Taken from c44c9a6 (perf/agc-bounded-address-passes) without its --store-beside-reads case, which needs that commit's store bounds.

(cherry picked from commit b3e94a5)
…ke the sampled one

StorageTextureBudget gave the storage image cache a quarter of the VK_EXT_memory_budget
budget of the largest device-local heap (capped at TextureCacheBudget, never below 512 MiB)
whatever else the heap held, while SampledTextureBudget takes what that budget leaves after
the heap's other use. On a 10 GB card the storage cache kept 2024-2170 MiB into a level load
whose depth and render target allocations then failed with VK_ERROR_OUT_OF_DEVICE_MEMORY
with the heap at its reported budget where a run logged it.

The storage image budget is now what the reported budget leaves after the heap's use other
than the cached storage images, the unused part of the sampled textures' 2 GiB floor and an
eighth of the budget; the caps at a quarter of the budget and at TextureCacheBudget and the
512 MiB floor stay. The sampled budget still reserves TextureCacheBudget for the storage
cache, at least the storage cap, and the storage budget reserves the sampled textures alive,
so neither counts bytes the other may take; while the sampled textures alive stay within
their floor the sampled budget falls first, then the storage budget, and above it both fall
together. The budget line also prints when the storage image budget moves by more than a
tenth, with its previous value, and StorageTextureCacheBudget is exported for the execution
test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 39c2366)
oneandonlydean and others added 11 commits October 18, 2026 17:34
…a pin wait

The Windows release of imports retired during a settle read belongs after the whole
overlapsImport block in the not-holding-the-lock branch of WaitForLeasesAndImports, as in
the original change, not inside it. It was placed inside while main was merged in.

(cherry picked from commit fd301c7)
…ved bits

An image_sample_*_a sampler whose dword 3 is built only from values
inside the reserved bits 12-29 was refused with "GetSamplerResource
dword 3 is not a valid runtime value" when the value was not an OR at the
top. The reserved bits of a sample-adjust S# are already dropped term by
term from an OR (canonicalizeSampleAdjustDword3); a dword 3 whose
possible bits all lie in that mask, such as a shifted field with nothing
else, is now dropped whole the same way. PPSA01317 builds such a dword 3
from a wave ballot shifted to bit 12 for image_sample_a in a pixel
shader. A plain image_sample with the same S# is still refused.

AI-assisted: yes
(cherry picked from commit 6e5d708)
select(c, a, select(c, b, d)) is select(c, a, d), and
select(c, select(c, a, b), d) is select(c, a, d). The recompiler leaves
these behind when a register is written more than once under the same
lane mask and when the f32 NaN selection tests the same source twice,
and nothing folded them. The constant folder now takes the inner arm
the outer condition already decides, then folds a select whose arms
became equal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 227d1e4)
On AMD's Windows driver 32.0.21045.5002 (RX 6700 XT), a product of two
values the driver knows fit in 16 bits, reduced to 16 bits and stored
with nothing else merged in, keeps all 32 bits of the product.
v_mul_lo_u16 and v_pk_mul_lo_u16 extracted 16-bit factors, so into a
register whose high half was zero they wrote 0xfffe0001 for
0xffff * 0xffff. A factor taken from the low half of its source is now
read as the low 24 bits of the register; the low 16 bits of the product
depend only on the low 16 bits of the factors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ffd468e)
macOS does not support getsockopt with SO_ACCEPTCONN; it fails with
ENOPROTOOPT. So on macOS the guest's getsockopt of SO_ACCEPTCONN failed,
connect did not see that a stream socket was listening (an address of
another family failed with EAFNOSUPPORT instead of EOPNOTSUPP), and the
read event of a listening socket reported FIONREAD's 0 instead of the
waiting connection and did not ignore its low watermark.

On macOS the listening state is read from the SO_ACCEPTCONN flag of the
socket options that proc_pidfdinfo reports, and the three places that ask
go through NativeListening. Other hosts keep getsockopt.

(cherry picked from commit 6f2a61a)
getrlimit(RLIMIT_NOFILE) reports the host's limits, and libkernel
raised the soft limit to the hard one at load only on Linux. On macOS
the soft limit stayed below an unlimited hard limit (1048576 and
infinity in a shell here, 256 and infinity by default), so the two
differed. XPSemu (PPSA97358) then raises the soft limit to the hard one
in QEMU's os_setup_limits, and setrlimit refuses the change.

macOS fails with EMFILE at kern.maxfilesperproc (10240 here) whatever
the soft limit, even an unlimited one. At load, libkernel now sets both
limits to the smaller of the hard limit and kern.maxfilesperproc, so
getrlimit reports the descriptor count after which opening fails. The
Linux path still raises the soft limit to the hard one.

(cherry picked from commit 7391082)
guest_getrlimit failed on macOS because the host's soft and hard
RLIMIT_NOFILE differed. On macOS it now also checks that the limit does
not exceed kern.maxfilesperproc, and, as on Windows, that guest pipes
fail with EMFILE within 64 descriptors of the reported limit. The pipe
loop moves into a helper that both use.

(cherry picked from commit 82af3e0)
macOS's strtoll and strtoull set errno to EINVAL when the subject has
no digits, as in "", "words" or " +". The guest's integer conversions
pass that to the title, while guest_atoi, guest_wide_conversion and
guest_math expect errno unchanged, as glibc leaves it. So atoi("")
returned 0 with errno 22 on macOS, and all three tests failed there.

A ParseInteger helper in SonyCrt.hpp calls the host's conversion and,
on macOS, restores the caller's errno when nothing was converted and
the base is valid. An overflow still sets ERANGE, and an invalid base
still sets EINVAL. strtol, strtoul, strtoll, strtoull, atoi, atol,
atoll, _Stoull and the wcsto* integer conversions in Strings.cpp use
it. On other hosts the helper calls the conversion unchanged.

(cherry picked from commit 839acfd)
The conversions in RuntimeSupport.cpp call the host's strtoimax,
strtoumax and strtoull directly, so they set EINVAL on macOS for a
subject without digits too. guest_math, once the strtol cases pass,
failed on strtoimax(""). They now go through ParseInteger.

(cherry picked from commit 8469126)
An earlier sync left an empty-vs-theirs conflict block in the file; keep
the storage view members it carried, which later code calls.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-18T23:02:54.321486Z f4e4c4c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 47fa1ebf-12b3-471d-bfb3-e088ed784923
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@RINNECODER
RINNECODER merged commit d650512 into main Oct 18, 2026
1 check passed
@RINNECODER
RINNECODER deleted the team/upstream39 branch October 18, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.