⚙️ Pipeline de compilation
Simulation interactive d'un pipeline de compilation. Saisissez une expression arithmétique et observez sa transformation en tokens, en arbre syntaxique abstrait, en code à trois adresses, puis en une passe d'optimisation par propagation de constantes — le tout avec un véritable analyseur syntaxique descendant récursif.
À propos du pipeline de compilation
Un compilateur est un programme qui traduit du code source écrit dans un langage de haut niveau vers une représentation de plus bas niveau — généralement du code machine ou du bytecode — qu'un processeur ou une machine virtuelle peut exécuter. Le pipeline est une série séquentielle d'étapes bien définies : l'analyseur lexical (tokeniseur) découpe le flux brut de caractères en tokens significatifs ; l'analyseur syntaxique construit un arbre syntaxique abstrait (AST) représentant la structure grammaticale ; l'étape de représentation intermédiaire (RI) produit une forme indépendante du langage adaptée à l'analyse ; et le générateur de code (codegen) produit des instructions cibles, souvent accompagné d'une passe d'optimisation telle que la propagation de constantes, qui évalue les expressions au moment de la compilation pour réduire le travail à l'exécution. Comprendre ce pipeline est fondamental pour la conception de langages de programmation, l'outillage des IDE et l'analyse de sécurité.
Saisissez ou collez une expression arithmétique dans l'éditeur et observez chaque étape du pipeline se mettre à jour en temps réel. Le panneau AST montre l'arbre d'analyse construit par un analyseur syntaxique descendant récursif ; le panneau RI montre le code à trois adresses ; et le panneau d'optimisation met en évidence les constantes qui ont été propagées. Expérimentez avec des parenthèses imbriquées et de grandes valeurs littérales pour voir comment la profondeur de l'arbre et la longueur de la RI évoluent.
Questions fréquentes
Que fait un analyseur lexical et qu'est-ce qu'un token ?
Un analyseur lexical (aussi appelé tokeniseur ou scanner) lit les caractères source bruts un par un et les regroupe en tokens — les plus petites unités significatives du langage. Pour une expression arithmétique simple comme « 3 + 4 * x », les tokens sont NUMBER(3), PLUS, NUMBER(4), STAR, IDENT(x). Chaque token possède un type et souvent une valeur, et les espaces sont généralement supprimés à ce stade. Les analyseurs lexicaux sont habituellement implémentés comme des automates finis dérivés d'expressions régulières.
Qu'est-ce qu'un arbre syntaxique abstrait (AST) ?
Un AST est une structure de données en arbre qui représente la structure grammaticale du code source, chaque nœud interne représentant un opérateur ou une construction et chaque feuille représentant un opérande ou un littéral. Contrairement à un arbre d'analyse concret, l'AST omet le bruit syntaxique tel que les parenthèses et les points-virgules, ne conservant que la structure sémantique nécessaire aux analyses ultérieures. Les compilateurs, interpréteurs, linters et formateurs de code opèrent tous principalement sur l'AST plutôt que sur le texte source brut.
Qu'est-ce qu'un analyseur syntaxique descendant récursif ?
Un analyseur syntaxique descendant récursif implémente la grammaire sous forme d'un ensemble de fonctions mutuellement récursives, une par règle de production grammaticale. Pour analyser une expression, il appelle la fonction expression, qui appelle la fonction terme pour la multiplication, qui appelle la fonction facteur pour les atomes — reflétant directement la hiérarchie de priorité des opérateurs dans la pile d'appels. Les analyseurs descendants récursifs sont faciles à écrire à la main, produisent d'excellents messages d'erreur, et sont utilisés par des compilateurs de production tels que GCC (frontal C) et Clang.
Qu'est-ce que le code à trois adresses (TAC) et pourquoi est-il utilisé comme représentation intermédiaire ?
Le code à trois adresses (TAC) est une représentation intermédiaire où chaque instruction comporte au plus un opérateur et trois opérandes (deux sources et une destination) : par exemple t1 = a * b ; t2 = t1 + c. Cette structure simple et uniforme facilite l'analyse de flux de données — chaque temporaire est défini une seule fois, ce qui facilite la forme SSA (Static Single Assignment). La plupart des compilateurs modernes (GCC, LLVM) utilisent une forme de TAC en interne avant de descendre vers l'assembleur ; la RI de LLVM en est un exemple bien connu et lisible par l'humain.
Qu'est-ce que la propagation de constantes et quel gain de performance apporte-t-elle ?
La propagation de constantes (constant folding) est une optimisation qui évalue les sous-expressions constantes au moment de la compilation plutôt que de générer du code pour les calculer à l'exécution. Par exemple, 2 * 3 + 1 devient 7 sans aucun coût à l'exécution. Les compilateurs modernes propagent aussi les constantes à travers les affectations de variables (propagation de constantes), permettent d'autres simplifications comme l'élimination de code mort, et évaluent même les appels de fonctions pures dont tous les arguments sont constants. L'effet combiné peut réduire considérablement le nombre d'instructions dans du code comportant de nombreux calculs littéraux.
Quelle est la différence entre un compilateur et un interpréteur ?
Un compilateur traduit le code source en une représentation cible à l'avance (AOT, ahead-of-time), produisant un artefact — un binaire, du bytecode, ou une RI — pouvant s'exécuter indépendamment du compilateur. Un interpréteur lit le source (ou le bytecode) et l'exécute directement, instruction par instruction, au moment de l'exécution. De nombreux systèmes modernes combinent les deux : Python compile vers du bytecode (.pyc) que l'interpréteur CPython exécute ensuite, et les moteurs JavaScript compilent vers du code machine à l'aide de la compilation à la volée (JIT) pour les chemins de code fréquemment exécutés.
Qu'est-ce qu'une grammaire et comment définit-elle un langage ?
Une grammaire formelle est un ensemble de règles de production qui définissent quelles chaînes de tokens sont valides dans un langage. Les grammaires hors contexte (CFG), décrites en forme de Backus-Naur (BNF) ou BNF étendue, sont utilisées pour la plupart des langages de programmation. Un analyseur syntaxique vérifie que l'entrée est conforme à la grammaire et construit l'AST. La célèbre hiérarchie de Chomsky classe les grammaires par pouvoir expressif ; les grammaires hors contexte sont suffisamment puissantes pour décrire des structures imbriquées comme des parenthèses équilibrées, ce que les expressions régulières ne peuvent pas faire.
Que se passe-t-il lors de la phase d'analyse sémantique ?
L'analyse sémantique vérifie des contraintes que la grammaire ne peut pas exprimer : vérification de types (ajoute-t-on un entier à une chaîne ?), résolution de portée (cette variable est-elle déclarée avant utilisation ?), et détection d'utilisation avant initialisation. Dans les langages à typage statique, cette phase construit une table des symboles associant les identifiants à leurs types et intègre des annotations de type dans l'AST. Les erreurs à ce stade produisent les fameux messages « incompatibilité de type » ou « variable non déclarée » qui apparaissent après une analyse syntaxique réussie.
Quelles sont les optimisations de compilateur courantes au-delà de la propagation de constantes ?
Les compilateurs modernes appliquent des dizaines de passes d'optimisation : l'élimination de code mort supprime les instructions dont les résultats ne sont jamais utilisés ; le déroulement de boucle réduit le coût des branchements ; l'inlining substitue les sites d'appel de fonction par le corps de la fonction pour éliminer le coût d'appel ; l'élimination de sous-expressions communes (CSE) évite de recalculer des expressions identiques ; et l'auto-vectorisation réécrit les boucles scalaires pour utiliser les instructions CPU SIMD (Single Instruction, Multiple Data). Le pipeline d'optimisation de LLVM applique environ 60 passes au niveau -O2, et les gains de performance obtenus vont couramment de 2x à 10x par rapport au code non optimisé.
Comment fonctionne un compilateur à la volée (JIT) ?
Un compilateur JIT compile le code au moment de l'exécution, généralement après avoir observé qu'il s'exécute fréquemment (« chemins chauds »). Le moteur V8 (utilisé dans Chrome et Node.js) commence par interpréter le bytecode JavaScript, profile les fonctions les plus appelées, puis les compile en code machine natif optimisé. Si une hypothèse faite précédemment (par exemple qu'une variable contient toujours un entier) est ensuite violée, V8 « désoptimise » et revient à l'exécution interprétée. Le JavaScript compilé en JIT peut s'exécuter à 2-3 fois la vitesse du code C équivalent pour de nombreuses charges de travail.
Observez le code source devenir du code machine : analyse lexicale vers des tokens, analyse syntaxique descendante récursive vers un AST, génération de code à trois adresses et une passe d'optimisation par propagation de constantes — saisissez n'importe quelle expression arithmétique et parcourez les étapes.
3D · Moteur de rendu Three.js / WebGL · Cible 60 FPS · s'exécute entièrement côté client, sans installation