.hl files) via a Just-In-Time (JIT) VM or directly into C code for native performance.HashLink (HL) is a modern, high-performance virtual machine (VM) specifically created for the Haxe programming language. Developed and supported by the Haxe Foundation, it represents a significant step forward in Haxe's compilation targets, offering enhanced speed and capabilities for a wide range of applications.
At its core, HashLink is designed for speed. It takes Haxe code, known for its strict typing and cross-platform nature, and provides optimized pathways for execution. Whether running interpreted bytecode or natively compiled code, HashLink aims to deliver performance suitable for demanding tasks like game development, server-side logic, and desktop applications.
HashLink is often considered the successor to the older NekoVM, another Haxe target. It incorporates lessons learned from Neko, focusing on improving performance bottlenecks and offering a more robust runtime environment. Key architectural choices, such as the use of strictly typed registers, contribute to its efficiency gains over previous Haxe virtual machines.
One of HashLink's most powerful features is its flexibility in compilation. Developers can choose the output format that best suits their needs: portable bytecode or high-performance native C code.
Games like Northgard leverage technologies associated with Haxe, demonstrating the capability for demanding graphical applications.
The default and often simplest way to use HashLink is by compiling Haxe source code into HashLink bytecode. This bytecode is stored in files with a .hl extension.
Using the Haxe compiler (version 3.4 or later), you can generate bytecode by specifying the --hl flag followed by the desired output filename. A typical command looks like this:
haxe --main MainClassName --hl output_bytecode.hl
This command tells the Haxe compiler to start compilation from the MainClassName.hx file and produce a single bytecode file named output_bytecode.hl.
HashLink bytecode isn't tied to a specific CPU architecture; it's described as a "virtual assembler." It contains instructions for the HashLink VM. Crucially, this bytecode is strictly typed. This means type information is preserved during compilation, allowing the VM to perform operations more efficiently and safely, avoiding much of the overhead associated with dynamically typed languages or VMs.
Once you have a .hl bytecode file, you execute it using the HashLink virtual machine executable, typically named hl:
hl output_bytecode.hl
When you run the hl command, the HashLink VM loads the bytecode. Its Just-In-Time (JIT) compiler then translates this platform-agnostic bytecode into native machine code specific to the CPU architecture it's running on (e.g., x86-64, ARM). This translation happens "on-the-fly" during execution.
JIT compilation offers a good balance between portability (the .hl file can run on any system with the HL VM installed) and performance. While there's a small startup cost for JIT compilation, the resulting native code often runs significantly faster than purely interpreted code.
For scenarios demanding maximum performance or easier distribution without requiring users to install the HL VM, Haxe can compile directly to C source code via HashLink.
To generate C code, you still use the --hl flag, but specify a .c output file, often within a designated output directory:
haxe --main MainClassName -hl output_directory/main.c
This command generates C source files (e.g., main.c and potentially others) in the specified output_directory.
The generated C code isn't directly executable. It must be compiled using a standard C compiler (like GCC, Clang, or MSVC) and linked against the HashLink runtime library (libhl.so on Linux, libhl.dylib on macOS, hl.dll on Windows). This requires having the HashLink development files installed and setting the HASHLINK environment variable to point to their location.
A typical compilation command using GCC might look like:
gcc -O3 -o my_application -std=c11 -I path/to/hashlink/include output_directory/main.c -L path/to/hashlink/lib -lhl
This produces a standalone native executable (my_application).
Compiling to C is the preferred method for targeting platforms like mobile (Android/iOS) and game consoles, where direct bytecode execution might not be feasible or allowed. It also provides the best possible runtime performance, as it eliminates the JIT overhead entirely, bringing execution speed closer to hand-written C code.
HashLink's design includes several key features that contribute to its efficiency and flexibility.
HashLink can be used with graphics libraries, enabling advanced rendering techniques.
Unlike some VMs that rely heavily on dynamic typing, HashLink uses strictly typed registers and function parameters internally. Basic Haxe types like Int and Float are represented using low-level types (e.g., i32, f64). This minimizes the need for "boxing" (wrapping primitive types in objects) and runtime type checks, leading to faster execution.
Haxe often uses anonymous objects (structs). HashLink provides fast access to the fields of these objects using a technique involving typed "virtuals," optimizing a common pattern in Haxe programming.
The core functionality needed to run HashLink code (either bytecode or compiled C) resides in the HashLink runtime library (libhl).
The runtime includes a custom garbage collector responsible for managing memory allocation and automatically freeing memory that is no longer in use, simplifying memory management for the developer.
It provides access to essential system functionalities like file I/O, networking, threads, and more, typically exposed through Haxe's standard sys package.
HashLink is designed to integrate smoothly with native C/C++ code. You can:
.hl files.This interoperability is facilitated by a "Natives table" mechanism that maps Haxe function calls to underlying C implementations.
To better understand where HashLink excels, the following chart compares several key aspects of the technology based on its design goals and capabilities. It visualizes factors like raw performance, ease of cross-platform deployment, simplicity for beginners, interoperability with native C code, and the maturity of its tooling ecosystem.
As the chart suggests, HashLink offers excellent performance, especially when compiling to C, strong cross-platform capabilities, and robust C/C++ interoperability. While basic usage is relatively straightforward, mastering advanced features like C compilation or native extensions requires more effort.
This mind map illustrates the central role HashLink plays within the Haxe ecosystem. It shows how Haxe source code is processed by the compiler to produce either HashLink bytecode or C code, which is then executed by the HashLink VM or compiled natively, ultimately targeting various platforms.
The diagram highlights the two main paths (Bytecode/JIT and C Code/Native) and how they enable Haxe code to run across diverse operating systems and devices, supported by the runtime library and surrounding tooling.
Visualizing the process can be helpful. The video below demonstrates compiling Haxe code using the Heaps.io game engine framework to HashLink/C and then using a C compiler (Visual Studio in this case) to create a native executable. This showcases the practical steps involved in the HL/C workflow, which is essential for targeting platforms requiring native binaries.
Watching this process clarifies the relationship between the Haxe compiler generating C code and the subsequent native compilation step needed to produce the final application, linking against necessary libraries like the HashLink runtime (hl.dll or equivalent).
Choosing between HashLink bytecode (.hl) and HashLink/C compilation depends on your project's specific requirements. This table summarizes the key differences, advantages, and disadvantages of each approach.
| Feature | HashLink Bytecode (.hl) + JIT VM | HashLink/C (.c) + Native Compilation |
|---|---|---|
| Compilation Command | haxe --main Main --hl output.hl |
haxe --main Main -hl out/main.c |
| Execution Method | Run with hl output.hl (requires HL VM) |
Compile C code (gcc, clang, etc.) linking libhl; run native executable |
| Performance | Good (JIT optimized) | Best (Native code, no JIT overhead) |
| Portability | High (runs anywhere HL VM is installed) | Lower (requires separate compilation for each target platform/architecture) |
| Distribution | Requires bundling/installing HL VM + .hl file |
Distribute standalone native executable (may need runtime bundled) |
| Build Complexity | Simpler (single Haxe compile step) | More Complex (Haxe compile + C compile/link steps, requires C toolchain) |
| Primary Use Cases | Rapid development, desktop apps, server apps, ease of deployment | Performance-critical apps, games, mobile (Android/iOS), consoles, embedding |
| Debugging | Can use HL interpreter/debugger features | Uses standard native C debugging tools (GDB, Visual Studio Debugger, etc.) |
Understanding these trade-offs helps developers select the optimal HashLink target for their Haxe project, balancing performance needs with development and deployment simplicity.
Using HashLink with Haxe involves a few setup steps and learning the basic compilation commands.
haxelib install hashlink in your terminal. This installs the necessary library for the Haxe compiler to recognize the HashLink target.HASHLINK environment variable to the path where you extracted the HashLink binaries.hl executable to your system's PATH environment variable for easier command-line access.1. Create a simple Haxe file named Test.hx:
class Test {
static public function main():Void {
trace("Hello from HashLink!"); // 'trace' prints to console
}
}
2. Compile to HashLink bytecode:
haxe --main Test --hl test.hl
3. Run the bytecode using the HashLink VM:
hl test.hl
You should see "Hello from HashLink!" printed to your console.