JIT – Generador de Código Nativo en Tiempo Real
Archivo: src/jit.rs (1903 líneas)
¿Por qué existe?
El compilador JIT (Just-In-Time) nativo de Forja traduce directamente micro-opcodes (Uop) a código máquina x86-64, eliminando la sobrecarga del bucle de interpretación de la VM. Está diseñado específicamente para bucles numéricos intensivos y programas de cómputo puro donde la interpretación agregaría overhead significativo.
Arquitectura
El JIT de Forja es un compilador de bajo nivel que genera código máquina directamente en memoria ejecutable, sin dependencia de ensambladores externos ni librerías como dynasmrt. Soporte completo para operaciones aritméticas, comparaciones, saltos condicionales y llamadas a funciones nativas.
Estructuras clave
CodeBuf — Buffer de código máquina
Buffer dinámico que acumula bytes de código máquina, con soporte para fixups (parches de saltos) y labels (marcadores de posición para saltos). Proporciona métodos helper para emitir instrucciones x86-64 comunes:
| Método | Descripción |
|---|---|
u8(b) | Emite un byte |
i32(v) | Emite un entero de 32 bits (little-endian) |
i64(v) | Emite un entero de 64 bits (little-endian) |
push8(v), push32(v) | Emite instrucciones push imm8/imm32 |
pop_r(r), push_r(r) | Emite pop/push reg para registro 0-7 |
binop(op) | Emite pop rcx; pop rax; op; push rax |
cmp_result(setcc) | Emite setcc al; movzx rax,al; push rax |
cmp_float(setcc) | Emite comparación SSE2 comisd + setcc |
call_abs(addr) | Emite mov rax, addr; call rax (llamada absoluta) |
ret() | Emite ret (0xC3) |
alloc_exec(size) | Aloca memoria ejecutable vía VirtualAlloc (Windows) |
make_exec(p, size) | Cambia protección a PAGE_EXEC_READ después de escribir código |
free_exec(p, size) | Libera memoria ejecutable |
NativeJIT — Compilador JIT principal
Estructura que mantiene el estado de compilación y genera código nativo a partir de Uops:
| Componente | Descripción |
|---|---|
compile(uops) -> CodeBuf | Compila una secuencia de Uops a código máquina |
ejecutar(uops) -> Result<Vec<String>> | Compila + ejecuta = devuelve output |
has_avx2() -> bool | Detección de AVX2 en tiempo de compilación/runtime vía CPUID |
valor_a_texto(v) -> String | Convierte ValorFast a string para debugging |
jit_print_output(output, val) | Helper extern "C" para Print desde JIT nativo |
Módulo mem — Gestión de memoria ejecutable
Implementa alocación de memoria con permisos de ejecución. Plataformas soportadas:
- Windows:
VirtualAlloc+VirtualProtect+VirtualFree - Unix (fallback): Implementación stub que retorna error (JIT solo Windows por ahora)
Opcodes JIT-compilables
El motor JIT puede compilar los siguientes Uops a código nativo:
| Categoría | Opcodes |
|---|---|
| Push de constantes | PushEntero, PushDecimal, PushBooleano, PushNulo |
| Stack | Pop, Dup |
| Aritméticas | Add, Sub, Mul, Div (+ variantes Int/Float) |
| Comparaciones | Igual, Menor, Mayor (+ variantes Int/Float) |
| Lógicas | Y, O, No |
| Variables | LoadIdx, StoreIdx, DeclareIdx (+ variantes tipadas) |
| Saltos | Jump, JumpSiFalso |
| Superinstrucciones | LoadAddInt, LoadIdx2, AddStoreIdx, etc. |
| AVX2 SIMD (experimental) | AddPacked, SubPacked, MulPacked, DivPacked, ReduceAdd |
| Módulo 2 | Modulo2 |
NO compilables (fallback a VM Fast): Call, Return, Print, FunctionDef, PushExacto (BigDecimal), Halt, y cualquier opcode que involucre objetos, strings, o I/O.
Pipeline de compilación JIT
- Detección:
jit_engine.rsanaliza el bytecode cones_jiteable()para determinar si es candidato a JIT. - Especialización: Los opcodes genéricos (Add/Sub/etc.) se convierten a versiones tipadas (AddInt/AddFloat) basado en inferencia estática de tipos.
- Compilación:
NativeJIT::compile()traduce cada Uop a código x86-64, emitiendo bytes directamente aCodeBuf. - Ejecución: El buffer se copia a memoria ejecutable y se ejecuta como una función nativa.
- Recolección: El output se recolecta a través del callback
jit_print_output(llamado desde código nativo).
¿Cuándo se activa?
Se activa automáticamente para bucles y bloques de código que consisten exclusivamente de opcodes JIT-compilables, detectados por es_jiteable() en jit_engine.rs. También puede forzarse explícitamente desde el REPL o la CLI con la opción --jit.
Limitaciones actuales
- Solo soporta arquitectura x86-64 en Windows (para otras plataformas, hace fallback a VM Fast)
- No soporta strings, objetos, arreglos, mapas ni operaciones de I/O
- No soporta BigDecimal (
PushExacto/AddExact) - Las llamadas a funciones definidas por el usuario causan fallback a VM interpretada