# How HLE Exports Are Registered and Dispatched in SharpEmu's ModuleManager

> Discover how SharpEmu's ModuleManager registers and dispatches HLE exports using attributes and CLR pre-warming for efficient runtime performance. Learn the core mechanism.

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

---

**SharpEmu uses the `ModuleManager` class to collect, store, and invoke High-Level Emulation (HLE) exports through an attribute-driven registration system that pre-warms the CLR before runtime dispatch.**

The **ModuleManager** in SharpEmu serves as the central registry for bridging guest system calls to host implementations. This article examines how the emulator registers HLE exports using source generators, freezes the registration state, and dispatches calls based on NID lookups according to the `par274/sharpemu` source code.

## Understanding the Export Structure

Each HLE function is represented by an **`ExportedFunction`** object defined in [`src/SharpEmu.HLE/ExportedFunction.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ExportedFunction.cs). This POCO encapsulates the metadata required to map a guest system call to its host implementation:

- **Library name**: The target PlayStation library (e.g., `libKernel`)
- **NID**: A unique identifier string (e.g., `"0x12345678"`)
- **Export name**: A friendly identifier (e.g., `sceKernelGetProcessId`)
- **Target generation**: The PlayStation generation this export supports
- **Delegate**: A `SysAbiFunction` containing the actual host implementation

The `SysAbiFunction` delegate type defines the standard signature for all HLE methods, accepting a `CpuContext` and returning an integer result.

## Attribute-Driven Export Generation

Rather than manually wiring exports, SharpEmu uses the **`[SysAbiExport]`** attribute found in [`src/SharpEmu.HLE/SysAbiExportAttribute.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiExportAttribute.cs) to mark host methods for automatic discovery.

Source generators scan the codebase for methods decorated with this attribute and emit registration code at compile time. The attribute properties specify:

- `Nid`: The hexadecimal identifier string
- `ExportName`: The human-readable function name
- `LibraryName`: The target system library
- `Target`: The supported `Generation` enum value (e.g., `Generation.Gen2`)

This approach eliminates manual registration errors and ensures type safety between the attribute metadata and the actual method signatures.

## Registering Exports in ModuleManager

When the emulator boots, generated code calls `ModuleManager.RegisterExports(IReadOnlyList<ExportedFunction>)` to populate the dispatch tables. This method, implemented in [`src/SharpEmu.HLE/ModuleManager.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs), performs several critical operations:

1. **Thread-safety**: Acquires the `_registrationGate` lock to ensure no race conditions during the registration window.
2. **Dispatch table population**: Adds each export's delegate to `_dispatchTable` using the NID as the key. Duplicate NIDs trigger a warning and are ignored.
3. **Metadata storage**: Stores the full `ExportedFunction` in `_exportTable` (by NID) and `_exportNameTable` (by export name) for lookups.
4. **Assembly tracking**: Records the defining assembly in `_warmupAssemblies` for later JIT compilation.

The method returns the count of successfully registered exports, allowing the bootstrap code to verify that all expected modules loaded correctly.

## Freezing and Pre-Warming the CLR

After all modules register their exports, the emulator calls **`ModuleManager.Freeze()`** to seal the registration state. This method sets `_isFrozen = true` and immediately invokes **`WarmHleTypeInitializers()`** to prevent CLR fail-fast crashes during guest execution.

The warming process resolves all assemblies that contributed exports plus any guest-reachable interop assemblies (such as Silk.NET). It then:

- Executes static constructors using `RuntimeHelpers.RunClassConstructor` for every type
- Forces JIT compilation via `RuntimeHelpers.PrepareMethod` for every method

This pre-warming ensures that when a guest thread first triggers an HLE export, the host implementation is already compiled and initialized, avoiding JIT-related delays or threading issues.

## Runtime Dispatch Mechanism

When guest code invokes a system call, the emulator routes the request through **`ModuleManager.TryDispatch(string nid, CpuContext context, out OrbisGen2Result result)`**. The dispatch flow follows these steps:

1. **Lookup**: Searches `_dispatchTable` for the delegate and `_exportTable` for the corresponding `ExportedFunction` metadata. If missing, returns a "not found" error.
2. **Generation validation**: Verifies that the export's `Target` generation matches the current guest generation. Mismatches return a "not implemented" error.
3. **Invocation**: Clears any pre-existing RAX-write flag, invokes the `SysAbiFunction` delegate with the provided `CpuContext`, and captures the returned integer.
4. **Result handling**: Casts the returned integer to `OrbisGen2Result` and stores it in the CPU context's RAX register unless the implementation already wrote to it.

This NID-based lookup allows the emulator to dynamically route system calls without hardcoded switch statements, maintaining clean separation between the HLE layer and the core emulation logic.

## Practical Implementation Examples

Defining an HLE export requires applying the attribute to a static method with the correct signature:

```csharp
[SysAbiExport(
    Nid = "0x12345678", 
    ExportName = "sceKernelGetProcessId", 
    LibraryName = "libKernel", 
    Target = Generation.Gen2)]
public static int GetProcessId(CpuContext ctx)
{
    ctx[CpuRegister.Rax] = (ulong)CurrentProcessId;
    return (int)OrbisGen2Result.ORBIS_GEN2_SUCCESS;
}

```

The source generator produces registration code similar to this simplified version:

```csharp
var exports = new List<ExportedFunction>
{
    new ExportedFunction(
        libraryName: "libKernel",
        nid: "0x12345678",
        name: "sceKernelGetProcessId",
        target: Generation.Gen2,
        function: (SysAbiFunction)GetProcessId)
};
moduleManager.RegisterExports(exports);

```

During emulation, the core invokes the export through the dispatch method:

```csharp
var result = moduleManager.TryDispatch("0x12345678", cpuContext, out var orbisResult);
// orbisResult contains the execution status from the host implementation

```

## Summary

- **`ExportedFunction`** objects encapsulate HLE metadata and delegates in [`src/SharpEmu.HLE/ExportedFunction.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ExportedFunction.cs).
- **`[SysAbiExport]`** attributes drive compile-time code generation that eliminates manual registration.
- **`RegisterExports`** populates thread-safe lookup tables (`_dispatchTable`, `_exportTable`) while tracking assemblies for warming.
- **`Freeze()`** seals the registration state and executes **`WarmHleTypeInitializers()`** to JIT-compile all HLE methods before guest execution begins.
- **`TryDispatch`** performs NID-based lookups with generation validation and manages CPU context state during invocation.

## Frequently Asked Questions

### What is the purpose of the NID in HLE exports?

The NID (Numeric ID) serves as a unique string key that identifies a specific system call within a PlayStation library. SharpEmu uses this string to look up the corresponding host implementation in `_dispatchTable` and `_exportTable`, allowing the emulator to decouple guest code addresses from host function pointers.

### Why does ModuleManager require a freeze mechanism before running games?

The freeze mechanism prevents dynamic registration after the emulator begins executing guest code. By calling `Freeze()`, SharpEmu sets `_isFrozen = true` and immediately runs `WarmHleTypeInitializers()`, which JIT-compiles all HLE methods and runs their static constructors. This prevents CLR threading issues and fail-fast crashes that could occur if a guest thread triggered JIT compilation during emulation.

### How does SharpEmu handle conflicts when two exports share the same NID?

During registration in `RegisterExports`, the method checks for existing NIDs in `_dispatchTable`. If a duplicate is detected, the registration logs a warning and ignores the conflicting export, keeping the first registered implementation. This ensures deterministic behavior when multiple modules expose the same system call.

### What happens if guest code calls an HLE export from the wrong generation?

The `TryDispatch` method validates the export's `Target` generation against the current guest generation before invocation. If they do not match, the method returns an error indicating the function is not implemented for that generation, preventing incompatible system calls from executing on incorrect hardware configurations.