How Burn works
The toolchain
One executable, several names
An installed toolchain looks like this:
~/.burn/
├── bin/
│ ├── burn the toolchain: one native executable, written in Burn
│ ├── burni -> burn
│ ├── burnc -> burn
│ ├── burn-lsp -> burn
│ ├── bvm -> burn
│ └── burnfmt a script: burni share/burn/tools/fmt.bn "$@"
└── share/burn/
├── tools/fmt.bn the formatter
└── lib/ the runtime and bvm for native executablesbin/burn is compiler/src/bin/burn.bn compiled to native code. It contains the command line
(compiler/src/cli/), the whole compiler, the language server, burn doc, and bvm with its JIT
(compiler/src/bvm/), linked with the runtime written in Burn (lib/rt/). When it starts, it looks at the name it
was started with: as burn, burni, burnc or burn-lsp it is the command line in that role, and under any other
name, such as the bvm link, it is the virtual machine with its own commands (bvm app.bvmc, bvm asm,
bvm link, ...).
How the command line runs programs
To run a program, the command line checks it and compiles it to a bvm module, then hands the module to the bvm
compiled into the same executable (compiler/src/cli/host.bn), which links, verifies and runs it. So burn app.bn
never starts a second process and never writes a file. The same layer writes .bvmc bytecode and .bar archives,
reads the exported functions of bytecode libraries a program imports, and keeps a REPL session's module and globals
between inputs.
burn build produces assembly in Burn and then does start one process: the C compiler, to assemble and link
(native executables).
The standard library and the runtime
- The standard library (
lib/std/*.bn) is embedded into the compiler as source text (compiler/src/project/stdlib.bn, generated bycompiler/src/bin/genstd.bn), soimport "std/strings"never reads a file and always matches the compiler.burn sourceswrites these sources to disk for editors. - The runtime (
lib/rt/*.bn) is written in Burn too: memory and the cycle collector, strings, collections, printing, files, processes, JSON, HTTP and tasks. It is compiled into the toolchain, where bvm calls it directly, and intoshare/burn/lib/libburn_runtime.afor native executables, so programs behave the same both ways.
The repository's tests check that the generated standard library, JavaScript runtime and burn doc assets are up to
date with their sources.
Packages
A project's burn.toml lists its dependencies, and burn.lock pins each to an exact revision. Installed packages
live in ~/.burn/packages (or $BURN_HOME/packages), one folder per package and revision:
~/.burn/packages/github.com/ada/colors@3f2a9c1d0b7e/The compiler only reads these folders; ash downloads and updates them. A dependency given
by path ({ path = "../shared" }) is read from that folder directly. Because the revision is part of the folder name,
projects that use different versions of a package never interfere.
The language server
burn lsp (or burn-lsp) is the command line in language server mode. For every change in the editor it runs the
front end of the compiler on the open files, with an index of what each name refers to, and answers hovers,
completions, go-to-definition, references, renames and diagnostics from it. Because it is the same compiler, the
editor shows exactly the errors burn check would. On Linux the server limits its own memory to 4 GB
(BURN_LSP_MEMORY_MB changes it, 0 removes the limit), so a runaway analysis stops the server, not the machine.
The REPL
The REPL keeps the code entered so far. Each new input is checked together with it, compiled to a small module and
run by bvm with the session's globals kept, so variables, functions and types survive between inputs. :source
prints the accumulated code.
How the compiler builds itself
The compiler is a Burn program, so it needs a Burn compiler to build. That compiler is in the repository as a seed:
compiler/boot/<platform>.tar.xz holds the compiler from an earlier commit and the runtime it links, compiled to
assembly for linux-x86_64, linux-aarch64, macos-x86_64 and macos-aarch64, about 1.3 MB each, the way Zig
keeps zig1.wasm. Only a C compiler, tar and xz are needed to start; no Burn has to be downloaded.
scripts/bootstrap.sh (which scripts/package.sh runs first):
- assembles the seed of this platform with the C compiler, giving stage 0, a native compiler;
- lets stage 0 compile
compiler/src/main.bnandlib/rtto assembly, which the C compiler assembles into stage 1; - lets stage 1 do the same, giving stage 2, and checks that the assembly of stage 1 and stage 2 is the same, byte for byte. When it is not, because the code generator changed since the seed was made, stage 2 builds once more, and stage 2 and stage 3 must be the same.
The last step proves that the compiler, compiled by itself, produces exactly what it was compiled from, so a release
does not depend on quirks of the compiler that built it. Each stage takes a few seconds. scripts/package.sh then
builds bin/burn and the runtime libraries with the final stage, in under a minute in all. CI runs this on every
change.
compiler/src may only use language features that the seed understands. To use a new one in the compiler, the
feature lands first; then sh scripts/bootstrap.sh --update replaces the seeds of all four platforms with the
compiler just built, and the first use comes with it. 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 that the Rust bvm ran.