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:
- Static Constructor Initialization: Forces the CLR to run each type's static constructor using
RuntimeHelpers.RunClassConstructor - 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:
ModuleManager.cs: Central coordinator for registration, freezing, and dispatchSysAbiExportAttribute.cs: Marks methods that become HLE exports during the registration scanExportedFunction.cs: Stores metadata (NID, name, target generation) for each exported functionCpuContext.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:
// 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_isFrozento 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
InvalidOperationExceptionimmediately
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →