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:

  1. Save the current thread’s state – The active CpuContext is captured and stored in the current GuestThreadExecution instance.
  2. Load the target thread’s state – The CpuContext from the next GuestThreadExecution is installed as the active context.
  3. Restore registers and stack – General-purpose registers, RIP, RSP, and flags are re-populated from the saved context.
  4. Resume execution – The engine fetches the next instruction using the restored RIP and 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 CpuContext class, avoiding hardware virtualization requirements.
  • The CpuContext indexer in CpuContext.cs provides fast register access while tracking RAX writes for return-value handling.
  • GuestThreadExecution manages per-thread state with SaveState() and LoadState() methods that capture and restore full CPU snapshots.
  • Stack consistency is maintained through atomic PushUInt64 and PopUInt64 operations that update RSP and memory simultaneously.
  • The CpuExecutionEngine coordinates 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →