Uops – Micro‑Operaciones del Backend
Archivo: src/uops.rs — 896 líneas
Este módulo define la unidad mínima de trabajo que la capa JIT y el backend ASM
del compilador Forja traducen a código máquina nativo. Cada Uop (micro‑operation)
representa una instrucción de nivel muy bajo que puede mapearse directamente a una o más
instrucciones ensamblador reales según la arquitectura objetivo.
Motivación
- Permite que el optimizador de alto nivel (
optimizer.rs) y el pipeline de uops trabajen sobre una representación más cercana al hardware sin depender de la sintaxis de ensamblador específica. - Facilita la generación de código para varias arquitecturas manteniendo una única lógica de transformación (
uops → asm). - Ofrece un punto de extensión donde se pueden añadir nuevas optimizaciones de peephole antes de la emisión final.
- Expande opcodes compuestos y superinstrucciones en secuencias atómicas manejables por el backend.
Enum Uop — 52 variantes
Operaciones de stack (5)
| Variante | Descripción |
PushEntero(i64) | Apila un entero de 64 bits |
PushDecimal(f64) | Apila un número de punto flotante |
PushTexto(Arc<str>) | Apila una cadena de texto (Arc compartido) |
PushBooleano(bool) | Apila un valor booleano |
PushNulo | Apila el valor nulo |
Pop | Desapila el valor del tope |
Dup | Duplica el valor en el tope del stack |
Operaciones de variables (3)
| Variante | Descripción |
LoadIdx(usize) | Carga el valor de la variable en posición idx al stack |
StoreIdx(usize) | Almacena el valor del tope del stack en la variable idx |
DeclareVar(usize) | Declara una nueva variable en posición idx |
Aritmética (12)
| Variante | Descripción |
Add / Sub / Mul / Div | Operaciones aritméticas genéricas (type dispatch) |
AddInt / AddFloat | Suma especializada para entero o flotante |
SubInt / SubFloat | Resta especializada para entero o flotante |
MulInt / MulFloat | Multiplicación especializada para entero o flotante |
DivInt / DivFloat | División especializada para entero o flotante |
Control de flujo (3)
| Variante | Descripción |
Jump(usize) | Salto incondicional a la posición (label) especificada |
JumpSiFalso(usize) | Salto condicional: salta si el tope del stack es falso |
Halt | Detiene la ejecución |
Funciones (3)
| Variante | Descripción |
Call(String, usize) | Llama a una función por nombre con N argumentos |
Return | Retorna de la función actual |
FunctionDef(String, Vec<String>) | Define una función: nombre y lista de parámetros |
Objetos (4)
| Variante | Descripción |
NewObject(String) | Crea una nueva instancia de la clase especificada |
SetField(String) | Establece el valor de un campo en un objeto |
GetField(String) | Obtiene el valor de un campo de un objeto |
CallMethod(String, usize) | Llama a un método por nombre con N argumentos |
Arreglos y mapas (6)
| Variante | Descripción |
ArrayNew(usize) | Crea un nuevo arreglo con capacidad para N elementos |
ArrayGet | Obtiene un elemento del arreglo por índice |
ArraySet | Establece un elemento del arreglo por índice |
ArrayLen | Obtiene la longitud del arreglo |
MapNew(usize) | Crea un nuevo mapa con capacidad para N pares |
MapGet / MapSet | Obtiene o establece un valor en el mapa por clave |
Built-ins e I/O (4)
| Variante | Descripción |
ParseInt | Convierte string a entero (pop string, push i64) |
TiempoActual | Obtiene el timestamp Unix actual |
Print | Imprime el valor del tope del stack |
ReadLine | Lee una línea de la entrada estándar |
Comparación y lógica (8)
| Variante | Descripción |
Igual / Diferente | Comparación de igualdad/desigualdad |
Menor / Mayor | Comparación menor/mayor que |
MenorIgual / MayorIgual | Comparación menor/mayor o igual que |
Y / O | Conjunción y disyunción lógica |
No | Negación lógica |
Micro-operaciones de expansión (7)
| Variante | Descripción |
PrepCall(usize) | Prepara el frame para llamada a función (N = número de args) |
ResolveMethod(String) | Resuelve método en objeto (inline cache) |
LoadSelf | Carga self/este en el tope del stack |
StorePop(usize) | Pop + Store combinado: almacena y desapila |
LoadPush(usize) | Load + Push fusionado |
DeclareInit(usize) | Declara variable y asigna valor inicial en un solo uop |
Try | Propagación de errores (expr?) |
Optimizaciones de uops (3)
| Variante | Descripción |
IncrVar(usize) | vars[idx] += 1 — incremento unitario fusionado |
AddAssign(usize, i64) | vars[idx] += n — suma con constante fusionada |
SubAssign(usize, i64) | vars[idx] -= n — resta con constante fusionada |
Exacto (BigDecimal) (10)
| Variante | Descripción |
PushExacto(i128, u32) | Apila un número exacto (coeficiente + escala) |
AddExact / SubExact / MulExact / DivExact | Operaciones aritméticas exactas |
IgualExact / MenorExact / MayorExact | Comparaciones exactas |
EnteroAExacto / DecimalAExacto | Conversión de tipos a Exacto |
Funciones nativas y sockets (2)
| Variante | Descripción |
CallNative(Arc<str>, usize) | Llama a una función nativa registrada en la VM |
SocketPoll(Arc<str>) | Polling no bloqueante de socket |
Marcador (1)
| Variante | Descripción |
Label(usize) | Marcador de posición (no ejecuta, usado como destino de saltos) |
Funciones principales
opcode_to_uop()
| Firma | Descripción |
pub fn opcode_to_uop(op: &Opcode) -> Uop |
Convierte un Opcode atómico (no compuesto) a su Uop
equivalente. Es el mapeo directo entre bytecode y micro-operaciones.
Los opcodes compuestos se mapean a marcadores que luego expandir_a_uops()
procesa. Maneja todos los casos: stack, variables, aritmética, control flow,
funciones, objetos, arrays, mapas, built-ins, I/O, comparaciones, lógica,
opcodes especializados, superinstrucciones, float, AVX2, Exacto, contratos,
pattern matching, concurrencia y debug.
|
expandir_a_uops()
| Firma | Descripción |
pub fn expandir_a_uops(bytecode: &[Opcode]) -> Vec<Uop> |
Expande opcodes compuestos en secuencias de uops atómicos (~213 líneas).
Descompone superinstrucciones como:
DeclareEnteroOp(idx, n) → DeclareVar + PushEntero + StorePop LoadIdx2(a, b) → LoadIdx(a), LoadIdx(b) LoadStoreIdx(src, dst) → LoadIdx(src), StoreIdx(dst) AddStoreIdx(idx) → AddInt, StoreIdx(idx) LoadAddInt(idx, n) → LoadIdx(idx), PushEntero(n), AddInt PushAddInt(n) → PushEntero(n), AddInt LoadJumpSiFalso(idx, target) → LoadIdx(idx), JumpSiFalso(target) - AVX2 packed: expande a 32 uops escalares (4 iter × 4 uops × 2 pares)
XorSign(idx) → PushDecimal(0.0), LoadIdx(idx), SubFloat, StoreIdx(idx) - Superinstrucciones Exacto (DeclareExactOp, AddStoreExact)
|
optimizar_uops()
| Firma | Descripción |
pub fn optimizar_uops(uops: &[Uop]) -> Vec<Uop> |
Optimiza secuencias de uops fusionando patrones comunes (~63 líneas).
Patrones detectados:
LoadIdx(a) + PushEntero(1) + Add + StoreIdx(a) → IncrVar(a) LoadIdx(a) + PushEntero(n) + Add + StoreIdx(a) → AddAssign(a, n) LoadIdx(a) + PushEntero(n) + Sub + StoreIdx(a) → SubAssign(a, n) PushEntero(n) + Pop → (eliminar ambos, código muerto) PushBooleano(b) + Pop → (eliminar ambos) PushNulo + Pop → (eliminar ambos) DeclareVar(a) + StorePop(a) → DeclareInit(a) |
Funciones auxiliares
| Firma | Descripción |
pub fn tiene_opcodes_compuestos(bytecode: &[Opcode]) -> bool | Verifica si un bytecode contiene opcodes compuestos que pueden expandirse a uops. Recorre el bytecode buscando superinstrucciones. |
pub fn remapear_saltos_uops(uops: &mut [Uop], bytecode: &[Opcode]) | Re-mapea los targets de Jump/Label en uops desde posiciones de bytecode a posiciones de uops, usando el mapeo de expansión. |
fn construir_mapeo_posiciones(bytecode: &[Opcode]) -> Vec<usize> | Construye un mapeo de posición bytecode → posición uops para re-mapear targets de Jump/Label después de expandir superinstrucciones. |
Flujo de generación
- El generador de bytecode (
bytecode.rs) produce opcodes (algunos compuestos como superinstrucciones) expandir_a_uops() descompone opcodes compuestos en secuencias de uops atómicos remapear_saltos_uops() actualiza los targets de saltos a las nuevas posiciones optimizar_uops() fusiona patrones comunes reduciendo el número de uops - Los uops optimizados se pasan al backend ASM (
compiler_asm.rs) o JIT (jit.rs) para emisión de código nativo
Relación con el resto del compilador
- bytecode.rs: Produce los Opcodes que
opcode_to_uop() y expandir_a_uops() consumen - jit.rs / jit_engine.rs: El JIT ejecuta uops o expande a código máquina
- compiler_asm.rs: El compilador ASM traduce uops a ensamblador específico de arquitectura
- vm_fast.rs: La VM rápida puede ejecutar uops directamente (modo uop)
- optimizer.rs: El optimizador de AST opera antes, pero los patrones de uops se optimizan aquí