Vous êtes sur la page 1sur 5

Programmation

Corriges du TD 5
E. Lozes
Corrig
e de lexercice 1

Corrig
e de lexercice 2

1.

La memoire est utilisee comme suit :


la pile contient les adresses de retour,
les registres sauves, les param`etres passes,...
le tas contient les variables allouees dynamiquement.

code
donnees statiques
pile de controle

tas

2. ...
3. En gros (mais ce nest quun exemple) : analyse lexicale (liste de token), analyse syntaxique (arbre de
syntaxe abstraite), analyse semantique (arbre semantique), linearisation (code intermediaire), selection
dinstructions (expressions courtes),traduction des appels de fonctions, allocation de registres , puis
resolution des addresses de saut (code machine). Bien s
ur, on a des optimisations possibles `a differents
niveaux : exemples, de la source vers le code final : recursivite terminale, sous-expressions communes,
propagation de constantes, extraction dinvariants de boucles, utilisation fine des registres, rapprochement de blocs...
4. beaucoup de choses `
a verifier suivant les possibilites du langage, mais en gros on sauve les registres
utiles pour quand on revient (certains peuvent etre sauves automatiquement par le microprocesseur),
on met les param`etres l`
a o`
u il faut, et on fait un call, i.e. un saut apr`es avoir empile ladresse de retour.
Corrig
e de lexercice 3
1. Une grammaire hors contexte est donnee par un ensemble fini T de terminaux, un ensemble fini N de
non-terminaux, un non-terminal initial S, et un ensemble fini de r`egles de production R N (N T ) .
Un arbre de derivation est un arbre dont les nuds intermediaires sont etiquettes par des r`egles r R,
avec les contraintes suivantes :
la racine est etiquettee par une r`egle S
si un noeud est etiquette X 1 ..k , il a n fils n1 ..nk . De plus, ni est feuille si i est un terminal,
et ni est etiquette par Y 0 si i est un non-terminal Y .
Le langage reconnu par la grammaire est celui des mots de feuilles de lensemble des arbres de derivation,
elle est non ambigue si tout mot admet au plus un arbre de derivation dont il est le mot de feuille.
En compilation, on ne sinteresse pas tant au langage reconnu par la grammaire qu`a la reconstruction dun arbre de derivation pour un mot donne, afin de construire larbre de syntaxe abstraite du
programme. Il est donc essentiel de manipuler des grammaires non ambigues.
2. Lexpression ambig
ue est
if x = 0 then if y = 0 then do a else do b.
1

Pour la rendre non ambig


ue, on ecrit une grammaire qui permet de deriver un if /then/else uniquement
en rattachant le else au if le plus proche. On remplace donc la r`egle stmt if test then stmt else stmt
par stmt if test then matched stmt else stmt, avec pour matched stmt uniquement des expressions dans lesquelles tous les then ont ete rattaches `a un else, donc finissant par un else ou une autre
expression. Cela donne :
stmt matched stmt | unmatched stmt
matched stmt if testthen matched stmt else matched stmt | do a | do b
unmatched stmt if test then stmt | if testthen matched stmt else unmatched stmt
3. Une table de prediction M donne pour un non-terminal X et un terminal a la r`egle X `a appliquer
pour deriver X bla bla a. A noter, soit ca marche sans le bla qui suit X, soit X  et
cest en realite le bla qui permettra de construire un a. Lautomate `a pile est le suivant : initialement,
le curseur du mot `
a analyser est sur la premi`ere lettre et la pile de but contient S :: $ :: []. A chaque
etape, on depile la lettre l de haut de pile du but. Si l est un terminal a, alors soit le curseur est sur
a et on lavance, soit cest une lettre differente et on rejette le mot. Si l est un non terminal X , on
empile P red[X, b] o`
u b est la lettre sur le curseur. Et cela jusqu`a depiler $.
4. Dans notre cas, cela donne la table de prediction suivante ($ est le symbole fin de mot) :
E
E0
T
T0
F

id
E T E0

(
E T E0

E 0 +T E 0
T FT

E0  E0 

T FT
T0 

T 0 F T 0

F id

T0 

T0 

F (E)

5. La grammaire propre equivalente est obtenue en substituant  `a S `a lavance :


S lSrS | lSr | lrS | lr
On ne peut plus predire quelle r`egle appliquer, meme en ayant une fenetre de lecture de plusieurs
lettres en avance (grammaires LL(k)), parce que lk .w peut aussi bien etre derive de S lSrS que de
S lSr.
6. Cette fois-ci, on part du mot de feuille et on remonte larbre :
lllrlrrlrr .. lllrlrrlrr lllrSrlrr llSrlrr .. llSrlrr llSrSr lSr S
Dans ce cas, la r`egle est de reduire un r si il est suivi dun r ou dun $, et de reduire un S si il est precede
dun lr, dun lSr ou dun lSrS. On a donc une table de prediction qui tient compte dun caract`ere en
amont et dun nombre fini de motifs en aval. Il existe plusieurs algorithmes pour construire de telles
tables, les plus connus etant SLR (encore pas trop complique) et LALR, qui est utilise dans yacc.
Linteret de la reconnaissance LR est quelle est plus puissante que la reconnaissance LL, poss`ede une
algorithmique connue, et couvre toutes les syntaxes courantes de langage de programmation.
Les conflits shift/reduce et reduce/reduce apparaissent dans les grammaires ambigue, lalgorithme de
construction de table ne parvenant pas `a decider quelle action mettre dans une entree. Par exemple,
pour la grammaire ambigue du if , on aurait un conflit shift/reduce. Un autre type de conflit shift/reduce
est celui de la grammaire des expressions arithmetiques sans indications de priorite sur les operateurs :
E E + E | E E | E E | id, qui se resout en yacc en precisant les priorites et lassociativite des
operateurs. Un conflit reduce/reduce est `a proscrire absolument, cest le signe que la grammaire est
mal concue. Pour donner un exemple, on pourrait considerer la grammaire suivante qui cherche `a faire
le typage en meme temps que lanalyse syntaxique :
IN T EXP id | IN T EXP + IN T EXP | ...
BOOLEXP id | BOOLEXP and BOOLEXP | ...
2

Corrig
e de lexercice 4
1. On remarque que
X 1 | .. | n | 1 | .. |m
0

peut se reecrire X X | .. |1 .. |m et X 0 1 | .. | n . On peut factoriser `a gauche en


appliquant cette transformation tant que necessaire. Le processus sarrete necessairement car on obtient
des productions de plus en plus courtes. Une grammaire LL(1) utile est factorisee `a gauche : si X 1
et X 2 , alors pour a tel que a0 on a au moins deux r`egles pour M [X, a]. La reciproque est
fausse : en appliquant la factorisation `a gauche `a la grammaire ambigue du if , on obtient la grammaire
stmt if bool then stmt stmt0 .
stmt0 else stmt | 
qui reste ambigue est nest donc pas LL(1).
2. On consid`ere une grammaire propre recursive `a gauche, et X tel que X + X. On suppose de
plus que X est utile, donc il existe , a tels que X + a. Soit (eventuellement = X) tel que
X est le prefixe commun aux deux reductions precedentes. Alors commence par un nonterminal Y , + a et + X commencent1 par deux r`egles Y 1 et Y 1 avec 1 6= 1 , et
M[Y, a] {Y 1 , X 1 }, ce qui montre que la grammaire nest pas LL(1).
On commence par remarquer que lon peut eliminer la recursivite `a gauche immediate en transformant
X X1 | .. | Xn | 1 | .. | m en
X 1 X 0 | .. | n X 0
X 0 1 X 0 | .. | n X 0 | 
mais en general la recursivite peut etre non immediate. On se ram`ene `a une grammaire non recursive
par lalgorithme suivant :
entree : une grammaire propre, sans cycle X + X, de non-terminaux X1 , .., Xn .
pour tout i allant de 1 `
a n faire :
- remplacer toute production Xi Xj avec i > j par les productions
Xi 1 ,..,Xi n , o`
u Xj 1 |..|n .
- eliminer la recursivite immediate de Xi
sortie : une grammaire equivalente, telle que toute production Xi Xj ,
verifie i < j (donc non recursive `a gauche).
3. Commencons par la construction de la table LL(1) :
entree : une grammaire G quelconque.
M[X, a] pour tout X, a.
pour tout production X :
- ajouter X `
a M[X, a] pour tout a FIRST().
- si  FIRST(), ajouter X `a M[X, b] pour tout b FOLLOW()
(en particulier b = $)
sortie : M[X, a] (permet tester si G est LL(1))
Voyons maintenant pour FIRST. On peut faire les remarques suivantes :
FIRST(a) = {a}
FIRST() = {}
FIRST(u1 ..un ) {} = FIRST(u1 ) .. FIRST(uk ) {}, o`
u uk est le premier ui tel que  6
FIRST(ui ), sinon
u
.
De
plus,


FIRST(u
..u
)
ssi
u
=
u
et
 FIRST(un ).
1
n
k
n
S n
FIRST(X) =
X FIRST()
1`
a commutation de contraction concurrentes pr`
es, pour rester simple on dira quon ne consid`
ere que des suites 1 .. n
o`
u les r
edex se d
eplacent de gauche `
a droite.

et en realite FIRST est la plus petite fonction satisfaisant ces equations. On calcule donc FIRST par
lalgorithme de point fixe classique :
entree : une grammaire G quelconque.
FIRST(a) {a} pour tout a.
FIRST(X) {} pour tout X .
repeter
- FIRST0 FIRST
S
- FIRST(X) FIRST0 (X) Xu1 ..un FIRST0 (u1 ) .. FIRST0 (uk ) {}
(on ajoute  quand il faut)
tant que FIRST 6= FIRST0
sortie : FIRST(a), FIRST(X) (et par consequent FIRST())
qui termine, puisque FIRST grossit et quil existe un nombre fini FIRST possibles. On peut de meme
definir FOLLOW comme la plus petite solution des equations suivantes :
$ FOLLOW(S)
si X Y , FOLLOW(Y ) FIRST() {}.
si X Y , FOLLOW(X) FOLLOW(Y ).
si X Y et  FIRST(), FOLLOW(X) FOLLOW(Y ).
4. Une table de prediction LL(k) est une table M [X, a1 ..ak ] telle que
M[X, a1 ..ak ] = { X : a1 ..ak }
On peut etendre facilement les algorithmes precedents `a ces tables plus generales. Pour le cas de la
grammaire consideree, on nest pas LL(1) parce que M[stmt, id] contient au moins les trois r`egles ecrites
pour stmt, mais en revanche M[stmt, id id] = {stmt type id}, etc. et la grammaire est LL(2).

Corrig
e de lexercice 5
1. On a le graphe de flot suivant :

2. si il y a une seule definition x = c possible pour un point de programme y = ...x..., on peut propager
la constante c et reecrire y = ...c...
3. in(B) est lensemble des definitions qui arrivent `a lentree de B, out(B) lensemble de celles qui en
sortent, gen(B) celles qui y sont generees, kill(B) celles qui sont masquees par celles de gen(B).
4. On surapproxime pour tous, sauf pour cdkill qui sous approxime. Ainsi pour R = R1 + R2 (execution
concurrente de R1 ou R2 ), on a in(R) = in(R1 ) = in(R1 ), out(R) = out(R1 ) out(R2 ), gen(R) =
gen(R1 ) gen(R2 ), et kill(R) = kill(R1 ) kill(R2 ).
5. Le syst`eme dequation peut secrire (out, in)S= F(out, in), o`
u F est la fonction croissante (out)(B) =
gen(B) (in(B) kill(B)), et (in)(B) = B 0 B out(B 0 ), et la solution recherchee est le plus petit
point fixe de F, donc la valeur stationnaire de la suite x0 = et xn+1 = F(xn ) (algo classique
de construction de plus petit point fixe). Par exemple, si lon developpe les equations de flot pour
lexemple precedent sur out(B2 ), on trouve out(B2 ) = {d3 , d4 , d5 , d6 } (out(B2 ) {d1 , d2 , d7 }, soit
out(B2 ) = {d3 , d4 , d5 , d6 }.
6. in(B) = use(B)(out(B)def (B)) o`
u use(B) est lensemble des variables lues avant detre ecrites, et
def (B) lensemble des variables ecrites avant detre lues. Quand on connait en chaque point lensemble
des variables vivantes, on peut calculer le graphe dinterference : deux variables interf`erent en un
meme point de programme si elles sont toutes deux vivantes. Colorier ce graphe = allouer les registres,
probl`eme NP-complet, il existe des heuristiques, par ex : en cas dimpossibilite on retire la variable
utilisee la moins recemment, on dit quelle part en memoire et on reessaie.