Lean-Proven Semantics
“Your code is what the spec says it is — and the spec is checked in Lean.”
Resilient ships its own operational semantics in Lean 4. The Lean project
lives at resilient/lean-spec/
and is built with lake build. For every pure-arithmetic single-return function,
the Resilient compiler can emit a Lean theorem stating that the AST means
exactly what eval says it means.
CompCert exists for C; no embedded-targeted language ships its evaluation rules in a proof assistant. This is what puts Resilient in a different league for tool qualification under DO-178C DAL-A: the certification authority can audit the proof, not just the test suite.
What’s in the Lean project
resilient/lean-spec/
├── lakefile.lean — Lake build manifest
├── lean-toolchain — Lean version pin (v4.13.0)
├── Resilient.lean — root re-export
└── Resilient/
├── AST.lean — inductive Expr definition
├── Semantics.lean — eval : Env → Expr → Option Int
└── Theorems.lean — proven correctness lemmas
The AST mirror
inductive Expr where
| int : Int → Expr
| bool : Bool → Expr
| var : String → Expr
| add : Expr → Expr → Expr
| sub : Expr → Expr → Expr
| mul : Expr → Expr → Expr
| div : Expr → Expr → Expr
| mod : Expr → Expr → Expr
| eq : Expr → Expr → Expr
| lt : Expr → Expr → Expr
| le : Expr → Expr → Expr
| neg : Expr → Expr
| not_ : Expr → Expr
| ite : Expr → Expr → Expr → Expr
The fragment is deliberately small — loops, calls, and structs are not part
of the Lean spec for the first slice. Adding them is a matter of extending
AST.lean plus the Rust lowering pass in src/lean_spec.rs.
The semantics
Big-step evaluation eval : Env → Expr → Option Int. Total in the Lean sense
(structural recursion on Expr); the Option reflects partial expressions like
x / 0.
def eval (env : Env) : Expr → Option Int
| .int n => some n
| .add l r =>
match eval env l, eval env r with
| some a, some b => some (a + b)
| _, _ => none
| .div l r =>
match eval env l, eval env r with
| some _, some 0 => none -- divide by zero is an error
| some a, some b => some (a / b)
| _, _ => none
...
Proven theorems
The first four theorems we ship:
| Theorem | Statement |
|---|---|
eval_int_lit_id |
eval env (Expr.int n) = some n |
eval_add_comm |
eval env (Expr.add a b) = eval env (Expr.add b a) |
eval_const_fold_sound |
eval env (Add (Int a) (Int b)) = eval env (Int (a+b)) |
eval_neg_involutive |
eval env (Neg (Neg e)) = eval env e |
Each subsequent compiler optimisation pass (constant folding, peephole, etc.)
can be discharged against the same eval relation. The proofs in
Theorems.lean are the foundation; the obligations grow with the compiler.
Emitting per-function theorems
For any function whose body is a single return EXPR; over the supported
fragment, run:
rz --emit-lean-spec=double my_file.rz
The output is a Lean source file:
import Resilient.Semantics
open Resilient
/-! Auto-generated from Resilient fn `double` -/
def double : Expr := Expr.add (Expr.var "x") (Expr.var "x")
theorem double_correct (x : Int) :
eval (env_of [("x", x)]) double = some (x + x) := by
simp [eval, env_of, double]
Save this into lean-spec/Resilient/Generated/Double.lean and run
lake build — Lean re-derives the double_correct theorem from the
operational semantics and the AST. If the proof fails, the Resilient
compiler’s lowering is incorrect.
What a function must look like to lower
For the first slice, --emit-lean-spec accepts:
- A single
return EXPR;statement EXPRis built from: integer/bool literals, parameter identifiers,+ - * / %,== < <= > >=, unary-and!,if expr
Functions with loops, calls, struct accesses, or multi-statement bodies
yield an unsupported diagnostic. Each restriction lifts as the Lean
fragment grows.
Why this changes the conversation
Tool qualification under safety standards (DO-178C, ISO 26262, IEC 61508) requires evidence that the compiler — not just the user code — preserves the semantics on the way from source to silicon. Today that evidence is built out of test suites and structural-coverage reports. With a Lean spec, the evidence shifts to machine-checked theorems. Two consequences:
- The certification authority audits a small, independently-built spec
(the
Theorems.leanfile plus the build manifest), not the entire compiler test suite. - The compiler itself becomes a trusted base: every commit either re-derives the same theorems or fails the build.
Combined with the existing certificate manifest (RES-071, RES-194, RES-195), every shipped Resilient binary can carry a Lean-checkable proof of correctness for each function it contains.
Roadmap
The current spec is the first slice. Planned extensions:
- Statements:
let, sequential composition, conditionals. - Loops: while-loops with explicit termination measures.
- Calls: function-call lowering and inlining proofs.
- Effects: separate
evalfor pure vs. IO functions, with the Resilientpure/io/failsannotations bridged in. - Linkage: a CI step that runs
lake buildon the emitted theorem bundle and fails the PR if any proof breaks.