burn / docs

Tools

How the compiler works

The pipeline from source to bvm, native code and JavaScript

The compiler is written in Burn (compiler/) and runs on bvm, like every Burn program. burn itself is bvm running the command line written in Burn, which contains the compiler, so burn run app.bn checks, compiles and runs the program without leaving bvm.

This page is a summary; how Burn works goes through every stage in detail.

Front end

The lexer and a recursive descent parser build an AST. Newlines end statements, which is how String name = x and var x = a followed by -1 on the next line stay unambiguous. Every node carries a byte span, so every error points at the right line and column.

The type checker resolves names across modules, checks visibility, infers types, applies smart casts with a flow-sensitive analysis and lowers everything into a small typed IR. At that point every operation is explicit: integer and float arithmetic are different instructions, conversions are visible, and every runtime helper call knows the static type of its arguments.

Runtime

All values are 64 bit words. Ints, floats, bools and enums are stored directly, everything else is a pointer to a heap object with a 16 byte header (kind, flags, type id, size). The runtime is written in Burn (lib/rt): memory and reference counting with a cycle collector, strings, arrays, maps, boxing for any, formatting, JSON, HTTP, files and async tasks. It calls the C library through @Extern, is compiled into the toolchain for bvm, and is linked into native executables as libburn_runtime.a.

Ownership

After type checking, an ownership pass makes memory management explicit in the IR. Every local variable, field, array slot and map entry owns the object in it; parameters are borrowed from the caller. The pass inserts retain where a borrowed value becomes owned (stored in a variable or field, returned), release where an owner lets go (a variable is overwritten, a temporary has been used, the function returns), and moves instead of copies when a local is returned. Temporaries are released at the end of the statement that created them, and arguments read from fields or globals are kept alive for the duration of the call. Cycles are found by trial deletion over the objects whose type can form a cycle, which is computed once from the type graph.

bvm

burn run compiles the IR to a bvm module and runs it right away. bvm is a general-purpose stack VM with its own assembly language, bytecode format and verifier. Burn structs become struct types, interface methods become dispatch tables, async calls become spawn, and every operation that can fail carries its source location. When the module is loaded, the verifier computes each function's frame size and common sequences are fused into superinstructions for loops, comparisons and field access; functions that run often are compiled to machine code by the JIT. Checking and compiling a typical program takes a few milliseconds, so running a file feels like using an interpreter. burnc --target bvm writes the same module to a .bvmc file, and --target bar into an archive.

Native code

burn build turns the IR into assembly for the processor: x86-64 (Intel syntax, compiler/src/native/x86.bn) or 64-bit ARM (compiler/src/native/arm64.bn), for Linux (ELF) and macOS (Mach-O). Locals live in the stack frame, expressions evaluate into rax or x0, comparisons in conditions become compare-and-jump, array access and field access are inlined, and interface calls go through per-method dispatch tables. Both generators share the data layout, the order of arguments on the stack and the code that decides what a native program exports (compiler/src/native/shared.bn), so the same runtime serves both. The program entry point and the trampoline that starts async tasks are generated assembly. The assembly is linked by cc with the runtime (libburn_runtime.a, built by scripts/native-runtime.sh into share/burn/lib), so the result is a standalone executable. A program that imports bytecode libraries is compiled to bytecode instead and linked with bvm (libburn_launcher.a), which runs it inside the executable.

--target js turns the IR into one JavaScript file instead (compiler/src/js/js.bn), with a runtime written in JavaScript in front of it (lib/js/prelude.js): every function becomes a JavaScript function, records become arrays tagged with their type, and checked operations call small helpers that report the same runtime errors as bvm.

Without the standard library

Programs built with --no-std or std = false cannot import the standard library, and the checker rejects the features it leaves out (details).

Bootstrapping

The compiler builds itself. The C compiler assembles the seed kept in the repository (compiler/boot/<platform>.tar.xz, the compiler as assembly), which compiles compiler/src once; that compiler builds itself again, and both results must be the same assembly (scripts/bootstrap.sh). Building needs only a C compiler, tar and xz (details). Until 26.1.0-experimental-4 a released Burn (stage0) started the bootstrap, and until 26.1.0-experimental-7 a bytecode copy of the compiler run by the bvm written in Rust. Releases up to 26.1.0-experimental-2 were compiled by a compiler written in Rust; it was retired once the compiler written in Burn did everything it did. 26.1.0-experimental-3 shipped without the native and JavaScript backends, and 26.1.0-experimental-4 brings both back, ported to Burn. Since 26.1.0-experimental-7 bvm and the runtime are written in Burn too, and no Rust is left.