Command line tool
Using the standalone `esper` CLI for parsing, optimization, code generation, and execution.
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
Print (default)
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:
- Stack-slot promotion — promote
sallocslots that are only loaded and stored into SSA values - Constant folding — evaluate all-constant expressions at compile time
- Peephole — simplify short instruction patterns (double neg, self-ops)
- Strength reduction — replace expensive ops with cheaper equivalents
- Copy propagation — eliminate trivial merge copies
- CSE — remove syntactically identical redundant computations
- GVN — remove redundant computations that are not syntactically identical
- LICM — hoist loop-invariant computations into a pre-header
- Devirtualization — resolve indirect calls with a known target
- Inlining — substitute small or hot callee bodies into their callers
- Tail call optimization — rewrite self-tail calls as jumps to the entry block
- Escape analysis — lower
alloctosallocorhalloc - SROA — decompose non-escaping aggregates into per-member values
- Control-flow simplification — resolve constant branches, remove unreachable blocks
- Dead code elimination — remove unused instructions
- 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
| Option | Description |
|---|---|
--emit=text | Print versioned esper 1 text (default) |
--emit=c-ir | Print the separate experimental C-shaped serialization |
--emit=c | Generate C source code |
--emit=llvm | Generate LLVM IR |
--run | Execute with the VM interpreter |
--entry=<func> | Entry function for --run (default: main) |
-O0 | No optimization (default) |
-O1 | Basic optimization pipeline |
-o <file> | Output file (default: stdout) |
-h, --help | Show 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