#50

ntdll ARM64EC: opt-in x64 fast paths for critical sections and TlsGetValue (MADEIRA_CS_X64, MADEIRA_TLS_X64)

Openmeshoklvwants to merge intofrom

Problem On ARM64EC every x64 call of an ntdll/kernelbase export goes through the export's fast-forward sequence (FFS) and leaves the emulator's JIT code for native code and back, roughly 100 cycles of glue per call. Games call EnterCriticalSection/LeaveCriticalSection and TlsGetValue hundreds of thousands to millions of times a second, almost always on the uncontended path. In Dark Souls III on an iPhone Air the process made ~1.2M x64→EC transitions a second, and RtlEnterCriticalSection alone was about two thirds of the sampled ones; after that, kernelbase TlsGetValue was the top entry at ~262k calls/s.

Change New dlls/ntdll/cs_x64_fastpath.c, included by loader.c and installed once from load_arm64ec_module() after arm64ec_process_init() (the emulator is up, no x64 code has run yet). Both switches are off by default.

  • MADEIRA_CS_X64=1 overwrites the FFS of RtlEnterCriticalSection, RtlLeaveCriticalSection and RtlTryEnterCriticalSection with jmp [rip] to small x64 routines that handle the uncontended and recursive cases in x64 (translated once by the JIT), using Wine's lock protocol (LockCount -1 = free), so x64 and ARM64EC callers can share a section. Contention, waking a waiter (RtlpUnWaitCriticalSection) and errors jump to the original ARM64EC functions. ARM64EC callers are unaffected.
  • MADEIRA_TLS_X64=1 does the same for kernelbase TlsGetValue: an index below 64 is read from TEB->TlsSlots (and last error is cleared, as Wine does); anything else goes to the ARM64EC function.
  • An FFS that is not the linker's byte pattern (already hooked) or a forwarder is left alone; the CS part is all-or-nothing. One [cs-x64] / [tls-x64] log line says what was installed. For TlsGetValue the line also says whether kernel32's thunk reaches the stub; in this build it does not, so callers bound to kernel32 keep the round trip.
  • The stubs hard-code the x64 layouts, which are checked with C_ASSERTs.

Tested (iPhone Air, iOS 27.2, Dark Souls III, ntdll.dll built from this code on the pinned commit)

  • MADEIRA_CS_X64=1 (26-minute session): x64→EC transitions, median of the 1 s [xp-api] windows, 1.21M/s → 0.79M/s; the remaining sampled RtlEnterCriticalSection transitions were ~7.6k/s. Heavy-scene frame rate ([gpu-perf] windows with > 300 draws/frame) went 16.9 → 21.5 fps median, but that run also turned on Madeira's existing MADEIRA_LEAN_DIAG and inproc-sync and a FEX change, so the share of this switch was not measured.
  • MADEIRA_TLS_X64=1 (12-minute session): kernelbase TlsGetValue was in the sampled top transitions ([xp-api-top]) in 1223 of 1225 windows before and in 0 of 554 with the switch. Heavy-scene frame rate went 21.5 → 36.7 fps median (p10 15.2 → 28.8; 32–34 fps after thermal throttling, performance cores at 1.31 GHz), but that run also turned on MADEIRA_SYNC_LEAN (#51), Madeira's MADEIRA_THREAD_QOS (willfaust/Madeira#401) and two DXMT switches (willfaust/dxmt#43, willfaust/dxmt#42); this switch's share was not measured either.
  • No freezes in either session with the switches on.
  • The only difference from the DLL that ran on the device is comments; loader.c recompiled with the arm64ec toolchain.

🤖 Generated with Claude Code