Ir al contenido

VM y Bytecode

La VM de Achronyme es un intérprete de bytecode basado en registros. Ejecuta archivos .achb compilados usando una pila de tamaño fijo de 65,536 slots de Value.

Fuente (.ach)
Compilador de Bytecode → Prototipos de función + bytecode
Serializador → archivo binario .achb
Cargador → heap + pila + frames de la VM
Intérprete → Ejecución basada en registros
VM {
heap: Heap, // arenas tipadas + GC
stack: [Value; 65536], // pila de registros de tamaño fijo
frames: Vec<CallFrame>, // pila de llamadas
globals: Vec<GlobalEntry>, // variables globales
natives: Vec<NativeObj>, // 23 funciones integradas
open_upvalues: lista enlazada, // variables de pila capturadas
stress_mode: bool, // forzar GC en cada ciclo
}

Cada llamada a función apila un CallFrame:

CallFrame {
closure: u32, // handle a Closure en el heap
ip: usize, // puntero de instrucción
base: usize, // offset base en la pila
dest_reg: usize, // dónde almacenar valor de retorno
}

El registro R[i] en el frame actual mapea a stack[frame.base + i].

Los valores son enteros de 64 bits etiquetados — sin boxing para tipos comunes:

Bits 63..60 = etiqueta de 4 bits
Bits 59..0 = payload de 60 bits
EtiquetaNombrePayload
0INTentero con signo i60 (en línea)
1NIL
2FALSE
3TRUE
4STRINGhandle u32 → arena de strings
5LISThandle u32 → arena de listas
6MAPhandle u32 → arena de mapas
7FUNCTIONhandle u32 → arena de funciones
8FIELDhandle u32 → arena de campos (BN254)
9PROOFhandle u32 → arena de pruebas
10NATIVEhandle u32 → tabla de nativos
11CLOSUREhandle u32 → arena de closures
12ITERhandle u32 → arena de iteradores

Los enteros (etiqueta 0) son el tipo de valor más común — usar la etiqueta 0 significa que no se necesita enmascaramiento para el caso común.

Rango de enteros: -2^59 a 2^59 - 1 (576,460,752,303,423,487). El desbordamiento genera IntegerOverflow.

Cada instrucción es un u32 en uno de dos formatos:

[opcode:8][A:8][B:8][C:8]

Usado para instrucciones de 3 operandos como Add R[A] = R[B] + R[C].

[opcode:8][A:8][Bx:16]

Usado para instrucciones con un operando de 16 bits, como LoadConst R[A] = K[Bx] o Jump IP = Bx.

OpcodeCódigoFormatoDescripción
LoadConst0ABxR[A] = K[Bx] — cargar del pool de constantes
LoadTrue1AR[A] = true
LoadFalse2AR[A] = false
LoadNil3AR[A] = nil
Move5ABR[A] = R[B]
OpcodeCódigoFormatoDescripción
Add10ABCR[A] = R[B] + R[C]
Sub11ABCR[A] = R[B] - R[C]
Mul12ABCR[A] = R[B] * R[C]
Div13ABCR[A] = R[B] / R[C]
Mod14ABCR[A] = R[B] % R[C]
Pow15ABCR[A] = R[B] ^ R[C]
Neg16ABR[A] = -R[B]
OpcodeCódigoFormatoDescripción
Eq20ABCR[A] = R[B] == R[C]
Lt21ABCR[A] = R[B] < R[C]
Gt22ABCR[A] = R[B] > R[C]
NotEq23ABCR[A] = R[B] != R[C]
Le24ABCR[A] = R[B] <= R[C]
Ge25ABCR[A] = R[B] >= R[C]
LogNot26ABR[A] = !R[B]
OpcodeCódigoFormatoDescripción
GetUpvalue34ABR[A] = Upvalue[B]
SetUpvalue35ABUpvalue[B] = R[A]
CloseUpvalue36ACerrar upvalue en slot de pila A
OpcodeCódigoFormatoDescripción
Return54ARetornar R[A] del frame actual
Call55ABCR[A] = Call(R[B], R[B+1]..R[B+C-1])
Closure56ABxR[A] = Closure(K[Bx])
OpcodeCódigoFormatoDescripción
Jump60BxIP = Bx
JumpIfFalse61ABxSi !R[A] entonces IP = Bx
GetIter65ABR[A] = Iterator(R[B])
ForIter66ABxSiguiente elemento o saltar a Bx
OpcodeCódigoFormatoDescripción
DefGlobalVar98ABxDefinir global mutable
DefGlobalLet99ABxDefinir global inmutable
GetGlobal100ABxR[A] = Global[K[Bx]]
SetGlobal101ABxGlobal[K[Bx]] = R[A]
Print102AImprimir R[A]
OpcodeCódigoFormatoDescripción
BuildList150ABCR[A] = [R[B]..R[B+C-1]]
BuildMap151ABCR[A] = {R[B]:R[B+1], ...}
GetIndex152ABCR[A] = R[B][R[C]]
SetIndex153ABCR[A][R[B]] = R[C]
OpcodeCódigoFormatoDescripción
Prove160Compilar + verificar circuito ZK
OpcodeCódigoDescripción
Nop255Sin operación

Cada función compilada produce un struct Function en el heap:

Function {
name: String, // para depuración
arity: u8, // conteo de parámetros
max_slots: u16, // uso pico de registros
chunk: Vec<u32>, // instrucciones de bytecode
constants: Vec<Value>, // pool de constantes
upvalue_info: Vec<u8>, // pares [is_local, index]
line_info: Vec<u32>, // número de línea por instrucción
}

El vector line_info es paralelo a chunk — cada instrucción tiene un número de línea fuente para reporte de errores.

El formato de archivo .achb (versión 9):

Magic: b"ACH\x09" (4 bytes)
Metadata: max_slots (u16 LE)
Global Strings:
count (u32 LE)
por cada uno: length (u32 LE) + bytes UTF-8
Global Constants:
count (u32 LE)
por cada uno: tag (u8) + payload
INT (0): i64 LE
STRING (1): handle u32 LE
FIELD (8): 4 × u64 LE (limbs Montgomery)
NIL (255): (sin payload)
Prototypes:
count (u32 LE)
por cada uno:
name_len (u32 LE) + bytes del nombre
arity (u8)
max_slots (u16 LE)
const_count (u32 LE) + constantes
upvalue_count (u32 LE) + info de upvalue
bytecode_len (u32 LE) + instrucciones (u32 LE cada una)
Main Bytecode:
instruction_count (u32 LE)
instrucciones (u32 LE cada una)

El formato es compatible con el módulo loader de la VM, que deserializa en objetos del heap.

Índice: 0..22 23..
Nativos Globales de usuario

Los primeros 23 slots (0–22) están reservados para funciones nativas. Las globales definidas por el usuario comienzan en el índice 23 (USER_GLOBAL_START).

ComponenteArchivo
Opcodesvm/src/opcode.rs
Intérprete VMvm/src/machine/vm.rs
Frames de llamadavm/src/machine/frame.rs
Despacho de nativosvm/src/specs.rs
Codificación de valoresmemory/src/value.rs
Struct Functionmemory/src/heap.rs
Compilador de bytecodecompiler/src/codegen.rs
Compilador de funcionescompiler/src/function_compiler.rs
Serializador binariocli/src/commands/compile.rs
Cargador binariovm/src/loader.rs