El problema fundamental que aborda este proyecto es cómo ejecutar software compilado para arquitecturas de hardware convencionales (basadas en registros) en una máquina virtual con una arquitectura fundamentalmente diferente (basada en pila o stack-based). Históricamente, las máquinas virtuales han utilizado sus propios compiladores o intérpretes para lenguajes de alto nivel, o han requerido la escritura de código directamente en su lenguaje ensamblador nativo. La tesis aquí es que, aprovechando la capacidad de Clang para generar LLVM IR y procesando este IR, es posible "puentear" la brecha entre un compilador maduro y optimizado como Clang y una VM con una ISA no convencional, permitiendo la ejecución de software complejo sin reescritura significativa.

La relevancia de este enfoque radica en la reutilización de la vasta infraestructura de optimización y soporte de lenguajes que ofrece LLVM. En lugar de desarrollar un compilador completo desde cero para la VM, se delega la mayor parte del trabajo de análisis y optimización de alto nivel a Clang/LLVM, enfocándose en la traducción del IR a la ISA específica de la VM. Esto acelera el desarrollo y mejora la calidad del código generado, un desafío recurrente en el diseño de VMs personalizadas.

Arquitectura del Sistema

La arquitectura del sistema se centra en un pipeline de compilación de tres etapas. Primero, el código fuente C (en este caso, PureDOOM) es compilado por Clang, que genera LLVM IR textual. Clang, como frontend de LLVM, es capaz de producir este IR intermedio, que es una representación de bajo nivel del programa, independiente de la arquitectura final. Esta etapa es crucial porque Clang está diseñado para CPUs con un número fijo de registros, lo que contrasta con la naturaleza stack-based de UVM.

La segunda etapa es un parser de LLVM IR, implementado en Rust. Este componente consume el LLVM IR textual y lo transforma en una representación intermedia que puede ser procesada. La elección de Rust y la generación asistida por IA para este parser subraya la necesidad de flexibilidad y adaptabilidad, dado que el formato textual de LLVM IR puede variar ligeramente entre versiones de Clang. Este parser actúa como un puente, interpretando las operaciones de LLVM IR (que a menudo implican registros virtuales) y preparándolas para la traducción a operaciones de pila.

Finalmente, la tercera etapa es la generación de bytecode para UVM. El parser de Rust traduce la representación intermedia del LLVM IR a las instrucciones stack-based de UVM. Esto implica mapear operaciones de registro virtual a manipulaciones de pila (push, pop, operaciones binarias en la cima de la pila). La VM UVM ejecuta este bytecode, que incluye soporte para aritmética de punto flotante, multithreading (a través de una API compatible con pthread) y APIs para framebuffer y audio. Las optimizaciones post-compilación, como el uso de lookup tables para paletas y memcpy para upsampling de framebuffer, se aplican para mejorar el rendimiento en la VM.

Flujo de Compilación y Ejecución de PureDOOM en UVM

  1. 1 Código Fuente C (PureDOOM) Código C de Doom, diseñado para fácil portabilidad.
  2. 2 Clang Compila el código C y genera LLVM IR textual.
  3. 3 Parser LLVM IR (Rust) Consume el LLVM IR textual, lo parsea y lo convierte a una representación int...
  4. 4 Generador de Bytecode UVM Traduce la representación intermedia a instrucciones stack-based de UVM.
  5. 5 Máquina Virtual UVM Ejecuta el bytecode, gestiona framebuffer, audio y threads.
  6. 6 Ejecución de Doom El juego Doom se ejecuta dentro de la VM, mostrando gráficos y sonido.
CapaTecnologíaJustificación
compute UVM (Custom VM) Máquina virtual stack-based que ejecuta el bytecode generado. Provee APIs para threads, framebuffer y audio.
data-processing Clang Frontend de compilador que genera LLVM IR a partir de código C. Utilizado por su madurez y capacidad de optimización.
data-processing LLVM IR (Textual) Lenguaje intermedio de bajo nivel, independiente de la arquitectura, utilizado como puente entre Clang y el backend de UVM.
data-processing Rust (Parser) Lenguaje de implementación para el parser de LLVM IR, elegido por su seguridad de memoria y rendimiento.
compute Claude Opus 4.8 / Claude Code (AI) Herramienta de asistencia para la generación del parser de LLVM IR y la resolución de problemas de compilación/optimización.

Trade-offs

Ganancias
  • Capacidad de compilar software C complejo
  • Reutilización de infraestructura de compilación madura (Clang/LLVM)
  • Rendimiento de ejecución (después de optimizaciones)
Costes
  • Dependencia de la versión de Clang para el parser de LLVM IR textual
  • Complejidad de la traducción de arquitecturas (registro vs. pila)

Fundamentos Teóricos

Este trabajo se conecta directamente con los principios de diseño de compiladores y máquinas virtuales, un campo con profundas raíces académicas. La idea de un lenguaje intermedio (IR) para desacoplar el frontend del backend de un compilador se remonta a trabajos seminales como el diseño del compilador de Pascal en los años 70. LLVM, en particular, se inspira en conceptos de compilación Just-In-Time (JIT) y optimización de código intermedio, popularizados por proyectos como el compilador Self y la JVM de Sun.

La traducción de una arquitectura basada en registros a una basada en pila es un problema clásico en el diseño de compiladores. Los algoritmos para la asignación de registros (register allocation) y la generación de código para máquinas de pila son bien conocidos en la literatura (ej. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools). La decisión de refactorizar UVM a un conjunto de instrucciones basado en registros en el futuro, mencionada al final del artículo, refleja el conocimiento de que las arquitecturas basadas en registros suelen ofrecer un mejor rendimiento debido a una menor presión en la pila y un uso más eficiente de la caché, un tema extensamente estudiado en la arquitectura de computadoras y la optimización de compiladores.