The arithmetization pass (pkg/asm/compiler/) converts each function's
vectorized instruction sequence into a MIR module of vanishing constraints,
range constraints, and lookup arguments.
Each function becomes one MIR module. The module contains one column per
register (inputs, outputs, and locals), plus — for functions with more than
one vector bundle — a program counter (PC) column and a return line
(RET) column. Each row of the module represents one execution step (one
vector bundle) of one invocation of the function. Range constraints are
added automatically for every column to enforce its declared bit-width.
Functions that vectorize to a single bundle are called atomic. They need
no PC or RET columns because every row is unconditionally one complete
invocation.
For each vector bundle at program counter k, the compiler emits a single
vanishing constraint guarded by PC[i] == k (i.e. the constraint need only
hold on rows where the current step is k). The body of the constraint is
the conjunction of the constraints generated for each micro-instruction in
the bundle:
-
Add/Mul— an equalitylhs = rhswhere the left-hand side is the target register on the current row, and the right-hand side is the polynomial over the source registers. When a source register was already written earlier in the same bundle (forwarding), its current-row valuereg[i]is used; otherwise the previous-row valuereg[i-1]is used. For subtraction (b, x := y - c), the sign bitbis moved to the right-hand side and the constraint is rebalanced:x + c = y + 2^N * b(whereNis the bit-width ofx). -
SkipIf/Skip— these do not emit a constraint themselves, but they build a branch table that records the path condition under which each subsequent micro-instruction executes. Every constraint generated from a later micro-instruction in the bundle is wrapped in that condition, so only the constraints on the active path are enforced. -
Jmp— emitsPC[i+1] = target, constraining the program counter transition. -
Return— emitsRET[i] = 1, marking the row as a return step. The return line is used as the enable signal for the lookup argument that enforces function-call correctness (see below). -
Fail— emits the constantfalse, making the constraint unsatisfiable on any row that reaches this instruction.
Registers that are not written by a bundle must retain their value from the
previous row. After building the per-micro-instruction constraints, the
compiler adds a constancy constraint reg[i] = reg[i-1] for every
register that the bundle does not definitely write. For registers that are
only sometimes written (e.g. on one branch of a SkipIf), the write
condition is derived from the branch table, negated, and used as a guard:
(not written) => reg[i] = reg[i-1].
Each call site (bus) in a function generates a lookup argument: the tuple
of argument and return columns at the call site must appear as a row in the
callee's module. For multi-line callees the lookup is filtered to rows where
RET == 1, ensuring that only complete, returning invocations are looked up.
As described above, each micro-instruction within a vector bundle is guarded by a branch condition — a logical formula over register equalities and inequalities that records the exact path through the bundle that must have been taken for that instruction to execute. These conditions can accumulate redundant atoms as the path through a bundle grows, and translating them naively into polynomial constraint terms produces unnecessarily large constraints.
Branch table optimisation (pkg/util/logical/) simplifies these conditions
before they are translated. Conditions are maintained in Disjunctive Normal
Form (DNF) and simplified using a set of rules including:
- Subsumption —
x==1 ∧ x!=0simplifies tox==1, sincex==1already impliesx!=0. - Contradiction —
x==1 ∧ x==2simplifies to⊥(false), and any conjunction containing⊥is dropped. - Tautology —
x==0 ∨ x!=0simplifies to⊤(true), eliminating the guard entirely. - Unit propagation —
x==0 ∧ y==xsimplifies tox==0 ∧ y==0by substituting the known value ofxinto subsequent atoms.
Simpler conditions translate into fewer and cheaper polynomial terms in the generated constraints, directly reducing proof cost.