Compiler Explorer a 10 ans

Posté par  . Édité par Benoît Sibaud et Xavier Teyssier. Modéré par Xavier Teyssier. Licence CC By‑SA.
Étiquettes :
43
30
mai
2022
Technologie

Matt Godbolt, l'auteur originel de Compiler Explorer nous apprend sur son blog que l'outil a atteint 10 ans le 22 mai 2022.

Compiler Explorer est un site web sur lequel l'utilisateur peut écrire un programme et observer l'assembleur généré par le compilateur. Il s'agit d'un logiciel libre, écrit en JavaScript, et disponible sous les termes du contrat BSD-2-clause.

Il s'agissait initialement d'un petit outil développé sur son temps libre pour trouver des réponses à des questions d'optimisations réalisées par le compilateur. Depuis, le service est toujours le même, à ceci près qu'il répond maintenant aux questions de milliers de développeurs sur de nombreux langages et compilateurs !

Entre temps l'outil a évolué et est capable d'afficher l'assembleur généré par différents compilateurs (GCC et Clang bien sûr, mais aussi ICC, ICX, MSVC et plein d'autres), et dans plusieurs versions. Il sait même afficher l'IR LLVM quand -emit-llvm est passé à Clang.

Le site s'est aussi étendu vers le support de plus de trente autres langages. On y trouve du C et du C++, mais aussi du Java, Pascal, Kotlin, Rust, Python…

Tout cela n'aurait pu être possible sans de nombreux contributeurs et soutiens, que Matt n'oublie pas de nommer dans son post :

  • Partouf, développeur et administrateur du site ;
  • Rubén, un des premiers gros contributeurs ;
  • Jason Turner, qui a grandement participé à la popularisation de Compiler Explorer en l'utilisant dans ses présentations ;
  • Austin, un autre administrateur qui s'occupe aussi de la sécurité du site et du bac à sable ;
  • Mats, Marc et Jeremy, les plus récents commiteurs ;
  • Nicole, Tim et Dale de Microsoft pour avoir ajouté le support des compilateurs basés sur Windows ;
  • Luka et Chedy pour l'amitié, les conversations et les conseils durant ces années ;
  • et enfin sa femme et ses enfants pour apporter leur soutien et accepter qu'il soit occupé avec son loisir bizarre.

Aller plus loin

  • # Quelques (petites) précisions :)

    Posté par  (site Web personnel) . Évalué à 6 (+5/-0).

    Matt a effectivement fait ça "sur du temps libre"… mais à son travail, c'est pour cela qu'il remercie son employeur de l'époque d'avoir accepté de publier librement le code (yoohoo).
    Et s'il est vrai qu'au début, il s'agissait uniquement de voir l'assembleur émit par GCC, maintenant, c'est bien bien plus large:
    - IR des compilateurs (tree/ipa/rtl de GCC, LLVM-IR, rustc HIR, GNAT Tree, d'autres) (d'ailleurs, pas besoin de passer -emit-llvm, c'est gérer par l'app qui l'affiche dans une fenêtre à part)
    - compilation + édition de lien et affichage du code désassemblé
    - exécution (x86_64 seulement sur le site public) du programme
    - utilisation d'outils sur le résultat (pahole, readelf, …)
    - outils d'analyse (OSACA, LLVM-MCA)

    Il y a un mode IDE/Tree qui permet de gérer plusieurs fichiers, le support de CMake, la possibilité de faire des "diff" des sorties, support de bibliothèques (Boost, …), fonction magique permettant de faire #include sur une URL, …

  • # Cas d'usage

    Posté par  . Évalué à 3 (+2/-0).

    Si certains d'entre vous utilisent ce logiciel, je suis curieux d'en savoir plus sur les cas d'usage possible.
    Je suis développeur Java, j'ai l'impression que ça correspond à des cas poussés à l'extrême. Si c'est n'est pas vrai, je serais très heureux d'en apprendre plus !

    • [^] # Re: Cas d'usage

      Posté par  (site Web personnel) . Évalué à 8 (+7/-0).

      Quelques cas me concernant:
      - tester un bout de code étrange "qui ne devrait même pas compiler" et voir ce que ça donne
      - mettre au point une petite fonction isolée
      - échanger un bout de code avec qq1
      - tester la présence d'un bug dans un compilateur en fonction des version (conformance view est très utile)
      - debug du backend de GCC avec la visualisation des sorties RTL. La possibilité de faire des diff permet de "trouver" la version de GCC ou l'option qui va changer le code généré (et pointer vers l'endroit à gratter)
      - regarder ce qui sort du frontend Rust de GCC dans la première sortie de l'IR generic
      - comparer le code émit pour le même code/compilateur mais avec options différentes
      - très pratique pour éplucher/étudier des bugs (le bugzilla de GCC a souvent des liens pointant vers des exemples pour reproduire)

      • [^] # Re: Cas d'usage

        Posté par  . Évalué à 2 (+1/-0).

        mettre au point une petite fonction isolée

        Si l'objet c'est d'avoir un bout de code avec l'assembleur que tu veux c'est pas plus logique d'écrire l'assembleur directement ?

        • [^] # Re: Cas d'usage

          Posté par  . Évalué à 2 (+0/-0).

          Tu peux vouloir mettre au point pour que ce soit optimal sur des plateformes différentes ?

          • [^] # Re: Cas d'usage

            Posté par  . Évalué à 2 (+0/-0).

            Tu peux écrire l'assembleur pour chacune d'entre elles et fallback sur du c.

            Si tu as en tête le code assembleur que tu veux ton source devrait être l'assembleur à mon humble avis et pas un code C qui va être potentiellement non idiomatique avec un commentaire dessus "j'ai vérifié gcc X.Y génère le code assembleur qui va bien (du moins avec les options qu'on utilise sur la version a.b.c)".

            • [^] # Re: Cas d'usage

              Posté par  . Évalué à 2 (+0/-0).

              Oui ça se fait, c'est assez lourd. J'imagine que ça se justifie de pas s'encombrer avec ça si c'est "suffisamment bon" sur toutes les plateformes.

            • [^] # Re: Cas d'usage

              Posté par  (site Web personnel) . Évalué à 2 (+2/-0). Dernière modification le 31/05/22 à 09:37.

              Oui et non. Parfois tu préfères quand même que ça soit du C, ça reste plus portable que de l'assembleur. Il arrive que de modifier un peu le code C permette à certaines optim de mieux fonctionner… J'ai pas de cas en tête, mais quand tu commences à avoir des boucles, des pointeurs, etc, c'est pas rare que ça freine pas mal le compilateur. Tu peux le constater dans l'assembleur (code sous optimal car manque de connaissance du compilo par ex), ou dans les passe d'optim qui t'informent du pourquoi elles ne font rien. Tu modifies le code (par ex en donnant/corrigeant des info de type, du restrict, changeant l'imbrication des boucles, …) et tu peux voir si ça va mieux. Le but n'est pas toujours de trouver le code C qui donnera le code ASM que tu as en tête. Sans compter que ce code devra être maintenu/porté. Souvent, ça guide aussi vers du code plus propre, plus lisible (j'ai bien dit parfois… parfois, c'est l'inverse).

              Et parfois, c'est mieux de faire un bout d'asm, aussi. Je n'ai pas l'impression qu'il existe une solution parfaite à tous les cas :) CE vient aider pour certains cas, pas tous.

              Cas particulier: debug de la description cible dans GCC: tu cherches à ce que le backend émette une instruction particulière.. Avoir CE qui te permet de facilement donner des petits coups de tournevis, ça aide à chercher.

              PS/Edit: et il n'y a pas que le C non plus, même si on l'oubli un peu.

              • [^] # Re: Cas d'usage

                Posté par  . Évalué à 2 (+0/-0).

                Je comprends ton point de vu et je suis d'accord qu'il n'y a pas de règle universelle.

                Il arrive que de modifier un peu le code C permette à certaines optim de mieux fonctionner…

                En fait c'est ça qui me semble fragile. Tu te base sur un comportement de compilateur qui dans une version avec les paramètres que tu utilise produit quelque chose, mais ça n'est pas une garantie que ce soit pérenne. De plus tu écris du code avec une part d'implicite qui ne se voit qu'avec la version asm du code. Devoir ajouter et maintenir un commentaire peut être plus subtile.

                Mais j'attire juste attention on est d'accord.

                PS/Edit: et il n'y a pas que le C non plus, même si on l'oubli un peu.

                Ma remarque tiens sur tout langage. Si tu écris dans un langage haut niveau en ayant en tête ce que ta toolchain va produire exactement ton langage de haut niveau génère plus de bruit de part le fait qu'il t'oblige à retrouver les abstractions qui vont bien et nécessite une phase de compilation ensuite.

                Dis autrement le code devrait être au plus proche de ce que l'on pense, si on pense en assembleur c'est ça que l'on devrait commiter.

                • [^] # Re: Cas d'usage

                  Posté par  . Évalué à 3 (+0/-0).

                  Tu te base sur un comportement de compilateur qui dans une version avec les paramètres que tu utilise produit quelque chose, mais ça n'est pas une garantie que ce soit pérenne.

                  Ça me semble aussi vrai avec l'assembleur, tu peux avoir des instructions supplémentaire ou un comportement différent avec une nouvelle génération de processeur (ou un autre fabricant). Alors que si le comportement est compris par le compilateur, il l'améliorera en fonction de la nouvelle cible.

                  « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

                  • [^] # Re: Cas d'usage

                    Posté par  . Évalué à 2 (+0/-0).

                    Ça me semble aussi vrai avec l'assembleur, tu peux avoir des instructions supplémentaire ou un comportement différent avec une nouvelle génération de processeur (ou un autre fabricant).

                    Dans une certaine mesure mais de manière bien plus légère. Il s'agit d'une abstraction plus simple, il me semble qu'elle est bien moins altérée.

                    Mais c'est surtout une question de la dernière phrase de mon commentaire :

                    Dis autrement le code devrait être au plus proche de ce que l'on pense, si on pense en assembleur c'est ça que l'on devrait commiter.

                    • [^] # Re: Cas d'usage

                      Posté par  . Évalué à 3 (+2/-0).

                      Au boulot on utilise la stratégie que tu décris. Nous avons environ 30 000 lignes d'assembleur (en utilisant des intrinsèques, ce qui est quand même plus pratique que l'assembleur brut) et la principale motivation est d'avoir ces fonctions aussi optimisées que possible. Le meilleur moyen d'y arriver est de les écrire avec les instructions que nous voulons voir dans le binaire. Le cadre est assez fixe cela dit puisque nous visons un seul couple architecture/compilateur.

                      Nous avons aussi une version C pour chaque fonction optimisée, à la fois comme fallback mais aussi comme référence pour la version assembleur.

                      En pratique on remarque que certains compilateurs arrivent à émettre de l'assembleur équivalent à celui écrit à la main depuis fonctions C. Ce n'était pas le cas avec de plus anciennes versions.

                      Perso je trouve qu'il nous manque quelque chose pour mesurer le gain apporté par les fonctions optimisées, pour voir si elles sont toujours pertinentes. Nous avons des mesures globales, qui englobent toutes les optims, mais rien individuellement. J'ai bien entamé un truc à base de Google Benchmark mais ce n'est pas évident d'avoir un test représentatif des cas d'utilisation réels.

                      • [^] # Re: Cas d'usage

                        Posté par  . Évalué à 1 (+0/-0).

                        Je correspond assez bien avec ce que tu décris (assembleur intrinsic…)

                        Pour répondre à ta dernière partie, j'utilise llvm-mca (dispo avec compiler explorer dans la section add tools) pour avoir une idée théorique des performances en asm (par exemple en deux versions). Sinon pour les mesures fines, j'utilise perf avec les compteur hardware ou parfois du rdtsc (ça dépend des cas et de ce que je veux mesurer).

    • [^] # Re: Cas d'usage

      Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

      Si certains d'entre vous utilisent ce logiciel, je suis curieux d'en savoir plus sur les cas d'usage possible.

      Je l'utilise régulièrement et ce n'est pas du tout réservé pour des cas extrêmes:

      Quelques cas d'utilisation dans le désordre:

      • Vérifier le support d'une fonctionnalité d'un langage sur plusieurs compilateurs

      • Prototyper très rapidement une idée et l'envoyer à quelqu'un pour une demo / revue.

      • tester la portabilité d'un snippet de code sur plusieurs plateformes / OS / compilateur

      • Vérifier la vectorization / inlining d'un bout de code sur un ensemble de compilateur.

      • Tester l'impacte en terme de code généré / performance d'un flag de compilateur.

      Et beaucoup d'autre.

      Il n'y a rien qui ne peut pas être fait sans compiler explorer, mais c'est un trés bon tool qui rend simplement plus productif.

    • [^] # Re: Cas d'usage

      Posté par  . Évalué à 2 (+2/-0).

      En C++ récent, le code devient très vite cryptique, d'autant plus qu'il faut contourner les interprétation fallacieuse du standard par les différents compilateurs.

      Pour cela, il y a cppinsights.io qui explique comment un meta programme est interprété (compris) par le compilateur.

      Cela permet d'écrire du code template plus robuste, plus complet et plus lisible parfois.

    • [^] # Re: Cas d'usage

      Posté par  . Évalué à 2 (+1/-0).

      Je l'utilise le plus souvent pour confirmer que le compilateur va bien effectuer certaines optims (genre éviter d'appeler systématiquement une fonction dans une boucle quand son résultat ne dépend pas de l'itération).

      Le deuxième cas d'utilisation que j'ai est de comparer les sorties de différents compilateurs.

      Et de temps en temps c'est aussi juste pour être impressionné par les transformations d'optimisation.

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.