Guía de Extensión
Cómo agregar nuevas instrucciones, funciones integradas y pases de optimización.
Esta guía explica cómo extender Achronyme con nuevas instrucciones IR, funciones integradas de circuito, pases de optimización y funciones nativas de la VM.
Agregar una Nueva Instrucción IR
1. Definir la variante
En ir/src/types.rs, agrega una nueva variante al enum Instruction:
pub enum Instruction {
// ... variantes existentes ...
MyOp { result: SsaVar, operand: SsaVar },
}
2. Implementar métodos del trait
En el mismo archivo, actualiza tres métodos:
// result_var() — devuelve la variable resultado
Instruction::MyOp { result, .. } => *result,
// has_side_effects() — true si la instrucción no debe eliminarse
// Agregar al matches! si tiene efectos secundarios
// operands() — devuelve todas las variables SSA de entrada
Instruction::MyOp { operand, .. } => vec![*operand],
3. Emitir desde la bajada a IR
En ir/src/lower.rs, maneja la nueva instrucción en el método apropiado (ej., lower_call para funciones integradas, lower_expr para operadores):
"my_op" => {
let arg = self.lower_expr(args[0])?;
let result = self.program.fresh_var();
self.program.push(Instruction::MyOp { result, operand: arg });
Ok(EnvValue::Scalar(result))
}
4. Agregar lógica de evaluación
En ir/src/eval.rs, maneja la instrucción en la función evaluate:
Instruction::MyOp { result, operand } => {
let val = values[operand];
let out = /* calcular resultado */;
values.insert(*result, out);
}
5. Manejar en pases de optimización
Plegado de constantes (ir/src/passes/const_fold.rs): Agrega lógica de plegado si la operación puede calcularse en tiempo de compilación sobre constantes.
Eliminación de código muerto (ir/src/passes/dce.rs): Si la instrucción es pura (sin efectos secundarios), agrégala al conjunto eliminable. Si tiene efectos secundarios, mantenla conservadora.
Propagación booleana (ir/src/passes/bool_prop.rs): Si el resultado es siempre booleano, agrégalo como semilla.
6. Compilar a R1CS
En compiler/src/r1cs_backend.rs, agrega un brazo de match en compile_ir:
Instruction::MyOp { result, operand } => {
let lc_op = self.vars[operand].clone();
// Construir restricciones...
let lc_result = /* ... */;
self.vars.insert(*result, lc_result);
}
7. Compilar a Plonkish
En compiler/src/plonkish_backend.rs, agrega un brazo de match en compile_ir:
Instruction::MyOp { result, operand } => {
let val = self.materialize_val(/* ... */)?;
// Emitir filas de puerta...
self.vals.insert(*result, PlonkVal::Cell(cell));
}
8. Agregar generación de testigos
Si la instrucción asigna variables intermedias, registra un WitnessOp en compiler/src/witness_gen.rs.
Agregar una Función Integrada de Circuito
Las funciones integradas de circuito son funciones de alto nivel que bajan a una o más instrucciones IR.
1. Agregar el nombre al parser
No se necesitan cambios — el parser ya maneja llamadas a funciones genéricamente.
2. Manejar en la bajada a IR
En ir/src/lower.rs, agrega un brazo de match en lower_call:
"my_builtin" => {
if args.len() != 2 {
return Err(IrError::WrongArgumentCount { ... });
}
let a = self.lower_expr_scalar(&args[0])?;
let b = self.lower_expr_scalar(&args[1])?;
let result = self.program.fresh_var();
self.program.push(Instruction::MyOp { result, lhs: a, rhs: b });
Ok(EnvValue::Scalar(result))
}
3. Agregar al evaluador, pases y backends
Sigue los pasos 4-8 de “Agregar una Nueva Instrucción IR” arriba.
Agregar un Pase de Optimización
1. Crear el archivo del pase
Crea ir/src/passes/my_pass.rs:
use crate::types::{IrProgram, Instruction, SsaVar};
pub fn my_pass(program: &mut IrProgram) {
// Recorrer program.instructions y transformar
}
2. Registrar en el gestor de pases
En ir/src/passes/mod.rs, agrega el módulo y llámalo desde optimize:
mod my_pass;
pub fn optimize(program: &mut IrProgram) {
const_fold::const_fold(program);
dce::dce(program);
bool_prop::bool_prop(program);
my_pass::my_pass(program); // nuevo pase
}
Directrices para pases
- Los pases hacia adelante (como
const_fold,bool_prop) iteranprogram.instructionsde frente a atrás, construyendo estado - Los pases hacia atrás (como
dce) iteran de atrás a frente, rastreando qué resultados se usan - Los pases deben ser O(n) en conteo de instrucciones
- Evita comportamiento cuadrático — usa
HashMap<SsaVar, ...>para búsquedas
Agregar una Función Nativa de VM
1. Implementar la función
En vm/src/stdlib/core.rs, agrega la implementación:
pub fn native_my_func(vm: &mut VM, args: &[Value]) -> Result<Value, RuntimeError> {
let arg = args[0];
// ... procesar ...
Ok(Value::int(result))
}
La firma siempre es fn(&mut VM, &[Value]) -> Result<Value, RuntimeError>.
2. Registrar en la tabla de despacho
En vm/src/specs.rs, agrega una entrada a NATIVE_TABLE:
pub const NATIVE_TABLE: &[NativeSpec] = &[
// ... entradas existentes ...
NativeSpec { name: "my_func", arity: 1 },
];
3. Actualizar NATIVE_COUNT
En el mismo archivo, actualiza la constante:
pub const NATIVE_COUNT: usize = 24; // era 23
La aserción en tiempo de compilación detectará cualquier discrepancia.
4. Mapear en bootstrap
En vm/src/machine/native.rs, agrega el brazo de match en bootstrap_natives:
"my_func" => stdlib::core::native_my_func,
Agregar un Opcode de VM
1. Definir el opcode
En vm/src/opcode.rs, agrega una constante:
pub const MY_OP: u8 = /* siguiente número disponible */;
2. Emitir en el compilador de bytecode
En compiler/src/compiler.rs, emite el opcode durante la compilación:
// En el método de compilación apropiado
self.emit(opcode::MY_OP, a, b, c);
3. Manejar en el intérprete de la VM
En vm/src/machine/vm.rs, agrega un brazo de match en interpret_inner:
opcode::MY_OP => {
let a = inst_a!(inst);
let b = inst_b!(inst);
// ... ejecutar ...
}
4. Rastrear info de línea
En el compilador de bytecode, asegúrate de que current_line esté establecido antes de emitir:
self.current_line = node.line as u32;
self.emit(opcode::MY_OP, a, b, c);
Esto habilita el rastreo de ubicación de errores ([line N] in func: Error).
Lista de Verificación
Al agregar una nueva instrucción o función integrada, verifica:
-
ir/src/types.rs— enumInstruction+result_var+has_side_effects+operands -
ir/src/lower.rs— emitir instrucción desde AST -
ir/src/eval.rs— evaluación concreta -
ir/src/passes/const_fold.rs— plegar sobre constantes (si aplica) -
ir/src/passes/dce.rs— marcar como eliminable o conservadora -
ir/src/passes/bool_prop.rs— propagar booleano (si aplica) -
compiler/src/r1cs_backend.rs— generación de restricciones R1CS -
compiler/src/plonkish_backend.rs— emisión de puertas Plonkish -
compiler/src/witness_gen.rs— computación de testigo (si hay cables intermedios) - Pruebas en todos los crates relevantes