Représenter le code intermédiaire
Le code intermédiaire 9/51
Chaque instruction du code intermédiaire est une
structure de données qui encode:
• le type de l’instruction (Load, :=, …)
• le type de chaque opérande (registre virtuel, variable
globale, variable locale, ou constante)
• l’opérateur (le cas échéant)
• des informations sémantiques supplémentaires – tout
ce qui est utile pour l’amélioration et génération du
code
Créer du code intermédiaire
Le code intermédiaire 10/51
Formellement: spécification par une grammaire
attribuée (attribute grammar)
Pour nous: Très similaire à l’interprétation du code
(voir les méthodes sémantiques):
• On parcourt l’arbre de syntaxe abstraite;
• à chaque nœud, on crée des instructions.
En vue d’une amélioration du code par la suite, on ne
réutilise jamais des registres; un nouveau registre
virtuel est prévu chaque fois.
Les blocs élémentaires
Le code intermédiaire 11/51
Pour gérer des sauts, il faut insérer des étiquettes.
Le morceau de code entre une étiquette et le prochain
saut constitue un bloc élémentaire (basic block).
Nous allons couper le code en blocs élémentaires (pas
essentiel ici, mais pour l’amélioration du code plus
tard).
Ainsi, on crée un graphe de flot de contrôle (control
flow graph).
Assignment
Le code intermédiaire 12/51
BasicBlock* generate(const Assignment* s, BasicBlock* bb) {
bb–>vec.push_back(new MStore(s–>target,
generate(s–>source, bb)));
return bb;
}
• Il faut passer le bloc pour savoir où ajouter le code.
• La source est un registre virtuel.
• Il faut renvoyer le bloc parce qu’une instruction peut
rompre le bloc élémentaire actuel.
Note
Cette implémentation fonctionne parce qu’ici, une
expression ne rompt jamais le bloc élémentaire
actuel.