Skip to content

IR & Optimization

The IR (intermediate representation) is the bridge between the parser’s AST and the constraint backends. It uses Static Single Assignment form — every variable is defined exactly once.

struct SsaVar(pub u32);

Each SsaVar is a unique, immutable variable identified by an integer. The IrProgram allocates them sequentially via fresh_var().

The IR has 19 instruction variants. Each defines exactly one result variable:

InstructionFieldsDescription
Constvalue: FieldElementCompile-time constant
Inputname, visibilityCircuit input (public or witness)
InstructionDescriptionR1CS Cost
Add(lhs, rhs)Field addition0 (LC arithmetic)
Sub(lhs, rhs)Field subtraction0
Mul(lhs, rhs)Field multiplication1 constraint
Div(lhs, rhs)Field division2 constraints
Neg(operand)Field negation0
InstructionDescriptionR1CS Cost
Mux(cond, if_true, if_false)Boolean selector2 constraints
InstructionDescriptionR1CS Cost
AssertEq(lhs, rhs)Enforce equality1 constraint
Assert(operand)Enforce operand == 12 constraints
RangeCheck(operand, bits)Enforce 0 ≤ operand < 2^bitsbits+1 constraints
InstructionDescriptionR1CS Cost
Not(operand)1 − operand1 constraint
And(lhs, rhs)lhs × rhs3 constraints
Or(lhs, rhs)lhs + rhs − lhs×rhs3 constraints
InstructionDescriptionR1CS Cost
IsEq(lhs, rhs)1 if equal, 0 otherwise2 constraints
IsNeq(lhs, rhs)1 if not equal, 0 otherwise2 constraints
IsLt(lhs, rhs)1 if lhs < rhs~760 constraints
IsLe(lhs, rhs)1 if lhs ≤ rhs~760 constraints
InstructionDescriptionR1CS Cost
PoseidonHash(left, right)Poseidon 2-to-1 hash361 constraints
struct IrProgram {
instructions: Vec<Instruction>,
next_var: u32,
var_names: HashMap<SsaVar, String>, // source names for errors
var_types: HashMap<SsaVar, IrType>, // Field or Bool
}

The program is a flat list of instructions — no phi-nodes needed because circuits have no dynamic branching. Type metadata (IrType::Field or IrType::Bool) is set by type annotations and used by backends to skip redundant boolean enforcement.

IrLowering converts the AST into an IrProgram:

// With explicit declarations
IrLowering::lower_circuit(source, public_vars, witness_vars) -> IrProgram
// With in-source declarations
IrLowering::lower_self_contained(source) -> (pub_names, wit_names, IrProgram)
  • let bindings are aliases — no instruction emitted, the name maps to an existing SsaVar
  • if/else compiles to Mux(cond, then_val, else_val)
  • for loops are statically unrolled (max 10,000 iterations)
  • Function calls are inlined at each call site with recursion detection via call_stack
  • Arrays are EnvValue::Array(Vec<SsaVar>) — indexing must be compile-time constant
  • len(arr) is resolved at compile time to a constant

Variables are tracked via HashMap<String, EnvValue>:

enum EnvValue {
Scalar(SsaVar),
Array(Vec<SsaVar>),
}

The optimizer runs three passes sequentially via passes::optimize(&mut program):

A forward O(n) pass that evaluates operations on known constants:

  • Arithmetic: Const(3) + Const(5)Const(8)
  • Identity: x * 1x, x + 0x
  • Annihilation: x * 0Const(0)
  • Self-operations: x - xConst(0), x / xConst(1) (when x is a non-zero constant)
  • Boolean logic: folds Not, And, Or on constant operands
  • Comparisons: folds IsEq, IsNeq, IsLt, IsLe on constant operands (canonical limb comparison)
  • Range checks: verifies RangeCheck on constants, replaces with pass-through

A backward pass that removes instructions whose results are never used.

Eliminable (pure): Const, Add, Sub, Neg

Conservative (may have side effects or be expensive): Mul, Div, Mux, PoseidonHash, RangeCheck, Not, And, Or, IsEq, IsNeq, IsLt, IsLe, Assert

Never eliminated: AssertEq, Input, RangeCheck, Assert (side-effecting)

A forward O(n) pass that computes the set of SSA variables proven to be boolean (0 or 1). The backends use this set to skip redundant boolean enforcement constraints.

Seeds (known boolean):

  • Const(0) and Const(1)
  • Comparison results: IsEq, IsNeq, IsLt, IsLe
  • RangeCheck(x, 1) targets
  • Assert operands and results
  • Variables with IrType::Bool annotation

Propagation:

  • Not(x) → result is boolean if x is boolean
  • And(a, b) → result is boolean if both are boolean
  • Or(a, b) → result is boolean if both are boolean
  • Mux(c, t, f) → result is boolean if both t and f are boolean

An additional analysis pass (not part of optimize()) that runs as a diagnostic:

  1. Forward taint propagation: marks all SSA variables derived from inputs
  2. Backward constraint reachability: marks variables used by assertions/constraints

Warnings:

  • UnderConstrained: an input influences no constraint — potential soundness issue
  • UnusedInput: an input is declared but never used

ir::eval::evaluate(program, inputs) provides concrete forward evaluation of an IrProgram:

evaluate(program: &IrProgram, inputs: &HashMap<String, FieldElement>)
-> Result<HashMap<SsaVar, FieldElement>, EvalError>

Used by compile_ir_with_witness() for early validation before constraint generation. Error variants: MissingInput, DivisionByZero, AssertionFailed, AssertEqFailed, RangeCheckFailed, UndefinedVar.

ComponentFile
Typesir/src/types.rs
Loweringir/src/lower.rs
Evaluatorir/src/eval.rs
Constant Foldingir/src/passes/const_fold.rs
Dead Code Eliminationir/src/passes/dce.rs
Boolean Propagationir/src/passes/bool_prop.rs
Taint Analysisir/src/passes/taint.rs