Bytecode y Opcodes de la Máquina Virtual de Forja
Archivo: src/bytecode.rs (5107 líneas)
¿Por qué existe?
Define el lenguaje intermedio (IR) entre el compilador y la máquina virtual. El bytecode es una secuencia de opcodes stack-based que la VM interpreta para ejecutar programas Forja. Separar el bytecode de la VM permite:
- Múltiples backends de ejecución (VM Fast, VM Direct Threading, JIT nativo, compilador AOT)
- Optimizaciones en el bytecode independientes de la VM (fusionado de opcodes, especialización adaptativa)
- Portabilidad: el mismo bytecode puede ejecutarse en distintas arquitecturas
Estructuras clave
BuiltinKind — Funciones built-in del lenguaje
Enum que identifica las funciones predefinidas de Forja mediante un discriminante numérico, evitando hash lookups por nombre en tiempo de ejecución:
| Variante | Descripción |
|---|---|
Escribir | Imprime un valor en consola |
Longitud | Retorna la longitud de un string/arreglo |
Len | Alias de Longitud |
Tipo | Retorna el tipo de un valor como string |
ATexto | Convierte un valor a su representación textual |
EsNumero | Verifica si un valor es número |
EsTexto | Verifica si un valor es texto |
Empujar | Empuja un elemento a un arreglo/mapa |
Obtener | Obtiene un elemento por índice/clave |
Remover | Elimina un elemento por índice/clave |
Nuevo | Constructor de clases |
Opcode — Códigos de operación de la VM
Enum principal con todas las instrucciones que la VM puede ejecutar. Organizado en categorías:
Gestión de pila
| Opcode | Descripción |
|---|---|
PushEntero(i64) | Empuja un entero al stack |
PushDecimal(f64) | Empuja un flotante al stack |
PushTexto(Arc<str>) | Empuja un string (Arc-compildo) al stack |
PushBooleano(bool) | Empuja un booleano al stack |
PushNulo | Empuja nulo al stack |
Pop | Elimina el tope del stack |
Dup | Duplica el tope del stack |
Pattern Matching (enum destructuring)
| Opcode | Descripción |
|---|---|
CheckTag(usize) | Verifica que el tag del enum en tope de pila sea igual al índice. Empuja booleano sin modificar la pila |
ExtractField(usize) | Extrae el campo i-ésimo de la variante en tope de pila. Reemplaza el objeto por el campo extraído |
Variables (por nombre — sistema original)
| Opcode | Descripción |
|---|---|
Load(Arc<str>) | Carga una variable por nombre al stack |
Store(Arc<str>) | Almacena un valor del stack en una variable por nombre |
Declare(Arc<str>, bool) | Declara una nueva variable (nombre, mutable) |
Variables (por índice — ultra rápido, O(1))
| Opcode | Descripción |
|---|---|
LoadIdx(usize) | Carga variable por índice al stack |
StoreIdx(usize) | Almacena valor del stack en variable por índice |
DeclareIdx(usize, bool) | Declara variable por índice |
DeclareIdxGlobal(usize, bool) | Declara variable global de módulo (persiste entre recargas) |
Aritméticas genéricas
Add, Sub, Mul, Div — operan según el tipo de los operandos en tiempo de ejecución.
Aritméticas especializadas (PEP 659)
Versiones tipadas generadas por el sistema de quickening adaptativo:
AddInt, AddFloat, SubInt, SubFloat, MulInt, MulFloat, DivInt, DivFloat
Comparaciones
Igual, Diferente, Menor, Mayor, MenorIgual, MayorIgual + variantes especializadas IgualInt, MenorInt, MayorInt, IgualFloat, DiferenteFloat, etc.
Lógicas
Y, O, No
Saltos y control de flujo
Jump(usize), JumpSiFalso(usize), Halt, Return, Call(Arc<str>, usize)
Funciones
FunctionDef(Arc<str>, Vec<String>) — Define una función con nombre y lista de parámetros.
Fusionados (Superinstructions)
Opcodes compuestos que eliminan push/pop redundantes:
DeclareEnteroOp(usize, i64), DeclareBooleanoOp(usize, bool), StoreEnteroOp(usize, i64), LoadAddInt(usize, i64), LoadIdx2(usize, usize), AddStoreFloat(usize), FusedDivAdd(usize, usize, usize), PushAddInt(i64), LoadJumpSiFalso(usize, usize) y más.
AVX2 SIMD (experimental)
AddPacked, SubPacked, MulPacked, DivPacked, ReduceAdd — operaciones vectoriales empaquetadas.
BigDecimal (Exacto)
PushExacto(i128, u32), AddExact, SubExact, MulExact, DivExact — aritmética de precisión arbitraria.
Design by Contract
EntraPre, SalePost, ContratoInicio(String), ContratoFin(String) — marcan los límites de contratos en el bytecode.
Generación de bytecode
El generador de bytecode recorre el AST y produce la secuencia de opcodes. Incluye:
GeneradorBytecode: Estructura principal que mantiene el estado durante la generacióngenerar(&ast) -> Result<Vec<Opcode>>: Recorre el AST recursivamente generando opcodesoptimizar_indices(&bytecode) -> Vec<Opcode>: Asigna índices únicos globales a variables para acceso O(1)fusionar_opcodes(&bytecode) -> Vec<Opcode>: Fusiona patrones comunes (PushEntero + DeclareIdx --> DeclareEnteroOp)
Interpretación del bytecode
- Se crea un
Vec<Opcode>con el bytecode generado. - La VM ejecuta un bucle
while pc < bytecode.len()que despacha cada opcode mediantematch. - El stack es
Vec<ValorFast>en ForjaFast (yValorDTen la VM Direct Threading). - Variables por índice usan vectores pre‑alojados para acceso O(1).
- Llamadas a built‑ins o funciones se delegan al dispatcher correspondiente.
- Opcode‑fusion combina operaciones comunes para reducir sobrecarga.