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=1overwrites the FFS ofRtlEnterCriticalSection,RtlLeaveCriticalSectionandRtlTryEnterCriticalSectionwithjmp [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=1does the same for kernelbaseTlsGetValue: an index below 64 is read fromTEB->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. ForTlsGetValuethe 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 sampledRtlEnterCriticalSectiontransitions 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 existingMADEIRA_LEAN_DIAGandinproc-syncand a FEX change, so the share of this switch was not measured.MADEIRA_TLS_X64=1(12-minute session): kernelbaseTlsGetValuewas 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 onMADEIRA_SYNC_LEAN(#51), Madeira'sMADEIRA_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.crecompiled with the arm64ec toolchain.
🤖 Generated with Claude Code