LLVM: pontos problemáticos
(npopov.com)- Analisa por vários ângulos as limitações estruturais e a dívida técnica do projeto LLVM, apontando de forma concreta as áreas que precisam de melhorias
- Apresenta gargalos na operação de um grande projeto open source, como falta de revisão, instabilidade de API, tempo de build e compilação e instabilidade de CI
- Entre os problemas de design do IR, inclui tratamento de valores
undef, codificação de restrições, semântica de ponto flutuante e incompletude da especificação - Aponta problemas estruturais de longo prazo, como heterogeneidade dos backends, confusão no tratamento de ABI e atraso na transição para o GlobalISel e o gerenciador de passes
- Em vez de negar o estado atual do LLVM, apresenta isso como uma oportunidade para melhoria contínua e ampliação das contribuições
Principais problemas estruturais
-
A falta de capacidade de revisão é apontada como o maior gargalo
- Há muitos autores de código, mas poucos revisores, o que leva à fusão de mudanças não suficientemente validadas
- Como a solicitação de revisão é responsabilidade do autor, novos contribuidores têm dificuldade para encontrar revisores adequados
- A adoção do sistema automático de atribuição de PRs do Rust é citada como possível melhoria
-
Mudanças frequentes (churn) na API e no IR sobrecarregam os usuários
- A API em C é relativamente estável, mas a API em C++ muda com frequência, aumentando o custo de manutenção de frontends e backends
- A filosofia “Upstream or GTFO” faz com que código não compartilhado não seja considerado nas decisões
-
Problema de tempo de build excessivo
- O LLVM é composto por mais de 2,5 milhões de linhas de código C++, e o build leva muito tempo; em builds de depuração, o uso de memória e disco cresce fortemente
- Cabeçalhos pré-compilados (PCH), build padrão com
dylibe daemonização de testes são discutidos como formas de melhoria
-
Instabilidade de CI
- Mais de 200 buildbots testam em vários ambientes, mas nem sempre conseguem manter um “estado verde”
- Testes flaky e problemas nos buildbots diluem os sinais de alerta, dificultando a detecção de erros reais
- A introdução de testes prévios para PRs trouxe alguma melhora, mas ainda não resolveu o problema de forma estrutural
-
Falta de testes end-to-end
- Os testes unitários de otimizações individuais são sólidos, mas quase não há testes cobrindo o pipeline completo ou a integração com backends
- O
llvm-test-suiteexiste, mas não cobre de forma suficiente combinações básicas de operações e tipos de dados
Problemas relacionados a backend e desempenho
-
Heterogeneidade entre backends
- As etapas intermediárias são unificadas, mas os backends têm muitas modificações independentes por alvo, o que aumenta a duplicação e a divergência
- Há uma tendência de adicionar hooks específicos por alvo em vez de investir em otimizações comuns
-
Tempo de compilação
- É lento em JIT e em linguagens que geram grandes volumes de IR, como Rust e C++
- Builds com
-O0são particularmente lentos, e o backend TPDE é apresentado como uma alternativa até 10 a 20 vezes mais rápida
-
Ausência de rastreamento de desempenho
- Não existe uma infraestrutura oficial de acompanhamento de desempenho em runtime
- O sistema LNT tem instabilidade de funcionamento, problemas de UX e falta de dados, o que reduz bastante sua eficácia
Problemas de design do IR
-
Complexidade no tratamento de valores
undef- Eles podem assumir valores diferentes a cada uso, o que provoca erros durante otimizações
- No futuro, podem ser substituídos por valores
poison, mas o tratamento depoisonem memória ainda é insuficiente
-
Incompletude e inconsistência da especificação
- Existem casos antigos de mau funcionamento que seguem sem solução
- Há desafios de design, como o modelo de provenance
- Para resolver isso, foi formado um grupo de trabalho de especificação formal
-
Falta de consistência na codificação de restrições
- Flags de
poison, metadados, atributos,assumese outros métodos se misturam - A perda de informação ou sua retenção excessiva impacta negativamente as otimizações
- Flags de
-
Problemas na semântica de ponto flutuante (FP)
- Ocorrem inconsistências com NaN sinalizador, ambientes não padronizados, tratamento de denormais e precisão extra do x87
- Isso é tratado separadamente por intrinsics de FP com restrições, o que aumenta a complexidade
Outros problemas técnicos
-
Atraso em migrações parciais
- O novo gerenciador de passes foi aplicado apenas à etapa intermediária; os backends ainda usam o modelo antigo
- O GlobalISel ainda não conseguiu uma transição completa após 10 anos, coexistindo com o SDAG
-
Confusão no tratamento de ABI e convenções de chamada
- A divisão de responsabilidades entre frontend e backend é pouco clara e mal documentada
- A introdução de uma biblioteca de ABI e implementações protótipo estão em andamento
- Há casos em que a ABI muda conforme a ativação de recursos do alvo
-
Inconsistência na gestão de funções built-in e libcalls
- TargetLibraryInfo e RuntimeLibcalls são separados, o que prejudica a consistência
- Não há como reconhecer a disponibilidade conforme o tipo de biblioteca de runtime (
libgcc,compiler-rtetc.) - Falta um ponto de customização para runtimes externos, como o Rust
-
Ineficiência da estrutura Context / Module
- Tipos e constantes ficam no Context, enquanto funções e globais ficam no Module
- A impossibilidade de acessar o layout de dados dificulta tarefas como constant folding
- Não é possível fazer link entre contextos diferentes, e a estrutura precisa ser simplificada
-
Pressão de registradores causada por LICM (loop-invariant code motion)
- O hoist é feito sem modelo de custo
- O backend não faz sink novamente, o que aumenta spill e reload
Conclusão
- Os problemas listados são desafios estruturais decorrentes da maturidade e da escala do LLVM e são apresentados como uma oportunidade para melhorar a qualidade do projeto e a experiência dos contribuidores
- Em algumas áreas, como otimização de build, biblioteca de ABI e rastreamento de desempenho, o trabalho de melhoria já está em andamento
- No geral, o LLVM continua muito poderoso, mas refatoração contínua e organização da especificação são essenciais
1 comentários
Comentários do Hacker News
O texto todo está bem organizado, então concordei bastante com muita coisa.
Hoje em dia, a estabilidade do LLVM IR está bem alta. Rebaseei o Fil-C do LLVM 17 para o 20 em um único dia.
Em outros projetos também mantive o mesmo pass em várias versões do LLVM sem grandes problemas.
Ainda assim, a pressão de registradores no LICM é especialmente séria em fontes que não são C/C++. O problema parece ser menos o LICM em si e mais o fato de o regalloc precisar aprender melhor a rematerialize
Seria bom abrir mais opções para que desenvolvedores de frontend possam benchmarkar várias configurações e escolher o melhor valor
Como cada ferramenta e componente tem suas próprias regras, diferenças entre versões parecem até naturais. Fico me perguntando se entendi isso errado
Tentei pedir ao responsável por compiler-rt para mudar um único boolean para conseguir compilar o LLVM 18 no macOS, mas a issue foi trancada como “heated” e continua sem solução há 4 anos.
Mesmo assim, ainda amo o LLVM. clang-tidy, ASAN, UBSAN, LSAN, MSAN, TSAN são realmente excelentes.
Acho que escrever código C/C++ sem usar clang-tidy é uma escolha errada.
Só que -fbounds-safety existe apenas no AppleClang, e MSAN/LSAN só existem no LLVM Clang. O Xcode também não inclui clang-tidy, clang-format nem llvm-symbolizer.
No fim, no macOS eu tive que compilar o Darwin LLVM por conta própria para usar.
O lado Linux também é confuso. O RHEL não fornece libcxx, mas o Fedora fornece. Porém, libcxx instrumentada para MSAN não existe em nenhuma distribuição.
O Fedora quase chegou lá, mas ainda é preciso compilar o compiler-rt manualmente
Ao passar por discussões recentes sobre LLVM, senti que é absolutamente necessário um test suite executável começando em LLVM IR, e não em C.
Quando você cria um backend diretamente, faltam documentos sobre SelectionDAG e GlobalISel, e o significado das operações é ambíguo, então é fácil implementar errado
A API C parece algo deixado de lado dentro do LLVM. Muitas opções e passes do opt não estão expostos
Como a maioria dos desenvolvedores usa a API C++ diretamente, a API C acaba ficando em segundo plano e termina como uma cidadã de segunda classe
Como code review não leva a uma recompensa imediata, as pessoas acabam não fazendo muito isso.
Se revisão também desse crédito de contribuição, talvez houvesse motivação.
Há 6 anos eu compilava o LLVM com frequência em um notebook Dell 9360 com 8 GB. Reduzindo o paralelismo na etapa de link, dava para ficar dentro do limite de memória.
Fico curioso se ainda hoje dá para compilar com 8 GB.
No começo do LLVM, uma vantagem era a velocidade de compilação mais alta que a do GCC.
Agora, 23 anos depois do LLVM, fico me perguntando se surgirá algo novo.
Também existem alternativas como o Cranelift, que não usa LLVM IR (Cranelift GitHub)
O maior sofrimento é lidar com ABI e convenções de chamada.
É preciso gerenciar a passagem de argumentos diretamente no frontend e, às vezes, até calcular a quantidade de registradores
O texto dizia que “frontends ficam protegidos graças a uma API C estável”, mas na prática não é bem assim.
Algumas APIs são estáveis, mas partes como Orc mudam com frequência.
O LLVM parece quase não ter um sistema de revisão de issues. Os relatórios de bug que enviei também continuam sem tratamento há anos