How SharpEmu Freezes HLE Modules: The Complete Technical Guide

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 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, this flag permanently seals the module registry.

All subsequent calls to RegisterFromAssembly check this flag at lines 25-28 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, 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, 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:

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:

// 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
  • 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 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, subsequent calls simply return immediately without re-processing, making the operation idempotent but irreversible.

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 →