luna-aot
Ahead-of-time compiler from Lua source to a self-contained native
binary, built on luna-core's VM. Sibling of luna-core (pure-interp
runtime, zero third-party deps) and luna-jit (the runtime
Cranelift JIT).
End-to-end today: Lua source → luna bytecode dump → ELF/Mach-O/PE
data section → linked native binary that constructs a Vm at
process start, undumps the bytecode, and runs it through the
interpreter. Stage 1-2 (parse/compile), Stage 3 (backend-agnostic
lowerer), Stage 4 (linker + runtime staticlib), Stage 5 (cross-compile
via --target), and Stage 6 (Alpine no-Lua deploy smoke) are all
landed. Trace JIT mcode emission via cranelift-object is the only
remaining sub-stage and lands as a follow-up.
Quick start (host triple, single-binary deploy)
# prints whatever `print(...)` calls in foo.lua produced
The output is one file: no .so, no .dll, no liblua* runtime
dep. ldd foo shows only libc + libm + libpthread + (on macOS) the
System framework. strip foo still leaves a working executable.
Cross-compile
--target <triple> flows through the full pipeline:
cargo build --target=<triple> -p luna-runtime-helpers --release(requiresrustup target add <triple>)- target-aware
objectwrite (ELF / Mach-O / PE magic per OS) - per-triple
ccdriver pick (aarch64-linux-gnu-gcc,x86_64-w64-mingw32-gcc,x86_64-linux-musl-gcc, ...) — falls back tocc -target <triple>when no named cross-cc is on PATH - per-OS lib set (
-lpthread/-ldl/-lmon glibc Linux, skip-lgcc_s/-lutilon musl,-luserenv/-lws2_32/-lbcrypton Windows-MinGW,-framework CoreFoundationon macOS)
Recipes
# darwin x86_64 from darwin aarch64 (Apple clang handles -target natively):
# linux aarch64 from any glibc host (needs gcc-aarch64-linux-gnu):
# windows x86_64 / MinGW from any unix host (needs mingw-w64):
# windows x86_64 / MSVC, native on Windows (Developer Command Prompt for VS 2022):
# or from a Unix host with the LLVM toolchain + Windows SDK mirror
# (clang-cl + lld-link via `brew install llvm` / `apt install clang lld`;
# Windows SDK + UCRT libs via an `xwin`-style setup):
# Alpine / musl deploy (single binary that runs on any musl distro):
Per-target prerequisites
| Triple | Rust target | C cross-compiler | macOS install | Debian install |
|---|---|---|---|---|
x86_64-apple-darwin |
rustup add | system clang (apple) | preinstalled | — (darwin SDK needed) |
aarch64-apple-darwin |
rustup add | system clang (apple) | preinstalled | — (darwin SDK needed) |
aarch64-unknown-linux-gnu |
rustup add | aarch64-linux-gnu-gcc |
brew install aarch64-elf-gcc |
apt install gcc-aarch64-linux-gnu |
x86_64-unknown-linux-gnu |
rustup add | x86_64-linux-gnu-gcc |
brew install x86_64-elf-gcc |
(host gcc) |
x86_64-unknown-linux-musl |
rustup add | x86_64-linux-musl-gcc |
brew install FiloSottile/musl-cross/musl-cross |
apt install musl-tools |
x86_64-pc-windows-gnu |
rustup add | x86_64-w64-mingw32-gcc |
brew install mingw-w64 |
apt install gcc-mingw-w64-x86-64 |
x86_64-pc-windows-msvc |
rustup add | clang-cl + lld-link (LLVM) or cl.exe + link.exe (Visual Studio Build Tools 2022) |
brew install llvm (provides both) |
apt install clang lld (provides both) |
Anything missing surfaces as a concrete error message naming the package; nothing is silently degraded.
Why a separate crate
luna-aot pulls third-party deps (object, clap, and — in the
follow-up codegen sessions — all of cranelift). Keeping it sibling
to luna-jit lets embedders pick exactly one of:
luna-core— pure interp, zero third-party deps.luna-core + luna-jit— runtime JIT (mmap RWX, recorded traces).luna-core + luna-aot— offline compile to a deployable binary; the produced binary statically linksluna-core+luna-runtime-helpersonly (noluna-jit, noluna-aot, no Cranelift dynamic link).
The luna-core zero-third-party-dep contract is unaffected by
this crate.
Pipeline (Stages 1-6)
foo.lua
│ luna_core::frontend::parser::parse (Stage 1)
▼
Chunk (AST)
│ luna_core::compiler::compile_chunk (Stage 2)
▼
Gc<Proto>
│ [trace JIT mcode emission — follow-up] (Stage 3)
│ luna_core::vm::dump::dump (luna's own body format)
▼
Vec<u8>
│ object::write::Object (.luna.bytecode + bracket symbols, target-aware)
▼ (Stages 4-5)
foo.luna_bytecode.o ELF / Mach-O / PE
│ cc bytecode.o cmain.o libluna_runtime_helpers.a -o foo
▼ (Stage 6)
foo single-binary, runs through luna_aot_run → Vm → call_value
luna_aot_run lives in crates/luna-runtime-helpers/ as a dual
staticlib + rlib. The staticlib carries rust stdlib + all of
luna-core; the rlib is what luna-aot's integration tests link
against so they can drive the same code path in-process without
shelling out to cc.
Stage matrix
| Stage | Status |
|---|---|
| 1. parse → AST | shipped — reused from luna-core |
| 2. AST → Proto | shipped — reused from luna-core |
3. Proto → Cranelift IR (shared lowerer over M: Module) |
shipped — luna-jit::jit_backend lowerers are generic over cranelift_module::Module; trace mcode emission via cranelift-object is the only follow-up |
4. emit .o via object::write::Object |
shipped — bytecode .o + C main .o |
5. embed bytecode + cross-compile via --target |
shipped — bytecode embed + target-aware ELF/Mach-O/PE magic + per-triple cc + per-OS lib set |
| 6. link + Alpine no-Lua smoke | shipped — cargo build -p luna-runtime-helpers --release [--target T] bootstrap + final cc link; Alpine smoke test skips cleanly when musl cross-cc / docker missing |
Limitations
- Trace JIT mcode is not yet AOT-compiled. The produced binary
runs the interpreter only; hot loops won't get the same native-speed
treatment they would under
luna --jit foo.luawith the runtime Cranelift JIT. The lowerer refactor that letscranelift-objectemit the same mcode at AOT time landed in Stage 3, but walking every reachableProto's hot loops + emitting them into the AOT binary's.textis the remaining work. Post-v1.3. loadstring(...)at runtime works — but it runs through the interpreter, no AOT codegen at runtime. Embedders that need runtime-loadstring-of-untrusted-source to be JIT-fast should useluna-jitinstead.- MSVC link path requires Windows SDK libs on Unix hosts. The
x86_64-pc-windows-msvctarget is fully driven (clang-cl/cl.exefor the C entry object,lld-link/link.exefor the final PE), but the final link needs Windows SDK + UCRT.libfiles which are not installed bybrew install llvm/apt install lldalone. On a Windows host (Developer Command Prompt for VS 2022) the toolchain is complete. On Unix hosts axwin-style SDK mirror is required to makelld-linkfinducrt.libet al.; otherwise use--target x86_64-pc-windows-gnu(MinGW), which carries its own runtime via mingw-w64.
CLI surface
luna-aot compile <input.lua> [--out <path>] [--target <triple>] [--dialect <5.X>] [--scaffold-only]
--target accepts any triple TargetSpec::from_triple parses
(tier 1: host + *-apple-darwin; tier 2: *-unknown-linux-{gnu,musl},
x86_64-pc-windows-{gnu,msvc}). Unsupported arch / OS strings
surface as a clean error naming the supported set.
--dialect accepts 5.1 / 5.2 / 5.3 / 5.4 / 5.5 / macrolua;
default is 5.5.
--scaffold-only falls back to the pre-Stage-4 path that emits a C
entry which only prints the embedded section length to stderr — useful
for benchmarking the link step in isolation or when the runtime
staticlib is known-broken on the host.
Status — supply-chain delta
| crate | direct deps added |
|---|---|
luna-aot (new) |
luna-core (workspace) + object 0.36 + clap 4 (+ tempfile dev-only) |
luna-runtime-helpers (new) |
luna-core (workspace) — nothing else |
luna-core |
none — 0-third-party-dep contract preserved |
luna-jit |
none |
luna-jit-derive |
none |
CI zero-dep job continues to report luna-core's dep tree as one
row (itself).
The deploy-side binary is even cleaner: it only embeds
luna-runtime-helpers (which depends solely on luna-core) +
rust stdlib. Cranelift, object, clap, and tempfile are all
build-time-only for luna-aot; none of them ship in the produced
executable.