Commit Graph

669 Commits

Author SHA1 Message Date
kuba ca76f7c8c8 [Gpu] Age cached images by frame, not by guest submission
The image collector runs after every completed guest submission as well as at each flip, and
every run advanced the age it compares against. A title submits dozens of command buffers per
frame, so "unused for 16 ticks" meant a fraction of a frame: every texture sampled once a frame
was deleted and recreated, and Superliminal uploaded 3,591 textures (2.2 GB) every 5 seconds.
Deleting a GPU-written image the cache cannot read back also dropped its contents.

Only a flip now advances the age. Collection after a submission still runs, so memory pressure is
relieved during long stretches without a flip. Superliminal's per-frame uploads went from ~55 to
none, and its intro scene from 12 to 45 fps.
2026-09-30 13:59:51 +02:00
kuba 0675cfa892 [SpirvTranslator] Structure loops, early exits and single-block programs
Only forward, nested branches were emitted as structured control flow; every program with a
loop, and every single-block program, went through the loop around a switch on the guest PC,
which keeps all registers live and stops the host compiler from optimizing across blocks.

A block that a later block branches back to - on a wave-uniform condition or with s_branch -
now opens a SPIR-V loop whose body is structured like any range, whose continue evaluates
that branch's condition, and whose iterations share the dispatcher's step guard so a guest
loop that never ends still cannot wedge the GPU queue. A conditional branch from the body to
the block after the latch is a break out of the innermost loop. Any other backward edge, or a
break past an outer loop, keeps the dispatcher as before.

Across eight games, programs left on the dispatcher went from 760 to 104 (Poppy, Silent Hill,
Oregon Trail and Dreaming Sarah to none); every emitted module passes spirv-val and the
frames match. Octopath's 12.5M-invocation instanced draw went from 12.2 to 10.8 ms.
2026-09-30 12:59:35 +02:00
kuba e4f0625c81 [SpirvTranslator] Mask a wave32 saveexec result at 64 bits
The low-half mask for s_*_saveexec_b64 on a 32-lane wave used the 32-bit AND helper on the
64-bit exec value, emitting OpBitwiseAnd %uint with a %ulong operand. spirv-val rejects the
module and the driver's behaviour for it is undefined; Octopath has four such programs.
2026-09-30 12:59:31 +02:00
kuba 94933b74d5 [ShaderCompiler] Solve user-data liveness on bit sets and hash graph nodes by id
The liveness fixpoint that picks a program's user-data registers walked 256 booleans per
block per pass; it now works on four 64-bit words per block with the same equations.

ScalarValue keys many graph dictionaries. It keeps reference equality but hashes its unique
id, which skips the runtime's identity-hash path.
2026-09-30 10:34:11 +02:00
kuba df5086f1eb [Cpu] Deliver guest access faults on macOS as signals, not runtime Mach exceptions
The runtime receives EXC_BAD_ACCESS for its threads through a Mach exception port before
the kernel raises SIGSEGV or SIGBUS. It treats a fault in the page holding RSP, or the page
below, as a stack overflow and aborts - assuming RSP is on a stack it owns. Guest code runs on
guest stacks whose pages fault whenever GPU or image write tracking protects them, so Demon's
Souls died with a bare "Stack overflow." about every other minute of play.

Threads now clear that port the first time they enter guest code. Their access faults reach
the signal handler, which resolves guest faults and chains the rest to the runtime's own
signal handlers, as on Linux. Each tracking fault also skips the Mach round trip, which
roughly doubled Demon's Souls' frame rate (about 2 to 4-5 fps).
2026-09-30 10:06:24 +02:00
kuba 4b210515bf [ShaderCompiler] Find each instruction's block by search and reuse register files
Several passes found a block's instructions by scanning the whole program once per block,
and the value graph did it again on every block visit. Blocks are sorted and contiguous, so
IrControlFlowGraph.BlockOf finds an instruction's block by binary search and each pass
groups the program once.

The value graph also allocated two 256-entry register files per block visit. Large Demon's
Souls compute shaders visit thousands of blocks, and the collections that caused stopped the
renderer for about 2.6 s of its first-frame shader work. Replaced entry and exit states are
now reused; a reused state is reset exactly as the constructor would, at the same point, so
the undefined values it starts with and the instructions they are charged to are unchanged.
The resource graphs and SPIR-V of the game's first 134 programs are identical.
2026-09-30 10:06:24 +02:00
kuba a01cffeb7b [HLE] Resolve single-view backing reads and writes without the views lock
Every HLE access to aliased guest memory took one lock shared by all guest threads, and
Demon's Souls' job threads queued on it for 8-byte mutex reads. Readers now binary-search a
copy of the view list; mutations drop the copy under the lock and the next reader rebuilds
it. A reader that races a mutation uses the list from before it, as if it ran first.
2026-09-30 10:06:24 +02:00
kuba 3fe148a262 [Kernel] Place mappings in untouched address space without waiting for the GPU worker
Every mapping change queued behind the GPU worker so it could not race a pending GPU read.
During loading that worker is busy translating shaders, and Demon's Souls' reservations and
batch maps waited seconds for a queue slot (23.6 s over 132 calls, up to 7.1 s each) while
the change itself took about a millisecond.

A change that only places memory where the GPU was never given anything - a kernel-chosen
address, or a fixed range that is empty or reserved and overlaps no registered span - cannot
race a GPU read and has nothing to unregister, so it now runs on the calling thread. Those
paths also skip the unregister request for free and reserved ranges, which are never
registered; waiting on the worker while holding the mapping lock that GPU reads take would
deadlock, so reaching a registered span there throws instead.
2026-09-30 10:06:24 +02:00
kuba 997b7df06f [Kernel] Take APR file sizes from the mount check's stat instead of listing directories
Resolving an APR path already stats every component to rule out links out of the mount.
The same stat now records the file's size, so size lookup no longer lists the file's whole
directory. Listing stats every entry: Demon's Souls keeps up to 7,390 files per asset
directory on a slow disk, and each first lookup in one paid for all of them.

FileInfo reports a missing component as -1 instead of throwing; it still ends the check.
2026-09-30 10:06:08 +02:00
kuba 467877d046 Merge pr/fiber-stack-bounds-windows-only 2026-09-30 02:45:10 +02:00
kuba 6f1075771f [Cpu] Keep fiber switches from writing stack bounds into the macOS TSD
The guest context-transfer stub that fiber switches go through keeps the
Windows TEB stack bounds in sync by storing the target stack top and
bottom to gs:[8] and gs:[16]. Unlike the guest-entry and exception stubs,
it did so on every host. On macOS gs addresses the pthread TSD, where
those slots hold errno's address and another key: a fiber switch wrote
its stack bounds over them, and the next failed libc call on the thread
(a timed-out condition wait) crashed reading errno through the pointer.
Demon's Souls died this way within 10-50 s of boot; a hardware
watchpoint on the thread's errno slot stopped in this stub.

The fiber bounds sync now lives next to the other TEB-bound helpers and,
like them, emits nothing off Windows.
2026-09-30 02:45:09 +02:00
kuba a6d7cfed0c Merge pr/sha-rewrite-register-spans 2026-09-30 00:57:24 +02:00
kuba 64a31401de Merge pr/batch-map-single-transaction 2026-09-30 00:57:24 +02:00
kuba 642f25d88c Merge pr/guest-path-resolution 2026-09-30 00:57:24 +02:00
kuba eb7521543c [Loader] Rewrite SHA instructions in spans that touch no guest memory
The red-zone patcher computes a shifted core for every span: the run of
faultable guest memory accesses that executes with RSP moved below the
red zone. A span over register-only instructions has no such access, so
the core could not be computed and the span was refused as too short.
SHA rewrite sites are exactly that shape (sha256msg1/msg2/rnds2 on
registers), so no SHA instruction was rewritten any more: on hosts
without the SHA extensions every one trapped and was emulated one at a
time. Poppy Playtime logged sha_rewrites=0 with 144 too-short refusals,
hit hundreds of thousands of illegal-instruction traps and stopped making
progress within a minute.

A span with no guest memory access has nothing that must run outside the
shift, and the span builder has already refused any instruction that
touches RSP, so the whole span is the core.
2026-09-30 00:57:23 +02:00
kuba aad66f89d5 [Kernel] Run one mapping transaction per sceKernelBatchMap
A mapping change has to be handed off to the GPU worker before any lock the
image and buffer reads need, and the handoff waits for the GPU to drain.
sceKernelBatchMap walked its entries calling the same per-mapping entry points
the single-mapping exports use, so every entry in the batch took its own
handoff and its own drain.

The batch now opens one transaction and runs all of its entries inside it. A
thread-local flag marks that a transaction is already open so a nested entry
point runs directly instead of handing off again, and the flag is cleared in a
finally so an entry that throws cannot leave it set.
2026-09-30 00:30:37 +02:00
kuba 84038be230 Merge #946 (pr/audio-abi-sizes)
# Conflicts:
#	src/SharpEmu.Libs/Audio/AudioOut2Exports.cs
2026-09-30 00:12:39 +02:00
kuba 60d0f0a4b2 Merge #950 (pr/draw-coverage) 2026-09-30 00:12:28 +02:00
kuba f3aa17d482 Merge #949 (pr/quiet-benign-logs) 2026-09-30 00:12:27 +02:00
kuba c1536c8798 Merge #948 (pr/hle-exports) 2026-09-30 00:12:27 +02:00
kuba 5e352878b0 Merge #947 (pr/structured-control-flow) 2026-09-30 00:12:26 +02:00
kuba 34ab5165a6 Merge #945 (pr/agc-interpolant-batching) 2026-09-30 00:12:25 +02:00
kuba 446ea699ed Merge #944 (pr/gpu-memory-allocations) 2026-09-30 00:12:25 +02:00
kuba c518335246 Merge #943 (pr/async-compute-pipelines) 2026-09-30 00:12:24 +02:00
kuba 1f56f7dd57 Merge #942 (pr/guest-thread-stacks) 2026-09-30 00:12:23 +02:00
kuba e702daeb59 Merge #941 (pr/gpu-fewer-drains) 2026-09-30 00:12:23 +02:00
kuba f254d5370a Merge #940 (pr/shader-isa-fixes) 2026-09-30 00:12:22 +02:00
kuba 73bfad7ed2 Merge #939 (pr/import-patch-serialization) 2026-09-30 00:12:22 +02:00
kuba f37b033b68 Merge #938 (pr/videoout-present-races) 2026-09-30 00:12:21 +02:00
kuba d12e404f9a Merge #937 (pr/cpu-fault-handling) 2026-09-30 00:12:21 +02:00
kuba 048933bfb8 Merge #936 (pr/tbdr-render-scope) 2026-09-30 00:12:20 +02:00
kuba 2f3ee0935c Merge #935 (pr/pixel-system-inputs) 2026-09-30 00:12:20 +02:00
kuba 585bf674c6 Merge #934 (pr/native-guest-fast-paths) 2026-09-30 00:12:19 +02:00
kuba ea6ac5028f Merge #933 (pr/spirv-constant-folding) 2026-09-30 00:12:19 +02:00
kuba 77aefa2bf3 Merge #932 (pr/sigterm-shutdown) 2026-09-30 00:12:18 +02:00
kuba 5f269ac87a Merge #931 (pr/sdl-audio-drain-sleep) 2026-09-30 00:12:18 +02:00
kuba d04084b6d5 Merge #930 (pr/wave-lane-state) 2026-09-30 00:12:17 +02:00
kuba 121e6fa062 Merge #929 (pr/audioout2-grain-pacing) 2026-09-30 00:12:17 +02:00
kuba e6d011eeb6 Merge #928 (pr/moltenvk-fence-retirement) 2026-09-30 00:12:16 +02:00
kuba a982b2800d Merge #923 (pr/spirv-register-variables) 2026-09-30 00:12:15 +02:00
kuba 6a041f2407 Merge #922 (pr/gfx10-mip-tail-block) 2026-09-30 00:12:15 +02:00
kuba fa6a3b6db8 Merge #921 (pr/avplayer-active-before-start) 2026-09-30 00:12:14 +02:00
kuba c04fd9029f Merge #920 (pr/audioout2-query-memory-size) 2026-09-30 00:12:14 +02:00
kuba 5943b3c433 Merge #919 (pr/red-zone-frame-pointer-spills) 2026-09-30 00:12:13 +02:00
kuba b219a8795c Merge #918 (pr/moltenvk-descriptor-sets) 2026-09-30 00:12:13 +02:00
kuba cb8feba766 Merge #917 (pr/agc-loaded-value-fill) 2026-09-30 00:12:12 +02:00
kuba 9c4ed82388 Merge #916 (pr/compute-lds-size) 2026-09-30 00:12:12 +02:00
kuba 3a9d755b7f [HLE] Do not log expected wait timeouts, mutex relocks and closed pad reads
The import gateway logs an error result unless the NID is on a list of calls
whose failure is a normal outcome. Four common ones were missing:
scePthreadCondTimedwait and sceKernelWaitEventFlag time out exactly as the
caller asked, scePthreadMutexLock reports EDEADLK to the owner of an
error-checking mutex (titles use that to build their own recursive locks), and
scePadReadState reports a handle that is not open because titles poll every pad
slot.

Poppy Playtime logs roughly 260k of these lines per session, which buries the
failures that do matter.
2026-09-30 00:11:15 +02:00
kuba fd8a78ad73 [Cpu] Read import stack arguments from the thread's own stack without a region query
Every import copies six stack argument slots (twice, with the per-thread
diagnostics). The per-thread readable-range cache is keyed by the host mapping
generation, which every tracker protection change bumps, so on macOS nearly
each slot fell through to a locked host region query: about half of the busy
managed samples at Octopath Traveler II's title were in that query or waiting
for its lock. Addresses inside the calling guest thread's stack, which the
backend maps read-write for the thread's lifetime, are now read directly.
2026-09-30 00:11:15 +02:00
kuba ae7ca8dfbe [CPU] Reuse import trampolines and leave patched stubs alone on module reloads
Every runtime module load sets up the import stubs of all loaded modules
again, and each setup built a fresh trampoline and rewrote the stub's 16 bytes
-- under guest threads that may be executing exactly those bytes.

A trampoline only encodes its import index, so the trampolines are now cached
by index and reused, and a stub that already jumps to the trampoline it would
be given is left untouched, with no unprotect/write/restore cycle at all. A
reload of a module whose stubs are already patched therefore writes no guest
code.

The trampoline ABI test builds its backend with GetUninitializedObject, which
skips field initializers, so it has to supply the new cache; both fields now
go through one helper that names a field it cannot find.
2026-09-30 00:11:15 +02:00