1 puntos por GN⁺ 2025-04-14 | 1 comentarios | Compartir por WhatsApp
  • rustc_codegen_clr es 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_clr en 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 popcount de C existe en tres variantes: __builtin_popcount, __builtin_popcountl y __builtin_popcountll
    • Pese a su nombre, __builtin_popcountll opera sobre unsigned long long, no sobre __int128_t
    • En x86_64 Linux con GCC, tanto unsigned long como unsigned long long son de 64 bits
  • 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 popcount de 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 b no 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

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_add debe 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
    • 255 es 1 byte más corto que 0xFF
    • 65536 también es más corto que 0xFFFF
    • 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 #line usada 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_clr se 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.s parece simple, pero en realidad debe manejar metadata de fat pointer
    • Usando compound literals de C99, se puede crear un struct FatPtr_str en una sola línea
    • En ANSI C, hay que poner data y meta por separado en variables temporales y devolverlas
  • 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

 
GN⁺ 2025-04-14
Opiniones de Hacker News
  • Sigo esperando que al menos alguno de los frontends o backends alternativos de Rust permita hacer bootstrap de Rust en alpha, hppa, m68k, sh4.
    Originalmente el proyecto rustc_codegen_gcc prometía eso, pero al final no lo cumplió.
    • Me pregunto si realmente usas las cuatro plataformas, o si es un criterio arbitrario para considerarlo soporte completo de plataformas.
    • ¿m68k-unknown-linux-gnu no se había fusionado como target Tier-3 de Rust? [0]
      [0] https://github.com/rust-lang/compiler-team/issues/458
    • ¿Abandonaron ese objetivo? La última vez que escuché, todavía estaba en desarrollo.
    • Rust todavía ni siquiera soporta bien OpenBSD en x86_64.
  • fractalfir es una persona realmente talentosa. Lo veo seguido en Reddit de Rust; no es que yo sepa mucho de compiladores, pero parece que a otras personas les gusta bastante su trabajo.
    • Tengo entendido que es bastante joven. Espero que tenga un futuro brillante por delante.
  • En la parte que dice “la mayoría de los componentes de std funcionan alrededor de 95% en .NET y 80% en C”, el título de HN omite que la tasa de aprobación del 95% aplica solo a .NET.
    En GCC/Clang es “solo” 80%.
    • El README estaba desactualizado. Esas cifras eran de principios de este año; ahora son las siguientes:
      | .NET Core tests | 1764 | 48 | 20 | 96.29% |
      | C Core tests | 1712 | 71 | 8 | 95.59% |
  • Hay muchos usos interesantes que se podrían probar con esto. Lo primero que se me ocurre es mejorar la interoperabilidad con otros lenguajes, como Python.
    • Con PyO3 la interoperabilidad ya es excelente. La excepción es cuando la gente intenta compilar la parte de Rust desde el código fuente y le resulta molesto instalar el compilador de Rust.
      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.
    • ¿Qué beneficio nuevo aporta esto que no se pueda lograr ya con funciones extern "c" de Rust?
  • ¿Va de LLVM IR a C? ¿O de Rust AST a C?
    • Encontré la respuesta en el README del proyecto:
      “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”.
    • Es un backend de rustc, es decir, una alternativa a los backends LLVM, GCC y Cranelift.
      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.
  • El compilador de Nim a C tiene una tasa de aprobación de tests del 100%.
  • Pero ¿mantiene también las garantías propias de Rust?
    • Si no hay bugs en la conversión en sí, no veo por qué no. Las garantías estáticas ya fueron verificadas y existen bajo la premisa de que se transforma código Rust que ya compila con el compilador normal de Rust.
      Las garantías dinámicas, como las verificaciones de límites, también pueden implementarse sin problema en el runtime de C.
    • ¿Hay alguna razón por la que no?
  • Muy genial. También sería excelente ir de C a Rust.
    • Para un programa no trivial, probablemente habría que meterlo todo en un gran bloque unsafe.
      C no transmite toda la información que Rust necesita conocer explícitamente para poder compilarlo.
    • Mark Russinovich dio hace poco una charla en una conferencia de Rust en Reino Unido donde mencionó los intentos a gran escala dentro de Microsoft para la conversión de C→Rust.
      https://www.youtube.com/watch?v=1VgptLwP588
    • Esas herramientas ya existen. El problema es que usan muchos bloques unsafe y, 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.
    • Mira https://github.com/immunant/c2rust.
    • ¿Qué beneficio esperas obtener con eso?
  • ¿Rust a C? ¿Para qué hacerlo? Mejor usa C directamente. Si puedes entender Rust, seguro que también puedes entender C y usarlo con soltura.
    • Para obtener las ventajas de Rust junto con la amplia compatibilidad y soporte de arquitecturas de gcc y los compiladores C en general.
      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.
    • Para obtener las ventajas de Rust en plataformas que rustc no soporta. Es bastante intuitivo.
    • Parece que aquí también hay bastante de “hacerlo porque se puede”, pero también se debe a que hay muchas plataformas que Rust no soporta.
      Un traductor de Rust→C que genere código C suficientemente estándar podría cerrar esa brecha.