1 puntos por GN⁺ 2024-08-26 | 1 comentarios | Compartir por WhatsApp
  • Dozer es un compilador de Rust basado en C puro que busca permitir usar Rust en una etapa más temprana del bootstrap; se está escribiendo sin C++, flex, yacc ni Makefile
  • El compilador oficial rustc está escrito en Rust, por lo que las versiones nuevas se construyen con versiones anteriores de rustc; esta cadena se remonta al compilador inicial de Rust escrito en OCaml y a capas de Guile y C
  • El bootstrap de Linux de Bootstrappable Builds parte de una semilla binaria de 512 bytes y se expande hacia compiladores simples, shell, un subconjunto de C, TinyCC, yacc, coreutils, Bash, autotools, GCC y Linux
  • Actualmente Rust aparece en una etapa tardía mediante mrustc, escrito en C++, que compila rustc 1.56; por eso es difícil usar Rust antes de introducir C++
  • Dozer apunta a ser un compilador de Rust que pueda arrancarse con TinyCC, y luego conectar con libcore, el backend Cranelift de rustc, una herramienta alternativa a cargo y la reconstrucción del rustc/cargo canónico

Objetivos y restricciones de Dozer

  • Dozer es un compilador de Rust que se está escribiendo en C puro
  • No usa C++, ni tampoco flex, yacc ni Makefile
  • Su objetivo principal es crear un compilador que permita hacer bootstrap de Rust desde C
  • En particular, debe poder arrancarse con TinyCC, asumiendo que el sistema no tiene herramientas útiles más allá de un compilador de C y un shell muy básico

El problema de que el compilador de Rust se construya a sí mismo

  • Para ejecutar código Rust hay que compilarlo y, por lo general, cargo build invoca internamente a rustc
  • rustc en sí también es un compilador de Rust escrito en Rust, así que un rustc nuevo se compila con una versión anterior de rustc
    • rustc 1.80.0 se compila con rustc 1.79.0
    • Esta cadena continúa hacia versiones todavía anteriores, como rustc 1.78.0
  • Las primeras etapas se remontan a Rust 0.7, momento en el que el compilador estaba escrito en OCaml
  • Como también se necesita un compilador de OCaml, la cadena de bootstrap vuelve a depender de implementaciones en otros lenguajes
    • camlboot puede compilar un compilador de OCaml usando Guile
    • El intérprete de Guile está escrito en C

La parte inferior de la cadena de Bootstrappable Builds

  • Bootstrappable Builds trata el flujo para hacer bootstrap de un sistema completo a partir de una pequeña semilla binaria
  • El proceso de bootstrap de Linux comienza con una semilla binaria de 512 bytes
    • Esta semilla contiene un compilador muy simple que recibe números hexadecimales y emite los bytes crudos correspondientes
    • Una lista de bytes hexadecimales, excluyendo comentarios y espacios en blanco, también se considera técnicamente código fuente analizable
  • Las etapas siguientes construyen gradualmente herramientas de mayor nivel
    • Un sistema operativo muy simple
    • Un shell básico
    • Un compilador un poco más avanzado
    • Una etapa que parece código ensamblador
    • Un subconjunto muy básico de C
    • Un compilador de C más avanzado escrito en ese subconjunto de C
  • Algunas etapas después ya se puede compilar TinyCC, y luego se continúa con yacc, coreutils básicos, Bash, autotools, GCC y Linux
  • Cada etapa está listada en live-bootstrap parts.rst

Rust aparece demasiado tarde en la cadena de bootstrap

  • Actualmente Rust aparece muy tarde en este proceso
  • La implementación usada es mrustc, una implementación alternativa de Rust escrita en C++
  • mrustc puede compilar rustc 1.56 y, a partir de ahí, continuar compilando código Rust moderno
  • Sin embargo, para cuando C++ se introduce en la cadena de bootstrap, el bootstrap en la práctica ya está casi terminado
  • Para usar Rust en etapas anteriores a la introducción de C++, se necesita un compilador de Rust que pueda arrancarse desde C

Estado actual de la implementación de Dozer

  • Dozer lleva unos dos meses de trabajo y se ha escrito sin extensiones
  • Actualmente puede compilar sin problemas tanto con TinyCC como con cproc
  • El backend usa QBE
  • La implementación todavía está en una etapa inicial
    • El lexer está completo
    • El parser está implementado en buena parte
    • La expansión de macros/módulos se está postergando todo lo posible
    • La verificación de tipos actualmente solo soporta i32
    • La generación de código todavía está en estado rudimentario
  • Actualmente puede compilar con éxito el siguiente código Rust
fn rust_main() -> i32 {
    (2 - 1) * 6 + 3
}

Plan para llegar hasta rustc

  • El objetivo es desarrollar Dozer gradualmente para que compile ejemplos básicos que usen libc, y luego compile libcore y rustc
  • Para compilar rustc se planea usar el backend Cranelift
    • El backend Cranelift está escrito completamente en Rust
    • Como se asume que no hay C++, LLVM no puede compilarse
  • También se planea crear una herramienta alternativa a cargo que permita compilar paquetes de Rust con Dozer
  • Habrá que encontrar y eliminar archivos generados automáticamente dentro del código fuente de rustc
    • Las reglas del proyecto Bootstrappable no permiten código generado automáticamente
  • El objetivo final es compilar rustc y cargo, y luego usar ese rustc/cargo compilado directamente para volver a compilar el rustc/cargo canónico
  • El proyecto es la tarea más difícil que ha asumido hasta ahora, y la postura es seguir intentándolo aunque existan dudas sobre si será posible completarlo

1 comentarios

 
GN⁺ 2024-08-26
Opiniones de Hacker News
  • Si fuera a bootstrappear Rust, creo que haría en C un proto-Rust con menos funcionalidades que Rust completo, y con ese proto-Rust escribiría un compilador de Rust completo
    Por ejemplo, proto-Rust no tendría borrow checker, tendría soporte limitado o nulo para macros, quizá ni siquiera liberaría memoria, y tampoco necesitaría generar buen código
    En la práctica sería más parecido a C con sintaxis de Rust, pero desde la perspectiva de un entusiasta de Rust, eso parece mejor que escribir un compilador de Rust en el “C con sintaxis de C” al que apunta este proyecto
    Me pregunto por qué no eligieron ese camino

    • Como referencia, mrustc, el compilador de Rust no escrito en Rust más representativo que ya existe, tampoco tiene borrow checker
      Quitar el borrow checker no rompe los programas correctos; solo permite que se compile una gran cantidad de programas incorrectos
      El uso principal de mrustc es compilar rustc, y como ya sabemos que rustc puede compilarse a sí mismo sin errores del borrow checker, no hay problema
    • En Mozart/Oz hicieron exactamente eso. Hay un compilador proto-Oz escrito en Scala, y con él compilan el compilador real escrito en Oz
      Como el compilador de Scala genera código ineficiente, después recompilan el compilador real con él mismo
      Así al final obtienen un compilador real eficiente que genera buen código, y este proceso forma parte de la compilación estándar del lenguaje
      https://github.com/mozart/mozart2
    • Entonces al final estás usando dos compiladores, y no veo qué se gana realmente aparte de trabajo adicional
  • Como hobby estoy haciendo un compilador de C en Rust, y lo llamo Small C Compiler como broma de que Rust obviamente es más pesado que C. Es una parodia de “Tiny C Compiler”
    Uso Cranelift como backend, pero estoy haciendo que la estructura completa del compilador se pueda enchufar y cambiar con muchos traits, y que sea fácil de hackear
    No pienso publicarlo como open source hasta que funcione lo suficiente como para manejar printf("%s", "Hello World!")
    Intenté implementar el preprocesador y el parser, y por el infame problema de typedef terminé involucrándome también con rust-peg y HimeCC
    Sé que en la industria se usan tablas de símbolos para mantener el contexto de typedef, pero eso tenía la limitación de no poder leer los tipos que vienen más abajo. Me pregunto cuál será la solución académica; lo único que se me ocurre es memoria transaccional
    Si llega a haber algo útil, probablemente terminaré publicándolo

  • Es realmente genial, y lo interesante es que el mismo tipo de problema de bootstrapping existe también en el hardware
    ¿Qué fabrica las computadoras? Computadoras fabricadas anteriormente y el software que corre sobre ellas. Mientras más lo piensas, más interesante se vuelve

    • El mismo problema de bootstrapping existe en todo. ¿Qué construye las carreteras? La maquinaria de construcción. Pero si todavía no hay carretera, ¿cómo llevas esa maquinaria hasta el sitio de obra?
      Hace unos meses conocí a alguien que trabaja en una startup de entrega/fulfillment de materiales para proyectos de construcción
      Este tipo de trabajo requiere una especialización distinta a la de la logística general tipo Amazon, no solo porque los materiales suelen tener propiedades extrañas o peligrosas, sino también porque muchas veces el destino de entrega todavía ni siquiera tiene dirección
      Se puede resolver, pero parece requerir una especialización que va más allá de las capacidades habituales de las empresas modernas de entrega
    • Trabajé en una empresa que construía centros de datos, y queríamos desarrollar software hasta el punto de poder poner en marcha un centro de datos completo con una sola laptop
      La razón era que, al colaborar con empresas europeas, necesitábamos demostrarles a los reguladores que no había backdoors
      Era un problema muy interesante pero muy difícil; nuestro equipo participó solo de forma indirecta, trabajando en pasar datos a través de un proxy que garantizara que todos los datos fueran auditables y que no se enviara nada que no debiera enviarse
      Me fui de la empresa antes de que terminara, y después escuché que lo descartaron porque era demasiado difícil
    • Al ver los opcodes octales de ensamblador de la vieja Cray-1 o los opcodes de palabra del IBM System/360, uno se da cuenta de que estaban diseñados de manera sorprendentemente simple, al punto de que una persona podía escribir bytes de opcode directamente y ensamblar a mano
      Luego apareció x86 sin un presupuesto enorme ni grandes compradores, y diseñaron el ensamblador para que fuera lo más eficiente y denso posible
      Como resultado, se perdieron características que en otras máquinas se podían tener cómodamente
    • Es una de las cosas más geniales de estos proyectos de bootstrapping y de las compilaciones reproducibles
      En teoría, podrías construir tú mismo una computadora muy simple solo con componentes individuales
      Sería grande, ineficiente y extremadamente lenta, pero podrías hacer que siguiera una arquitectura de conjunto de instrucciones específica, y sobre ella construir un programa de bootstrapping
      Entonces podrías afirmar que el resultado obtenido en una mala computadora completamente comprensible es el mismo que el obtenido en hardware moderno en el que no confías por completo
    • Es una idea interesante incluso al nivel de la civilización humana. Si la humanidad de algún modo volviera desde el presente a la Edad de Piedra, ¿podríamos reconstruir todo hasta el nivel actual?
      Es una especie de problema de bootstrapping. Por ejemplo, los yacimientos actuales de petróleo son más difíciles de explotar que hace 100 años, y me pregunto si podríamos volver a bootstrappear hasta llegar ahí
  • Me molestó un poco tener que seguir enlaces 4 veces para encontrar una justificación de alto nivel que explicara los beneficios del bootstrapping
    Esperaba que la parte de “Why” del título cubriera eso
    https://bootstrappable.org/benefits.html

    • Puede ser difícil explicar por qué el bootstrapping es importante. Por eso también puse una sección “Why?” en el README de mi compilador con bootstrapping
      La seguridad es una razón importante, y es el punto que el equipo de bootstrappable suele destacar
      Para evitar el problema de trusting trust y ataques como la reciente puerta trasera de xz, hay que poder hacer bootstrap de todo a partir de código fuente puro
      Ellos incluso eliminan todos los archivos pregenerados para depender solo de cosas escritas a mano y auditables. Por ejemplo, el bootstrapping de Python se vuelve bastante complejo porque el código fuente incluye código generado por scripts de Python
      A mí, en cambio, me interesa más el aspecto de preservación cultural. Quiero preservar medios modernos en lugares como el Arctic World Archive para los arqueólogos del futuro, pero no tiene sentido si no hay forma de decodificarlos
      Podemos preservar las especificaciones, pero no podemos esperar que implementen x265 y todo lo necesario desde cero. Si preservamos binarios, tendrían que hacer funcionar hardware de mil años de antigüedad o virtualizar una CPU de hace mil años
      También podríamos darles una definición simple de Lisp y código que corra encima, pero ¿quién va a implementar x265 en Lisp básico? No es realista
      Por eso, en mi proyecto creé una máquina virtual simple e hice bootstrap de C sobre ella
      Es muy fácil de portar no solo a arquitecturas actuales, sino también a arquitecturas futuras o extraterrestres. Arqueólogos del futuro o una civilización extraterrestre podrían implementar la VM en un día, ejecutar el bootstrap de C encima y luego compilar ffmpeg y demás para decodificar nuestros medios
      No hay cajas negras; todo es código fuente abierto, escrito a mano, depurable y auditable
      https://github.com/ludocode/onramp?tab=readme-ov-file#why-bo...
      https://en.wikipedia.org/wiki/Arctic_World_Archive
  • Me resulta un poco confuso. Recién a mitad del artículo aparece la razón por la que empezó el recorrido del título, y el punto central es que, para cuando C++ entra en la cadena de bootstrapping, el bootstrap básicamente ya terminó, así que aunque uno quisiera usar Rust antes de eso, no hay manera
    Entonces parece que la idea es que estaría bueno tener un compilador de Rust escrito en C, específicamente uno que pueda bootstrapppearse desde TinyCC en un sistema donde se supone que todavía no hay herramientas útiles
    Pero eso contradice la premisa inicial. rustc 1.80.0 se compila con 1.79.0, 1.79.0 con 1.78.0, y así sucesivamente hasta 0.7; el compilador de ese momento estaba escrito en OCaml
    Además, se mencionaba que existe un proyecto que compila con éxito un compilador de OCaml usando Guile, y que el intérprete de Guile está escrito en C
    Entonces el camino sin C++ que el autor quiere ya existe; simplemente no es el camino que el equipo de rustc usa en el día a día
    Al final, la motivación no queda clara. No sé si quiere crear un proceso de bootstrapping basado en C mejor, convertirlo en el método cotidiano de bootstrapping de rustc, por qué quiere eliminar el paso de C++ o por qué prefiere el paso por C
    Si lo hace simplemente porque quiere, está bien, pero después de leer un artículo bastante largo, no me queda claro ningún otro propósito

    • Aunque técnicamente es posible hacer bootstrap de Rust desde Guile y el compilador de Rust 0.7, habría que recompilar el compilador de Rust unas 100 veces
      Cada etapa tarda horas, y como 1.80 requiere 1.79, 1.79 requiere 1.78, y así hasta 0.7, no se puede saltar ninguna etapa
      Incluso totalmente automatizado, este bootstrap podría tardar meses
      Además, tengo entendido que las primeras versiones de rustc solo generaban salida para LLVM, así que de todos modos habría que bootstrapppear un compilador de C++ para compilar LLVM
      Si ya tienes un compilador de C++, simplemente puedes compilar mrustc. Actualmente mrustc solo soporta hasta rustc 1.54, así que aun así habría que compilar pasando por unas 35 versiones
      Todo este proceso no es práctico. El objetivo de Dozer es hacer bootstrap de un compilador de C pequeño, compilar Dozer y luego compilar directamente el rustc moderno
      Así se puede obtener Rust directamente, sin hacer bootstrap de C++ ni de etapas intermedias
  • Si se pudiera separar GCC 4 y binutils de sus scripts de compilación originales, creo que se podría recortar más o menos la mitad de la lista
    Gran parte de los elementos ahí son simplemente reconstruir una y otra vez herramientas tipo autoconf y sus dependencias
    https://github.com/fosslinux/live-bootstrap/blob/master/part...

  • No entiendo bien el punto. Para crear un binario nuevo que funcione en la máquina destino, rustc tiene que soportar la arquitectura destino
    Si ya agregaste ese soporte a rustc, entonces simplemente haces que rustc se compile a sí mismo

    • Más que soportar una arquitectura nueva, lo central es tener un proceso de bootstrap mucho más corto y auditable
  • A veces imagino escribir un intérprete o compilador de C++ en Scheme
    Ir directamente desde Scheme hasta el GCC actual podría ser un atajo enorme
    Pero, según la sabiduría convencional, escribir un compilador de C++ se considera casi imposible. Aun así, creo que serviría para aprender

  • Si se observa toda la pila desde el subensamblador, ¿podría ser una forma de esquivar el problema de trusting trust?
    https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_Ref...

    • Solo sería posible auditándolo todo y ejecutando uno mismo todo el proceso.
      Aun así, existen cosas como https://en.m.wikipedia.org/wiki/Underhanded_C_Contest, y creo que algunas de esas participaciones se me habrían escapado incluso si yo las hubiera auditado.
    • ¿No era ese el punto central?
  • Cuando estaba aprendiendo un poco de C, busqué cómo hacía la gente cosas tipo C++ en C, y vi implementaciones de objetos, excepciones y concurrencia.
    Si mrustc está escrito en C++, ¿no sería más fácil portar a C el código C++ que funciona usando esos primitivos estilo C++ en C?
    También parece posible ir migrándolo poco a poco aprovechando la fuerte interoperabilidad entre C++ y C.
    Claro que sé que sería un trabajo de portabilidad difícil y lleno de trampas. Pero hay que recordar que el punto de comparación es escribir desde cero un compilador de Rust en C.
    También me vienen a la mente los compiladores de C++ a C que existían antes. No sé si todavía existen.
    Incluso hoy, creo que serían útiles compiladores de Rust a C/C++ y de C++ a C legible para humanos, porque permitirían combinar las ventajas de seguridad de un lado con el ecosistema de herramientas del otro.