Skip to content
→ All work

Case study 17 / 26

Bellows

An optimizing compiler you can read end to end — from source text to x86-64 machine code, with an IR interpreter that must agree with the native binary on every program.

Status
Research
Period
2026
Domain
tools · experiments
Language
Rust
Last push
06 SEP 2026
License
MIT
Source of claims
README.md, docs/LANGUAGE.md, ARCHITECTURE.md

A complete pipeline for Ash, a small typed language: lexer, parser, semantic analysis with rustc-style diagnostics, three-address IR and interpreter, a fixpoint optimizer, and x86-64 code generation for Linux and Windows with runtime traps and DWARF line info. Zero dependencies, about 2,600 lines of Rust.

01/The problem

Real compilers are too large to read, and tutorial compilers stop at the first thing that prints 42 — no type checker with real diagnostics, no optimizer, no proof that optimization preserved semantics, no runtime traps.

02/The system

A complete pipeline for Ash, a small typed language: lexer, parser, semantic analysis with rustc-style diagnostics, three-address IR and interpreter, a fixpoint optimizer, and x86-64 code generation for Linux and Windows with runtime traps and DWARF line info. Zero dependencies, about 2,600 lines of Rust.

03/Implementation

  1. 01Lexer and recursive-descent parser with precedence climbing; every malformed input is a spanned error.
  2. 02Semantic analysis: scopes, types, mutability, arity, missing-return analysis, unreachable code — 20 diagnostic codes rendered rustc-style with a caret and suggested fix.
  3. 03Three-address IR over basic blocks with a printer and an interpreter.
  4. 04Optimizer to a fixpoint: constant folding and propagation, store→load forwarding, dead code, branch folding, jump threading, block merging.
  5. 05x86-64 assembly for System V and MS x64 with checked arithmetic that traps, and DWARF line info with -g.
  6. 06--emit tokens|ast|ir|asm and a stats command that reports what the optimizer did.

04/Engineering

Two backends that must agree

The interpreter and native code share one arithmetic definition; golden tests run every example interpreted and native at -O0 and -O1, and all four outputs must match.

Traps are part of the language

Overflow and division by zero are runtime errors in both backends, and the optimizer never folds a trapping operation away.

The optimizer is observable

Tests check IR shape, behavioural equivalence and that fewer instructions execute.

05/Interface

No product screenshots are published for this project. The visual above is a code-driven representation of how it behaves, built from the repository source — not a screenshot.

06/Tech stack

  • Rust
  • Zero dependencies
  • x86-64 (System V + MS x64)
  • DWARF
  • Fuzz testing

07/Result

Verified outcomes

  • 36 negative tests pin the exact line:col of every diagnostic; a deterministic fuzzer feeds 30,000 token soups and 5,000 random byte strings through the pipeline.
  • Golden suite compiles and runs every example natively on Linux and Windows in CI.

Known limitations

  • The code generator spills every value to the stack, so native code is only marginally faster than the interpreter; register allocation is the first roadmap item.

08/Links

Next case study

Tarn →