monkey-asm
AOT arm64 (AArch64) assembly backend for Monkey, implementing
docs/arm64-asm-backend-design.md:
a single-pass lowering from the AST to AArch64 assembly text, assembled and
linked against a Rust runtime static library. No IR, no JIT, no register
allocation — the accumulator lives in x0 and temporaries on the machine
stack. One instruction stream, two output flavors selected with
--platform linux|macos (default: the host): Linux/ELF (GNU as spelling)
and macOS/Mach-O (Apple Silicon, _-prefixed symbols, @PAGE relocations).
Playground
The compiler playground has an
ARM64 tab — a godbolt-style source ↔ assembly view with bidirectional span
highlighting and a Download .s button, backed by the compile_to_arm64 wasm
export (which reuses lower in the browser). Nothing executes arm64 there; the
tab renders the exact text monkey-asm emit writes, and the downloaded .s
cross-assembles with the commands in Usage below.
One crate, built twice
| Build | Command | Product |
|---|---|---|
| host | cargo build -p monkey-asm |
monkey-asm CLI (parse + lower to .s) |
| aarch64 cross | cargo build -p monkey-asm --lib --release --target aarch64-unknown-linux-gnu |
libmonkey_asm.a, the runtime the generated .s links against |
(--platform macos uses --target aarch64-apple-darwin for the runtime
build instead.)
The generated .s file is the only interface between the two: the CLI never
executes Monkey code, the runtime never parses it.
Usage
# One-time setup, Linux flavor (Debian/Ubuntu example, works on any host)
# One-time setup, macOS flavor (Apple Silicon macOS host)
# Optional pre-warm (--lib: the staticlib needs no cross linker). `build` and
# `run` perform this Cargo freshness check automatically unless overridden.
# Compile and run; --platform defaults to the host (macos on macOS), and the
# Linux flavor uses qemu-aarch64 outside Linux AArch64
# Just look at the assembly in either spelling (any host, no toolchain needed)
# Produce an executable + its .s next to it (static ELF / arm64 Mach-O)
Environment overrides: MONKEY_ASM_CC (default aarch64-linux-gnu-gcc, or
cc for --platform macos), MONKEY_ASM_QEMU (default qemu-aarch64),
MONKEY_ASM_RUNTIME (path to libmonkey_asm.a). Without
MONKEY_ASM_RUNTIME, every build/run asks Cargo to build the exact
release runtime target for the selected platform; Cargo's freshness check
keeps it current. An explicit runtime path bypasses that build.
Platform reach: --platform linux produces a fully static Linux AArch64 ELF —
it builds on any host with the cross toolchain and runs anywhere via
qemu-aarch64. --platform macos produces an arm64 Mach-O executable:
linking needs a macOS host (the Apple SDK; set MONKEY_ASM_CC to a
cross-capable clang to override) and running needs Apple Silicon, because
qemu-user only emulates Linux ELF. emit works for either platform on any
host.
--observe builds the differential-testing variant: the program writes one
canonical result record (u64 big-endian length + JSON) to fd 3 at exit, while
stdout stays the untouched puts byte stream; run --observe decodes the
record to stderr.
Layout
runtime_core.rs— storage-agnostic semantics: tagged values (SMI + boxed integers, heap refs, builtin immediates), checked arithmetic, the equality/truthiness/display matrices, builtins, call/construct dispatch, canonical observer JSON.runtime_backend.rs—ValueStoretrait with the two backends:PointerStore(validated native tagged pointers into store-owned cells) andHandleStore(arena indices, used by tests and a future wasm simulator).runtime.rs— theextern "C"rt_*shells the generated assembly calls; a process-wide synchronized native store, fatal-error and observer plumbing.emitter.rs— assembly text buffers, labels,.rodatainterning, source span map, and the AArch64 encoding-limit helpers (load_imm64, frame/sp addressing) that lowering must never bypass.lower.rs— AST → assembly for the full language (functions, closures, classes), reusing the bytecode compiler'sSymbolTablefor scope analysis.main.rs—emit/build/runCLI.testdata/*.s— handwritten ABI probes freezing the.s↔ runtime contract.
Tests
The e2e suite follows the host: Apple Silicon macOS exercises the Mach-O
flavor natively, every other host exercises the Linux flavor (under qemu when
off-architecture). The tests skip themselves with a message when a toolchain
piece is missing. CI sets MONKEY_ASM_E2E_REQUIRED=1 — on both the Linux
cross/qemu job and the macos-15 Apple Silicon job — which turns any missing
requirement into a test failure.