Skip to content

AOT Compilation Case Studies: Summary for MEP-42

Cross-cutting patterns from twelve production AOT pipelines, with a recommendation for which one Mochi should learn from most.

§1 Scope

This summary distils the case studies in 01_*.md through 12_*.md: GraalVM Native Image, .NET NativeAOT, Zig self-hosted, Crystal, Nim, Hare, V, Julia/juliac, Mojo, GHC NCG, Swift / Embedded Swift, and Static Python / Cinder / CPython 3.13 JIT. Each file has its own provenance, architecture, target matrix, runtime, status, Mochi adaptation note, and open questions. This document looks only at the cross-cutting patterns.

§2 Closed-world vs open-world

The most consequential architectural axis. Closed-world AOT requires the compiler to see every reachable function/type/resource at build time; open-world allows the program to extend itself at runtime.

  • Closed-world by construction: Zig, Crystal, Hare, V (C backend), GHC, Embedded Swift, Mochi. These compilers can reason globally and trim aggressively.
  • Closed-world by retrofitting: GraalVM Native Image, .NET NativeAOT, Julia juliac, Static Python. These started open-world and added a closed-world AOT path with escape hatches (reachability metadata, source generators, trim-safe/trim-unsafe, [DynamicallyAccessedMembers]).
  • Open-world even in AOT: standard Swift (dynamic dispatch via vtables/witness tables that can be extended via dynamic loading), Mojo (Python interop).

Mochi is already closed-world, which is the more valuable starting point. The Native Image / NativeAOT / juliac stories are mostly cautionary: do not paint yourself into a corner where dynamic features force a “trim warning” UX nightmare.

§3 Runtime-bundled vs runtime-shared

  • Runtime entirely bundled into the executable: Native Image (SubstrateVM bundled), NativeAOT (CoreCLR trimmed and bundled), Crystal (Boehm GC linked statically when desired), Zig (no runtime to bundle), Nim (tiny runtime statically linked), Hare (almost no runtime), V (Boehm or none), GHC (RTS statically linked).
  • Runtime as a shared library next to the binary: Julia/juliac (libjulia-internal.so + bundle layout), Swift (libswiftCore.dylib), Embedded Swift (no runtime to share, fully bundled).
  • Runtime is the host process: Cinder, Static Python, CPython JIT (the binary is python plus extension modules).

Bundling is the dominant pattern for systems where users expect a single artefact to drop on a server or container. Sharing makes sense only when the runtime itself is heavy (Julia: libLLVM at runtime) or when an OS or app store wants to manage the runtime separately (Apple’s libswiftCore).

§4 Single-backend (LLVM) vs polyglot backend

  • LLVM-only: Crystal, Swift, Mojo (LLVM under MLIR), Julia, Rust (not covered here but the canonical example), Mono AOT.
  • Multiple backends: Zig (in-house + LLVM + C), GHC (NCG + LLVM + C + Wasm + JS), Nim (C + C++ + JS + LLVM via clang), V (C + native + JS + Go + LLVM via community).
  • Non-LLVM single backend: Hare (QBE only), GraalVM Native Image (Graal compiler).
  • In-house only: GraalVM (Graal compiler is in-house), GHC NCG (per-ISA hand-written).

LLVM gives you peak codegen and broad target reach at the cost of distribution size and compile time. In-house backends give you fast compile, control over your platform matrix, and independence from LLVM regressions, at the cost of significant engineering. The two-backend strategy (fast in-house for debug, LLVM for release) is the pragmatic compromise GHC and Zig both adopted.

§5 GC choices

  • Conservative Boehm-Demers-Weiser: Crystal, V (default). Easy to integrate, predictable, has a well-known throughput ceiling.
  • Reference counting (ARC) with optional cycle collection: Swift (ARC), Nim (ARC/ORC).
  • Generational copying / parallel-mark / nonmoving hybrid: GHC RTS, Java’s G1/ZGC on Substrate VM, .NET CoreCLR GC.
  • No GC at all: Zig, Hare, Embedded Swift (when explicit).
  • Multi-mode selectable per build: Nim (--mm:), V (-gc).

Mochi currently has an arena allocator (MEP-40). The natural progression is to add an ARC option (Swift/Nim path) before considering tracing, because ARC plays well with closed-world ownership analysis and avoids a stop-the-world pause story.

§6 Cross-compilation

  • Best in class: Zig (zig build -Dtarget=... from any host to any target, bundled libcs), Nim (delegated to host C cross-compiler, often paired with zig cc).
  • Solid: .NET NativeAOT (RID matrix via NuGet packages), Rust (rustup target add).
  • Workable but manual: Crystal (--cross-compile emits object plus suggested cc command), Embedded Swift (LLVM cross + vendor SDK).
  • Limited: GraalVM Native Image (essentially host-target), Julia juliac (host-target), Hare (BYO toolchain).

If Mochi cares about cross-compilation, Zig’s model is the gold standard but expensive to copy fully. Crystal’s “emit object plus cc command” is the minimal viable thing. Pairing with zig cc gives Nim-level cross capability with little Mochi engineering.

§7 Hello-world binary sizes (May 2026 best efforts, stripped)

  • Hare: ~25 KB
  • Zig (no libc, ReleaseSmall): ~6 KB; with musl ~14 KB
  • Embedded Swift on Cortex-M: hundreds of bytes
  • Nim with --mm:none: ~50–80 KB
  • V with -gc none -skip-unused: ~20–40 KB
  • Crystal (musl static): ~2.4 MB
  • Rust release (not covered, for reference): ~300 KB stripped
  • .NET NativeAOT: ~1.3 MB stripped, ~9 MB default
  • Mojo 1.0 beta: ~1–3 MB
  • GHC static: ~12 MB
  • GraalVM Native Image: ~8–12 MB stripped, ~4–6 MB with Epsilon GC
  • Julia juliac trim-safe: ~5–20 MB

Mochi will likely land in the 1–10 MB band for a hello-world depending on whether MEP-42 chooses Boehm GC, ARC, or a Native-Image-style bundled runtime.

§8 Recommendation for Mochi

Mochi is a statically-typed, closed-world, Go-hosted bytecode VM with an arena allocator and a typed compiler IR. Of the twelve case studies, the closest architectural analog is Crystal:

  • Both are closed-world by construction with whole-program type information.
  • Both ship a managed runtime (Mochi’s vm3, Crystal’s libgc plus fiber scheduler).
  • Both have an existing IR that can be lowered to LLVM in a single step.
  • Both target the same OS/ISA tier: linux/macos/windows on x86_64/arm64.
  • Both face the same GC-vs-arena vs ARC trade-off.

The case study Mochi should learn most from, however, is .NET NativeAOT, because:

  • It is the most mature production example of “managed language + bytecode VM + AOT pipeline that trims the runtime”.
  • The ILC trim model, the [DynamicallyAccessedMembers] annotation pattern, and the source-generator alternative to runtime reflection are all directly applicable to Mochi’s stdlib (json, yaml, sql, http).
  • The cross-platform RID matrix and single-file deployment story are exactly the UX Mochi users will expect.

Secondary patterns to import:

  • From Zig: zig cc as a model for mochi cc (lower priority, defer to a later MEP) and the bundled-libc cross-compilation experience.
  • From GraalVM Native Image: build-time heap snapshotting (Mochi can serialise its arena into a read-only data section).
  • From Nim/V: a --gc mode flag exposing arena / ARC / tracing / none.
  • From Embedded Swift: an explicit mochi build --mode=embedded subset that disables dynamic features for smallest binaries.
  • From GHC: the discipline of a clean IR (Cmm-style) that admits multiple backends without entanglement.
  • From Crystal: the pragmatic “emit object plus print cc command” cross-compile MVP.

The case studies that are interesting but not the right template for Mochi: Julia (Mochi does not start open-world, so the trim work is unnecessary), Mojo (MLIR is too heavy a dependency for v1), Hare (too constrained, no Windows), Static Python (Mochi is not retrofitting a dynamic language).

§9 Reading order

For someone implementing MEP-42, the recommended reading order of the per-case-study files is:

  1. 02_dotnet_nativeaot.md (primary architectural template)
  2. 04_crystal.md (closest existing analog)
  3. 01_graalvm_native_image.md (heap-snapshot pattern, closed-world rigor)
  4. 03_zig_self_hosted.md (cross-compilation gold standard, two-backend strategy)
  5. 10_ghc_ncg.md (IR discipline, multi-backend sustainability)
  6. 11_swift_embedded.md (embedded-mode subset pattern)
  7. 05_nim.md (compile-to-C backend, ARC/ORC mode flags)
  8. 07_v_language.md (multi-mode memory flags, tcc-for-debug)
  9. 08_julia.md (cautionary tale, bundle layout)
  10. 09_mojo.md (long-horizon MLIR option)
  11. 12_static_python_facebook.md (typed-subset compilation lessons, copy-and-patch as JIT option)
  12. 06_hare.md (QBE as alternative small backend; size discipline)