Compilador de LLVM IR
Archivo: src/compiler_llvm.rs
El backend de LLVM (Ahead-of-Time / Representación Intermedia) de Forja genera código **LLVM IR (Intermediate Representation)** en formato de texto plano sin requerir dependencias externas de librerías nativas (como la API de C++ de LLVM). El código resultante puede ser procesado directamente por herramientas oficiales como llc (el compilador de LLVM) o clang para generar ejecutables optimizados nativos.
Estructura del LlvmBackend
Estructura LlvmBackend
pub struct LlvmBackend &brace;
out: String,
vars: HashMap<String, String>,
funcs: Vec<String>,
cur_fn: Option<String>,
lc: u64,
rc: u64,
sc: u64,
} out: Búfer de string donde se acumula la representación intermedia de LLVM IR.vars: Mapea identificadores de variables de Forja a sus respectivos punteros de registro de pila creados por la instrucciónalloca.funcs: Listado de nombres de funciones declaradas en el programa.lc(Label Counter): Contador autoincremental para generar nombres únicos de etiquetas de salto (labels).rc(Register Counter): Contador de registros virtuales locales temporales (formato%0, %1, %2).sc(String Counter): Contador de constantes de strings de solo lectura almacenadas de forma global.
Macros Auxiliares
macro_rules! line: Helper de formato que escribe una instrucción enoutagregando sangría (dos espacios) al inicio y un salto de línea al final.macro_rules! raw: Helper que escribe texto enoutsin ningún sangrado (usado para labels, firmas globales y declaraciones de funciones).
Desglose de Funciones de LlvmBackend
pub fn new(_ctx: &str, module: &str) -> Self
- ¿Por qué?: Constructor principal del backend.
- ¿Cómo?: Inicializa la estructura e inyecta la cabecera estándar de LLVM IR indicando el triple de la plataforma de destino (Windows x86-64 MSVC) y declarando funciones nativas de biblioteca de C como
printfymalloc.
pub fn compile(&mut self, decls: &[Declaracion]) -> Result<(), String>
- ¿Por qué?: Generar el código LLVM IR completo del programa.
- ¿Cómo?:
- Realiza una pasada previa sobre las declaraciones recolectando y emitiendo firmas de funciones locales y externas (FFI).
- Genera el código LLVM IR correspondiente a cada declaración.
- Si el programa de Forja no contiene una función
mainexplícita, genera automáticamente un entrypointmaininvocando aauto_main()para permitir ejecutar scripts simples directamente.
pub fn emit_bitcode(&self, path: &str) -> Result<(), String>
- ¿Por qué?: Guardar la representación intermedia de LLVM IR a un archivo físico.
- ¿Cómo?: Llama a
std::fs::writepara guardar el búferout.
pub fn emit_ir(&self) -> String
- ¿Por qué?: Obtener el LLVM IR generado como un String en memoria para análisis o pruebas unitarias directas.
Helpers de Generación
fn r(&mut self) -> String: Genera e incrementa el nombre del siguiente registro virtual temporal de la SSA (Static Single Assignment) (ej:%3).fn lb(&mut self, p: &str) -> String: Genera e incrementa una etiqueta única concatenándole un prefijo descriptivop(ej:%if_true_4).fn sl(&mut self) -> String: Genera el nombre de una nueva variable global estática para almacenar un literal string (ej:@.str.2).fn alloca(&mut self, name: &str) -> String: Emite una instrucción de reserva de pilaalloca i64en LLVM y asocia el identificador de variable en el mapavars.fn load(&mut self, name: &str) -> Option<String>: Genera una instrucciónload i64, i64*para cargar el valor de la variable en un nuevo registro temporal SSA y retorna dicho registro.fn store(&mut self, ptr: &str, val: &str): Genera una instrucciónstore i64 [val], i64* [ptr]para persistir valores de registros a la pila de variables locales.
Generación de Código
fn decl(&mut self, d: &Declaracion) -> Result<(), String>: Traduce declaraciones individuales del AST en código de control LLVM:- Variables: Ejecuta
allocay guarda el valor inicial si existiese. - Condicionales (
Si): Genera etiquetas para las ramas verdadera y falsa, y el bloque de convergencia (PHI merge label), y realiza saltos condicionales usandobr i1 [cond], label %verdadero, label %falso. - Bucles (
Mientras): Crea una etiqueta de cabecera de bucle, evalúa la condición e introduce saltos de escape.
- Variables: Ejecuta
fn expr(&mut self, e: &Expresion) -> Result<String, String>: Procesa expresiones de forma recursiva y retorna el registro temporal SSA que almacena el resultado de la expresión:- Literales numéricos: Retorna el número textual (ej:
42) listo para ser usado como operando inline. - Binarias: Emite instrucciones aritméticas SSA directas de LLVM como
add i64,sub i64,mul i64,sdiv i64o instrucciones relacionalesicmp eq,icmp sgt, etc. - Llamadas: Emite instrucciones
call i64 @nombre(...)pasando los registros que contienen los argumentos evaluados.
- Literales numéricos: Retorna el número textual (ej: