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étodoDescripció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:

ComponenteDescripción
compile(uops) -> CodeBufCompila una secuencia de Uops a código máquina
ejecutar(uops) -> Result<Vec<String>>Compila + ejecuta = devuelve output
has_avx2() -> boolDetección de AVX2 en tiempo de compilación/runtime vía CPUID
valor_a_texto(v) -> StringConvierte 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:

Opcodes JIT-compilables

El motor JIT puede compilar los siguientes Uops a código nativo:

CategoríaOpcodes
Push de constantesPushEntero, PushDecimal, PushBooleano, PushNulo
StackPop, Dup
AritméticasAdd, Sub, Mul, Div (+ variantes Int/Float)
ComparacionesIgual, Menor, Mayor (+ variantes Int/Float)
LógicasY, O, No
VariablesLoadIdx, StoreIdx, DeclareIdx (+ variantes tipadas)
SaltosJump, JumpSiFalso
SuperinstruccionesLoadAddInt, LoadIdx2, AddStoreIdx, etc.
AVX2 SIMD (experimental)AddPacked, SubPacked, MulPacked, DivPacked, ReduceAdd
Módulo 2Modulo2

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

  1. Detección: jit_engine.rs analiza el bytecode con es_jiteable() para determinar si es candidato a JIT.
  2. Especialización: Los opcodes genéricos (Add/Sub/etc.) se convierten a versiones tipadas (AddInt/AddFloat) basado en inferencia estática de tipos.
  3. Compilación: NativeJIT::compile() traduce cada Uop a código x86-64, emitiendo bytes directamente a CodeBuf.
  4. Ejecución: El buffer se copia a memoria ejecutable y se ejecuta como una función nativa.
  5. 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