LLVM : les points faibles
(npopov.com)- Analyse sous plusieurs angles des limites structurelles et de la dette technique du projet LLVM, en pointant concrètement les domaines à améliorer
- Présentation des goulets d’étranglement dans l’exploitation d’un grand projet open source, comme le manque de revue, l’instabilité des API, les temps de build et de compilation, ou encore l’instabilité de la CI
- Parmi les problèmes de conception de l’IR, l’article cite la gestion des valeurs
undef, l’encodage des contraintes, la sémantique en virgule flottante et l’incomplétude de la spécification - Il relève aussi des problèmes structurels de long terme, comme l’hétérogénéité des backends, la confusion autour de la gestion de l’ABI, et les retards de transition de GlobalISel et du pass manager
- Plutôt que de dresser un constat négatif sur LLVM, l’article présente ces sujets comme une opportunité d’amélioration continue et d’élargissement des contributions
Principaux problèmes structurels
-
Le manque de capacité de revue est présenté comme le principal goulet d’étranglement
- Il y a beaucoup d’auteurs de code, mais pas assez de relecteurs, ce qui conduit parfois à l’intégration de modifications insuffisamment vérifiées
- Comme la demande de revue repose sur l’auteur, les nouveaux contributeurs ont du mal à trouver les bons relecteurs
- L’adoption d’un système d’attribution automatique des PR comme celui de Rust est évoquée comme piste d’amélioration
-
Les changements fréquents (churn) des API et de l’IR pèsent sur les utilisateurs
- L’API C reste relativement stable, mais l’API C++ change souvent, ce qui augmente les coûts de maintenance des frontends et des backends
- La philosophie « Upstream or GTFO » fait que le code non partagé n’est pas pris en compte dans les décisions
-
Le problème des temps de build excessifs
- LLVM se compose de plus de 2,5 millions de lignes de code C++, ce qui allonge fortement les builds, avec une forte hausse de l’usage mémoire et disque en build debug
- Des pistes comme les en-têtes précompilés (PCH), le build en
dylibpar défaut ou la daemonisation des tests sont discutées
-
Instabilité de la CI
- Plus de 200 buildbots testent divers environnements, mais l’état « vert » n’est pas maintenu en permanence
- Les tests flaky et les problèmes de buildbots diluent les signaux d’alerte, ce qui rend la détection des erreurs réelles plus difficile
- L’introduction de tests avant PR a permis quelques améliorations, sans résoudre le problème de fond
-
Manque de tests end-to-end
- Les tests unitaires d’optimisation sont solides, mais il existe très peu de tests du pipeline complet ou d’intégration backend
llvm-test-suiteexiste, mais ne couvre pas suffisamment les combinaisons d’opérations de base et de types de données
Problèmes liés aux backends et aux performances
-
Hétérogénéité entre les backends
- Les couches intermédiaires sont unifiées, mais les backends accumulent de nombreuses modifications propres à chaque cible, ce qui accroît les duplications et divergences
- Il existe une tendance à ajouter des hooks spécifiques à une cible plutôt qu’à généraliser les optimisations communes
-
Temps de compilation
- LLVM est lent pour les JIT et pour les langages qui génèrent de gros volumes d’IR comme Rust ou C++
- Les builds en
-O0sont particulièrement lents, et le backend TPDE est présenté comme une alternative jusqu’à 10 à 20 fois plus rapide
-
Absence de suivi des performances
- Il n’existe pas d’infrastructure officielle de suivi des performances d’exécution
- Le système LNT souffre d’instabilité, de problèmes d’UX et d’un manque de données, ce qui limite fortement son utilité
Problèmes de conception de l’IR
-
Complexité de la gestion des valeurs
undef- Elles peuvent prendre une valeur différente à chaque utilisation, ce qui provoque des erreurs lors des optimisations
- Elles pourraient à terme être remplacées par des valeurs
poison, mais la gestion depoisonen mémoire reste incomplète
-
Spécification incomplète et incohérente
- Il existe d’anciens cas de mauvais fonctionnement toujours non résolus
- Des difficultés de conception persistent, comme le modèle de provenance
- Pour y répondre, un groupe de travail sur la spécification formelle a été mis en place
-
Manque de cohérence dans l’encodage des contraintes
- Drapeaux
poison, métadonnées, attributs,assumes: plusieurs approches coexistent - Cette situation nuit aux optimisations, soit par perte d’information, soit par conservation excessive d’informations
- Drapeaux
-
Problèmes de sémantique en virgule flottante (FP)
- Des incohérences apparaissent avec les NaN signalants, les environnements non standard, la gestion des denormals ou la précision étendue du x87
- Le traitement séparé via des intrinsic FP contraints ajoute encore de la complexité
Autres problèmes techniques
-
Retards dans certaines migrations partielles
- Le nouveau pass manager n’est appliqué qu’aux couches intermédiaires, tandis que les backends utilisent encore l’ancien
- GlobalISel n’a toujours pas remplacé complètement SDAG après 10 ans et coexiste avec lui
-
Confusion autour de la gestion de l’ABI et des conventions d’appel
- La répartition des responsabilités entre frontend et backend manque de clarté, et la documentation est insuffisante
- L’introduction d’une bibliothèque ABI et une implémentation prototype sont en cours
- Le problème existe aussi lorsque l’ABI varie selon l’activation de fonctionnalités de la cible
-
Gestion incohérente des fonctions builtin et des libcalls
- TargetLibraryInfo et RuntimeLibcalls sont séparés, ce qui nuit à la cohérence
- LLVM ne peut pas déterminer la disponibilité selon le type de bibliothèque d’exécution (
libgcc,compiler-rt, etc.) - Il manque des points de personnalisation pour des runtimes externes comme celui de Rust
-
Inefficacité de la structure Context / Module
- Les types et constantes résident dans le Context, tandis que les fonctions et globales sont dans le Module
- L’absence d’accès au data layout complique des opérations comme le constant folding
- L’impossibilité de lier entre contextes différents montre la nécessité de simplifier la structure
-
Pression sur les registres due à LICM (loop invariant code motion)
- Le déplacement (
hoist) est effectué systématiquement sans modèle de coût - Le backend ne refait pas ensuite de déplacement inverse (
sink), ce qui augmente les spill/reload
- Le déplacement (
Conclusion
- Les problèmes énumérés relèvent de défis structurels issus de la maturité et de l’ampleur de LLVM,
présentés comme une opportunité d’améliorer la qualité du projet et l’expérience des contributeurs - Dans certains domaines, comme l’optimisation des builds, la bibliothèque ABI ou le suivi des performances, des améliorations sont déjà en cours
- Dans l’ensemble, LLVM reste très puissant, mais un refactoring continu et une mise à niveau de la spécification restent indispensables
1 commentaires
Avis sur Hacker News
Le texte est globalement bien structuré, et je m’y retrouve beaucoup.
Ces derniers temps, la stabilité de LLVM IR est devenue assez élevée. J’ai rebasé Fil-C de LLVM 17 à 20 en une seule journée.
Dans d’autres projets aussi, j’ai maintenu le même pass sur plusieurs versions de LLVM sans gros problème.
En revanche, la pression sur les registres de LICM est particulièrement sévère pour des sources autres que C/C++. Le problème semble moins venir de LICM lui-même que du fait qu’il faudrait apprendre à regalloc à mieux rematérialiser
Ce serait bien d’ouvrir davantage d’options pour que les développeurs de frontend puissent benchmarker différents réglages et choisir la meilleure valeur
Chaque outil et composant a ses propres règles, donc il semble presque naturel qu’il y ait des différences entre versions. Je me demande si je comprends mal quelque chose
J’ai essayé de compiler LLVM 18 sur macOS et j’ai demandé au responsable de compiler-rt de ne changer qu’un booléen, mais l’issue a été verrouillée comme discussion « heated » et cela reste non résolu depuis 4 ans
Malgré ça, j’adore toujours LLVM. clang-tidy, ASAN, UBSAN, LSAN, MSAN, TSAN sont vraiment excellents.
À mon avis, ne pas utiliser clang-tidy quand on écrit du code C/C++ est un mauvais choix
En revanche, -fbounds-safety n’existe que dans AppleClang, tandis que MSAN/LSAN n’existent que dans LLVM Clang. Xcode n’inclut pas non plus clang-tidy, clang-format ni llvm-symbolizer.
Au final, sur macOS, j’ai dû compiler moi-même Darwin LLVM pour l’utiliser.
Côté Linux aussi, c’est confus. RHEL ne fournit pas libcxx, mais Fedora oui. En revanche, aucune distribution ne fournit un libcxx instrumenté pour MSAN
Fedora y est presque, mais il faut encore compiler compiler-rt à la main
Après les discussions récentes autour de LLVM, j’ai le sentiment qu’il faut absolument une suite de tests exécutable qui commence directement à partir de LLVM IR, pas de C
Quand on implémente soi-même un backend, la documentation sur SelectionDAG ou GlobalISel est insuffisante, et le sens des opérations manque de clarté, ce qui rend les erreurs d’implémentation faciles
La C API donne l’impression d’être laissée de côté dans LLVM. Beaucoup d’options ou de pass
optne sont pas exposésComme la plupart des développeurs utilisent directement l’API C++, la C API passe après tout le reste et finit par rester un citoyen de seconde zone
Les revues de code n’apportent pas de récompense immédiate, donc les gens ont tendance à ne pas en faire
Donner aussi du crédit de contribution pour les reviews pourrait créer une motivation
Il y a 6 ans, je compilais souvent LLVM sur un Dell 9360 avec 8 Go de RAM. En réduisant le parallélisme à l’édition de liens, ça passait dans la limite mémoire
Je me demande si compiler avec 8 Go reste possible aujourd’hui
Au tout début, LLVM avait pour avantage une vitesse de compilation supérieure à GCC.
Maintenant que 23 ans ont passé depuis l’arrivée de LLVM, je me demande si quelque chose de nouveau apparaîtra encore
Il existe aussi des alternatives comme Cranelift, qui n’utilise pas LLVM IR (Cranelift GitHub)
La gestion de l’ABI et des conventions d’appel est la plus grande source de douleur.
Il faut gérer soi-même le passage des arguments dans le frontend, et parfois aller jusqu’à compter le nombre de registres
L’article disait que « les frontends sont protégés grâce à une C API stable », mais en pratique ce n’est pas vrai
Certaines API sont stables, mais des parties comme Orc changent souvent
LLVM donne l’impression de n’avoir pratiquement aucun système de revue des issues. Les rapports de bugs que j’ai soumis moi aussi ne sont toujours pas traités après plusieurs années