Skip to content

docs(shader): record the measured s_call_b64 and null s_swappc_b64 behaviour - #12

Merged
SleglJan merged 242 commits into
mainfrom
docs/shader-scalar-measurements
Oct 10, 2026
Merged

SleglJan merged 242 commits into
mainfrom
docs/shader-scalar-measurements

Conversation

@SleglJan

@SleglJan SleglJan commented Oct 9, 2026

Copy link
Copy Markdown
Owner

What

Two TechnicalDebt entries said what the recompiler assumes without a hardware measurement. Measured on RDNA2 (gfx1036, Ryzen 7950X3D iGPU) with tools/hw-oracle (Linux, CachyOS kernel 7.2.9, hsa-rocr 7.2.4, clang 23.1.1 as the assembler, tools at main d70b899, mode IEEE 0, f32 denormals flushed, f16/f64 kept, DX10_CLAMP 1, RNE), in wave32 and wave64, and recorded in the entries:

  • s_call_b64 (SIMM16 = 3): the link pair receives PC + 4 and the branch goes to PC + 4 + signext(SIMM16) * 4; s_setpc_b64 of the link returns to the instruction after the call (a counter shows the callee, +1, and the return path, +2, each ran once). The entry's "native RDNA2 ... execution" becomes measured; execution on the PS5 (gfx1013) and title compatibility stay unverified.
  • s_swappc_b64 with SDST 125 (the null register): the hardware branches to the source pair (the instruction at the skipped address is not executed) and leaves a marker SGPR pair untouched; with an SGPR destination the link is PC + 4. So the existing decode as s_setpc_b64 matches the hardware.
    The s_memtime measurements from the same run are posted on fix(agc): detect AMD proprietary narrow clock boykopovar/AnyPS5#1632, which rewrites that entry.

No code changes. Bodies, rows, outputs and the script: https://github.com/SleglJan/AnyPS5/tree/audit/oracle-2026-10-08/oracle-audit/scalar-misc.

Tested

tools/check_conventions.py --base upstream/main passes; the entries are documentation only.

Overlap: boykopovar#2302 (open) edits the same s_swappc_b64 entry; whichever merges second needs a one-line rebase.

Checklist

  • 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
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • 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: none
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

TideOPL and others added 30 commits October 8, 2026 10:06
…sed memory

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.
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>
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>
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>
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.
… RELEASE_MEM packets

Co-Authored-By: Claude Opus 5.5 (1M context) <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>
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>
…RROR_UNLOADED

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>
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>
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.
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>
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>
…buffers

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.
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.
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.
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.
… load

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mpty PT_TLS

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>
oneandonlydean and others added 28 commits October 10, 2026 17:43
…-restart-index

fix(agc): leave the primitive restart index out of an indexed draw's vertex range
…data-tail-padding

fix(agc): read shader user data without tail padding
…nal-prim-register

fix(agc): accept unused geometry output register
…viron

feat(libkernel): export environ as an empty environment
…te-range-wrap

fix(relinker): reject a relocation target whose range wraps the address space
…rgets

fix(agc): render into 1D color targets as one-row 2D targets
…ps-input-cntl-clear-state

fix(libSceAgcDriver): SPI_PS_INPUT_CNTL registers start at their clear-state value
…d-log

fix(agc): remove display upload logs
…t-pre-vblank-start-events

feat(libSceVideoOut): deliver pre-vblank start events
test(relinker): isolate Windows diagnostic fixtures
…caltime-state

fix(libkernel): initialize the full local-time conversion state
…ize-blits

fix(agc): draw nothing for resummarize draws without depth or color work
…unds-absent-planes

fix(agc): pass the depth bounds test when both depth planes are absent
…wport

fix(agc-driver): rasterize nothing through a zero-scale viewport
…rker-pop

fix(agc-driver): ignore marker pops without a pushed marker
…-name

fix(libc): define APS5_LOG_OUT_DEBUG when APS5_DEBUG_LOG is set
…location-bounds

fix(relinker): validate relative relocation table bounds
…mory-unmaps-views

fix(libkernel): unmap direct memory views when the memory is released
…taken-tls-roots

fix(relinker): root code addresses taken by RIP-relative LEA
…nker-inputs

fix(relinker): protect executable inputs from output overwrites
…-sleb128-overflow

fix(relinker): reject overflowing signed unwind LEB128 values
…before-report

fix(agc): fail a failed submission's flips before reporting the failure
…d-eh-frame-headers

fix(relinker): reject truncated EH frame headers
…-identification

fix(relinker): validate ELF identification before parsing
…concurrent-first-lock

fix(libkernel): initialize guest rwlocks before they are shared
…ader-module-lifetime

fix(agc): release shader modules after pipeline creation
…link-directory-eperm

fix(libkernel): return EPERM when unlinking a directory
…haviour

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
@adriadam10
adriadam10 force-pushed the docs/shader-scalar-measurements branch from c81b5af to 9376a02 Compare October 10, 2026 06:17
@SleglJan
SleglJan merged commit b5315f0 into main Oct 10, 2026
11 checks passed
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.