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:

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:

VarianteDescripción
EscribirImprime un valor en consola
LongitudRetorna la longitud de un string/arreglo
LenAlias de Longitud
TipoRetorna el tipo de un valor como string
ATextoConvierte un valor a su representación textual
EsNumeroVerifica si un valor es número
EsTextoVerifica si un valor es texto
EmpujarEmpuja un elemento a un arreglo/mapa
ObtenerObtiene un elemento por índice/clave
RemoverElimina un elemento por índice/clave
NuevoConstructor 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

OpcodeDescripció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
PushNuloEmpuja nulo al stack
PopElimina el tope del stack
DupDuplica el tope del stack

Pattern Matching (enum destructuring)

OpcodeDescripció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)

OpcodeDescripció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))

OpcodeDescripció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:

Interpretación del bytecode

  1. Se crea un Vec<Opcode> con el bytecode generado.
  2. La VM ejecuta un bucle while pc < bytecode.len() que despacha cada opcode mediante match.
  3. El stack es Vec<ValorFast> en ForjaFast (y ValorDT en la VM Direct Threading).
  4. Variables por índice usan vectores pre‑alojados para acceso O(1).
  5. Llamadas a built‑ins o funciones se delegan al dispatcher correspondiente.
  6. Opcode‑fusion combina operaciones comunes para reducir sobrecarga.