La compilación ahead-of-time (AOT) de WebAssembly (Wasm) a código nativo presenta desafíos fundamentales en la gestión de tipos, el manejo de llamadas de función y la escalabilidad del proceso de compilación para módulos complejos. Este proyecto aborda la viabilidad de traducir Wasm, un bytecode diseñado para ejecución en máquinas virtuales, a un lenguaje intermedio como C, aprovechando compiladores maduros como GCC. La motivación principal es lograr un rendimiento cercano al nativo y una integración más profunda con el ecosistema de sistemas operativos, superando las limitaciones de rendimiento y las dependencias de runtime de las máquinas virtuales Wasm.

El problema fundamental de la computación que se aborda es la brecha entre la portabilidad y seguridad de un bytecode intermedio (Wasm) y la eficiencia y control de un binario nativo. Al traducir Wasm a C, Wastrel busca cerrar esta brecha, permitiendo que programas complejos generados por lenguajes de alto nivel como Scheme (vía Hoot) se ejecuten con un rendimiento optimizado, sin sacrificar la capacidad de utilizar características avanzadas como la recolección de basura o el manejo de excepciones de manera eficiente. La elección de C como lenguaje intermedio permite delegar gran parte de la optimización de bajo nivel a compiladores como GCC, que han sido perfeccionados durante décadas.

Arquitectura del Sistema

Wastrel opera como un compilador de dos fases. La primera fase toma un módulo WebAssembly como entrada y genera código fuente C. La segunda fase compila este código C a binario nativo utilizando un compilador de sistema como GCC o Clang. La arquitectura interna de Wastrel se centra en una traducción type-preserving, donde los tipos de Wasm se mapean a tipos C equivalentes, lo que facilita la depuración y la verificación de corrección. Para manejar tipos de datos complejos y externos, como los bignums de Scheme, Wastrel implementa un sistema de asignación de códigos de tipo para 'host data types' que se ocultan detrás de externref en Wasm, utilizando mini-gmp para las operaciones de precisión arbitraria.

El manejo de excepciones en Wastrel se basa en las primitivas setjmp y longjmp de C, aprovechando que el conjunto de instancias es conocido en tiempo de compilación para asignar códigos de tipo estáticos a las etiquetas de excepción. Para la optimización de tail calls, Wastrel utiliza la anotación musttail de GCC/Clang, asegurando que las llamadas recursivas se transformen en saltos, evitando el crecimiento de la pila. Un desafío clave fue la escalabilidad del proceso de compilación de C. Inicialmente, Wastrel generaba un único archivo C grande, lo que resultaba en tiempos de compilación excesivos para módulos Wasm de gran tamaño. La solución fue implementar una estrategia de particionamiento, donde el módulo Wasm se descompone en un grafo de llamadas de funciones, y este grafo se divide en subárboles para generar múltiples archivos C más pequeños. Esto permite que el compilador C procese el código en paralelo y reduce el impacto de la complejidad cuadrática de ciertas estructuras Wasm como br_table en el tamaño del archivo C generado.

Para la introspección y depuración de funciones, Wastrel, en conjunto con Hoot, ha adoptado la generación de metadatos DWARF. En lugar de generar funciones br_table gigantescas para mapear índices a nombres de función o strings, se utiliza una tabla de funcref para el mapeo de funcref-to-index y se incrusta información de depuración en formato DWARF. Wastrel puede parsear este DWARF en tiempo de compilación para generar funciones que resuelven nombres y ubicaciones de código, mejorando la eficiencia y reduciendo el tamaño del código generado.

Flujo de Compilación de Wastrel

  1. 1 Módulo Wasm Entrada: Archivo WebAssembly generado por Hoot (Scheme-to-Wasm)
  2. 2 Fase 1: Wasm-to-C Wastrel traduce el Wasm a múltiples archivos fuente C, gestionando bignums, e...
  3. 3 Particionamiento El grafo de llamadas de funciones Wasm se divide para generar archivos C más ...
  4. 4 Fase 2: C-to-Native GCC/Clang compila los archivos C particionados a código objeto.
  5. 5 Enlazado Los objetos se enlazan para formar un ejecutable nativo.
  6. 6 Ejecutable Nativo Salida: Programa Scheme compilado a código de máquina.
CapaTecnologíaJustificación
data-processing WebAssembly (Wasm) Lenguaje intermedio de entrada para la compilación AOT.
data-processing C Lenguaje intermedio de salida de Wastrel, objetivo para la compilación nativa.
data-processing GCC / Clang Compiladores de C a código nativo, utilizados en la segunda fase de compilación. -O1, -flto, -Winfinite-recursion
data-processing mini-gmp Implementación de bignums para operaciones de precisión arbitraria en el runtime de Wastrel. vs GMP (GNU Multi-Precision Library)
data-processing Hoot Scheme-to-Wasm compiler Generador de los módulos Wasm de entrada para Wastrel.
observability DWARF Formato de metadatos de depuración para la introspección de funciones y backtraces.

Trade-offs

Ganancias
  • ▲▲ Tiempo de compilación (C-to-native)
  • Tamaño de archivo C generado
  • Rendimiento de ejecución
Costes
  • Complejidad del compilador (Wastrel)
  • Paralelización de la fase Wasm-to-C

Fundamentos Teóricos

El concepto de traducción de un lenguaje intermedio a otro de más bajo nivel, como Wasm a C, se alinea con los principios de los compiladores de múltiples pases y la generación de código intermedio, como se describe en obras fundamentales de compilación como 'Compilers: Principles, Techniques, & Tools' de Aho, Lam, Sethi y Ullman (conocido como el 'Dragon Book'). La estrategia de traducción type-preserving de Wastrel refleja la importancia de mantener la información de tipo a través de las fases de compilación para garantizar la corrección y facilitar la depuración, un concepto central en la teoría de tipos y los lenguajes de programación.

El uso de setjmp y longjmp para el manejo de excepciones se remonta a los mecanismos de control de flujo no local en lenguajes como C, que son análogos a las continuaciones de primera clase o los mecanismos de excepción en lenguajes funcionales. La problemática de la volatilidad de las variables en C entre setjmp y longjmp es un tema bien documentado en la especificación de C99, que subraya la necesidad de un análisis de flujo de datos cuidadoso en compiladores que generan código C a partir de lenguajes con semánticas de excepción más robustas. La optimización de tail calls es un área de investigación activa en la implementación de lenguajes funcionales, con trabajos como los de Steele y Sussman en Scheme que demostraron la importancia de la 'tail recursion elimination' para la eficiencia de programas recursivos, un principio que Wastrel busca aplicar a través de las anotaciones musttail.