01

Keep the language small enough to inspect

The language has one numeric type, f64, with functions, branches, loops, and mutable bindings. Parsing and basic code generation follow LLVM's Kaleidoscope tutorial; the JIT header is vendored from LLVM. My work here is concentrated in the optimization passes and their integration.

A single type keeps the project focused on IR transformations. There is no general type checker, object-file output, garbage collector, or module system.

02

The order of the passes matters

Mutable bindings initially become stack allocations, stores, and loads. LLVM's mem2reg pass promotes those values into SSA form before my passes run. LoopSimplify supplies a preheader for loop-invariant code motion.

The custom pipeline then folds constant floating-point operations, removes unused instructions without side effects until no more can be removed, and moves eligible loop-invariant instructions into the preheader. Each pass reports which analyses remain valid after its changes.

03

Inspect the transformed IR

An unchanged program result does not prove that an optimization ran. FileCheck tests inspect the emitted IR. The loop-invariant example checks that multiplication appears in the entry block and is absent from the loop body.

End-to-end examples also check execution results. The repository records IR comparisons, but a smaller textual module is not a general runtime speedup. These passes are deliberately limited teaching implementations, especially compared with LLVM's production LICM.