Build pipeline: `mochi build` command surface, output layout, amalgamated runtime, cross-compilation via bundled zig cc, APE via cosmocc, WASM via wasi-sdk, content-addressed caching, reproducibility.
MEP-45 research note 10, Build system
Author: research pass for MEP-45. Date: 2026-05-22 (GMT+7).
This note covers the build driver. The driver is the thing that takes a Mochi source tree and turns it into a binary. The transpiler is one stage of the driver.
1. Command surface
mochi build [PATH] # build the current module
mochi build --target=x86_64-linux-musl PATH # cross-compile
mochi build --target=wasm32-wasi PATH # WASM/WASI
mochi build --apex PATH # Cosmopolitan portable binary
mochi build --release PATH # release profile
mochi build --secure PATH # release + CFI + safe-stack
mochi build --debug PATH # sanitisers on
mochi build --emit=c PATH # stop after C emission
mochi build --emit=mir PATH # stop after MIR
mochi build --emit=ast PATH # stop after parse
mochi build --out PATH/bin/foo PATH # output path
mochi build -j N PATH # parallelism
mochi build --no-cache PATH # bypass cache
mochi build --reproducible PATH # extra reproducibility flagsmochi run builds with --debug defaults into a cache and then
executes. mochi test is mochi run against tests/*.mochi.
2. Output layout
.mochi/cache/
ast/
<module-hash>.bin
mir/
<module-hash>.bin
c/
<module-hash>.c
<module-hash>.h
obj/
<target-triple>/
<module-hash>.o
bin/
<target-triple>/
<output-name>All filenames are deterministic functions of (source hash, compiler flags, runtime version). A cache hit is “is the file already there?”; no metadata DB needed.
<module-hash> is a 128-bit BLAKE3 hash of:
- The module’s source bytes.
- The hashes of every module it imports (recursive).
- The transpiler version string.
- The compilation profile name.
This makes the build “Bazel-shaped” without Bazel.
3. Pipeline stages
source.mochi
|
parse --> AST (.mochi -> .ast.bin)
|
type-check --> typed AST + errors
|
elaborate --> MIR (.ast.bin -> .mir.bin)
|
monomorphise --> MIR (specialised)
|
lower --> C IR
|
print --> .c + .h (.mir.bin -> .c)
|
cc --> .o (per-target)
|
link --> binaryEach arrow is a cacheable step. Failures surface at the producing stage; downstream stages don’t re-run.
4. C compiler invocation
The driver shells out to a cc selected per target:
| Target | Driver chosen | Flags appended |
|---|---|---|
| host (current) | ${CC} or cc | -std=c23 -O2 -g -Wall |
| native cross | zig cc -target ${triple} | same |
| WASI | zig cc -target wasm32-wasi | -O2 -g |
| APE | cosmocc | -O2 |
| Windows MSVC | clang-cl --target=x86_64-pc-windows-msvc | /std:c17 /O2 |
The driver does not require the user to install LLVM separately; the
default install bundles zig (small, MIT) for cross-compilation.
5. Cache strategy
- Per-module compilation. A change to one module rebuilds that module and re-links; modules whose hashes did not change stay cached.
- Content-addressed; no mutable index.
- The cache root is
.mochi/cache/per project; a global root at$XDG_CACHE_HOME/mochi/is shared across projects when content matches. - Cache eviction: LRU on a
--cache-max=10GiBbudget.
6. Parallelism
- AST/MIR/C phases parallelise across modules with no shared state.
ccinvocations parallelise across object files.- The link step is serial.
-j defaults to the number of cores.
7. The runtime library
libmochi.a is an amalgamated static archive containing:
mochi/core(memory, types, errors, panic)mochi/sched(M:N scheduler, fibers, channels, streams, agents)mochi/query(Swiss-table, omap, sort, arena)mochi/io(file, network, time, env)mochi/text(utf8proc, simdutf, regex-Phase2)mochi/data(JSON via yyjson, YAML via libfyaml, CSV)mochi/net(libcurl-backed HTTP)mochi/llm(provider abstraction)mochi/ffi(Python embedding, Go RPC, TS via QuickJS-NG)
Per-target builds of libmochi.a live in
prebuilt/<triple>/libmochi.a in the install tree. The driver picks
the right one based on --target. Cross-compilation of the runtime
itself happens at install time, or on first use, via zig cc.
8. APE / cosmocc
The --apex mode uses cosmocc instead of zig cc. Output is a
single .com file that runs on macOS, Linux, Windows, FreeBSD,
NetBSD, OpenBSD without modification. The runtime is rebuilt against
Cosmopolitan libc for this profile.
Limitations:
- No
dlopen(so Python FFI and llama.cpp local LLM are disabled). - No threading on Windows under the polyfill (so streams degrade to single-threaded).
- Binary size larger (~5 MB minimum).
This is a niche but valuable feature for “ship one binary” stories.
9. WASM / WASI
--target=wasm32-wasi produces a .wasm binary that runs under
wasmtime, wasmer, wasmedge, or in a browser via wasi-snapshot-preview2
shims.
Limitations:
- BDWGC disabled; we use a precise allocator with a shadow stack.
- No threads (wasi-threads is unstable in 2026); scheduler falls back to single-fiber cooperative mode.
- Asyncify is applied post-link for coroutine support.
- No subprocess; Go and Python FFI disabled.
A separate --target=wasm32-emscripten for browser playground use
emits with emscripten and Asyncify; needed because wasi-libc lacks DOM
bindings.
10. Static vs dynamic
Default: fully static link. Single binary, no runtime dependencies besides libc.
Optional dynamic mode (--shared) builds libmochi.so and links the
final binary against it. Useful for memory-saving in distros that ship
many Mochi binaries.
11. Debug info
DWARF 5 on Linux, BSD, macOS; CodeView on Windows. The transpiler
emits #line directives so the C compiler attaches Mochi source spans
to every instruction. Split-debug-info (--gsplit-dwarf) on by
default; the binary stays small.
For --release builds, debug info goes to a sidecar (foo.dSYM on
macOS, foo.debug on Linux, foo.pdb on Windows). The main binary
is stripped.
12. Symbol resolution
The mangling rules in note 05 §3 guarantee no symbol collisions across packages. The link step resolves them via the system linker’s normal rules; we do not use a custom linker.
For the mochi-core runtime, every public symbol is prefixed with
mochi_ and exposed via a single header mochi/core.h. No
“surprise” exports.
13. Embedding mochi-core in a host C program
libmochi.a plus mochi/core.h is a valid C library. A host C
program can call mochi_runtime_init, mochi_eval_str, or link
against a transpiled .o directly.
Use case: a game engine in C++ that wants to script with Mochi.
14. Source maps
For the WASM target, the driver emits a .wasm.map (V3 source map
JSON) that points back to Mochi source. This lets browser dev tools
show the right file:line on a stack trace.
15. Diagnostics post-processing
The driver wraps the C compiler’s invocation. On non-zero exit, it
parses the gcc -fdiagnostics-format=sarif (or clang’s -fdiagnostics-print-source-range-info)
output and rewrites file:line refs from generated C to Mochi source
using the #line map. The user sees foo.mochi:42:1: error: ...,
not a generated-C path.
This is critical UX: 90% of compile errors should surface from the type-checker; the remaining 10% (linker errors, header conflicts, ICEs) must still be navigable.
16. Distribution
The Mochi CLI distribution includes:
mochi(Go driver binary, ~12 MB)zigbundled (small, MIT)cosmoccbundled (optional, large; can fetch on demand)runtime/include/mochi/*.h(runtime headers)runtime/lib/<triple>/libmochi.a(per-target prebuilds for tier 1)runtime/src/(runtime source for cross-compile fallback)
Install methods:
- Single-binary download from releases.
brew install mochi.apt install mochi(PPA).winget install mochi.cargo binstall mochi(since the project tracks rust-toolchain features anyway).
17. Versioning
The CLI follows SemVer. The runtime ABI is not stable across major versions; users are expected to rebuild. The transpiler’s IR (MIR) is internal and unstable. The generated C is reasonably stable; we publish diff stats per release so users tracking the C output can see drift.
18. Open questions
- Whether to ship
zig(~80 MB) bundled or fetch on first use. - Whether
--apexis a tier-1 feature or experimental. - Whether the per-module C cache is on by default or opt-in.
- Whether to expose a
mochi build --plugin=<so>API for codegen plugins (e.g., a custom backend). - Whether
mochi build --emit=ir-prettyis worth shipping (dumps a human-readable MIR for the user to inspect).