Énoncé et corrigé en Postscript | Réponses intégrées ou à la demande |
Examen de majeure 2 Compilation (Durée 2h) 23 mars 2001 |
On attachera une importance particulière à la clarté, à la précision, mais aussi à la concision de la rédaction. Les trois exercices sont indépendants. La plupart des questions sont également indépendentes dans les exercices 1 et 2.
|
break
qui peut être
utilisée à l'intérieur d'une boucle while
comme dans le langage C
:
l'exécution de l'instruction break
à l'intérieur d'une boucle while
interrompt l'exécution de cette boucle et continue avec
l'instruction qui suit immédiatement la boucle (c'est une erreur
si l'opération break
apparaît en dehors d'une boucle
while
). break
.break
.
x
dans un tableau
d'entiers positifs t
de taille n
: La valeur trouvée est retournée,
sauf en cas d'échec, où la valeur -1
est retournée.
function recherche (var x : integer, var n : integer, var t : array of integer) : integer; var i : integer; begin i := 0; while i < n do if t[i] = x then break else i := i+1; if i < n then recherche := t[i] else recherche := -1 end; |
break
.
function recherche (var x : integer, var n : integer, var t : array of integer) : integer; var i : integer; var b : boolean; begin i := 0; b := i < n; while b do if t[i] = x then b := false else begin i := i+1; b := i < n end; if i < n then recherche := t[i] else recherche := -1 end; |
Pp.instruction
dans le cours) un noeud Break
.
On donne le squelette d'un interprète du langage Pseudo-Pascal définit par
deux fonctions récursives:
val instruction : environnement -> Pp.instruction -> unit val expression : environnement -> Pp.expression -> valeur |
let rec instruction env = function | While (e, i1) -> while bool (expression env e) do instruction env i done | ... and expression env = function ... |
bool : valeur -> bool
convertit son argument en un booléen si
c'est possible et lance un erreur d'exécution sinon.While
et l'ajout du cas
Break
dans la fonction instruction
afin de traiter la construction
break
.
exception Break_while let rec instruction env = function | While (e, i) -> begin try while bool (expression env e) do instruction env i done with Break_while -> () end | Break -> raise Break_while ... and ... |
While
et
Break
(et d'autres modifications éventuelles à apporter à la fonction de
compilation). On utilisera de préférence une description mathématique
à l'aide des fonctions [[]]er et [[]]sr
utilisés dans le cours et que l'on pourra raffiner.
While
on ajoute une étiquette optionnelle en
paramètre à la fonction d'évaluation. Cette étiquette est propagée au
travers des séquences. La traduction de l'instruction Break
utilise cette étiquette. Les autres instructions ignorent cet argument et se
traduisent comme auparavant.
[[While (e, s)]]sr= | Stm [ [[While (e, s)]]er, l; Label l ] | où l est une étiquette fraîche |
[[Break ]]sr, l = | Jump l | |
[[Seqence [s1; ... sn]]]sr,l = | [[s1]]sr, l; ...; [[s]]sr, l | |
[[s]]sr, l = | [[s]]sr |
|
t1 + Call (f, [])
peut être canonisée en le code réduit à l'instruction
Move (t2, Call (f, []))
et l'expression résiduelle
t1 + t2
parce que t1
commute avec
Move (t2, Call (f, []))
.
Mem t1
ne commute pas avec
Move (t2, Call (f, []))
:
l'appel à f peut, a priori, écrire en mémoire et changer la valeur à
l'adresse
t1
.
Mem t1
en premier et placer le résultat
dans un temporaire auxiliaire. Par exemple, une forme canonique de
l'expression: t1 + Call (f, [])
est composée du code extrait
[ Move (t3, Mem t1); Move (t2, Call (f, [])) ]
et de l'expression résiduelle t3 + t2
.
t3
survit à l'appel
de la fonction f qui, par défaut, ne préserve que les registres sauvés par
l'appelant. Ainsi, t3 sera au mieux placé dans un tel registre qui devra à
son tour être sauvé en pile.
type exp = and stm = Const of int | Label of label | Name of label | Move_temp of temp * exp | Temp of temp | Move_mem of exp * exp | Mem of exp | Exp of exp | Bin of binop * exp * exp | Seq of stm list | Call of frame * exp list | Jump of exp | Cjump of relop * exp * exp * label * label |
lus_par_exp
de type
exp -> temp list
qui prend une expression canonique
(sans Call) du code intermédiaire et retourne la liste des temporaires
lus par celle-ci (on admet les répétitions dans le résultat).
let rec lus_par_exp = function | Const _ | Name _ -> [] | Bin (_, e1, e2) -> lus_par_exp e1 @ lus_par_exp e2 | Mem e -> lus_par_exp e | Temp t -> [ t ] | Call (_, _) -> assert false (* expression canonique *);; |
écrits_par_instr
de type
stm -> temp list
qui prend
une instruction canonique (sans saut, sans séquence et sans
Call
emboîtés) du code intermédiaire et qui retourne la liste des
temporaires écrits par celle-ci.
let rec écrits_par_stm = function | Move_temp (t, _) -> [ t ] | Move_mem (_, _) | Exp _ | Label _ -> [] | Seq _ | Cjump (_,_,_,_,_) | Jump _ -> assert false (* instruction canonique *);; |
lus_par_exp
et écrits_par_stm
?
m
et de considérer la lecture en mémoire comme la lecture de m
et l'écriture en mémoire comme l'écriture de m.
On écrira alors les lignes
| Mem e -> m :: lus_par_exp e |
| Move_mem (t, e) -> [ m ] |
lus_par_exp
et
écrits_par_stm
.
|
A0
, ..., A7
) des registres de donnée (D0
,
..., D7
). Pour simplifier, nous considérons un processeur
expérimental CIRC dont le jeu d'instructions est identique à celui du
code SPIM mais dont les registres sont séparés en registres d'adresses
(A0
, ..., A7
) et registres de données
(D0
, ..., D7
) et dont certaines occurrences
des registres dans les instructions sont contraintes.
En particulier, on considère les contraintes suivantes,
add
, mul
, etc.) n'utilisent
que des registres de donnée.
lw
D, n(A) et sw
D, n(A) le registre D est un
registre de donnée, le registre A est un registre d'adresse (et n un
entier).
Ass.code
dans le
cours, abstrait par rapport au choix final des registres et
code machine final le code émit à la fin de la compilation et pouvant
être lu par l'assembleur. Dans l'une ou l'autre expression, on remplace
machine par SPIM ou CIRC pour préciser la version du cours
pour l'architecture SPIM ou la version de cet exercice pour une architecture
CIRC. Oper
,
de type (temp * temp list) list
représentant des ensembles de
contraintes entre un registre particulier et un ensemble de registres.
i.e. avoir
type Oper = ... | Oper of string * temp list * temp list * label list option * (temp * temp list) list |
Oper
(i, s, d, l, c).
Bin (Plus, Temp t1, Temp t2)
.
Décrire le code CIRC abstrait produit pour ce code On ne se préoccupera pas
ici du contexte dans lequel l'instruction est placée. (On pourra supposer
que les variables rA
et rD
sont liées à l'ensemble des registres
d'adresses et de données respectivement.)
Oper ("add_^d0,_^s0,_^s1", [ t1; t2 ], [ t0 ], None, [ t0, rA; t1, rA; t2, rA ]) |
debut
est
"debut"
):
[ Label debut; Move_mem (Temp t1, Temp t2); Move_temp (t1, Bin (Plus (Temp t1, Temp t3))); Jump (debut); ] |
[ Label ("debut", debut); Oper ("sw___^d0,_^s0", [ t1 ], [ t2 ], None); Oper ("add__^d0,_^s0,_^s1", [ t1; t3 ], [ t1 ], None); Oper ("j_debut", [], [], Some [ debut ]); ] |
debut: sw t2, 0(t1) add t1, t1, t3 j debut |
sw
et add
.
debut: sw D2, 0(A1) move D1, A1 add D1, D1, D3 move A1, D1 j debut |
move
intermédiaire:
[ Label (debut); Oper ("sw___^d0,_^s0", [ t1 ], [ t2 ], None, [ t1, rD; t2, rA ]); Move ("move_^d0,_^s0", [ t1 ], [ t1' ]); Oper ("add__^d0,_^s0,_^s1", [ t1'; t3 ], [ t1' ], None, [ t1', rA; t3, rA ]); Move ("move_^d0,_^s0", [ t1' ], [ t1 ]); Oper ("j____debut", Some debut); ] |
Oper
:
Move ("move_^d0,_^s0", d, s)
. move
intermédiaires. Mais
l'allocateur de registres devrait pourvoir éliminer au moins la moitié de
ces move
. En effet, considérons un temporaire contraint t, et ses
registres éphémères associés t1, ...tk. Les contraintes sur les
ti n'étant que de deux types (registre d'adresse ou registre de donnée),
un bon allocateur devrait identifier t avec un registre respectant la
contrainte la plus fréquente de façon à éliminer le plus d'instructions
move
! move
restantes ne seront pas éliminés.
move
par l'allocateur de registres
comme décrit à la question c), il peut rester une configuration de la forme:
move t, t1 |
... |
move t2, t |
...
''. Or, il n'est pas
nécessaire ici de sauvegarder t1 dans t entre t1 et t2:
un transfert direct de t1 dans t2 suffirait et permettrait d'identifier
t1 avec t2. On peut donc faire mieux!
(Voir, par exemple, l'examen de l'an 2000
et sa suite.)
This document was translated from LATEX by HEVEA.