# How SharpEmu Freezes HLE Modules: The Complete Technical Guide

> Learn how SharpEmu freezes HLE modules. Discover the technical steps including locking registration, pre-JIT-compiling methods, and preventing modifications for stable guest execution.

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

---

**SharpEmu freezes HLE modules by invoking `ModuleManager.Freeze()`, which locks the registration state, pre-JIT-compiles all exported methods via `WarmHleTypeInitializers()`, and prevents further modifications to ensure stable guest execution.**

The `par274/sharpemu` repository implements a robust High-Level Emulation (HLE) system that requires deterministic behavior before guest code execution begins. Understanding how to freeze HLE modules is critical for emulator stability, as this process transforms the mutable module registry into an immutable, pre-compiled environment safe for multi-threaded guest access.

## The Three-Step Freezing Process

The freezing implementation resides in [`src/SharpEmu.HLE/ModuleManager.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs) and executes atomically to prevent race conditions during emulator initialization.

### Step 1: Locking Registration with _isFrozen

The freeze operation begins by setting a private `_isFrozen` flag inside a lock on the `_registrationGate` object. According to the source code at [lines 78-84](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L78-L84), this flag permanently seals the module registry.

All subsequent calls to `RegisterFromAssembly` check this flag at [lines 25-28](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L25-L28) and throw `InvalidOperationException` if the manager is frozen. This guarantees that no new HLE exports can be added after the freeze point, ensuring function pointer stability for the guest.

### Step 2: Warming HLE Type Initializers

After locking the registry, `Freeze()` calls `WarmHleTypeInitializers()` to eliminate just-in-time (JIT) compilation overhead during guest execution. This method, implemented at [lines 88-107](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L88-L107), performs two critical operations on every scanned assembly:

1. **Static Constructor Initialization**: Forces the CLR to run each type's static constructor using `RuntimeHelpers.RunClassConstructor`
2. **Method Pre-compilation**: JIT-compiles every non-generic, non-abstract method using `RuntimeHelpers.PrepareMethod`

This warming occurs on the host thread to avoid the CLR's "fail-fast" behavior that would otherwise terminate the process when a guest thread (running on a hijacked stack) first triggers JIT compilation.

### Step 3: Warming Framework Assemblies

The final phase handles framework dependencies differently. At [lines 75-86](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L75-L86), SharpEmu runs static constructors for framework assemblies (e.g., `System.*`) without JIT-compiling their methods. The Base Class Library (BCL) is too large for full pre-JIT, but warming the type initializers prevents static constructor deadlocks during guest execution.

## Source Code Architecture

The freezing mechanism relies on several key components within the HLE subsystem:

- **[`ModuleManager.cs`](https://github.com/par274/sharpemu/blob/main/ModuleManager.cs)**: Central coordinator for registration, freezing, and dispatch
- **[`SysAbiExportAttribute.cs`](https://github.com/par274/sharpemu/blob/main/SysAbiExportAttribute.cs)**: Marks methods that become HLE exports during the registration scan
- **[`ExportedFunction.cs`](https://github.com/par274/sharpemu/blob/main/ExportedFunction.cs)**: Stores metadata (NID, name, target generation) for each exported function
- **[`CpuContext.cs`](https://github.com/par274/sharpemu/blob/main/CpuContext.cs)**: Represents the emulated CPU state passed to HLE functions during dispatch

When `Freeze()` completes, the `Dispatch` method can safely resolve function pointers using the immutable registry without triggering lazy initialization or compilation.

## Practical Implementation Example

The following pattern demonstrates the complete lifecycle from registration to frozen execution:

```csharp
// Initialize the module manager
var manager = new ModuleManager();

// Register HLE modules from user-provided assemblies
int count = manager.RegisterFromAssembly(
    typeof(SomeHleModule).Assembly,
    Generation.Gen2
);

// Freeze the registration - makes the module set immutable
manager.Freeze();

// After freezing, any attempt to register new assemblies throws InvalidOperationException
// manager.RegisterFromAssembly(anotherAssembly, Generation.Gen2); // Throws!

// Dispatch a registered function by NID during guest execution
var cpu = new CpuContext(...);
var result = manager.Dispatch("0x1234", cpu);

```

Running `Freeze()` before starting the guest CPU ensures that all HLE functions are compiled and ready, preventing CLR fail-fast errors when guest threads invoke native-like functions.

## Summary

- **Immutable Registry**: `ModuleManager.Freeze()` sets `_isFrozen` to permanently lock the module set at [lines 78-84](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L78-L84)
- **Pre-JIT Compilation**: `WarmHleTypeInitializers()` forces static constructors and JIT-compiles all exported methods to avoid runtime compilation on guest threads
- **Framework Safety**: Secondary warming pass handles BCL static constructors without full pre-JIT to balance memory and safety
- **Exception Guarantee**: Any post-freeze registration attempts throw `InvalidOperationException` immediately

## Frequently Asked Questions

### What happens if I try to register modules after calling Freeze?

The `RegisterFromAssembly` method checks the `_isFrozen` flag at [lines 25-28](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L25-L28) and throws `InvalidOperationException` with a clear message indicating the module manager has been frozen. This prevents runtime modification of the HLE function table that would destabilize guest execution.

### Why does SharpEmu need to pre-JIT compile HLE methods?

Guest threads in SharpEmu run on hijacked stacks managed by the emulator's CPU context. If the CLR encounters an un-JIT-compiled method on such a stack, its fail-fast behavior terminates the entire process. By calling `RuntimeHelpers.PrepareMethod` during `WarmHleTypeInitializers()`, SharpEmu ensures all HLE code is already compiled before any guest thread can invoke it.

### Can I freeze the module manager multiple times safely?

While calling `Freeze()` multiple times will not corrupt state (the `_isFrozen` flag remains true), the method is designed as a one-way transition. After the first call completes the warming phases at [lines 88-107](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs#L88-L107), subsequent calls simply return immediately without re-processing, making the operation idempotent but irreversible.