Frdrique Carrere
8 septembre 2014
Chapitre 1
Introduction
1.1
1.1.1
1.1.2
Rle du compilateur
Son rle est de permettre de programmer avec un haut niveau dabstraction (ex : langages ddis) plutt quen assembleur. Il permet donc dcrire
des programmes lisibles, modulaires, maintenables, rutilisables.
Il permet de sabstraire de larchitecture de la machine et donc dcrire des
programmes portables.
Il permet de dtecter des erreurs et donc dcrire des programmes plus fiables.
On peut crire des compilateurs certifis par des logiciels de vrifications
comme coq.
Ses principale tches sont :
lire et analyser le programme
dtecter des erreurs (lexicales, syntaxiques, smantiques)
crire dans un langage intermdiaire la squence dinstructions effectuer par la machine
optimiser cette squence dinstructions (taille du code, vitesse dxecution)
traduire ces instructions dun langage intermdiaire dans le langage
cible
1.1.3
1.2
le coeur qui travaille sur une reprsentation intermdiaire du programme, indpendante la fois du langage source et du langage cible
le back-end : fortement li au langage cible
Lavantage de ce dcoupage est la rutilisabilit du coeur pour diffrents langages sources et pour diffrents langages cibles.
1.2.1
La production dun excutable comprend une succession dtapes importantes, implmente par diffrents modules :
prprocesseur
analyseur lexical
analyseur syntaxique
gestion dune tables de symboles
analyse smantique, typage
gnrateur de code intermdiaire
optimisation de code
gnrateur de code assembleur
diteur de liens
Nous distinguons le compilateur des outils qui sont utiliss en amont : Editeur, Prprocesseur, et des outils qui sont utiliss en aval : Assembleur, lieur,
chargeur.
Sources -> Prprocesseur -> Programme source -> Compilateur -> programme cible -> Assembleur -> Lieur-chargeur
1.2.2
Les performances
1.2.3
Les Entres/sorties
En entre :
des donnes : variables, structures, tableaux, pointeurs, classes
des instructions de haut niveau : boucles, conditionnelles, fonctions
En sortie :
registres, adresses mmoires, tiquettes
instructions de bas niveau : lecture/criture de registres, oprations
arithmtiques ou logiques de base, sauts, directives (appel de fonctions)
1.3
Exemple : PGCD
ELle identifie les instructions structures, les blocs imbriqus, les dclarations, et transforme le code en arbre.
3. Analyse smantique, gnration de code intermdiaire :
Elle analyse la signification du texte, que reprsentent les identificateurs, quelles oprations sont associes aux instructions.
- Types :
P GCD int int int
a int
b int
- Instructions :
SUCC(WHILE(TEST (!=, a, b), IF (TEST (>, a, b), AFF (a, OP (-, a, b)),
SUCC(BLOC(VAR(tmp, int), AFF (tmp, a), SUCC(AFF (a, b), AFF (b,
tmp)))))),
RETURN (a))
Chapitre 2
Analyse lexicale
Rle : grouper les lettres pour former des mots.
Les mots sont appels lexmes (ou token).
Au passage, cette phase :
peut reconnatre les mots rservs, constantes, identificateurs
signale les mots mal orthographis
peut supprimer les commentaires.
peut prprocesser le texte : expansion de macros.
2.1
2.1.1
Expression rationnelle
2.1.2
"_") *
Automate
2.2
JFLEX : fonctionnement
Fichier flex -> JFlex -> Lexer.java -> Java -> Lexer.class
Lanalyseur lexical reconnait des ensembles de mots. Chaque ensemble de
mots (lexme) est dcrit par une expression rationnelle ou expression rgulire.
Lanalyseur lexical associe chaque expression une action (traduction, spcification du lexme,...). Pour Jflex cette action est du code Java.
Lanalyseur lexical produit par Jflex lit le fichier dentre, identifie les mots
appartenant aux ensembles dcrits par les expressions rgulires et pour
chaque mot reconnu, effectue une sries dactions crites en Java dans le
fichier source de lanalyseur : lexer.jflex.
2.2.1
<partie dfinitions>
%%
<partie rgles>
expression { <code java> }
%%
<partie code utilisateur (java)>
Dans la classe java Lexer gnre par Jflex, la fonction danalyse lexicale
sappelle yylex().
Exemple dappel de la fonction danalyse lexicale :
2.2.2
a : le caractre a
si e,e0 sont deux expressions :
e|e0 : soit e, soit e
ee0 : e suivie de e
si e est une expression :
e : un nombre arbitraire de e.
e+ : au moins une occurrence de e.
e? : zro ou une occurrence de e.
e : jusqu une occurrence de e.
e{n} : n occurrences de e.
e{n, m} : de n m occurrences de e.
2.2.3
%{
StringBuffer str = new StringBuffer();
%}
LineTerminator = \r|\ n|\ r\ n
InputCharacter = [\^\ n\ r]
WhiteSpace = \{LineTerminator\}|[ \ f\ t]
\%\%
/* Keywords */
if { System.out.printf("KEYWORD:\%s\ n", yytext());}
else {System.out.printf("KEYWORD:\%s\ n", yytext());}
/* Operators */
"+" {System.out.printf("OPERATOR:\%s\ n", yytext());}
/* Literals */
/* Comments and whitespace */
{WhiteSpace} {/* Nothing */}
2.2.4
Etats :
10
%%
...
Lorsque lanalyseur est dans ltat etat1, les seules rgles actives sont :
. celles prfixes par <etat1>,
. celles non prfixes.
Lorsque lanalyseur est dans ltat etat2, les seules rgles actives sont celles
prfixes par <etat2>.
Exemple :
%x SPECIAL
specialmotif expression1
endmotif
expression2
%%
{specialmotif}
{yybegin(SPECIAL);}
<SPECIAL>blabla do_something();
{endmotif}
{yybegin(YYINITIAL);}
11
Chapitre 3
Analyse syntaxique
3.1
Introduction
fonc_list
fonc
3.2
3.2.1
3.2.2
Analyseurs ascendants
C++). Mais les logiciels (cup ou yacc) peuvent traiter des grammaires ambiges si on leur ajoute des rgles de priorits.
Lalgorithme de reconnaissance dun programme est alors en temps n3 .
3.2.3
Analyseurs tabulaires
3.3
Rle de lanalyseur
ou dltions ne sont pas lendroit mme de lerreur). Exemple : algorithme de Burke-Fisher -> pas dinsertions ou dltions au del de k
tokens avant lerreur, avec k fix.
3.4
Grammaires algbriques
Moyen de dcrire quels sont les flux de lexmes corrects et comment ils
doivent tre structurs. lanalyseur lexical dcrit comment former les mots du
langage. Lanalyseur syntaxique dcrit ensuite comment assembler les mots
pour former des phrases correctes.
La grammaire fournit une description des phrases (= programmes) syntaxiquement corrects.
3.4.1
Definition
G = {T, V, S, R}
T : alphabet des terminaux
V : alphabet des variables
S V : symbole de dpart ou axiome de la grammaire
R : ensemble fini de rgles. Chaque rgle r R est de la forme : X u ,
o X V et u (T V ) .
Une grammaire sert dcrire un langage. Pour engendrer un langage, on
part dun symbole particulier de la grammaire appel axiome, gnralement
not S.
On appelle drivation de u partir de A et on note A u, si u est obtenu
partir de A par lapplication squentielle dun nombre fini de rgles de G.
Langage engendr par la grammaire G : L(G) = {u T |S u}
Lanalyseur, connaissant la grammaire G :
reoit en entre un mot u sur lalphabet terminal, qui correspond au
programme source.
reconstitue une drivation de u, si le mot u est engendr par G.
indique une erreur syntaxique sinon.
Exemple : la grammaire "ET F " pour engendrer des expressions arithmtiques comme a + b * (c + d) :
E -> E + T
E -> T
15
T -> T * F
T -> F
F -> ( E )
F -> IDENTIFIER
3.4.2
Larbre de drivation
16
3.4.3
Ambigut
Une grammaire est ambige si il existe un mot u qui possde deux arbres
de drivation diffrents dans G, cest--dire sil existe deux structures grammaticales possibles donnant le mme mot.
Exemple : expressions arithmtiques :
E -> E + E
E -> E * E
E -> IDENTIFIER
Lanalyse pose problme si la grammaire est ambigue. On recherche alors
une autre grammaire engendrant le mme langage et qui soit non-ambige.
Exemple de la grammaire "ifthenelse" :
instr -> si expr alors instr
instr -> si expr alors instr sinon instr
Deux analyses possibles de la phrase :
si E1 alors S1 sinon si E2 alors S2 sinon S3
On prfre le alors le plus proche (p.201)
Grammaire quivalente :
instr -> instr_close
instr -> instr_non_close
instr_close -> si expr alors instr_close sinon instr_close
instr_non_close -> si expr alors instr_non_close
instr_non_close -> si expr alors instr_close sinon instr_non_close
3.5
tribut associ un lexme donn (par exemple la valeur pour une constante
entire).
Pour calculer les attributs des noeuds interne de larbre, lanalyseur syntaxique associe chaque rgle de grammaire une action smantique. Cette
action indique comment calculer linformation attache un symbol de la
grammaire (ie : un noeud de larbre syntaxique) en fonction de linformation
attache aux autres symboles prsents dans la rgle.
On appelle traduction dirige par la syntaxe le fait dattacher de actions
smantiques aux rgles de la grammaire.
Une grammaire attribue est une grammaire dans laquelle :
chaque symbole, terminal ou non, peut avoir des attributs,
chaque attribut possde des rgles de calcul en fonction dautres attributs et de valeurs initiales,
chaque rgle de calcul est attache une rgle de la grammaire.
But : calculer les attributs pendant lanalyse syntaxique.
Principe :
LE
imprimer(E.val)
EE+T
E.val = E1.val + T.val
ET
E.val = T.val
TT*F
E.val = E1.val * T.val
TF
T.val = F.val
F(E)
F.val = E.val
F IDENTIFIER
F.val = IDENTIFIER.vallex
Soit la rgle calculant lattribut a associe la production A u :
a = f (a1 , ..., an )
a est synthtis si cest un attribut de A calcul en fonction des attributs
des symboles de u. Une grammaire est S-attribue si tous ses attributs sont
synthtiss. Linformation remonte dans larbre syntaxique.
Pour une grammaire S-attribue, le calcul des attributs se fait de faon mcanique si on a un analyseur LR (ex :CUP). Lors de la rduction par A u
on calcule le (ou les) attribut(s) de A en fonction des attributs des symboles
de u. On peut empiler les attributs calculs au cours de lanalyse. Les attributs des tokens sont initialiss par lanalyseur lexical.
Soit la rgle calculant lattribut a associe la production A u :
b = f (a1 , ..., an )
Lattribut b est hrit si b est un attribut dun symbole Xj de u, calcul en
fonction des attributs dautres symboles de u ou du symbole A.
18
Une grammaire est L-attribue si lattribut b du jme symbole Xj est calcul en utilisant seulement les attributs hrits de X1 , ..., Xj1 et celui de A.
Linformation descend dans larbre syntaxique.
Lutilisation des attributs permet une traduction dirige par la synatxe.
Cette technique est utilise par le compilateur pour effectuer le contrle
des types, lvaluation des expressions, la construction de reprsentations
intermdiaires.
3.6
Le logiciel CUP
...
{:
/*code Java */
:}
...
Le nom des terminaux de la grammaire doit tre donn dans le fichier CUP,
par exemple ID pour un identificateur. Il doit tre le mme que celui utilis par le lexer lors de lappel du constructeur de Symbol, par exemple
CalculetteSymbol.ID. Le logiciel CUP gnre automatiquement un fichier
CalculetteSymbol.java qui associe un code (i.e. une constante entire)
chaque terminal de la grammaire :
- vrifier quil y a autant de constantes dfinies dans ce fichier que de terminaux dclars dans cup par le mot-cl terminal,
- vrifier que pour chacun de ces terminaux, le lexer effectue un appel au
constructeur de Symbol (dans le fichier jflex).
Lanalyse syntaxique est donc ncessairement prcde dune analyse lexicale. Le constructeur du parseur prend donc en paramtre un analyseur
19
lexical (de la classe Lexer). La mthode appele pour effectuer lanalyse syntaxique sappelle parse().
Exemple de construction dun parseur :
public static void main(String[] args) {
FileReader myFile = new FileReader(args[0]);
Lexer myLex = new Lexer(myFile);
Parser myP = new Parser(myLex);
try {
result=myP.parse();
}
catch (Exception e) {
System.out.println("parse outor...");
}
}
Chaque fois que la mthode parse() a besoin dun nouveau terminal, elle
appelle la mthode de lanalyseur lexical yylex(). Lorsque yylex reconnat
un lexme, elle retourne au parseur une instance de la classe Symbol pour
dcrire le type de lexme rencontr.
Le symbole transmis peut aussi contenir des informations (du type Object)
sur le lexme reconnu, par exemple son nom ou sa valeur. Ces informations
constitue ce que lon appelle lattribut du symbole.
3.6.1
Dans jflex, les lexmes ou tokens sont reprsents par des instances de la
classe Symbol. Les attributs dun token sont stocks dans une variable dinstance de cette classe. Le constructeur de Symbol, qui est appel lors de la
reconnaissance du token dans le flux dentre, peut prendre en paramtre
une valeur (appartenant une sous-classe de la classe Object) :
Symbol symbol (int type, Object value);
Cette valeur permet linitialisation de lattribut du token. Le token (instance
de Symbol) construit est alors transmis (par un "return") la fonction danalyse syntaxique.
Les non terminaux de la grammaire peuvent galement avoir des attributs. Lorsquun non terminal figure gauche dune rgle, son attribut est
20
21
3.6.2
3.7
Principe de lanalyse LR
lors dune analyse LR(k), il compare avec les k symboles suivants sur le flux
dentre.
Pour mmoriser ce qui a t analys et quelles sont les suites possibles de
lanalyse dj effectue, on utilise des rgles pointes (cest--dire des rgles
de grammaire dans lesquelles on rajoute un point en partie droite) appeles
items. Le point indique quelle portion de la rgle a dj t utilise pour
reconnatre lentre lue jusqu prsent.
Si une rgle peut scrire X uv o u et v sont deux mots, alors X u.v
est un item, avec le point entre u et v.
Lanalyseur alterne des tapes dempilement (en anglais shift), qui consistent
transfrer sur la pile le terminal lu sur le flux dentre, et des tapes de
rduction (en anglais reduce), qui consistent remplacer les items en sommets de pile par de nouveaux items rsultant de lapplication dune rgle de
grammaire (sans consommer le flux en entre). Les tapes de rduction permettent de "remonter" dans larbre de drivation. A chaque rduction, on
empile un nouvel ensemble ditems sur la pile. Ces nouveaux items traduisent
toutes les analyses futures possibles.
Exemple :
1. E -> E + T
E -> T
T -> T * F
T -> F
F -> ( E )
F -> IDENTIFIER
2. F -> IDENTIFIER
T -> F
E -> T
3. F -> ( E )
E -> E + T
E -> T
T -> T * F
T -> F
F -> ( E )
F -> IDENTIFIER
23
4. F -> ( E )
E -> E + T
...
3.7.1
3.8
Algorithme dEarley
3.9
Lapproche choisie dans les analyseurs que nous construirons est la possibilit dutiliser une production derreurs.
Lorsque lanalyseur rencontre une erreur :
24
25
26
Chapitre 4
4.1
27
4.1.1
1. arbre de drivation :
- li la grammaire
- utilis dans des compilateurs simple passe ou des interprteurs
- permet danalyser les codes syntaxiquement corrects
2. IR de haut niveau : arbre de syntaxe abstraite ou AST
- indpendant de la grammaire coeur du compilateur rutilisable
- utilis dans compilateurs multi-passes plusieurs parcours de lAST
- permet de grer les environnements et de contrler les types
- ne permet pas toutes les optimisations de code
3. IR de bas niveau : code trois adresses
- ne garde plus trace de la structure du programme source
- code linaire plus doptimisations (rorganisation du code, suppression dinstructions redondantes)
- permet le calcul du nombre de registres utiles
4.2
4.2.1
Reprsentation arborescente
Dfinition
Larbre de syntaxe abstraite est un arbre qui ne garde plus trace des
dtails de lanalyse syntaxique, cest--dire qui est indpendant de la grammaire, mais qui mmorise la structure du programme et les actions qui le
composent.
Ces arbres permettent de saffranchir des spcificits du langage source,
pour ne garder quune reprsentation gnrique du code source. Par exemple,
dans larbre de syntaxe abstraite, les symboles de ponctuation, la notation
spcifique des oprateurs algbriques auront disparu. Tout ce qui est dpendant du langage source doit tre limin de larbre de syntaxe abstraite. Un
mme arbre de syntaxe abstraite doit tre associ un mme programme,
quil soit crit en C, en python ou en Java.
28
if
variable: x (int)
==
thenelse
integer: 0
variable: y (int)
integer: 5
variable: y (int)
s0
4.2.2
4.3
Implmentation
Dans le cas dun compilateur crit en java, deux approches sont possibles,
lapproche fonctionnelle ou lapproche objet.
29
integer: 7
4.3.1
Approche fonctionnelle
Dans un compilateur crit en Java, les noeuds de larbre de syntaxe abstraite seront implments laide de classes abstraites et de sous-classes :
- abstract class Statement, avec une sous-classe pour chaque alternative :
Block
If
While
...
- abstract class Expr, avec une sous-classe pour chaque alternative :
Plus
Times
Ident
...
Chaque traitement correspond une unique fonction qui reoit en paramtre
la classe de noeud traiter. En java on utilise le design pattern Visiteur
(interface Visitor). Il existera un visiteur pour valuer (classe Interpreter),
un visiteur pour vrifier les types (classe TypeVerificator, un visiteur pour
traduire en code intermdiaire (classe Traductor). Chaque visiteur doit surcharger sa mthode de visite pour quelle puisse prendre en paramtre toutes
les sous-classes de noeuds qui apparaissent dans lAST.
30
4.3.2
Approche Objet
4.3.3
Extensibilit :
31
4.4
32
Chapitre 5
5.1
Tables de Symboles
La table de symboles est remplie pendant la compilation des parties contenant des dclarations :
dfinition de classes (en Prog. Objet),
dfinition de fonctions,
dclaration de variables.
La table de symboles est consulte pendant la compilation des parties excutables :
accs une instance de classe (en Prog. Objet),
appel de fonction,
accs une variable,
rfrence une variable (pointeurs).
33
a = a-b;
else {
int tmp = a; /* ajout de la variable tmp dans la table de
symboles, avec le type int et la valeur a */
a = b;
b = tmp;
}
/* suppression de la variable tmp de la table de symbole */
}
return a;
}
5.2
Dclarations, Portes
ajout de la variable i */
5.3
Environnements
36
5.4
Considrons une table de symboles pour la fonction principale du programme. Plusieurs implmentations sont possibles. Le choix de la structure
de donnes est important pour la rapidit du compilateur.
On associe la fonction principalke du programme source une pile de
portes. Lorsque lon sort dune porte, on doit dpiler tous les symboles
qui ne peuvent plus tre utiliss. Lopration "dpiler" doit tre facile implmenter. La recherche dun identificateur (opration frquemment utilise
pour la compilation des instructions) doit tre efficace.
5.4.1
On empile une nouvelle table de hachage chaque fois que lon rentre dans
une nouvelle porte.
Avantage : gestion facile des portes.
Inconvnient : la recherche dun identificateur se fait en parcourant toutes
les portes de la pile, elle peut donc tre trs lente, sil nest pas dans la
porte courante. Accs au pire en O(n) pour une table de n identificateurs.
5.4.2
37
table de n identificateurs.
Pour palier cet inconvnient, on peut rajouter une pile de portes, qui
pointe sur toutes les dclarations dune mme porte.
Inconvnient : place plus importante en mmoire.
5.4.3
On empile un nouvel arbre de recherche, chaque fois que lon rentre dans
une nouvelle porte. Chaque arbre empil reprsente une porte ; larbre possde des pointeurs vers les identificateurs des portes englobantes qui nont
pas t redfinis.
Avantage : recherche et ajout en temps moyen O(log(n)).
Implmentation des arbres persistants :
fonction ajouter( x : nom, t : type, u : Arbre ) : Arbre
dbut
si ( u==NULL)
retourner new Arbre( x, t, NULL, NULL ) ;
38
s3
s2
s0
G: int
G: int
G: int
right
Z: int
left
X: int
left
right
B: string
left
A: string
left
right
B: string
right
right
left
C: int
left
B: string
left
right
Y: int
/* dbut de porte s0 */
var
G: int;
B: string;
Z: array [100] of int;
A: array [12] of array [10] of pointer of string;
39
40
Chapitre 6
Introduction
6.2
Si le type dune variable ou dune expression est dtermin lors de la compilation : on parle de typage statique. Cest le coeur du compilateur qui a pour
rle de dterminer les types. Cela peut tre fait au moment de la cration
de larbre de syntaxe abstraite. Lavantage est alors quil ny aura pas de
cration darbre si le typage est incorrect.
Si le type dune variable est dtermin lors de lexcution : on parle de typage
dynamique. Certains contrles, comme le fait que les indices dun tableau
soient corrects, cest--dire ne dpassent pas les bornes, ne peuvent tre faits
que dynamiquement.
En java, par exemple, on a un mlange de contrle statique et de contrle
41
dynamique. On dira quun systme de typage est sain, sil assure que toutes
les valeurs utilises lexcution sont bien du type dtermin statiquement.
Sil y a une relation dordre entre les types, il faut que les valeurs lexcution soit des sous-types des types dtermins la compilation.
Un langage typage statique peut rejeter des programmes qui aurait qui
aurait pu tre excuts sans erreur :
if (test)
return 42 + 75;
else
return 42 + "ab";
Si le test est toujours faux, le programme peut sexcuter.
Mais si le contrle de type est statique, il est rejet.
Un langage typage dynamique implique de faire des tests unitaires, qui
testent les excutions possibles du programme. Mais il est impossible est
trop coteux de faire des tests exhaustifs.
6.3
Sret de typage
Dfinition La sret de typage est la capacit du langage rejeter les programmes absurdes.
Un langage est sr ou fortement typ si tout typage incorrect entrane la
violation dune rgle de typage et donc provoque une erreur ( la compilation
ou lxecution).
langage fortement typ programmes sans erreur de type.
A contrario, un langage est faiblement typ si le comportement du programme nest plus spcifi en cas de typage incorrect.
Remarque : il existe diffrentes dfinitions de fortement typ. Pour certains, un langage est fortement typ si toutes les variables sont types et
aucune conversion implicite de type nest possible.
Attention : il ny a pas de lien entre le fait que le typage soit statique ou
dynamique et le fait que le langage soit fortement ou faiblement typ.
- langage C : typage static / faiblement typ
- langage PhP, JavaScript : typage dynamique / faiblement typ
42
6.4
6.4.1
43
6.4.2
Expressions de types
Un type complexe est dcrit par une expression de type faisant intervenir des
constructeurs de types et des types de base.
Exemple : char char pointeur(int)
On peut reprsenter une expression de type par un graphe. Le graphe est un
arbre si lexpression nutilise pas de types rcursifs.
Types dun langage pseudo C Les types dun langage pseudo C peuvent
tre construits en utilisant les rgles de grammaire suivantes :
Prog ->
Declist
Decl ->
Type ->
Expr ->
Decls Expr
-> Decl Declist | Decl
id : Type | Type id (Type)
char | int | float | Type[] | Type*
littral | integer | real | Expr(Expr) | Expr[Expr] | *Expr
| Expr PLUS Expr |...
44
6.5
Dans quelle mesure peut-on dire que deux types sont quivalents ?
Deux types sont quivalents sils sont construits laide du mme constructeur de type (tableau, pointeur,...) avec des paramtres qui sont eux-mmes
des types quivalents.
Solution simple :
fonction Equiv ( s , t ) : boolen
dbut
si s et t sont le mme type de base alors
retourner vrai
sinon si s = tableau( s1, s2) et t = tableau( t1, t2)
alors
retourner Equiv( s1, s2) et Equiv( t1, t2)
sinon si (s = s1 x s2 ) et (t = t1 x t2 )
alors
retourner Equiv( s1, s2) et Equiv( t1, t2)
. . .
sinon
retourner faux
fin
Attention : des cycles peuvent apparatre dans la construction des types,
ce qui pose problme pour crire une fonction dquivalence rcursive.
Exemple :
type lien = pointer of cellule ;
45
6.6
Problme : une mme constante peut reprsenter des types diffrents en machine. Un mme symbole doprateur ou fonction peut reprsenter diffrnets
code excutables en machine.
Donc le type associ une expression peut tre diffrent selon le contexte.
Et un identificateur peut correspondre non pas un seul type, mais un
ensemble de types.
6.6.1
Cohercition
Definition : Coercition : conversion implicite (par le compilateur) dun oprande dans le type attendu par lexpression.
Cela implique une relation dordre entre les types. Le compilateur convertira
vers un type suprieur. Dans le cas dune conversion dans lautre sens (cest-dire vers un type infrieur) il faut que le programmeur utilise un mcanisme
de conversion explicite appel cast.
Exemple :
{ a : int ;
b : real ;
x : int ;
y : real ;
x = a + b;
y = a + b; }
Infrence de types laide des attributs :
E -> E :e1 op E :e2 {: ...
if ( e1.getType() == INT && e2.getType() == INT )
RESULT.putType( INT );
46
6.6.2
Surcharge
Dans ce cas, plusieurs expressions de type vont tre associes au mme oprateur ou la mme fonction. Donc les attributs des noms doprateurs ou
des noms de fonctions seront des ensembles de types. Pour contler le type
dune expression, il sagira alors de faire des parcours de larbre de syntaxe
abstraite de lexpression, jusquau moment o lon pourra selectionn par
limination un type unique dans lensemble.
Exemple :
Loprateur daddition peut reprsenter selon le contexte une addition dentiers, de rels, de complexes, ou une concatnation de chaines.
47
Problme :
function f (i, j : int) : real ;
int int -> real
function f (i, j : int) : int ;
int int -> int
Quel est le type de f (4, 5) ? ? ? En particulier dand le cas suivant :
i : i n t;
x : r e a l;
f oo(4, 5) + x ; real
f oo(4, 5) + i ;
int
6.6.3
Polymorphisme
D -> D ; D
| id : Q
Q -> id. Q
| T
T -> T T
| T | T T | pointeur(T ) | liste(T ) |type | ID | (T )
E -> E(E) | E, E | ID
Exemple 1 :
deref : x . pointeur(x) x
Vrifier le type :
q : pointeur ( pointeur ( entier ) )
deref ( deref ( q ) )
pointeur(y) = pointeur(pointeur(entier))
ppcu = {(y, pointeur(entier))}
pointeur(x) = pointeur(entier)
ppcu = {( x, entier)}
Exemple 2 :
first : x, y . x y x
+ : int int int
Infrer le type de :
f un : p (+(f irstp)1)
p: x X y
(first p) :
1 : int
49
Chapitre 7
Gnration de code
intermdiaire
Le coeur du compilateur, aprs avoir vrifier les portes et les types,
doit traduire lArbre de Syntaxe Abstraite en Code Intermdiaire. Ensuite
le Back-End du compilateur traduit le Code Intermdiaire en Assembleur.
7.1
la facilit de manipulation,
la taille du code produit,
le pouvoir dexpression.
On commencera par une Reprsentation Intermdiaire structure sous
forme darbres ou de dags (sauts), pour aboutir une Reprsentation
Intermdiaire linaire (pseudo-code assembleur pour machine abstraite).
Paralllement ces Reprsentations Intermdiaires du code, on utilise en
gnral un graphe de flot de contrle, refltant les excutions possibles du
programme et permettant diverses optimisations.
7.2
(int value)
(Temp temp)
(Label label)
51
SEQ
CJUMP
EQ
TEMP
CONST
t0
SEQ
L0
L1
LABEL
SEQ
L0
MOVE
SEQ
TEMP
CONST
JUMP
t1
SEQ
NAME
LABEL
SEQ
L2
L1
MOVE
TEMP
CONST
t1
LABEL
L2
52
7.2.1
Arbres canoniques
54
ESEQ(s,MEM(e))
MOVE(TEMP t, ESEQ(s,e))
Si s et e1 ne commutent pas :
le code intermdiaire :
ESEQ(MOVE(TEMP t, e1),
ESEQ(s, BINOP(op, TEMP t, e)))
SEQ(MOVE(TEMP t, e1),
SEQ(s, CJUMP(op, TEMP t, e, L1, L2)))
7.3
55
56
Chapitre 8
8.1
Optimiations locales
8.2
Optimiations globales
8.3
Lordre dans lequel on effectue les optimisations est important car elles interagissent.
Exemple :
x=5;
...
if (x<10) then ... // propagation des cstes -> elimination code mort
Reduction de force
8.4
Il sagit dun raisonnement statique ( la compilation) sur des flots dynamiques de donnes (qui seront obtenus lexcution). Lanalyse statique
permet dobtenir des informations qui sont ensuite utilises pour optimiser
le programme. Le programme est dabord reprsent sous forme dun graphe,
le graphe de flot de contrle, indpendant du langage source ou du langage cible du compilateur.
Les questions auxquelles doit rpondre lanalyse du flot de donnes sont :
58
Quelles affectations de variables "atteignent" un certain point du programme (i.e. sont encore valides en ce point, quelque soit lexcution) ?
Quelles variables sont lues/modifies dans un bloc donn ?
Est-ce que une occurrence de la variable x est la dernire du programme ?
Lanalyse du graphe de flot de donnes consiste :
crire un systme dquations dont les inconnues sont des ensembles
de variables ou des ensembles dexpressions du programme,
rsoudre ces quations en recherchant le plus petit ou plus grand point
fixe (programmation dynamique).
Les solutions de ces quations sont ensuite utilises pour en dduire des optimisations (suppression de code mort, de code redondant,...).
Lanalyse peut se faire de deux faons :
- Analyse avant : on dtermine les proprits dun bloc en fonction de ses
prdecesseurs,
- Analyse arrire : on dtermine les proprits dun bloc en fonction de ses
succsseurs.
8.4.1
Les sommets du graphe de flot de contrle sont des blocs dinstructions et les arcs reprsentent le flot des donnes au cours des diffrentes
excutions possibles.
Dcoupage du code linaris en blocs :
Toute tiquette marque le dbut dun bloc. Tout saut ou branchement marque
la fin dun bloc. Toute instruction suivant un branchement ou un saut marque
le dbut dun autre bloc.
Un arc relie deux sommets du graphe de contrle si le contrle peut passer du premier sommet au deuxime au cours dune excution particulire
du programme.
Exemple :
B1 :
a = 0
B2 :
LABEL L1
59
b = a+1
c=c+b
a=a *2
if a<n goto L1 else goto L2
B3 :
LABEL L2
print c
Les arcs sont :
B1 -> B2
B2 -> B2
B2 -> B3
8.4.2
Une variable est vivante la sortie dun bloc B si elle est utilise par un bloc
que lon peut atteindre depuis B.
On notera succ(B) lensemble des blocs successeurs du bloc B dans le graphe
de flot de contrle. Il sagira dune analyse arrire, car le calcul des ensembles
de variables pour un bloc B se fera en fonction des valeurs obtenues pour les
successeurs de B.
On dfinit dabord quatre ensembles de variables.
Out(B) : variables vivantes en sortie de B
In(B) : variables vivantes en entre de B
Def (B) : variables qui figurent dans un membre gauche dans B
U se(B) : variables qui figurent dans un membre droit dans B avant dtre
dfinies (i.e. de figurer dans un membre gauche)
Les quations reliant ces ensembles sont les suivantes :
In(B) = U se(B) (Out(B) Def (B))
Out(B) = B 0 Succ(B) In(B 0 )
Algo :
1. Dterminer U se et Def pour chaque bloc.
60
8.4.3
Optimisation
61
8.4.4
x=z
L4:
t2=x-1
L5:
t1=a+x
En rsolvant les quations, on trouve quaucune des deux copies natteint
B5 . Elles ne peuvent donc pas tre propages.
8.5
8.5.1
63
10
8.5.2
Ce sont les instructions qui calculent les mmes valeurs depuis lentre du
contrle dans la boucle jusqu la sortie de la boucle.
Une des optimisations importantes des boucles consiste dtecter les instructions invariantes, afin de les sortir de la boucle.
Algorithme de calcul des instructions invariantes
1. Marquer comme invariantes les instructions dont les oprandes sont
des constantes ou ont toutes leurs definitions visibles en dehors de la
boucle.
2. Rpter ltape 3 jusqu aucune nouvelle instruction invariante ne soit
marque.
3. Marquer comme invariantes les instructions dont les oprandes vrifient
une des conditions suivantes :
. tre une constante
. avoir toutes les dfinitions visibles lextrieur de la boucle
. avoir exactement une dfinition visible lintrieur de la boucle et
cette dfinition est marque.
64
8.5.3
L4:
if (j < i) goto L2
L5:
print(t3,t5)
else
goto L5
67
Chapitre 9
Allocation de registres
La valeur des variables doit tre stocke depuis la dfinition de la variable
jusqu ses diffrentes utilisations.
Deux possibilits : stocker la valeur en mmoire (sur la pile) ou stocker la
valeur dans un registre de la machine.
Problme : le nombre de registres est limit.
Il faut savoir sil est possible de stocker toutes les variables dans des registres,
car cest plus avantageux en temps daccs, ou si il est ncessaire de mettre
certaines variables sur la pile. Ces dcisions ont un fort impacte sur le temps
dexcution du programme.
On utilise les informations de lanalyse du flot de donnees :
. si une variable nest pas vivantes la fin dun bloc, alors on peut librer
son registre,
. si deux variables sont vivantes en mme temps, on ne peut pas les stocker
dans le me registres, il faudra deux registres diffrents.
9.1
68
9.2
69
9.3
Variables spilles
70
Chapitre 10
Le code compil
Definition : Lensemble des structures de donnes maintenues lxecution
(pile, tas, zone de mmoire statique) pour implmenter les concepts de haut
niveau est appel Environnement dexcution ("runtime environment").
Il est ncessaire de tenir compte du futur Environnement dexcution, lors
de la traduction en Code Intermdiaire :
comment les variables et les fonctions sont-elles stockes en mmoire ?
comment sont implments les appels de fonctions, les passages de
paramtres ?
comment sont implments les tableaux ?
comment est implmente lallocation et ventuellement la libration
de la mmoire ?
10.1
71
10.2
Enregistrement dactivation
72
10.3
Allocation mmoire
Allocation statique :
on dispose dun pointeur GP (Global Pointer) sur la zone statique.
Une nouvelle adresse de variable globale est obtenue en dcalant le
pointeur de la zone statique.
La taille allouer doit tre connue la compilation (pas dallocation
dynamique dans cette zone).
Pas de fonctions rcursives utilisant cette zone (tous les appels utiliseraient la mme adresse).
Allocation en pile :
Pas de perte despace mmoire (les variables locales sont supprimes)
La taille de la mmoire librer (enregistrement dactivation au retour
dune fonction) est connue du compilateur.
Allocation dans le tas : allocation dynamique.
Le tas est souvent gr comme une suite de blocs mmoire libres ou
occups.
- Allocation explicite prvue par le programme source (ex : malloc).
Dans ce cas la libration de la mmoire doit aussi tre explicite.
- Allocation implicite lorsque le processeur le demande.
73
10.3.1
En pratique
En java, une classe Frame.java implmentera les enregistrements dactivations. Une instance de cette classe sera associ chaque fonction.
Cette classe contiendra plusieurs variables statiques : les pointeurs
SP et F P (pointeurs sur la pile), le pointeur GB (pointeur sur le
tas), un pointeur RV (return value) vers ladresse de retour, un entier
W ordSize donnant la taille dun mot de pile.
Le back-end du compilateur rajoutera deux morceaux de code chaque
appel de fonction : un "prologue " rajout au dbut et un "pilogue"
rajout la fin.
Le prologue se charge daller chercher les paramtres de la fonction, l
o la machine cible les attend.
Lpilogue se charge de placer la valeur de retour, l o la machine
cible lattend.
74