Guía de Extensió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
Sección titulada «Agregar una Nueva Instrucción IR»1. Definir la variante
Sección titulada «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
Sección titulada «2. Implementar métodos del trait»En el mismo archivo, actualiza tres métodos:
// result_var() — devuelve la variable resultadoInstruction::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 entradaInstruction::MyOp { operand, .. } => vec![*operand],3. Emitir desde la bajada a IR
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «Agregar un Pase de Optimización»1. Crear el archivo del pase
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «Agregar una Función Nativa de VM»1. Implementar la función
Sección titulada «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
Sección titulada «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
Sección titulada «3. Actualizar NATIVE_COUNT»En el mismo archivo, actualiza la constante:
pub const NATIVE_COUNT: usize = 24; // era 23La aserción en tiempo de compilación detectará cualquier discrepancia.
4. Mapear en bootstrap
Sección titulada «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
Sección titulada «Agregar un Opcode de VM»1. Definir el opcode
Sección titulada «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
Sección titulada «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 apropiadoself.emit(opcode::MY_OP, a, b, c);3. Manejar en el intérprete de la VM
Sección titulada «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
Sección titulada «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
Sección titulada «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