Ir al contenido

IR y Optimización

La IR (representación intermedia) es el puente entre el AST del parser y los backends de restricciones. Usa forma de Asignación Estática Única — cada variable se define exactamente una vez.

struct SsaVar(pub u32);

Cada SsaVar es una variable única e inmutable identificada por un entero. El IrProgram las asigna secuencialmente vía fresh_var().

El IR tiene 19 variantes de instrucción. Cada una define exactamente una variable result:

InstrucciónCamposDescripción
Constvalue: FieldElementConstante en tiempo de compilación
Inputname, visibilityEntrada del circuito (pública o testigo)
InstrucciónDescripciónCosto R1CS
Add(lhs, rhs)Suma de campo0 (aritmética CL)
Sub(lhs, rhs)Resta de campo0
Mul(lhs, rhs)Multiplicación de campo1 restricción
Div(lhs, rhs)División de campo2 restricciones
Neg(operand)Negación de campo0
InstrucciónDescripciónCosto R1CS
Mux(cond, if_true, if_false)Selector booleano2 restricciones
InstrucciónDescripciónCosto R1CS
AssertEq(lhs, rhs)Forzar igualdad1 restricción
Assert(operand)Forzar operando == 12 restricciones
RangeCheck(operand, bits)Forzar 0 ≤ operando < 2^bitsbits+1 restricciones
InstrucciónDescripciónCosto R1CS
Not(operand)1 − operando1 restricción
And(lhs, rhs)lhs × rhs3 restricciones
Or(lhs, rhs)lhs + rhs − lhs×rhs3 restricciones
InstrucciónDescripciónCosto R1CS
IsEq(lhs, rhs)1 si iguales, 0 en caso contrario2 restricciones
IsNeq(lhs, rhs)1 si no iguales, 0 en caso contrario2 restricciones
IsLt(lhs, rhs)1 si lhs < rhs~760 restricciones
IsLe(lhs, rhs)1 si lhs ≤ rhs~760 restricciones
InstrucciónDescripciónCosto R1CS
PoseidonHash(left, right)Hash Poseidon 2-a-1361 restricciones
struct IrProgram {
instructions: Vec<Instruction>,
next_var: u32,
var_names: HashMap<SsaVar, String>, // nombres fuente para errores
var_types: HashMap<SsaVar, IrType>, // Field o Bool
}

El programa es una lista plana de instrucciones — no se necesitan phi-nodes porque los circuitos no tienen ramificación dinámica. Los metadatos de tipo (IrType::Field o IrType::Bool) se establecen por anotaciones de tipo y los backends los usan para omitir verificación booleana redundante.

IrLowering convierte el AST en un IrProgram:

// Con declaraciones explícitas
IrLowering::lower_circuit(source, public_vars, witness_vars) -> IrProgram
// Con declaraciones en el código fuente
IrLowering::lower_self_contained(source) -> (pub_names, wit_names, IrProgram)
  • Las vinculaciones let son alias — no se emite instrucción, el nombre mapea a un SsaVar existente
  • if/else se compila a Mux(cond, then_val, else_val)
  • Los bucles for se desenrollan estáticamente (máximo 10,000 iteraciones)
  • Las llamadas a funciones se insertan en línea en cada punto de llamada con detección de recursión vía call_stack
  • Los arrays son EnvValue::Array(Vec<SsaVar>) — la indexación debe ser constante en tiempo de compilación
  • len(arr) se resuelve en tiempo de compilación a una constante

Las variables se rastrean vía HashMap<String, EnvValue>:

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

El optimizador ejecuta tres pases secuencialmente vía passes::optimize(&mut program):

Un pase hacia adelante O(n) que evalúa operaciones sobre constantes conocidas:

  • Aritmética: Const(3) + Const(5)Const(8)
  • Identidad: x * 1x, x + 0x
  • Aniquilación: x * 0Const(0)
  • Auto-operaciones: x - xConst(0), x / xConst(1) (cuando x es una constante no cero)
  • Lógica booleana: pliega Not, And, Or sobre operandos constantes
  • Comparaciones: pliega IsEq, IsNeq, IsLt, IsLe sobre operandos constantes (comparación canónica de limbs)
  • Verificaciones de rango: verifica RangeCheck sobre constantes, reemplaza con pass-through

Un pase hacia atrás que elimina instrucciones cuyos resultados nunca se usan.

Eliminables (puros): Const, Add, Sub, Neg

Conservadores (pueden tener efectos secundarios o ser costosos): Mul, Div, Mux, PoseidonHash, RangeCheck, Not, And, Or, IsEq, IsNeq, IsLt, IsLe, Assert

Nunca eliminados: AssertEq, Input, RangeCheck, Assert (con efectos secundarios)

Un pase hacia adelante O(n) que calcula el conjunto de variables SSA probadas como booleanas (0 o 1). Los backends usan este conjunto para omitir restricciones de verificación booleana redundantes.

Semillas (booleanas conocidas):

  • Const(0) y Const(1)
  • Resultados de comparación: IsEq, IsNeq, IsLt, IsLe
  • Objetivos de RangeCheck(x, 1)
  • Operandos y resultados de Assert
  • Variables con anotación IrType::Bool

Propagación:

  • Not(x) → resultado es booleano si x es booleano
  • And(a, b) → resultado es booleano si ambos son booleanos
  • Or(a, b) → resultado es booleano si ambos son booleanos
  • Mux(c, t, f) → resultado es booleano si tanto t como f son booleanos

Un pase de análisis adicional (no parte de optimize()) que se ejecuta como diagnóstico:

  1. Propagación de contaminación hacia adelante: marca todas las variables SSA derivadas de entradas
  2. Alcanzabilidad de restricciones hacia atrás: marca variables usadas por aserciones/restricciones

Advertencias:

  • UnderConstrained: una entrada no influye en ninguna restricción — posible problema de solidez
  • UnusedInput: una entrada se declara pero nunca se usa

ir::eval::evaluate(program, inputs) proporciona evaluación concreta hacia adelante de un IrProgram:

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

Usado por compile_ir_with_witness() para validación temprana antes de la generación de restricciones. Variantes de error: MissingInput, DivisionByZero, AssertionFailed, AssertEqFailed, RangeCheckFailed, UndefinedVar.

ComponenteArchivo
Tiposir/src/types.rs
Bajadair/src/lower.rs
Evaluadorir/src/eval.rs
Plegado de Constantesir/src/passes/const_fold.rs
Eliminación de Código Muertoir/src/passes/dce.rs
Propagación Booleanair/src/passes/bool_prop.rs
Análisis de Contaminaciónir/src/passes/taint.rs