Trace the dispatcher fallback for PS 0x9e1133a6 to an acyclic RDNA2
S_CBRANCH_SCC0 ladder. Several inner headers reach both a shared
continuation and a shared terminal, so they do not dominate the common
post-dominator. The old shared-merge splitter redirected only the dominated
terminal edge; the merge therefore remained shared and forced dispatcher
lowering.
Canonicalize this control flow with bounded cross-entry tail duplication:
- find the complete selection region before its post-dominator
- clone only region blocks that the header does not dominate
- redirect header-owned edges into those clones
- join every owned exit through a private synthetic merge
- retain the existing dispatcher when loops, invalid merges, or growth bounds
make duplication inappropriate
Add the recovered seven-block topology as a focused regression. Assert SCC0
taken/fallthrough orientation, unique merges, full post-transform reachability,
structured lowering without OpSwitch, and valid SPIR-V.
Validation:
- shader_cfg_tests --overlapping-cfg-only
- shader_cfg_tests --loop-break-merge-only
- shader_cfg_tests --loop-canonicalization-only
- kyty_emulator built with _Build/vscode-clang
- launch.json visible run advanced continuously to frame 570
- observed seven-block PS e83e3fb5 structured into 19 blocks without fallback
- run stopped at the separately deferred sparse PRT BufferCache backing fatal
Independent audits verified progress, ID remapping, vector lifetime, invalid
merge handling, bounded termination, and dirty-tree staging scope.
Trace the invalid SPIR-V emitted for the real 0x6c326400 and
0x090291ef00 compute shaders back to natural-loop construction. A guest
conditional could serve as both an OpLoopMerge and OpSelectionMerge
header, while multiple native latches could produce more than one SPIR-V
backedge for a loop.
Canonicalize those RDNA2 control-flow shapes before merge splitting:
- join multiple latches through one empty continue block
- put an internal guest-header selection behind an empty loop header
- rebuild CFG analyses after each bounded rewrite
This follows shadPS4's dedicated loop header/continue architecture
without introducing a dispatcher or compatibility fallback. Add focused
CFG and SPIR-V validation tests for both real failure shapes.
Validation:
- shader_cfg_tests --loop-canonicalization-only
- shader_cfg_tests --loop-break-merge-only
- shader_recompiler_compute_tests
- spirv-val Vulkan 1.1 for regenerated 0x6c326400 and 0x090291ef00
- launch.json runtime advanced continuously to frame 846 without fatal,
crash, or Vulkan validation error
The no-argument CFG suite still exposes the independently reproducible,
loop-free cube descriptor identity failure in the concurrent dirty tree.
Emit innermost break, continue, and repeat conditionals without selection
merges, matching SPIR-V structured-loop rules and shadPS4's control-flow
model. Split nested construct merges that would otherwise alias an outer
merge or continue target, using the full dominance-defined construct.
Add focused regressions for early loop control, nested local and nonlocal
exits, conditional latches, illegal mixed exits, and acyclic exit tails.
The focused suite passes embedded Vulkan 1.2 validation. The real
0x6c341200 compute shader now structurizes to 61 blocks and its dumped
98,650-word module passes standalone spirv-val.
Why: Thread-atexit registrations were discarded, leaving objects alive after their guest TLS storage was released.
What: Store registrations per host thread and run them in LIFO order before pthread keys and guest TLS are destroyed.
Why safe: Only callbacks registered on the exiting thread run, once, before existing teardown continues.
* Sampler dword 3 only matters when a clamp mode uses border color
(values >= 4). When no border mode is active, dword 3 is unused
but can still vary across loop iterations due to wave-lane spills.
This makes resource tracking think the descriptor is dynamic and
fail with "unsupported GPU selection".
* Fix by zeroing dword 3 when all clamp modes are non-border.
Signed-off-by: Claxten <claxten10@gmail.com>
fix(cmake): anchor the macOS guest address space for all full-emulator tests
Every target created by add_kyty_full_emulator_test links against the
full kyty_emulator sources, so it drags in the same 620 GiB .zerofill
guest address space segments as the emulator itself. Only the emulator
target and virtual_memory_allocation_tests had the linker flags that
anchor those segments; every other full-emulator test target got the
segments without the anchoring, and the kernel killed them on exec
(posix_spawn EIO / SIGKILL) before main() ever ran.
Move the configure_macos_guest_address_space() call into
add_kyty_full_emulator_test() itself so every target it creates gets
it automatically, and drop the now-redundant explicit call on
virtual_memory_allocation_tests.
* fix: keep hint-less guest mappings at the canonical PS5 base
FindGuestFreeRange searched the low system-managed range first for
mappings with no address hint, so the first hint-less direct-memory map
could land as low as 0x200000. The PS5 kernel never places hint-less
user mappings below 0x200000000 and guest code relies on that: Sony's
libc maps 4 MiB of direct memory for its internal heap, fails its
mspace setup when the returned address is that low, and the first
malloc then dereferences a null mspace (a read at 0x38, the mspace
magic check). On macOS this made Raiden III crash on the main guest
thread a couple of seconds after boot, 100 percent reproducible with
--printf-direction Silent.
Search from the canonical base first, fall back to the user range, and
keep the low system-managed range only as a last resort. The mmap path
already anchored hint-less searches at 0x200000000; this aligns the
shared search helper with it.
Adds two regression tests: the libc-shaped allocation must come back at
or above the canonical base and hold writes, and direct-memory content
must survive an unmap and remap of the same physical range.
* macos: make the fatal-report memory dumps fault-safe
IsReadableRange returned true for any nonzero address on macOS, so the
fatal report's guest memory dumps dereferenced whatever the crashed
thread had in its registers. A fault inside the reporter re-enters the
signal handler and wedges the reporting thread, which hid real guest
crashes whenever logging was enabled: the game kept running with a dead
thread and the report was never completed.
Walk the Mach regions covering the range and require read permission
before dumping, the same contract the Linux implementation provides.
* do not fallthrough HOST_SYSTEM_MANAGED_MIN
---------
Co-authored-by: nmzik <Nmzik@mail.ru>
* A one-dword type-2 NOP is a valid packet tail. Parse it normally instead of aborting command-buffer dumps.
Signed-off-by: Claxten <claxten10@gmail.com>
The README had macOS badges and an experimental-support note but no build,
run, or system-requirement information for the platform. Document the
Rosetta 2 / MoltenVK setup, the x86-64 configure invocation, the Qt
universal-build requirement, MoltenVK installation and signing, and the
SDL_VULKAN_LIBRARY variable needed at run time.
macos: enable guest signal dispatch on the target thread
The POSIX signal-dispatch path (pthread_kill based, added with the Linux
port) was compiled out on macOS, leaving KernelRaiseException to run the
guest handler on the calling thread. IL2CPP's garbage collector raises its
stop-the-world signal at every managed thread and each handler parks its
own thread until resume, so the collector parked itself and every Unity
title froze on the first collection.
Enable the same delivery path on macOS:
- translate between the Darwin mcontext (uc_mcontext->__ss) and the guest
ucontext in CreateSignalUcontextFromHost/ApplySignalUcontextToHost
- use SIGUSR1 as the host dispatch signal (macOS has no realtime signals)
- block the dispatch signal inside the host fault handler so a suspend
request cannot preempt fault resolution between the protection fix and
the retry
Windows and Linux are unchanged.
src: platform: Linux: Drop redundant PROT_NONE tracking in reserve paths
* Some UE4 games, such as The Pathless, reserve a 512 GiB virtual address range during libc startup.
Tracking every 4 KiB page causes a long delay and is unnecessary since the range is already PROT_NONE,
and untracked pages are treated as NoAccess.
Signed-off-by: Claxten <claxten10@gmail.com>