How SharpEmu's Direct CPU Execution Backend Runs PS5 x86-64 Instructions at Native Speed
SharpEmu's DirectExecutionBackend feeds PS5 x86-64 guest binaries directly to the host CPU by mapping virtual memory, patching import stubs to native trampolines, and invoking the entry point with a direct native call, bypassing software interpretation entirely.
The par274/sharpemu project implements a high-performance PlayStation 5 emulator that leverages the architectural similarity between the PS5 and modern PCs. By utilizing a direct CPU execution backend, SharpEmu eliminates the overhead of software instruction emulation, allowing guest code to run at near-native speeds while maintaining a robust HLE layer for system services.
Architecture Overview of the Direct Execution Backend
At the heart of this mechanism lies the DirectExecutionBackend class located in src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs. This component orchestrates the transition from managed runtime to raw native execution.
When the runtime initiates a binary via SharpEmuRuntime.Run, the flow proceeds through CpuDispatcher.DispatchEntry to lazily instantiate the backend:
// CpuDispatcher.cs - lazily creates the native backend
_nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager);
Unlike traditional emulators that fetch-decode-execute in software, this backend prepares the host environment to execute guest instructions directly on the physical CPU.
Memory Mapping and TLS Initialization
Before jumping into guest code, the backend establishes the guest's execution environment. This involves mapping critical memory regions including the stack, thread-local storage (TLS), and bootstrap areas.
The initialization sequence calls TryMapStackRegion, TryMapTlsRegion, and SeedTlsLayout to satisfy the PS5's memory layout expectations:
- Stack mapping: Allocates and protects the guest stack region
- TLS setup: Configures the static TLS layout defined in
src/SharpEmu.HLE/GuestTlsTemplate.cs - Bootstrap regions: Maps low-level memory required for PS5 process initialization
These steps ensure that when the CPU enters x86-64 mode, the FS and GS segment registers point to valid TLS data, and the stack pointer (RSP) references mapped host memory.
Import Stub Patching and Native Intrinsics
PS5 binaries rely on NID (name identifier) imports to access system services. The backend handles these through SetupImportStubs, which iterates over every import address in the ELF and patches them to either direct native handlers or managed trampolines.
For each import, the backend either:
- Resolves a direct bridge via
TryResolveDirectImportTargetto a native address, or - Emits a trampoline using
CreateImportHandlerTrampolinethat forwards to the managedModuleManagerdispatch layer
Native intrinsics provide optimized paths for well-known functions. For example, the NID fgxnMeTNUtY (QueryPerformanceCounter) is replaced with hand-written machine code allocated via VirtualAlloc and marked executable with VirtualProtect:
// Conceptual example of native intrinsic injection
backend.TryCreateNativeImportIntrinsic("fgxnMeTNUtY", out var nativeAddr);
backend.PatchImportStub((nint)importAddress, nativeAddr);
This patching strategy ensures that HLE calls incur minimal overhead, often executing as direct native jumps rather than expensive context switches.
Native Entry and Context Transfer
Once memory is mapped and imports are patched, the backend prepares the initial CPU state. The CpuDispatcher populates a CpuContext structure with register values (RIP, RSP, flags, FS/GS base) and constructs the initial stack frame via InitializeProcessEntryFrame.
Execution commences when DirectExecutionBackend.TryExecute invokes CallNativeEntry:
// DirectExecutionBackend.cs - final native transition
backend.TryExecute(context, entryPoint, Generation.Gen5,
importStubs, runtimeSymbols, new CpuExecutionOptions(),
out var execResult);
At this point, the host CPU begins executing the PS5 binary's x86-64 instructions directly. The guest runs natively until it hits a patched import stub or triggers a hardware exception.
Threading Support
Multi-threaded PS5 applications are supported through GuestThreadState objects. The backend creates native worker threads via GuestExecutionRunner and performs context transfers using GetOrCreateGuestContextTransferStub. Each guest thread executes on its own host thread, maintaining the direct execution model across the entire process.
Exception Handling and Diagnostics
Since the guest runs directly on the CPU, faults must be intercepted and translated. The backend registers a vectored exception handler named RawVectoredHandlerManaged that catches hardware exceptions (access violations, illegal instructions, etc.) and converts them into CpuTrapInfo or CpuMemoryFaultInfo structures for the runtime to handle.
To prevent HLE import loops from hanging the emulator, the backend maintains an import loop guard using _recentImportTrace with a configurable ImportLoopHistoryLength. This detects tight loops where guest code repeatedly calls imported functions without making progress, allowing the runtime to break out or log diagnostics.
Summary
- Direct execution bypasses software interpretation by running PS5 x86-64 code directly on the host CPU through
DirectExecutionBackend. - Memory mapping initializes guest stacks, TLS regions, and bootstrap areas before entry, ensuring the PS5 memory model is satisfied.
- Import stub patching redirects NID calls to native handlers or managed trampolines, with optimized intrinsics for performance-critical functions.
- Native context transfer occurs via
CallNativeEntryafterCpuDispatcherprepares the initial register state and stack frame. - Exception handling uses a vectored handler to translate hardware faults into managed trap information, while import loop guards prevent infinite recursion in HLE code.
Frequently Asked Questions
How does SharpEmu handle PS5 system calls without interpreting every instruction?
SharpEmu patches the PS5 binary's import stubs during load time. When the guest code calls a system function (e.g., sceKernel*), it jumps to a trampoline that forwards to the managed ModuleManager via CreateImportHandlerTrampoline. For performance-critical functions, it injects native intrinsics that execute directly without leaving the native context.
What happens when the guest code triggers a memory fault or exception?
The RawVectoredHandlerManaged in DirectExecutionBackend.cs intercepts all hardware exceptions. It translates the fault into a CpuMemoryFaultInfo structure, unwinds the execution context, and returns control to the runtime. This allows the emulator to handle PS5-specific memory behaviors or deliver signals without crashing the host process.
Can SharpEmu run multiple PS5 threads simultaneously using direct execution?
Yes. The backend supports multi-threading through GuestThreadState and GuestExecutionRunner. Each guest thread runs on a dedicated host native thread, maintains its own register context, and uses GetOrCreateGuestContextTransferStub for context switches. All threads execute PS5 x86-64 instructions directly on the host CPU cores.
How does the import loop guard prevent infinite loops in HLE functions?
The backend tracks recent import calls using _recentImportTrace and monitors the sequence length against ImportLoopHistoryLength. If the guest enters a tight loop calling imported functions repeatedly without advancing the program counter, the guard detects this pattern and can break the loop or trigger diagnostic logging to prevent the emulator from hanging.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →