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.

ComponenteDescripción
stack: Vec<(VirtReg, RegClass)>Pila virtual que simula el stack del bytecode; cada elemento es un registro virtual con su clase
current_blockBloque en construcción, con su label, instrucciones y terminador
PushEntero / PushDecimal / PushBooleano / PushNuloEmiten LoadImm / LoadFloat y empujan el VirtReg (booleano/nulo como 1/0)
Add/Sub/Mul/Div y variantes tipadasPop de dos operandos, emisión de RegAdd/RegSub/RegMul/RegDiv, push del resultado
LoadIdx/StoreIdx y variantesLoadVar/StoreVar contra var_idx, con clase Integer o Float según el opcode
Comparaciones (genéricas y tipadas)Emiten RegCmp mapeando a CmpOp (IgualEq, MenorLt, etc.)
Y / O / NoRegAnd, RegOr, RegNot
Jump / JumpSiFalso / Label / HaltCierran 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
ReturnPop 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).

Regeneración de bloques: Cada opcode de salto cierra el bloque actual (empujándolo a 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.
Opcodes no soportados: Cualquier opcode fuera de los listados (p. ej. superinstrucciones, print, objetos) se traduce a Nop por ahora, marcando las limitaciones del backend register-based actual.