A fast HashLink virtual machine.
ASH runs HashLink bytecode. It is a drop-in replacement for hl: the same
.hl file produced by haxe -hl, the same standard library semantics, the
same @:hlNative HDLLs. It differs in how the code executes.
- Tiered JIT. Hot functions are compiled while the program runs, first by Cranelift (fast to compile) and then by LLVM (fast to run). A loop that is already executing is transferred into the compiled version without returning from it.
- Native AOT.
ash --buildproduces a standalone executable: no bytecode, no interpreter, no warm-up. - WebAssembly. The same compiler targets
wasm32-wasip1, with a browser host and a native host for testing. Exceptions, threads and sockets work. - SIMD. The
ash-simdhaxelib exposes 128-bit vector types that compile to vector instructions on ASH and run through an HDLL on stock HashLink.
ASH passes the executable part of the Haxe 4.3.6 test suite under the interpreter, as a native executable and as a wasm module. The badges are live from CI; the conformance page has the per-engine breakdown and the benchmark page compares ASH with HashLink's JIT, HashLink/C and the JVM on the same programs.
curl -fsSL https://raw.githubusercontent.com/rayzor-blade/ash/main/install.sh | shirm https://raw.githubusercontent.com/rayzor-blade/ash/main/install.ps1 | iexInstalls ash into ~/.ash/bin and adds it to PATH. Prebuilt binaries:
macOS arm64 and x86_64, Linux x86_64, Windows x86_64. The standard library
and the wasm linker are inside the binary; nothing else is installed.
ASH requires a 64-bit target. Other platforms build from source — see
CONTRIBUTING.md.
ash main.hl # hybrid: interpret, compile hot functions in the background
ash --mode interp main.hl # interpreter only
ash --mode jit main.hl # compile every function at its first callArguments after the .hl file go to the program, as with hl.
The default is hybrid. Functions start interpreted; call counts decide
promotion; Cranelift compiles at 100 calls and LLVM recompiles at 1000
(--preset picks thresholds for a script, a game or a server). Compilation
runs on background threads and the new code is installed atomically at the
next call, or mid-loop for a function that never returns. interp is the
reference every other mode is checked against, and the right choice for a
script that finishes before compilation would pay for itself.
docs/cli.md lists every option.
ash --build mygame main.hl
./mygameThe runtime is linked in statically. A program that loads HDLLs gets the
runtime as a shared library instead, staged beside the executable under the
names HDLLs import (libhl.dylib, libhl.1.dylib), so program and
extensions share one garbage collector. The .hdll files are yours to place
next to the executable.
Build time, memory, haxe.CallStack in compiled code, cross-compilation and
the troubleshooting table are in docs/aot.md.
ash --build mygame.wasm --target wasm32-wasip1 main.hlNo external toolchain: the linker is part of ash. The output is a WASI
preview-1 module that exports main and imports the few things a sandbox
cannot do for itself — suspending a fiber, sockets. ASH ships a browser host
and a wasmtime-based one; docs/wasm/README.md covers
embedding, threads via Workers, and native libraries as wasm side modules.
Native .hdll files do not load in a sandbox. A library that needs one
guards it with #if wasm or ships a .wasm side module
(docs/wasm/hdlls.md).
import ash.simd.Float32x4;
var acc = Float32x4.splat(0);
var i = 0;
while (i < n) {
acc = acc + Float32x4.load(a, i << 2) * Float32x4.load(b, i << 2);
i += 4;
}
var dot = acc.sum();-lib ash-simd (or -cp haxelib/ash-simd). Float32x4 and Int32x4 are
abstracts over a 16-byte hl.Bytes; ash.simd.Vec is the underlying set of
memory-to-memory primitives for f32x4, f64x2, i32x4, i16x8, i8x16 and u8x16.
On stock HashLink the primitives come from simd.hdll and every operator
allocates its result. On ASH they are part of the runtime: the compiled tiers
emit each one as a vector instruction, and a value that does not escape the
function is kept in a register — the loop above compiles to a load, a load,
fmul, fadd with the accumulator in a phi. docs/simd.md
documents the API and the lane semantics.
Any HDLL built for HashLink loads unchanged from the directory of the .hl
file (or of the executable, for an AOT build). Writing one:
docs/hdll.md.
examples/heaps_base2d/ runs a Heaps Base2D application
through HashLink's SDL3 sdl.hdll:
ash --mode hybrid examples/heaps_base2d/bin/game.hlThe Heaps on Ash guide has the haxelib versions, HDLL placement and Apple Silicon notes. MarbleGame (SDL2) has its own pinned workflow in docs/mbhaxe.md.
A result that differs between --mode interp and a compiled mode is a bug in
ASH. --jit-tier cranelift|llvm|off pins one tier so the report can name
it; ASH_PROFILE=sample is a built-in sampling profiler that attributes time
to Haxe functions, including JIT-compiled ones. Both are in
docs/debugging.md.
Bug reports and questions: Discord, or an issue via git-bug (CONTRIBUTING.md).
| docs/cli.md | command-line reference |
| docs/aot.md | native and wasm builds |
| docs/debugging.md | tier tuning, profiling, bisecting a wrong answer |
| docs/simd.md | ash-simd |
| docs/hdll.md | writing an HDLL |
| docs/wasm/ | hosting a wasm build |
| docs/mbhaxe.md | MarbleGame workflow |
| CONTRIBUTING.md | building from source, architecture, tests, internals |
