# How Guest Threads Are Executed in SharpEmu: Inside the PlayStation 4 Emulator's Thread Scheduler

> Explore how SharpEmu executes PlayStation 4 guest threads via a cooperative scheduler. Learn about interleaving with continuations and context switching for efficient emulation.

- Repository: [Berk/sharpemu](https://github.com/par274/sharpemu)
- Tags: internals
- Published: 2026-07-13

---

**SharpEmu runs PlayStation 4 guest threads through a cooperative scheduler that interleaves multiple guest threads on a single host thread using continuations and context switching.**

SharpEmu is an open-source PlayStation 4 emulator that implements a sophisticated guest thread execution model to handle the console's multi-threaded workloads on host systems. Understanding how guest threads are executed in SharpEmu requires examining the tight coupling between the high-level scheduler interface and the native execution backend that actually dispatches guest code.

## The Guest Thread Scheduler Architecture

The execution model centers on two core components: a scheduler interface that defines management contracts and a static helper class that maintains thread-local execution state.

### IGuestThreadScheduler Interface

The `IGuestThreadScheduler` interface in [`src/SharpEmu.HLE/GuestThreadExecution.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/GuestThreadExecution.cs) (lines 26-55) establishes the contract for all thread operations. It declares methods for starting threads (`TryStartThread`), joining threads (`TryJoinThread`), pumping execution (`Pump`), and handling entry/exit notifications. This abstraction allows the emulator to support different scheduling backends while maintaining consistent semantics for guest code.

### GuestThreadExecution Static Helper

The `GuestThreadExecution` static class serves as the central coordination point for all guest thread operations. Located in [`src/SharpEmu.HLE/GuestThreadExecution.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/GuestThreadExecution.cs) (lines 88-146), it stores thread-local state including the current thread handle, fiber address, and pending block information. All thread-related requests route through `GuestThreadExecution.Scheduler`, which holds the active `IGuestThreadScheduler` implementation.

## Starting and Pumping Guest Threads

The `DirectExecutionBackend` class in [`src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs) provides the concrete implementation of the scheduler interface, handling the actual creation and execution of guest threads.

### Thread Initialization with TryStartThread

When the emulator creates a new guest thread, it calls `DirectExecutionBackend.TryStartThread` (lines 81-86 and 97-105). This method validates the `GuestThreadStartRequest`, creates a `GuestThreadState` object to track the thread's lifecycle, enqueues it in the ready queue, and immediately calls `Pump` to begin execution.

```csharp
var startRequest = new GuestThreadStartRequest(
    threadHandle: 0x1234,
    entryPoint:  0x400000,
    argument:    0,
    attributeAddress: 0,
    name:        "MyThread",
    priority:    31,
    affinityMask: 0xFFFFFFFF);

// backend is an instance of DirectExecutionBackend
backend.TryStartThread(cpuContext, startRequest, out var error);

```

### The Execution Pump Loop

The `Pump` method (lines 72-80 and 91-99) drives the actual execution. It repeatedly dequeues ready threads, switches the `GuestThreadExecution` context using `EnterGuestThread`, executes the guest code, and restores the previous context. The pump also handles waking expired blocked threads and respects the "entry_return" reason for synchronous execution scenarios.

## Blocking and Resuming Threads

Guest threads frequently need to block while waiting for events, mutexes, or timed conditions. SharpEmu implements this through continuations rather than host OS threads.

### Requesting a Block

Guest code requests a block via `GuestThreadExecution.RequestCurrentThreadBlock` (lines 108-140). This method captures the current CPU state as a `GuestCpuContinuation` and stores it in thread-local fields along with wake-up metadata. The method returns a boolean indicating whether the caller is actually running on a guest thread.

```csharp
// Inside guest-side library implementation
if (!GuestThreadExecution.RequestCurrentThreadBlock(
        context: cpuContext,
        reason: "waiting_for_event",
        wakeKey: "event42",
        resumeHandler: () => 1,
        wakeHandler: () => EventIsSignaled()))
{
    // Not a guest thread – ignore
}

```

### Consuming Blocked Continuations

When the blocking condition resolves, the scheduler calls `GuestThreadExecution.TryConsumeCurrentThreadBlock` (lines 220-238) to retrieve the saved state. This extracts the `GuestCpuContinuation`, wake key, and handler delegates, allowing the scheduler to restore registers and resume execution.

```csharp
if (GuestThreadExecution.TryConsumeCurrentThreadBlock(
        out var reason,
        out var continuation,
        out var hasContinuation,
        out var wakeKey,
        out var resumeHandler,
        out var wakeHandler,
        out var deadline))
{
    if (EventIsSignaled(wakeKey))
    {
        backend.ResumeGuestContinuation(continuation);
    }
}

```

## Context Transfer Between Schedulers

Certain PlayStation 4 operations, such as fiber switches, require moving guest execution context between different schedulers. The `RequestCurrentContextTransfer` method (lines 96-104) records a continuation containing the full CPU state, while `TryConsumeCurrentContextTransfer` (lines 97-105) allows a different scheduler to resume from that point.

```csharp
// Guest code requests fiber switch
GuestThreadExecution.RequestCurrentContextTransfer(
    new GuestCpuContinuation(
        Rip: newRip,
        Rsp: newRsp,
        ReturnSlotAddress: 0,
        Rflags: cpuContext.Rflags,
        FsBase: cpuContext.FsBase,
        GsBase: cpuContext.GsBase
    ));

```

This mechanism enables complex threading scenarios like fiber-based cooperative multitasking implemented in [`src/SharpEmu.Libs/Fiber/FiberExports.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/Fiber/FiberExports.cs).

## Joining Threads

The `TryJoinThread` method (lines 109-130) handles host-level synchronization when the emulator needs to wait for a guest thread to exit. It blocks the host thread until the target reaches the *Exited* state, periodically checking for forced exits and sleeping to avoid busy-waiting. This bridges the gap between the host OS threading model and the emulator's internal thread representation.

## Summary

- **Guest threads** are represented by `GuestThreadState` objects managed by the `DirectExecutionBackend`.
- **Scheduling** occurs through the `IGuestThreadScheduler` interface, with `GuestThreadExecution` providing static access to thread-local state.
- **Execution** is driven by the `Pump` method, which dequeues ready threads and dispatches them via context switching.
- **Blocking** uses `GuestCpuContinuation` objects to save register state, allowing cooperative multitasking without host threads.
- **Context transfers** enable operations like fiber switches by moving execution state between schedulers.
- **Joining** synchronizes host code with guest thread completion through polling and sleep mechanisms.

## Frequently Asked Questions

### How does SharpEmu handle blocking system calls without freezing the emulator?

SharpEmu converts blocking operations into continuations. When guest code calls a blocking syscall, `RequestCurrentThreadBlock` captures the CPU state as a `GuestCpuContinuation` and yields control back to the scheduler. The `Pump` loop then continues executing other ready threads. When the blocking condition resolves (checked via wake handlers), the scheduler restores the continuation and resumes execution.

### What is the difference between `DirectExecutionBackend` and `IGuestThreadScheduler`?

`IGuestThreadScheduler` is the abstract interface defining scheduling contracts like `TryStartThread` and `Pump`. `DirectExecutionBackend` is the concrete implementation in [`src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs) that actually manages thread queues, dispatches execution, and handles native code generation. This separation allows for potential alternative backends while maintaining consistent HLE (High Level Emulation) behavior.

### Can guest threads migrate between host CPU cores?

According to the source in [`src/SharpEmu.HLE/GuestThreadExecution.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/GuestThreadExecution.cs), thread affinity is controlled through the `affinityMask` parameter in `GuestThreadStartRequest`. However, the actual execution occurs through the single `Pump` method in the current implementation, suggesting that while the API supports affinity hints, the scheduler may coalesce execution onto the host thread running the pump. The `SupportsGuestContextTransfer` property in fiber exports indicates that context switching between schedulers is supported for specific scenarios like fiber migration.

### How does thread joining work when multiple guest threads need to synchronize?

The `TryJoinThread` method blocks the host thread polling for the target guest thread's exit state. For guest-to-guest synchronization, the PlayStation 4 kernel compatibility layer in [`src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs) implements pthread-style primitives using the blocking continuation system. Threads waiting on mutexes or condition variables call `RequestCurrentThreadBlock`, and the kernel module uses `WakeBlockedThreads` to resume them when signaled.