- SectorC, escrito em assembly x86-16, é um compilador C ultracompacto que cabe no setor de boot (512 bytes) de uma máquina x86 e oferece suporte a um subconjunto da linguagem C suficiente para escrever programas realmente executáveis
- Inclui variáveis globais, funções, instruções if/while, operadores, desreferenciação de ponteiros, comentários e assembly inline, permitindo executar programas completos mesmo com uma estrutura mínima
- Para simplificar o tokenizador, foi projetada a linguagem Barely C, com tokenização baseada em espaços e uso de hash com
atoi(), preservando a sintaxe existente de C ao mesmo tempo em que alcança uma redução extrema de tamanho
- No processo de otimização do código, foram aplicadas várias técnicas de compressão em nível de assembly, como eliminação de saltos, fusão de chamadas, uso de offsets de 8 bits e uso de
stosw/lodsw, reduzindo o tamanho de 468 bytes para 303 bytes
- Como resultado, foi implementado dentro de 512 bytes um compilador C completo contendo tokenizador, parser, gerador de código e runtime, mostrando um caso extremo de minimização de software
Visão geral do SectorC
- SectorC é um compilador C escrito em assembly x86-16 que cabe inteiramente em um setor de boot de 512 bytes
- O repositório no GitHub é xorvoid/sectorc
- A linguagem suportada é um subconjunto de C em nível suficiente para escrever programas reais
- Os recursos suportados incluem variáveis globais, funções, estruturas de controle (if/while), vários operadores, desreferenciação de ponteiros, assembly inline e comentários
- Como programa de exemplo, é apresentado um código que desenha uma animação de onda senoidal em modo VGA
Contexto do projeto e abordagem
- Como um tokenizador C tradicional seria grande demais para caber em 512 bytes, foi necessário simplificar a própria estrutura da linguagem
- Grande insight #1: foi adotada uma estrutura de tokens separados por espaços, como em Forth, para projetar uma linguagem derivada chamada “Barely C”
- Exemplo: uma construção como
int(main)(){while(!done){ é tratada como um único “mega token”
- Ainda assim, ela continua sendo reconhecida como código C válido por compiladores C existentes
- Grande insight #2: a função
atoi() é usada como se fosse uma função hash para converter tokens em números
- Literais inteiros, palavras-chave e identificadores são todos tratados com base no resultado de
atoi()
- Os identificadores são acessados como índices em um array de 64K
Implementação do Barely C
- A primeira implementação tinha 468 bytes e usava uma estrutura de parser recursivo descendente com tokens baseados em
atoi
- Sem tabela de símbolos, acessando diretamente um segmento de 64K por valor de hash
- A geração de código segue o estilo do OTCC, usando o registrador
ax como armazenamento do resultado
- Depois, foi testada uma abordagem de byte-threaded code para experimentar uma estrutura no estilo Forth, mas ela acabou sendo mais ineficiente dentro do limite de 512 bytes e foi descartada
Técnicas de minimização de código
- Retornando a uma estrutura linear, o tamanho foi reduzido de 468 bytes → 303 bytes
- Foram usadas técnicas como eliminação de saltos (fall-through), tail-call, fusão de chamadas (call fusion), uso de
stosw/lodsw e manutenção de offsets de salto de 8 bits
- Isso garantiu 207 bytes livres para implementar recursos adicionais
Expansão para recursos completos de C
- Com mais 200 bytes, foi alcançado o suporte a uma sintaxe C completa
- Blocos
if/while aninhados, vários operadores binários (+, -, *, &, |, ^, <<, >>, ==, !=, <, >, <=, >=)
- Suporte a definição de funções e chamadas recursivas, assembly inline (
asm) e comentários de uma linha e múltiplas linhas
- Por meio da tabela de operadores (
binary_oper_tbl), cada operador é definido em 4 bytes, permitindo tratar 14 operadores em 56 bytes
Estrutura da gramática
- A gramática completa é composta por
program, var_decl, func_decl, statement, expr e outros elementos
- Há suporte tanto para comentários
// quanto /* */
- O próprio texto de definição da gramática tem 704 bytes, maior que a implementação real
Assembly inline e runtime
- Com a instrução
asm, é possível inserir diretamente código de máquina x86-16
- Trata-se de um recurso essencial para lidar com I/O
- O runtime (
rt/) é composto por dois arquivos escritos em C
rt/lib.c: rotinas de biblioteca baseadas em assembly inline
rt/_start.c: ponto de entrada do programa _start()
Programas de exemplo
examples/hello.c: imprime texto diretamente na memória 0xB8000
examples/sinwave.c: exibe uma animação de onda senoidal no modo VGA 0x13
examples/twinkle.c: toca “Twinkle Twinkle Little Star” no alto-falante do PC (com aviso de som)
Conclusão
- SectorC é um compilador C ultracompacto que realizou um objetivo que parecia impossível,
mostrando um caso extremo de minimização de software e design criativo de linguagem
- O texto termina com opções humorísticas de “o que aprendemos”,
enfatizando de forma satírica a postura de desafiar o impossível e o valor de simplificar software
1 comentários
Comentários no Hacker News
Os compiladores otimizadores dos anos 2000 talvez simplesmente otimizassem esses tokens para no-op 😉
Criar jogos de boot sector é uma experiência realmente mágica e nostálgica. Faz lembrar a época em que programar era de fato divertido
É uma pena que, na era atual de IA, projetos assim sejam tão subestimados
Link do meu projeto
Seu projeto parece ter uma opção para gerar código para boot sector.
Ambos são interessantes, mas em comum eles têm só “boot sector”, “C” e “compiler”
Então dá a sensação de que eu já não sou mais especial
Continuamos empilhando abstrações, a ponto de até para rodar um “Hello World” precisarmos de 200MB de node_modules
Enquanto isso, alguém coloca um compilador C em 512 bytes
Não que todo mundo devesse escrever código de boot sector, mas ler projetos assim é uma experiência realmente humilhante. Também tem muito valor educacional
Este projeto mostra muito bem como a estrutura central de C é simples.
É interessante que C tenha evoluído da linguagem B, e B de uma versão reduzida de Fortran
if,whileeforfossem convertidos em rotinas simples de gotoPorque em assembly, no fim das contas, só existe
jmp.E também deveria ser “chose your own adventure”, não “choose your own adventure” :)
Eu amo minimalismo
Algo como começar de um binário minúsculo e específico de plataforma e ir construindo ferramentas e compiladores cada vez mais complexos
Como exemplo, dá para ver os projetos mishmashvm e tcc_bootstrap_alt
A discussão da época aconteceu aqui
SectorC: A C Compiler in 512 bytes - link - maio de 2023 (80 comentários)
Já este projeto só consegue lidar com parte de C
Ou seja, isto é mais um compilador para um subconjunto de C do que um compilador C completo.
O código que este compilador consegue compilar também pode ser compilado por um compilador C de verdade, mas o contrário não
Espero não ter usado símbolos suficientes para provocar colisões em 32 bits
Aliás, ainda sobram 21 bytes de espaço livre entre 0x01e0 e 0x01fd. Talvez dê para colocar mais alguma coisa ;)
atoi()agir como uma função de hash ruim para texto comum” é interessanteMas, pelo que eu lembro,
atoi()era definido para retornar 0 em entradas inválidasAo rodar no Linux, basta trocar a chamada do qemu de coreaudio para alsa
Enviei um pull request no GitHub para isso. Se o autor achar meu estilo verboso de script shell aceitável, talvez faça merge
Acho que chegou a hora de adicionar um compilador C nele
Link do meu projeto de SO