This page describes the current SSA builder and backend implementation. Start with the new text walkthrough for esper 1 syntax and the roadmap for the library-first migration boundary.

Overview

The esper command-line tool wraps the library for everyday use: parsing .esp files, running them through the VM, emitting C or LLVM IR, and applying optimizations.

esper [options] <input.esp>

Modes

Parse an .esp file and print versioned esper 1 text with mutable locals. Compatibility inputs are accepted too; printing converts their surface syntax.

esper input.esp
esper input.esp -o output.esp

Run

Execute an .esp file directly in the register-file VM interpreter. The process exit code is the i32 return value of the entry function.

esper --run input.esp
esper --run --entry=test_func input.esp

Emit C

Generate portable C source code from the SSA IR. Each SSA value becomes a named C local variable; control flow uses labeled gotos; block parameters are lowered to temporary-based copies.

esper --emit=c input.esp -o output.c
clang -o program output.c -lm

Emit LLVM IR

Generate LLVM IR that can be compiled with clang or llc.

esper --emit=llvm input.esp -o output.ll
clang -o program output.ll

Optimization

The -O1 flag enables the optimization pipeline before output:

esper -O1 input.esp                  # optimized print
esper -O1 --emit=c input.esp         # optimized C output
esper -O1 --run input.esp            # optimize then execute

The -O1 pipeline runs these passes in order:

  1. Stack-slot promotion — promote salloc slots that are only loaded and stored into SSA values
  2. Constant folding — evaluate all-constant expressions at compile time
  3. Peephole — simplify short instruction patterns (double neg, self-ops)
  4. Strength reduction — replace expensive ops with cheaper equivalents
  5. Copy propagation — eliminate trivial merge copies
  6. CSE — remove syntactically identical redundant computations
  7. GVN — remove redundant computations that are not syntactically identical
  8. LICM — hoist loop-invariant computations into a pre-header
  9. Devirtualization — resolve indirect calls with a known target
  10. Inlining — substitute small or hot callee bodies into their callers
  11. Tail call optimization — rewrite self-tail calls as jumps to the entry block
  12. Escape analysis — lower alloc to salloc or halloc
  13. SROA — decompose non-escaping aggregates into per-member values
  14. Control-flow simplification — resolve constant branches, remove unreachable blocks
  15. Dead code elimination — remove unused instructions
  16. Symbol-table sweep — remove unreferenced globals, constants, and externs

The CLI runs the same esper_optimize an embedder would, so -O1 means the same thing on either side. Use -O0 (the default) to skip the optional optimization pipeline. Parsing versioned mutable locals still performs the promotion needed by the SSA core.

Options

OptionDescription
--emit=textPrint versioned esper 1 text (default)
--emit=c-irPrint the separate experimental C-shaped serialization
--emit=cGenerate C source code
--emit=llvmGenerate LLVM IR
--runExecute with the VM interpreter
--entry=<func>Entry function for --run (default: main)
-O0No optimization (default)
-O1Basic optimization pipeline
-o <file>Output file (default: stdout)
-h, --helpShow help

Examples

# Round-trip a file
esper program.esp

# Run fibonacci
esper --run fib.esp
echo $?  # prints the return value

# Generate optimized C and compile
esper -O1 --emit=c program.esp -o program.c
clang -O2 -o program program.c -lm

# Generate LLVM IR for further optimization
esper --emit=llvm program.esp | llc -O2 -filetype=obj -o program.o