An Elixir-to-native compiler built on
Beaver and the Slang-defined ex
dialect.
The first milestone wires a minimal closed loop (see tsai/beaver#6 for the plan):
- frontend: expanded module snapshot →
exIR (boundary only, no macro semantics); - lowering:
ex→func/arith/scf/cf→ LLVM viaBeaver.MLIR.Conversion.Plan(the conversion patterns themselves live in Beaver) —Batata.to_llvm/2runs the plan plus the standardarith-to-llvm/func-to-llvmpasses; - execution: ExecutionEngine (JIT) —
Batata.execute/2lowers the source and runsmainthrough the MLIR JIT; AOT —Batata.build/3emitslib<Module>.aplus a C driver that calls the entry function.
M2 adds the first slice of the term universe on top (see tsai/beaver#17):
- a Zig term runtime (
native/term_runtime.zig) implementing the declaration-first ABI innative/ABI.md(tagged word + bump heap); - lowering of
ex.tuple/ex.list/ex.map/ex.binaryconstruction and theex.is_*predicates toex.term.*runtime calls (the patterns live in Beaver,Beaver.MLIR.Conversion.Ex); - JIT execution of term construction and predicates through the runtime
shared library (
Batata.execute/2attaches it viashared_lib_paths).
The pipeline now has an explicit transform layer between lift and lowering (see tsai/beaver#16):
Batata.Transformis the information-preserving IR-to-IR rewrite layer; anything that changes representation or drops information belongs inBatata.Lower(discipline transplanted from expandable);Batata.Transform.InlineScalarCallsinlines local calls whose callee stays in the scalar slice, soex.callresults (typed!ex.dyn) can feed arithmetic — e.g.add(1, 2) + 3compiles to 6.
M3 starts the stdlib domain registry (see tsai/beaver#6):
Batata.Stdlibdeclares which{module, function, arity}entries are natively replaceable (:native_term), which await the BEAM callback bridge (:beamer_callback, e.g.Enum), and which are declared-but-unlowered (:unsupported); anything outside the surface raises explicitly at lift;- module-qualified calls (
Kernel.length/1,List.first/1, ...) and auto-imported Kernel BIFs (length/1,hd/1,elem/2,map_size/1, ...) resolve through the registry and lower toex.term.*runtime intrinsics; - the first slice added
ex.map_length(map_size/Map.size) to the Zig runtime, completing the read-intrinsic family for tuple/list/map/binary.
The Enum slice adds the first callback-shaped stdlib calls:
Enum.count/1lowers toex.term.enumerable_count, dispatching on the term tag (list/tuple/map/binary);Enum.map/2with an identity mapper andEnum.reduce/3with sum/return-accumulator reducers are recognized before closure extraction and lower toscf.whilecursor loops over the list (ex.list_get+ex.to_int); const mappers (fn _x -> c end) and capture-add mappers (fn x -> x + c end, literal or captured scalar) lower to descending cons-collection loops (ex.list_cons); other mapper/reducer shapes keep the explicit:beamer_callbackrejection; sum reduce over non-list enumerables (tuple/binary literals or variables) dispatches through the runtime's tag-basedex.term.enumerable_reduce; map reduce with afn {_k, v}, acc -> acc + v endvalue-sum reducer dispatches through the same runtime (continuation 3), andfn {k, _v}, acc -> acc + k endkey-sum through continuation 4;fn {k, v}, acc -> acc + k + v end(any addition order) sums key and value per entry through continuation 5; product reducers (fn x, a -> x * a end) use the cursor loop for list literals and runtime continuation 6 otherwise; subtraction is order-sensitive (a - xvsx - a, runtime continuations 7/8); arbitrary arithmetic combination reducers (e.g.a + x * 2) compile the reducer body into the cursor loop for list literals; integerdiv/2andrem/2reducers (order-sensitive, zero divisor yields 0) dispatch through runtime continuations 9-12; capture-sum reducers (fn x, a -> a + x + c end, c a captured scalar or literal) dispatch throughex.term.enumerable_reduce_c(continuation 13), capture-product (a + x * c, continuation 14) likewise; range literals (1..3) reduce throughex.term.enumerable_reduce_range(scalar reducers and count; combination/map shapes raise); arbitrary combination reducers are extracted to synthetic functions and called by the runtime through function pointers (ex.term.enumerable_reduce_fun), supporting list/tuple/binary for any pure-arithmetic reducer body.
The String/Base slice adds UTF-8 and byte-string conversions in the Zig runtime:
String.length/1(ex.term.binary_utf8_length, codepoint count) andString.to_integer/1/Integer.to_string/1(decimal round-trip);Base.encode16/1/Base.decode16/1(uppercase hex,ex.term.binary_encode16/ex.term.binary_decode16); invalid hex decodes to nil.
M5 starts the native_elixirc equivalent (see
tsai/beaver#29):
Batata.build/3now emits an export bundle (bundle.json,artifact_index.json,manifest.json) with module/entry/source digest/ runtime version/artifact digest and a per-file digest index, plus a symbol-levelexportslist (every definition'sModule.fun/arityand its native symbol, entry renamed tobatata_main);Export.verify_symbols!/2checks the symbols against the archive vianm;Batata.Upgrade.Diff.compare/2compares two bundle directories and reports file additions/removals/changes,artifacts_changed, andmigration_required(artifact change implies migration), plus a bundleschema_drift(field set and schema version: unchanged / changed / incompatible);test/semantic_gates_test.exsruns a gate per slice (scalar, term patterns, cursor scanners, Kernel/Enum/String/Base stdlib, closures, receive, try/throw), asserting Batata's compiled result matches the BEAM oracle of the equivalent expression;test/fixtures/self_bootstrap.exsis a self-bootstrap fixture: a batata-shaped module (stand-in forUpgrade.Diff's status classification) that is compiled by Batata, checked against the BEAM oracle, and built into a runnable AOT binary with verified symbols (zig cclinks the Zig term runtime).
Protocol consolidation starts with the native provider layer:
Batata.Native.Provideris adefprotocolextension point (native_plan/1,@fallback_to_any true); project-local IR nodes contribute replacement plans throughdefimpl, and consolidation exposes the closed-world provider set at compile time (Registry.impls/0);Batata.Stdlib.Plancarries the replacement class (:native_term/:beamer_callback/:unsupported) for an{module, function, arity};Batata.Stdlib.plan/1mirrors the built-in registry as a plan.
Beaver and Kinda are pre-release, so development uses local checkouts:
export BEAVER_PATH=/path/to/beaver
export BEAVER_KINDA_PATH=/path/to/kinda
mix deps.get
mix testThe Zig runtime is built on demand with zig build-lib (zig 0.16 is required
and preinstalled in CI).