Ashash
Heaps.io · macOS arm64

Run Heaps games
on Apple Silicon.

Ash runs Heaps HashLink bytecode natively on Apple Silicon. Use matching Heaps and hlsdl sources, and place the HDLLs beside your .hl file. Ash provides the shared HashLink runtime.

Download sdl.hdll · macOS arm64 → Upstream patch #974
Artifact: macOS arm64 · hlsdl 1.17 · SDL 3.4.10 · SHA-256 b58e23e4f631f3edcc0041fd4c8ecb89df43afe5db6f0f4846d5b544ce33cdca. It uses LC_RPATH @loader_path, preventing a copied HDLL from reaching back into the machine that built it and loading a second HashLink runtime.
Heaps.io · Windows x86_64

Run Heaps games
on Windows.

Ash runs Heaps HashLink bytecode on Windows x86_64 with HashLink's own prebuilt HDLLs. Place them beside your .hl file along with the DLLs they import. Ash provides the shared HashLink runtime.

HashLink win64 releases → Base2D example
Library downloads: use fmt.hdll, ui.hdll, uv.hdll, and sdl.hdll from a HashLink win64 release archive. Copy any required DLL dependencies from the same archive, including SDL3.dll and OpenAL32.dll.
01 · Install matching libraries

Match the externs to the native libraries

The Haxe externs determine which native symbols appear in game.hl. Pair current Heaps with HashLink's hlsdl source; mixing an older hlsdl checkout with a newer sdl.hdll produces unresolved or mismatched symbols.

$ haxelib git heaps https://github.com/HeapsIO/heaps.git
$ haxelib git hlsdl https://github.com/HaxeFoundation/hashlink.git master libs/sdl
$ haxelib path heaps
$ haxelib path hlsdl
02 · Compile

Compile to HashLink bytecode

compile.hxml
-lib heaps
-lib hlsdl
-main Main
-hl bin/game.hl
Build
$ haxe compile.hxml
$ cp /path/to/sdl.hdll bin/

Place the required native libraries in the same directory as game.hl. Common dependencies are sdl.hdll, fmt.hdll, ui.hdll, and uv.hdll.

03 · Run

Start with the hybrid runtime

$ ash --mode hybrid bin/game.hl

[ash] Loading HDLL-compatible ash_std at .../libhl.dylib
[ash] Loading HDLL: sdl from "bin/sdl.hdll"

The first log line confirms that Ash and the HDLLs share a garbage collector and HashLink type globals. No DYLD_LIBRARY_PATH is required. Ash's macOS release places both libhl.dylib and its libhl.1.dylib alias beside the executable.

Heaps lifecycle: Main.main() runs as soon as bytecode and HDLL loading complete. hxd.App.init() runs later, after Heaps creates its window, event loop, engine, and graphics context. In the Base2D example the measured gap was about 1.39 seconds. Do not add sleeps, pause the sentinel, or manually drive hxd.System.mainLoop(); those workarounds create an artificial delay.
04 · Ship

Package the runtime and libraries

The Ash binary includes its standard library, but HDLLs need a shared runtime on disk. They import hl_* symbols from @rpath/libhl.dylib, which dyld loads separately. Include Ash's shared runtime when packaging a game that uses HDLLs.

Packaging checklist
Include both filenames. HDLLs may link to either libhl.dylib or libhl.1.dylib. Upstream HashLink uses the versioned name.
Use Ash's runtime for both. Both files must come from the same Ash release. An old copy can take precedence during loading.
Re-sign after modifying libraries. Changes to Mach-O load paths can invalidate their signatures.
Check quarantine attributes. Browser downloads may carry a quarantine attribute that prevents loading.
Match the architecture. lipo -archs on every HDLL.
Load one runtime instance. Loading two copies of libhl creates separate garbage collectors and can cause memory corruption.
Stage
$ cp libash_std.dylib game/libhl.dylib
$ cp libash_std.dylib game/libhl.1.dylib
$ install_name_tool -id @rpath/libhl.dylib   game/libhl.dylib
$ install_name_tool -id @rpath/libhl.1.dylib game/libhl.1.dylib

# point each HDLL at that runtime, and let @rpath mean "next to me"
$ install_name_tool -change /abs/path/libash_std.dylib \
      @rpath/libhl.dylib game/mylib.hdll
$ install_name_tool -add_rpath @loader_path game/mylib.hdll

# clear quarantine BEFORE signing, or the signature covers a modified file
$ xattr -cr game/
$ for f in game/*.hdll game/*.dylib; do codesign --force -s - "$f"; done
Verify
$ lipo -archs game/*.hdll
$ otool -L game/*.hdll | grep -E 'libhl|libash_std'
$ codesign -v game/*.hdll

An rpath outside the game directory can resolve to another runtime. Check LC_RPATH entries with otool -l and use @loader_path for the packaged libraries.

05 · Troubleshoot

Troubleshoot loading and crashes

Shader calls crash after startup

Check for a second libhl instance. glCreateShader may succeed, with the crash occurring when hlsdl boxes the returned ID. Use the linked relocatable HDLL and check that the compatibility-runtime log line appears before any HDLL load.

Unresolved sdl@hlp_* symbol

Your Haxe hlsdl externs and native sdl.hdll are from different revisions. Recompile game.hl after selecting hlsdl 1.17.

No compatibility-runtime line

Update Ash. Older macOS builds mishandled a bare bytecode filename such as ash game.hl and selected the static runtime before discovering HDLLs in the current directory.

Minimize/restore crash in hybrid mode

Use a current Ash build. Its HashLink callback compatibility layer preserves safe defaults when hlsdl supplies null wrapper callbacks; the Base2D example is tested through repeated minimize/restore cycles.

library load disallowed by system policy

The library may be present but blocked by an invalid signature. Re-sign the affected file with codesign --force -s -, followed by its path.

code signature not valid for use in process

Check the signature and quarantine attributes. Clear quarantine on the affected files with xattr -cr, then re-sign them.

slice is not valid mach-o file

Not a macOS binary, or the wrong architecture. Some Haxe library releases ship a Windows DLL under the .hdll name. Check with file x.hdll — expect Mach-O … arm64.

Required library 'x' not found

The bytecode imports an HDLL that is not in the directory. Ash requires it at startup even when the feature goes unused, so stage it or rebuild the bytecode without that library.

No error, but random crashes or corruption

Two runtime instances, usually an rpath resolving @rpath/libhl.dylib to a copy outside the game directory. Two libhl images mean two garbage collectors managing the same objects.

Include the HDLL dependencies. An fmt.hdll built against shared libpng and libjpeg needs those dylibs staged too; one that links them statically does not. otool -L tells you which you have.
06 · Diagnose

Diagnose performance problems

Set these environment variables before starting Ash. They are read once at startup.

ASH_GC_STATS=1 — logs the pause duration, freed blocks, and live set for each collection. Start here when investigating stutter. Pauses around 50 ms can cause visible hitches and audio interruptions.
ASH_GC_HEAP_MB=n — heap cap, defaulting to a share of machine RAM. Raise it if allocation fails.
ASH_PROFILE=phases — reports elapsed time for each execution phase.
ASH_TIER1_PROBE=1 — logs each compilation with a function index and timestamp. Use the timings to investigate compilation stalls.
ASH_WORKERS=n — scheduler threads, defaulting to cores minus one.
ASH_GC_STRESS=1 — runs frequent collections to help reproduce GC rooting bugs. Expect a substantial slowdown.
ASH_LIBHL=system|embedded — overrides runtime selection when you suspect the wrong libhl.
Reporting a stall. Capture sample <pid> while the game is stalled and include it in the report. The thread stacks help distinguish a GC pause, compilation, and a lock wait.
01 · Install matching libraries

Match the externs to the native libraries

The Haxe externs determine which native symbols appear in game.hl. Pair current Heaps with HashLink's hlsdl source, and take the compiled HDLLs from a HashLink release of the same generation; older externs against a newer sdl.hdll produce unresolved or mismatched symbols.

> haxelib git heaps https://github.com/HeapsIO/heaps.git
> haxelib git hlsdl https://github.com/HaxeFoundation/hashlink.git master libs/sdl
> haxelib path heaps
> haxelib path hlsdl

Install Ash with PowerShell:

> irm https://ash.rayzor.tech/install.ps1 | iex

That puts ash.exe in ~\.ash\bin and adds the directory to your user PATH. The HashLink runtime travels with it — libhl.dll and the libhl.1.dll alias that HashLink 1.x builds import — so there is nothing further to install, and nothing to copy per game.

02 · Compile

Compile to HashLink bytecode

compile.hxml
-lib heaps
-lib hlsdl
-main Main
-hl bin/game.hl
Build
> haxe compile.hxml
> copy hashlink-win64\*.hdll bin\

Place the required HDLLs beside game.hl, along with their DLL dependencies. Common HDLLs include sdl.hdll, fmt.hdll, ui.hdll, and uv.hdll. SDL needs SDL3.dll; OpenAL needs OpenAL32.dll. These DLLs are included in the HashLink release archive.

03 · Run

Start with the hybrid runtime

> ash --mode hybrid bin\game.hl

[ash] Loading HDLL-compatible ash_std at ...\libhl.dll
[ash] Loading HDLL: sdl from "bin\sdl.hdll"

The first log line confirms that Ash and the HDLLs share a garbage collector and HashLink type globals. Nothing needs adding to PATH for the runtime itself — Ash's Windows release places libhl.dll and its libhl.1.dll alias beside the executable, and the loader searches that directory first.

Heaps lifecycle: Main.main() runs as soon as bytecode and HDLL loading complete. hxd.App.init() runs later, after Heaps creates its window, event loop, engine, and graphics context. Do not add sleeps or manually drive hxd.System.mainLoop(); those workarounds create an artificial delay.
ui.Sentinel is disabled. HashLink's watchdog modifies the watched thread's instruction pointer, which is incompatible with Ash's JIT frames. Ash returns null from hl_thread_start for this request, so the program runs without the watchdog.
04 · Ship

Package the game libraries

On a machine with Ash installed, HDLLs use the libhl.dll beside ash.exe. Package your game with its required HDLLs and their DLL dependencies. Leave the runtime in the Ash installation directory.

In the game directory
bin\
  game.hl
  sdl.hdll fmt.hdll ui.hdll uv.hdll
  SDL3.dll       <- found because it sits beside sdl.hdll
  OpenAL32.dll   <- only if the program uses openal.hdll

Ash loads HDLLs with LOAD_WITH_ALTERED_SEARCH_PATH, so a DLL beside the HDLL is found first. Windows would otherwise start its search at the executable's directory and never look in the HDLL's own.

Packaging checklist
Use the installed runtime. Ash includes libhl.dll and its libhl.1.dll alias beside ash.exe. Keep copies out of the game directory: loading a second runtime creates a separate garbage collector and can corrupt memory.
Include dependent DLLs. Copy SDL3.dll and, if needed, OpenAL32.dll from the HashLink archive into the game directory.
Match the architecture. Every HDLL must be a Windows x86_64 binary. Ash reports the detected format if a library is built for another platform.
Install Ash on the target machine. install.ps1 places Ash and its runtime in ~\.ash\bin and adds the directory to PATH. That installation can run multiple games.
05 · Troubleshoot

Troubleshoot loading and crashes

The specified procedure could not be found (127)

The HDLL imports a symbol that the runtime does not export. Windows rejects the library before running its code, but this error does not identify the missing symbol. Update Ash first. If the error persists, check whether the HDLL requires a HashLink ABI that Ash does not yet support.

The specified module could not be found (126)

Check for missing DLL dependencies beside the HDLL. sdl.hdll requires SDL3.dll, and openal.hdll requires OpenAL32.dll.

%1 is not a valid Win32 application

Not a Windows binary, or the wrong architecture — some Haxe library releases ship a macOS or Linux build under the .hdll name. Ash names the format it actually found in its own error, ahead of the loader's text.

Unresolved sdl@hlp_* symbol

Your Haxe hlsdl externs and native sdl.hdll are from different revisions. Recompile game.hl against externs matching the HDLL you staged.

No compatibility-runtime line

Ash chose its built-in runtime, which leaves the HDLLs bound to a second copy whose collector was never started. It selects the shared one when it finds HDLLs beside the bytecode — check they are in that directory, and that libhl.dll sits beside ash.exe.

Required library 'x' not found

The bytecode imports an HDLL that is not in the directory. Ash requires it at startup even when the feature goes unused, so stage it or rebuild the bytecode without that library.

No error, but random crashes or corruption

Check whether a HashLink libhl.dll is being loaded before Ash's copy. Two runtime instances can leave separate garbage collectors managing the same objects, causing crashes later in execution.

Include the HDLL dependencies. An fmt.hdll built against shared libpng and libjpeg needs those DLLs staged too; one that links them statically does not. Check the HDLL's import table to see which DLLs it needs.
06 · Diagnose

Diagnose performance problems

Set these environment variables before starting Ash. They are read once at startup.

ASH_GC_STATS=1 — logs the pause duration, freed blocks, and live set for each collection. Start here when investigating stutter. Pauses around 50 ms can cause visible hitches and audio interruptions.
ASH_GC_HEAP_MB=n — heap cap, defaulting to a share of machine RAM. Raise it if allocation fails.
ASH_PROFILE=phases — reports elapsed time for each execution phase. The sampling profiler is macOS and Linux only; this one works everywhere.
ASH_TIER1_PROBE=1 — logs each compilation with a function index and timestamp. Use the timings to investigate compilation stalls.
ASH_WORKERS=n — scheduler threads, defaulting to cores minus one.
ASH_GC_STRESS=1 — runs frequent collections to help reproduce GC rooting bugs. Expect a substantial slowdown.
ASH_STD_LINKAGE=dynamic — forces the shared runtime even with no HDLLs present, to A/B against the built-in one.
Reporting a crash. Ash has no crash handler on Windows yet, so a fault may end the process without a diagnostic. Include the last output, the steps to reproduce the crash, and whether it occurs at the same point on each run. That helps narrow down GC, timing, and code-generation issues.

Working example: examples/heaps_base2d

← Back to Ash