Esper is a C99 compiler backend library. Your frontend decides what a program means. Esper stores that program, transforms it, and hands it to a backend. You can begin with a text file before writing any C integration code.

esper 1
fn main() i32 {
entry:
  answer i32 = add 20, 22
  return answer
}

Save this as hello.esp. Build the project, then run esper hello.esp --run. The program returns 42; a shell reports that as exit status 42, not printed text. Use esper hello.esp --emit=text to inspect the IR Esper reads back.

The path through the library

  1. Create a translation unit using the builder, or parse esper 1 text.
  2. Verify that values, types and control flow agree.
  3. Run selected optimization passes. These modify the unit in place.
  4. Select a backend and emit code, or execute through the interpreter.
  5. Free the unit when finished.

Read the text guide first, then embedding. The language guides show how frontend features become lower-level operations. You do not need to implement every feature before compiling a useful first language: integers, functions, conditions and loops are enough.

What works today

The checkout builds a static C library, an optional C++ wrapper, a CLI, and test runners. Versioned text supports named values, numeric temporary names, mutable locals, aggregates, embedded bytes and a subset of annotations. Internally, passes and the public builder still use SSA with block parameters. The text reader promotes locals into that representation; the printer inserts edge copies so ordinary text does not need PHI nodes or block arguments.

C99, LLVM text, WebAssembly, native tinyasm and shader emitters exist. Their coverage differs; inspect --list-backends and the backend tests before choosing one. Native object/link output depends on sibling tinyasm and tinylinker builds. LLVM text output does not include an LLVM optimizer or machine-code compiler.

Where we are going

The main product is a small IR library and format. The intended distribution is one amalgamated C core header, a convenient C++ API, and separate amalgamated backend headers. Binary unit archives and independent-result merging now work; see saving and linking units for the supported boundaries. A public mutable-local builder, portable layout policies and richer source-type descriptions remain work to do. See the implementation roadmap for exact boundaries.

The native backend’s performance goal is roughly 90% of LLVM’s generated program speed with much lower compilation cost. That is a target, not a measured claim. Benchmark reports begin tracking compiler cost now.