1 pontos por GN⁺ 2024-08-26 | 1 comentários | Compartilhar no WhatsApp
  • Dozer é um compilador Rust baseado em C puro que busca permitir o uso de Rust em uma etapa mais inicial do bootstrap, e está sendo escrito sem C++, flex, yacc nem Makefile
  • O compilador oficial rustc é escrito em Rust, então novas versões são compiladas com versões anteriores do rustc, e essa cadeia remonta ao compilador Rust inicial escrito em OCaml e, depois, às camadas de Guile e C
  • O bootstrap de Linux do projeto Bootstrappable Builds começa a partir de uma semente binária de 512 bytes e se expande para compiladores simples, shell, subconjunto de C, TinyCC, yacc, coreutils, Bash, autotools, GCC e Linux
  • Atualmente, Rust só aparece em uma etapa tardia, quando o mrustc, escrito em C++, compila o rustc 1.56; por isso, é difícil usar Rust antes da introdução do C++
  • O objetivo do Dozer é ser um compilador Rust inicializável com TinyCC, conectando depois libcore, o backend Cranelift do rustc, uma ferramenta substituta do cargo e até a recompilação do rustc/cargo canônicos

Objetivos e restrições do Dozer

  • Dozer é um compilador Rust em desenvolvimento escrito em C puro
  • Não usa C++, e também não usa flex, yacc nem Makefile
  • O objetivo central é criar um compilador que permita fazer o bootstrap de Rust a partir de C
  • Em especial, ele precisa ser inicializável com TinyCC, assumindo que o sistema não tem ferramentas úteis além de um compilador C e um shell bem básico

O problema de um compilador Rust construir a si mesmo

  • Para executar código Rust, é preciso compilá-lo, e normalmente cargo build chama internamente o rustc
  • O próprio rustc também é um compilador Rust escrito em Rust, então um novo rustc é compilado com uma versão anterior do rustc
    • rustc 1.80.0 é compilado com rustc 1.79.0
    • Essa cadeia continua para versões ainda mais antigas, como rustc 1.78.0
  • As etapas iniciais remontam até o Rust 0.7, quando o compilador era escrito em OCaml
  • Como também é preciso um compilador OCaml, a cadeia de bootstrap continua por outras implementações de linguagem
    • camlboot consegue compilar o compilador OCaml usando Guile
    • O interpretador Guile é escrito em C

A parte inferior da cadeia do Bootstrappable Builds

  • Bootstrappable Builds trata do processo de inicializar um sistema completo a partir de uma pequena semente binária
  • O processo de bootstrap do Linux começa com uma semente binária de 512 bytes
    • Essa semente inclui um compilador extremamente simples que recebe números hexadecimais e imprime os bytes brutos correspondentes
    • Uma lista de bytes hexadecimais, excluindo comentários e espaços em branco, também é tecnicamente tratada como código-fonte analisável
  • Depois disso, as etapas passam gradualmente a construir ferramentas de nível mais alto
    • um sistema operacional extremamente simples
    • um shell básico
    • um compilador um pouco mais avançado
    • uma etapa que se parece com código assembly
    • um subconjunto muito básico de C
    • um compilador C mais avançado escrito nesse subconjunto de C
  • Algumas etapas depois, já é possível compilar o TinyCC, e em seguida vêm yacc, coreutils básicos, Bash, autotools, GCC e Linux
  • Cada etapa está listada em live-bootstrap parts.rst

Rust entra tarde demais na cadeia de bootstrap

  • Hoje, Rust só aparece em uma fase muito tardia desse processo
  • A implementação usada é o mrustc, uma implementação alternativa de Rust escrita em C++
  • O mrustc consegue compilar o rustc 1.56 e, a partir daí, continuar a compilação até código Rust moderno
  • Mas, quando o C++ é introduzido na cadeia de bootstrap, o processo já está praticamente no fim
  • Para usar Rust antes da entrada do C++, é preciso um compilador Rust inicializável a partir de C

Estado atual da implementação do Dozer

  • O Dozer vem sendo desenvolvido há cerca de dois meses, sem extensões
  • Atualmente, ele já pode ser compilado sem problemas tanto com TinyCC quanto com cproc
  • O backend usa QBE
  • A implementação ainda está em estágio inicial
    • o lexer está concluído
    • o parser foi implementado em boa parte
    • a expansão de macros/módulos está sendo adiada o máximo possível
    • a checagem de tipos atualmente só suporta i32
    • a geração de código ainda está bruta
  • No momento, ele já consegue compilar com sucesso o seguinte código Rust
fn rust_main() -> i32 {
    (2 - 1) * 6 + 3
}

O plano para chegar até o rustc

  • O objetivo é evoluir o Dozer gradualmente para compilar exemplos básicos usando libc e, depois, chegar a compilar libcore e o rustc
  • Para compilar o rustc, o plano é usar o backend Cranelift
    • O backend Cranelift é inteiramente escrito em Rust
    • Como se assume a ausência de C++, não é possível compilar o LLVM
  • Também há planos de criar com o Dozer uma ferramenta substituta do cargo capaz de compilar pacotes Rust
  • Será preciso localizar e remover arquivos gerados automaticamente no código-fonte do rustc
    • Pelas regras do projeto Bootstrappable, código gerado automaticamente não é permitido
  • O objetivo final é compilar o rustc e o cargo e, depois, usar esse rustc/cargo compilado diretamente para recompilar o rustc/cargo canônicos
  • O projeto é visto como o trabalho mais difícil já assumido até agora, e, mesmo com dúvidas sobre a possibilidade de concluí-lo, a intenção é continuar tentando

1 comentários

 
GN⁺ 2024-08-26
Opiniões do Hacker News
  • Se fosse para fazer o bootstrap do Rust, acho que eu criaria em C um proto-Rust, com menos recursos que o Rust completo, e escreveria o compilador Rust completo nesse proto-Rust
    Por exemplo, o proto-Rust não teria borrow checker, teria suporte limitado ou inexistente a macros, talvez nem liberasse memória, e também não precisaria gerar código bom
    Na prática, seria algo mais próximo de C com sintaxe de Rust, mas, do ponto de vista de um entusiasta de Rust, isso parece melhor do que escrever um compilador Rust em “C com sintaxe de C”, que é o objetivo deste projeto
    Fico curioso por que não escolheram esse caminho

    • A propósito, o mrustc, que é o compilador Rust não escrito em Rust mais representativo já existente, também não tem borrow checker
      Remover o borrow checker não quebra programas corretos; apenas permite que uma grande quantidade de programas incorretos seja compilada
      O principal uso do mrustc é compilar o rustc, e já sabemos que o rustc consegue compilar a si mesmo sem erros do borrow checker, então tudo bem
    • Em Mozart/Oz, fizeram exatamente isso. Há um compilador proto-Oz escrito em Scala, e com ele compilam o compilador real escrito em Oz
      Como o compilador Scala gera código ineficiente, depois o compilador real é recompilado por ele mesmo
      Assim, no fim, obtém-se um compilador real eficiente que gera código bom, e esse processo faz parte do build padrão da linguagem
      https://github.com/mozart/mozart2
    • Então, no fim, você acaba usando dois compiladores; não vejo o que se ganha de fato além de trabalho extra
  • Estou fazendo por hobby um compilador C em Rust e, como piada por Rust ser obviamente mais pesado que C, chamo de Small C Compiler. É uma paródia de “Tiny C Compiler”
    Uso Cranelift como backend, mas a arquitetura geral do compilador pode ser trocada por meio de muitos traits e estou tentando deixá-la fácil de hackear
    Não pretendo abrir o código como open source até ele funcionar minimamente, a ponto de lidar com printf("%s", "Hello World!")
    Eu queria implementar o pré-processador e o parser, e por causa do infame problema do typedef acabei me envolvendo também com rust-peg e HimeCC
    Sei que, na indústria, usa-se uma tabela de símbolos para manter o contexto de typedef, mas havia a limitação de não conseguir ler os tipos mais abaixo. Fico curioso sobre qual é a solução acadêmica, e a única coisa que me vem à mente é memória transacional
    Se houver algo útil, acho que vou acabar publicando

  • Muito legal mesmo, e o interessante é que o mesmo tipo de problema de bootstrapping também existe em hardware
    O que cria computadores? Computadores criados anteriormente e o software que roda neles. Quanto mais se pensa nisso, mais interessante fica

    • O mesmo problema de bootstrapping existe em tudo. O que cria estradas? Equipamentos de construção. Mas, se ainda não há estrada, como levar esse equipamento de construção até o canteiro de obras?
      Alguns meses atrás, conheci alguém que trabalha em uma startup de entrega/fulfillment de materiais para projetos de construção
      Esse tipo de trabalho exige uma especialização diferente da entrega comum, como a da Amazon, não só porque os materiais muitas vezes têm propriedades incomuns ou perigosas, mas também porque é comum o local de entrega ainda nem ter endereço
      Dá para resolver, mas parece exigir uma especialização além das capacidades típicas das transportadoras modernas
    • Trabalhei em uma empresa que construía data centers, e eles tentavam desenvolver software a ponto de conseguir inicializar um data center inteiro a partir de um único laptop
      O motivo era provar aos órgãos reguladores, ao colaborar com empresas europeias, que não havia backdoors
      Era um problema muito interessante, mas muito difícil; nossa equipe só se envolveu indiretamente, trabalhando no encaminhamento de dados por meio de um proxy que garantia que todos os dados fossem auditáveis e que nada que não deveria ser enviado fosse enviado
      Saí da empresa antes de terminar, e depois ouvi dizer que o projeto foi descartado por ser difícil demais
    • Ao olhar para opcodes de assembly em octal do antigo Cray-1 ou para opcodes de word do IBM System/360, dá para perceber que eles foram feitos de forma surpreendentemente simples, a ponto de uma pessoa conseguir escrever bytes de opcode diretamente e montar à mão
      Depois, o x86 surgiu sem orçamentos enormes nem grandes compradores, e seu assembly foi projetado para ser o mais eficiente e compacto possível
      Como resultado, perdeu características que outras máquinas podiam ter de forma conveniente
    • Essa é uma das coisas mais legais nesses projetos de bootstrapping e em builds reproduzíveis
      Em teoria, dá para construir manualmente um computador muito simples apenas com componentes individuais
      Ele seria grande, ineficiente e tremendamente lento, mas poderia ser feito para seguir uma determinada arquitetura de conjunto de instruções, e nele seria possível construir um programa de bootstrap
      Então seria possível afirmar que o resultado obtido em um computador ruim, mas totalmente compreensível, é igual ao resultado obtido em hardware moderno no qual não se confia totalmente
    • É uma reflexão interessante também no nível da civilização humana. Se a humanidade, de algum modo, voltasse do ponto atual para a Idade da Pedra, conseguiríamos reconstruir tudo até o nível de hoje?
      É uma espécie de problema de bootstrapping. Por exemplo, as reservas de petróleo atuais são mais difíceis de extrair do que há 100 anos, e fico pensando se conseguiríamos fazer o bootstrap até esse ponto de novo
  • Foi meio irritante ter que seguir links 4 vezes para encontrar uma justificativa de alto nível explicando os benefícios do bootstrapping
    Eu esperava que a parte “Why” do título tratasse disso
    https://bootstrappable.org/benefits.html

    • Pode ser difícil explicar por que o bootstrapping é importante. Por isso também coloquei uma seção “Why?” no README do meu compilador bootstrap
      Segurança é um grande motivo, e é o ponto que a equipe do bootstrappable enfatiza principalmente
      Para evitar o problema de trusting trust e ataques como o backdoor recente no xz, é preciso conseguir fazer bootstrap de tudo a partir de código-fonte puro
      Eles removem até todos os arquivos pré-gerados, para depender apenas de coisas escritas à mão e auditáveis. Por exemplo, o bootstrapping do Python fica bem complicado porque o código-fonte inclui código gerado por scripts Python
      Eu, por outro lado, tenho mais interesse no aspecto de preservação cultural. Quero preservar mídias modernas em lugares como o Arctic World Archive para arqueólogos do futuro, mas isso não tem sentido se não houver como decodificá-las
      Podemos preservar especificações, mas não dá para esperar que eles implementem x265 e tudo o mais necessário do zero. Se preservarmos binários, seria preciso operar hardware de mil anos atrás ou virtualizar uma CPU de mil anos atrás
      Também poderíamos entregar uma definição simples de Lisp e código que roda sobre ela, mas quem implementaria x265 em Lisp básico? Não é realista
      Então, no meu projeto, criei uma máquina virtual simples e fiz bootstrap de C sobre ela
      Ela é muito fácil de portar não só para arquiteturas atuais, mas também para arquiteturas futuras ou alienígenas. Arqueólogos do futuro ou uma civilização alienígena poderiam implementar a VM em um dia, rodar o bootstrap de C sobre ela e então compilar ffmpeg etc. para decodificar nossa mídia
      Não há caixas-pretas; tudo é código-fonte aberto, escrito à mão, depurável e auditável
      https://github.com/ludocode/onramp?tab=readme-ov-file#why-bo...
      https://en.wikipedia.org/wiki/Arctic_World_Archive
  • Fiquei um pouco confuso. Só no meio do texto aparece o motivo para iniciar a jornada mencionada no título, e o ponto central é que, quando C++ entra na cadeia de bootstrapping, o bootstrap na prática já acabou, então não há como usar Rust antes disso mesmo que se queira
    Por isso, a ideia parece ser ter um compilador Rust em C, mais especificamente um que possa ser bootstrapped a partir do TinyCC em um sistema que se assume ainda não ter ferramentas úteis
    Mas isso contradiz a premissa inicial. O rustc 1.80.0 é compilado com o 1.79.0, o 1.79.0 com o 1.78.0, e assim por diante até chegar ao 0.7; nesse ponto, o compilador era escrito em OCaml
    Também foi dito que existe um projeto que compila com sucesso um compilador OCaml usando Guile, e que o interpretador Guile é escrito em C
    Então o caminho sem C++ que o autor quer já existe; ele apenas não é o caminho que a equipe do rustc usa no dia a dia
    No fim, a motivação não fica clara. Não sei se a intenção é criar um processo de bootstrap melhor baseado em C, transformar isso no método rotineiro de bootstrap do rustc, por que querem eliminar a etapa em C++ ou por que preferem uma etapa em C
    Se for apenas porque querem fazer, tudo bem, mas mesmo depois de ler um texto bem longo não entendi bem qual seria o objetivo além disso

    • Embora seja tecnicamente possível fazer bootstrap do Rust a partir do Guile e do compilador Rust 0.7, seria preciso recompilar o compilador Rust cerca de 100 vezes
      Cada etapa leva horas, e não dá para pular nenhuma etapa até o 0.7, porque o 1.80 exige o 1.79, o 1.79 exige o 1.78, e assim por diante
      Mesmo totalmente automatizado, esse bootstrap poderia levar meses
      Além disso, pelo que sei, as versões iniciais do rustc só emitiam LLVM, então de qualquer forma seria preciso fazer bootstrap de um compilador C++ para compilar o LLVM
      Se você tem um compilador C++, basta compilar o mrustc. Atualmente, o mrustc só dá suporte até o rustc 1.54, então ainda seria preciso compilar passando por cerca de 35 versões
      Todo esse processo não é prático. O objetivo do Dozer é fazer bootstrap de um compilador C pequeno, compilar o Dozer e então compilar diretamente o rustc moderno
      Assim, é possível obter Rust imediatamente, sem fazer bootstrap de C++ nem de etapas intermediárias
  • Se der para separar o GCC 4 e o binutils dos scripts de build originais, acho que dá para cortar mais ou menos metade da lista
    Muitos dos itens ali são apenas reconstruções repetidas de ferramentas do tipo autoconf e suas dependências
    https://github.com/fosslinux/live-bootstrap/blob/master/part...

  • Não entendi bem o ponto. Para criar novos binários que rodem na máquina-alvo, o rustc precisa dar suporte à arquitetura-alvo
    Se esse suporte já foi adicionado ao rustc, então é só deixar o rustc compilar a si mesmo

    • Mais do que suporte a uma nova arquitetura, o ponto principal é ter um processo de bootstrap muito mais curto e auditável
  • Às vezes imagino escrever um interpretador ou compilador de C++ em Scheme
    Ir diretamente de Scheme para o GCC atual poderia ser um atalho enorme
    Mas, pela sabedoria convencional, escrever um compilador C++ é visto como algo quase impossível. Ainda assim, acho que ajudaria a aprender

  • Observando a pilha inteira a partir do sub-assembler, isso poderia ser uma forma de contornar o problema de trusting trust?
    https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_Ref...

    • Só seria possível auditando tudo e executando o processo inteiro por conta própria
      Mesmo assim, existem coisas como https://en.m.wikipedia.org/wiki/Underhanded_C_Contest, e acho que eu teria deixado passar algumas dessas submissões mesmo que as tivesse auditado
    • Esse não era justamente o ponto?
  • Quando eu estava aprendendo um pouco de C, procurei como as pessoas faziam coisas parecidas com C++ em C, e vi implementações de objetos, exceções, concorrência etc.
    Se o mrustc é escrito em C++, talvez não fosse mais fácil portar para C o código C++ em funcionamento usando esses primitivos C ao estilo C++?
    Também parece possível migrar aos poucos aproveitando a forte interoperabilidade entre C++ e C.
    Claro que sei que é um trabalho de port difícil, cheio de armadilhas. Mas é bom lembrar que o ponto de comparação é escrever um compilador Rust do zero em C.
    Também me vêm à mente os compiladores C++ para C que existiam antigamente. Não sei se ainda existem.
    Mesmo hoje, acho que compiladores Rust para C/C++ e C++ para C legível por humanos seriam úteis, porque poderiam combinar as vantagens de segurança de um lado com o ecossistema de ferramentas do outro.