
Zig implementa compilação incremental confiável e em milissegundos
O que é compilação incremental no Zig
A compilação incremental permite que o compilador Zig detecte quais funções e declarações individuais mudaram desde o último build, recompile apenas esse código e faça patch direto dos bytes resultantes no binário de saída. O resultado: rebuilds de 50–70ms em projetos reais e complexos, contra ~5 segundos de um build frio.
O autor (membro da equipe core do Zig) demonstra isso com o Fizzy, um editor de pixels. A feature já está em uso diário pela equipe core, mas para quem usa releases estáveis, só estará disponível a partir do Zig 0.17.0.
Pipeline do compilador
O processo é dividido em quatro fases, cada uma com estratégia incremental própria:
1. Processamento de arquivos-fonte - Cada arquivo é lido, parseado em AST e convertido para ZIR (Zig Intermediate Representation). Como é uma função pura do conteúdo do arquivo, é trivialmente paralelizável e cacheável em disco. Basta detectar se o arquivo mudou para saber se precisa reprocessar. Essa parte já é rápida há anos.
2. Análise semântica - A parte mais difícil. Inclui type checking e avaliação de comptime. O compilador divide a compilação em "unidades de análise" (layout de structs, tipo de declarações, valor de consts, corpo de funções) e constrói um grafo de dependências entre elas. Cada unidade também depende de hashes de regiões específicas do código-fonte. Quando um hash muda, o compilador percorre o grafo e reanalisa apenas o que foi invalidado. O design da linguagem Zig foi ajustado ao longo dos anos (às vezes de forma controversa) justamente para facilitar essa decomposição.
3. Geração de código - Converte AIR (Analyzed IR) em MIR (Machine IR), com mapeamento quase 1:1 para instruções de máquina. É paralelizável e opera na mesma granularidade (funções individuais) da compilação incremental, então não precisa de cache adicional.
4. Linking - A parte mais complexa. Como não existem linkers incrementais genéricos maduros (o Wild, por exemplo, mudou foco para performance de cold-link), o Zig integrou o linker diretamente ao compilador. Uma abstração chamada link.MappedFile (criada por Jacob Young) faz memory-map do arquivo de saída e gerencia uma árvore de nós. Quando uma função é atualizada, o nó correspondente é redimensionado ou criado; se não há espaço, outros nós são movidos e marcados como "dirty" para fixup posterior. Com fatores de crescimento exponencial nos nós, a necessidade de mover dados é rara e amortizada.
Trace de um update real
Usando Tracy (profiler em tempo real), o autor mostra um update de 37ms no Fizzy. Os primeiros ~6ms cobrem processamento de arquivos, análise semântica (~1,2ms), codegen (~240µs) e linking (~170µs). Os ~31ms restantes são gastos em resolveReferencesInner, uma travessia do grafo de referências para determinar quais declarações estão ativas - mesmo quando o grafo não mudou. O autor considera isso uma oportunidade de otimização futura (evitar recomputação quando o grafo é inalterado, ou usar algoritmos de "dynamic single-source shortest path").
Como usar
Requer Zig master (ou 0.17.0+) e atualmente só funciona bem em x86_64-linux. O comando é:
zig build --watch -fincremental
--watch monitora o filesystem e dispara rebuilds; -fincremental ativa a compilação incremental. É possível habilitar por step específico no build.zig com uma opção -Dincremental. A feature ainda não é estável - pode haver bugs, incluindo falsos positivos de erros de compilação e até miscompilações.