Codegen Register-Based (codegen_reg.rs)
Archivo: src/codegen_reg.rs (1060 líneas)
¿Por qué existe?
Genera código máquina x86-64 a partir del IR register-based después de la asignación de registros. Reemplaza al codegen stack-based para las funciones donde la asignación de registros produce código más eficiente: emite los bytes directamente a un buffer, sin dependencia de ensambladores externos ni librerías.
Arquitectura / Estructuras clave
La estructura CodegenReg mantiene el estado de emisión: asignaciones, buffer de bytes, labels, fixups de saltos, puntero a variables y frame de spill.
| Componente | Descripción |
|---|---|
Operand | Operando en código máquina: Reg(PhysReg) o Stack(offset) accedido como [RBP - (off+8)] |
CodegenReg | Estado: assignments, bytes, label_offsets, fixups, vars_ptr (RBX), spill_frame, call_addresses |
resolve(vreg) | Convierte un VirtReg en Operand según la asignación (RAX como fallback para dead code) |
emit_program(prog, vars_ptr, spill_frame) | Emite prólogo (push rbp; push rbx; push r14; mov rbp, rsp; cargar vars_ptr desde RCX y output desde RDX; sub rsp si hay spills), todos los bloques y resuelve los fixups de saltos |
emit_instruction(&RegInstruction) | Traduce cada instrucción a bytes (mov/imm64, aritmética, idiv con cqo, cmp+setCC+movzx, load/store a variables, jumps, return, call) |
stub_nop_addr() | Dirección de un stub extern "C" que retorna 0; se usa cuando un Call no encuentra la función en la tabla, evitando saltos a direcciones inválidas |
emit_program(...) / emit_program_with_calls(...) | Funciones públicas que compilan un RegProgram completo a Vec<u8> con o sin tabla de direcciones de funciones |
Cómo se integra
Cierra el backend register-based:
Bytecode → (stack_to_reg) → IR register-based → (register_alloc) → asignaciones → (codegen_reg) → x86-64 Los Calls usan la convención del JIT (fn(vars_ptr: RCX, output: RDX) -> RAX): guarda los registros caller-saved en un frame temporal de 64 bytes alineado, escribe los argumentos en vars[0..nargs] (el callee los lee con LoadVar), carga la dirección desde call_addresses y emite call rax. El epílogo restaura rsp/r14/rbx/rbp y ejecuta ret.
[rbp+16]=ret_addr, [rbp+8]=saved_r14, [rbp]=saved_rbx, saved_rbp, y desde [rbp-8] hacia abajo los slots de spill. Sin spills no se emite sub rsp.RegDiv sigue la convención rdx:rax / b → rax: mueve el dividendo a RAX, emite cqo (0x48 0x99) para extender signo y luego idiv.