Conversión Stack-Based → Register-Based (stack_to_reg.rs)
Archivo: src/stack_to_reg.rs (477 líneas)
¿Por qué existe?
Es el punto de entrada del backend register-based del JIT. Convierte el bytecode stack-based de Forja en IR register-based (de register_ir.rs) usando un algoritmo de evaluación de expresiones con una pila de registros virtuales: cada Push* crea un VirtReg nuevo, cada operación los consume de la pila y empuja el resultado.
Arquitectura / Estructuras clave
La función stack_to_reg(ops: &[Opcode]) -> RegProgram recorre los opcodes y construye bloques BasicBlock a medida que aparecen saltos.
| Componente | Descripción |
|---|---|
stack: Vec<(VirtReg, RegClass)> | Pila virtual que simula el stack del bytecode; cada elemento es un registro virtual con su clase |
current_block | Bloque en construcción, con su label, instrucciones y terminador |
PushEntero / PushDecimal / PushBooleano / PushNulo | Emiten LoadImm / LoadFloat y empujan el VirtReg (booleano/nulo como 1/0) |
Add/Sub/Mul/Div y variantes tipadas | Pop de dos operandos, emisión de RegAdd/RegSub/RegMul/RegDiv, push del resultado |
LoadIdx/StoreIdx y variantes | LoadVar/StoreVar contra var_idx, con clase Integer o Float según el opcode |
Comparaciones (genéricas y tipadas) | Emiten RegCmp mapeando a CmpOp (Igual→Eq, Menor→Lt, etc.) |
Y / O / No | RegAnd, RegOr, RegNot |
Jump / JumpSiFalso / Label / Halt | Cierran el bloque actual con el terminador apropiado y abren uno nuevo (labels secuenciales con label_counter) |
Call(nombre, nargs) | Pop de nargs argumentos (revertidos al orden original), emisión de Call, y push de un VirtReg de resultado |
Return | Pop del valor, termina el bloque con RegInstruction::Return { src } |
Cómo se integra
Encabeza el pipeline register-based que alimenta al compilador nativo:
Bytecode → (stack_to_reg) → IR register-based → (register_alloc) → asignaciones → (codegen_reg) → x86-64 El RegProgram resultante (bloques + VirtRegs) es analizado por register_ir.rs::compute_live_intervals y luego asignado y compilado. Si el último bloque queda sin terminador, se emite un Return con el tope de pila (o 0).
prog.blocks) y crea uno nuevo; Label también fuerza el corte, de modo que los targets de Jump/JumpSiFalso siempre coinciden con un bloque.Nop por ahora, marcando las limitaciones del backend register-based actual.