How SharpEmu Manages CPU Context Switching in Guest Threads
SharpEmu performs CPU context switching by saving the complete register state of a guest thread into a CpuContext object, then loading that state into the execution engine when the thread resumes.
SharpEmu is an open-source emulator designed to replicate PlayStation®-style environments, and it implements multi-threaded execution entirely in software. Because the emulator does not rely on hardware virtualization, CPU context switching becomes a matter of swapping structured data between managed objects rather than triggering processor-level task switches.
The CpuContext Data Structure
At the heart of SharpEmu’s threading model lies the CpuContext class defined in src/SharpEmu.HLE/CpuContext.cs. This object encapsulates the complete architectural state of a guest CPU, including general-purpose registers (R0 through R15), SIMD registers (XMM/YMM), the instruction pointer (RIP), flags (RFLAGS), and segment bases (FSBASE and GSBASE).
Register Storage and Access
The CpuContext exposes registers through an indexer that maps enum values to an internal array. When SharpEmu performs CPU context switching, it uses this indexer to rapidly copy values between the active execution state and the saved thread state.
public ulong this[CpuRegister register]
{
get => _registers[(int)register];
set
{
_registers[(int)register] = value;
if (register == CpuRegister.Rax) _raxWritten = true;
}
}
The setter logic includes a special flag for RAX writes, which the emulator uses to detect return values after function calls.
Stack Operations
Before the emulator can safely switch threads, it must ensure the stack state is consistent. The CpuContext provides atomic push and pop operations that adjust RSP and perform memory writes in a single call:
public bool PushUInt64(ulong value)
{
var rsp = this[CpuRegister.Rsp];
rsp -= sizeof(ulong);
this[CpuRegister.Rsp] = rsp;
return TryWriteUInt64(rsp, value);
}
These methods guarantee that when a thread resumes after CPU context switching, its stack pointer and memory contents match the exact state at the point of suspension.
The Context Switching Workflow
When the CpuExecutionEngine needs to move execution from one guest thread to another, it follows a strict four-step sequence:
- Save the current thread’s state – The active
CpuContextis captured and stored in the currentGuestThreadExecutioninstance. - Load the target thread’s state – The
CpuContextfrom the nextGuestThreadExecutionis installed as the active context. - Restore registers and stack – General-purpose registers,
RIP,RSP, and flags are re-populated from the saved context. - Resume execution – The engine fetches the next instruction using the restored
RIPand continues interpreting with the newly loaded registers.
Because the emulator runs as a pure interpreter, no hardware context switch instructions are involved; the operation is simply a structured memory copy between managed objects.
Thread State Management
The GuestThreadExecution class (found in src/SharpEmu.HLE/GuestThreadExecution.cs) maintains a dedicated CpuContext per thread and exposes explicit methods for state transitions.
Saving State
Before a thread yields or blocks, SaveState() clones the current CPU state into the thread’s private storage:
public void SaveState()
{
_savedContext = new CpuContext(Memory, Generation)
{
Rip = this[CpuRegister.Rip],
Rflags = this[CpuRegister.Rflags],
Rsp = this[CpuRegister.Rsp],
// copy remaining registers...
};
}
This captures the exact snapshot required for later restoration.
Loading State
When the scheduler selects a thread to run, LoadState() reverse the process:
public void LoadState()
{
this[CpuRegister.Rip] = _savedContext.Rip;
this[CpuRegister.Rflags] = _savedContext.Rflags;
this[CpuRegister.Rsp] = _savedContext.Rsp;
// restore remaining registers...
ClearRaxWriteFlag(); // reset RAX-written tracking
}
The ClearRaxWriteFlag() call ensures that stale return-value flags from previous executions do not interfere with the new context.
Execution Engine Integration
The CpuExecutionEngine (located in src/SharpEmu.Core/Cpu/CpuExecutionEngine.cs) orchestrates the actual switch by invoking the save and load methods when a thread yields, blocks on a synchronization primitive, or is pre-empted by the scheduler.
public void SwitchThread(GuestThreadExecution current, GuestThreadExecution next)
{
current.SaveState(); // snapshot current CPU registers
next.LoadState(); // load registers for the next thread
// execution will continue with next thread's RIP
}
After LoadState() completes, the CpuDispatcher (in src/SharpEmu.Core/Cpu/CpuDispatcher.cs) routes the next decoded instruction using the freshly loaded CpuContext, ensuring seamless continuity for the guest application.
Summary
- SharpEmu implements CPU context switching purely in software through the
CpuContextclass, avoiding hardware virtualization requirements. - The
CpuContextindexer inCpuContext.csprovides fast register access while trackingRAXwrites for return-value handling. GuestThreadExecutionmanages per-thread state withSaveState()andLoadState()methods that capture and restore full CPU snapshots.- Stack consistency is maintained through atomic
PushUInt64andPopUInt64operations that updateRSPand memory simultaneously. - The
CpuExecutionEnginecoordinates switches by calling these methods during thread yields, blocks, or pre-emption events.
Frequently Asked Questions
What registers are preserved during a context switch in SharpEmu?
SharpEmu preserves all general-purpose registers (R0 through R15), SIMD registers (XMM/YMM), the instruction pointer (RIP), the flags register (RFLAGS), and segment bases (FSBASE and GSBASE). These values are stored in the CpuContext object and restored when the thread resumes execution.
How does SharpEmu handle the stack pointer during CPU context switching?
The stack pointer (RSP) is treated as a standard register within the CpuContext. During a context switch, the current value is saved via the SaveState() method and restored via LoadState(). Additionally, the PushUInt64 and PopUInt64 helpers ensure that stack memory contents remain synchronized with the pointer value before any switch occurs.
Why does SharpEmu track RAX writes separately during context switching?
The emulator tracks RAX writes using the _raxWritten flag to detect when a function return value has been placed in the RAX register. This is critical for emulating calling conventions correctly after a context switch, as the SetReturn method uses this flag to determine whether the guest thread has completed a function call and produced a result.
Is hardware assistance used for CPU context switching in SharpEmu?
No. SharpEmu operates as a pure software interpreter. CPU context switching is implemented entirely by copying CpuContext objects between threads in managed code. This design choice allows the emulator to run on platforms without hardware virtualization support while maintaining precise control over the guest CPU state.
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 →