Compilador de Rust a C alcanza una tasa de aprobación de pruebas del 95.9%
(fractalfir.github.io)rustc_codegen_clres un proyecto que genera código Rust hacia C y .NET, y se prepara para una presentación en Rust Week junto con mejoras en la tasa de aprobación de pruebas- La tasa de aprobación de las pruebas de Rust core subió de 92% hace dos meses a 95.9%, y las cerca de 65 pruebas restantes parecen tener causas similares entre sí
- Las correcciones recientes se enfocaron en intrínsecos de enteros de 128 bits, checked arithmetic y subslicing; el lado de .NET también ya puede ejecutar el 96.3% de las pruebas de Rust core
- Con el objetivo de apuntar a más compiladores de C, busca generar una salida cercana a C99 o ANSI C y usar APIs POSIX estándar; en algunos compiladores ANSI C ya logró ejecutar programas Rust muy simples
- También avanzan optimizaciones para reducir el tamaño de archivos en la generación de grandes volúmenes de código C y una refactorización del IR interno, sentando bases para ampliar el alcance de Rust a plataformas antiguas o especiales
Presentación en Rust Week y estado del proyecto
- Está prevista una presentación del proyecto
rustc_codegen_clren Rust Week, que se realizará en Utrecht, Países Bajos - La charla busca equilibrar un enfoque accesible para principiantes con un tema avanzado como la compilación de Rust a C
- En los últimos meses se trabajó en correcciones de pruebas, compatibilidad con compiladores de C, mejoras de rendimiento y refactorización interna
Aumento en la tasa de aprobación de pruebas de Rust core
- La tasa de aprobación de las pruebas de Rust core aumentó de 92% hace dos meses a 95.9%
- Todavía quedan alrededor de 65 pruebas, pero las causas parecen similares entre sí, por lo que el tipo de correcciones restantes está relativamente acotado
- El lado de .NET también se benefició de las mismas correcciones y ahora puede ejecutar el 96.3% de las pruebas de Rust core
Correcciones de enteros de 128 bits y checked arithmetic
- Una parte importante de las mejoras provino de correcciones en intrínsecos de 128 bits, checked arithmetic y subslicing
- El intrínseco
popcountde C existe en tres variantes:__builtin_popcount,__builtin_popcountly__builtin_popcountll- Pese a su nombre,
__builtin_popcountllopera sobreunsigned long long, no sobre__int128_t - En x86_64 Linux con GCC, tanto
unsigned longcomounsigned long longson de 64 bits
- Pese a su nombre,
- La implementación anterior truncaba silenciosamente los enteros de 128 bits a 64 bits en los intrínsecos de conteo de bits, produciendo resultados incorrectos
- El
popcountde 128 bits se emula calculando por separado la cantidad de bits set en los 64 bits inferiores y superiores, y luego sumándolas - Para el overflow check de multiplicación de 128 bits no se encontró un método eficiente, así que se maneja con un enfoque simple
- Si
bno es 0 y(a * b) / b == a, se considera que no hubo overflow - No es un método revolucionario, pero alcanza para hacer pasar algunas pruebas adicionales
- Si
Bug de subslicing e intrínsecos fallback
- El bug de subslicing se debía a que faltaba
sizeof, por lo que el puntero de datos del slice se desplazaba en bytes en vez de hacerlo por elementos - Este bug solo rompía al hacer subslicing desde el final del slice, no desde el inicio, y como esa forma se usa sobre todo en pattern matching, tardó en descubrirse
- En byte slices y string slices, el tamaño de un byte y de una unidad de código UTF-8 es de 1 byte, por lo que las pruebas propias pasaban, y el problema apareció en toda la suite de pruebas del compilador de Rust
- Algunos intrínsecos pueden usar la implementación fallback del compilador de Rust en lugar de implementarse directamente
carrying_mul_adddebe realizar la multiplicación en un entero del doble de tamaño que sus entradas- Se puede manejar hasta 64 bits, pero para entradas de 128 bits se necesita un entero de 256 bits
- LLVM soporta enteros de 256 bits, pero C y .NET no
- La implementación fallback del compilador de Rust realiza multiplicaciones y sumas de 256 bits usando enteros de 128 bits, por lo que puede servir como referencia para emular operaciones de 128 bits usando solo enteros de 64 bits
Soporte para más compiladores de C
- El proyecto busca aumentar la posibilidad de que el código Rust funcione en más compiladores de C especiales
- Hubo casos de código Rust ejecutándose en Game Boy, compilándose solo con el comando move, o corriendo en Holly C de Temple OS
- No es posible dar soporte directo a compiladores C propietarios a los que no se tiene acceso, pero ampliar el soporte a compiladores C oscuros aumenta la posibilidad de que el código Rust se ejecute en esos entornos
- Muchas plataformas no reciben soporte por falta de documentación y de accesibilidad
- En relación con las discusiones sobre escribir partes de Git en Rust, plataformas propietarias como NonStop no soportan Rust, LLVM ni GCC, lo que podría causar una degradación o interrupción del soporte de Git
- Si Rust se compila a C, en teoría se puede ejecutar Rust en cualquier lugar donde haya C
- Aún no está claro si será posible sortear los problemas en todas las plataformas
- Obtener legalmente un compilador para esa plataforma requería comprar un servidor, lo que superaba ampliamente el presupuesto
- Parece poco probable que Rust pueda ejecutarse pronto en esas plataformas
Estrategia actual de salida en C
- El plan actual es generar código lo más cercano posible a C99 conforme al estándar o a ANSI C
- Como se necesita algo de soporte de threading para la inicialización thread-local, la dirección elegida es usar solo APIs POSIX estándar
- El proyecto mantiene sus propias implementaciones fallback para ciertos intrínsecos, y esa lista se está ampliando gradualmente
- Ya se logró ejecutar programas Rust muy simples en algunos compiladores ANSI C
- El objetivo es que incluso plataformas difíciles de soportar por ahora puedan agregarse con relativa facilidad cuando surja la necesidad
Pequeñas mejoras de rendimiento y tamaño de archivos
- En la salida de literales enteros se refleja que, para enteros menores que 2^32, la representación decimal es igual o más corta que la hexadecimal
255es 1 byte más corto que0xFF65536también es más corto que0xFFFF- Debido al prefijo
0x, la representación hexadecimal no se vuelve más pequeña antes de 2^32
- En un caso extremo de convertir todo el compilador de Rust a C, llegó a generar un archivo fuente C de hasta 1GB, por lo que incluso una reducción pequeña en porcentaje del tamaño del archivo tiene impacto
- La directiva
#lineusada para insertar debug info también se volvió más inteligente- El nombre del archivo fuente solo se incluye cuando cambia
- Al usar debug info, esto puede reducir mucho el tamaño del archivo
Refactorización interna y limpieza del IR
- Algunas funcionalidades internas de
rustc_codegen_clrse separaron en crates independientes para acelerar los incremental builds - También está en marcha la migración a un IR interned más eficiente en memoria
- El IR anterior tenía algunos rvalue/lvalue peculiares que no se mapeaban bien a C, y los problemas crecían con funcionalidades de Rust como los dynamically sized types
- Por ejemplo, en un custom dynamically sized type como
MyStr,&self.sparece simple, pero en realidad debe manejar metadata de fat pointer- Usando compound literals de C99, se puede crear un
struct FatPtr_stren una sola línea - En ANSI C, hay que poner
dataymetapor separado en variables temporales y devolverlas
- Usando compound literals de C99, se puede crear un
- A medida que una línea de Rust y MIR se expande a varias líneas de C, el IR antiguo lo manejaba con un esquema de scopes internos que tenían temporary locals y sub-statements
- El nuevo enfoque tiene una fase de setup más compleja, pero simplifica el IR completo; una vez que se resuelva el último caso complicado restante, se podrá eliminar esa funcionalidad del IR antiguo
Próximos trabajos
- El proyecto lleva alrededor de 1.5 años de trabajo, y a medida que disminuyen los bugs, encontrar los restantes requiere más tiempo
- Está en desarrollo la segunda parte de “Rust panics under the hood”, que busca explicar paso a paso el proceso de panic en Rust
- Solo la explicación de la generación del panic message ya ocupa 10 minutos, por lo que también se considera dividir el texto en dos partes
- También se está trabajando en un perfilador de memoria pequeño y preciso para Rust
- El tamaño del código es de aproximadamente 2K LOC
- El calendario está ajustado, pero se espera escribir un artículo relacionado en las próximas semanas
1 comentarios
Opiniones de Hacker News
Originalmente el proyecto rustc_codegen_gcc prometía eso, pero al final no lo cumplió.
[0] https://github.com/rust-lang/compiler-team/issues/458
En GCC/Clang es “solo” 80%.
| .NET Core tests | 1764 | 48 | 20 | 96.29% |
| C Core tests | 1712 | 71 | 8 | 95.59% |
Este hack es un backend del compilador de Rust. Como los backends reciben instrucciones específicas de la plataforma como entrada, el código C no trivial generado no será portable.
El usuario tendría que recibir código fuente pregenerado específico para su plataforma, o instalar el compilador de Rust y este backend para generarlo por su cuenta.
extern "c"de Rust?“Mi representación de IR de .NET se mapea bien a C, y gracias a eso pude agregar soporte para compilar Rust a C con entre 2 y 3 mil líneas de código. Reutiliza casi toda la base de código, y el código específico de C y .NET solo existe en la etapa final de la compilación”.
Al principio empezó como un backend de .NET, pero se dieron cuenta de que el enfoque también podía soportar fácilmente la generación de código C, así que lo agregaron. Convierte lo que le entrega rustc a su propia representación intermedia (IR) y lo procesa.
Las garantías dinámicas, como las verificaciones de límites, también pueden implementarse sin problema en el runtime de C.
unsafe.C no transmite toda la información que Rust necesita conocer explícitamente para poder compilarlo.
https://www.youtube.com/watch?v=1VgptLwP588
unsafey, por lo general, el código resultante no se siente muy Rust.Por ejemplo, convertir una máquina de estados basada en variables globales en una máquina de estados más idiomática en Rust, usando algo como enums con nombre, sería muy difícil.
Quizá sería posible con ayuda de una IA lo suficientemente potente, pero la IA todavía está muy lejos de hacer realmente lo que se pretende, así que no diría que ya está lista. También se necesitaría memoria suficiente para meter toda la base de código C y Rust en la ventana de contexto; cuando el código supera cierto tamaño, eso rápidamente exigiría hardware muy caro. De lo contrario, como muchos LLM asistentes de código, terminaría generando código incompatible de forma independiente.
Aun así, si quieres extender un proyecto C en Rust o reescribirlo poco a poco, https://c2rust.com/ ya se puede usar.
Rust es un lenguaje moderno con gestión de paquetes, herramientas integradas de build/test más simples, mucha menos carga heredada, y características y sintaxis de alto nivel que a la gente de verdad le gustan.
C también es limpio, pero las bases de código complejas se benefician de un lenguaje moderno que ayuda a crear abstracciones robustas manteniendo la velocidad de C. Y, por supuesto, no hay que olvidar el borrow checker y la seguridad de memoria.
Un traductor de Rust→C que genere código C suficientemente estándar podría cerrar esa brecha.