Une fois cette archive décompressée (par exemple avec tar zxvf ertl-ocaml.tar.gz), vous obtenez un répertoire mini-c/ contenant un module OCaml :
Ertltree | Explicit Register Transfer Language (ERTL) |
Ertlinterp | Interprète de code ERTL (pour tester) |
Ces modules sont complets. Cliquer sur le nom du module pour accéder à sa documentation. On prendra le temps de bien comprendre le code fourni.
On pourra récupérer ce nouveau minic.ml qui ajoute au compilateur une option --interp-ertl pour exécuter le code ERTL et qui affiche le code ERTL quand on passe l'option --debug.
On suggère très fortement de procéder construction par construction, en testant systématiquement à chaque fois.
let instr = function | Rtltree.Econst (r, n, l) -> Econst (r, n, l) | ...La seule exception est le cas d'une instruction Embinop (Mdiv, r1, r2, l) pour laquelle il faut placer r2 dans %rax avant de faire la division, puis récupérer le résultat dans %rax pour le mettre dans r2. Ne pas traiter le cas de l'instruction Ecall pour l'instant.
Traduire ensuite une fonction RTL vers une fonction ERTL, en se contentant pour l'instant de traduire chaque instruction avec la fonction ci-dessus. Comme pour la traduction vers RTL, on pourra stocker le graphe de flot de contrôle en cours de construction dans une référence globale. Ajouter dans ce graphe une instruction Ereturn au niveau de l'étiquette de sortie de la fonction.
Tester avec le programme :
int main() { return 42; }On doit obtenir quelque chose comme :
=== ERTL ================================================= main(0) entry : L2 locals: L2: mov $42 #1 --> L1 L1: returnTester avec d'autres programmes plus complexes (mais sans appel de fonction pour l'instant).
Tester avec des programmes comme
int fact(int n) { if (n <= 1) return 1; return n * fact(n-1); } int main() { return fact(42); }Au niveau de l'appel fact(42), par exemple, on doit obtenir quelque chose comme
L3: mov $42 #2 --> L2 L2: goto L16 L16: mov #2 %rdi --> L15 L15: call fact(1) --> L14 L14: mov %rax #1 --> L1(un seul argument, passé dans %rdi).
Tester avec des programmes comme
int fact(int n) { if (n <= 1) return 1; return n * fact(n-1); } int main() { return fact(42); }Pour la fonction fact, par exemple, on doit obtenir quelque chose comme
fact(1) entry : L31 locals: #11, #12 L31: alloc_frame --> L30 L30: mov %rbx #11 --> L29 L29: mov %r12 #12 --> L28 L28: mov %rdi #3 --> L13 ... L4: goto L36 L36: mov #4 %rax --> L35 L35: mov #11 %rbx --> L34 L34: mov #12 %r12 --> L33 L33: delete_frame --> L32 L32: returnLes pseudo-registres #11 et #12 sont ici utilisés pour sauvegarder les registres callee-saved (limités ici à %rbx et %r12 pour simplifier).
On teste comme pour le code RTL :
> ./run -i "mon/chemin/vers/mini-c --interp-ertl" Test de mon/chemin/vers/mini-c --interp-ertl Interprétation Execution normale ----------------- ........................................................... Execution conduisant à un échec ------------------------------- ... Compilation: Compilation : 62/62 : 100% Comportement du code : 62/62 : 100%
val liveness: Ertltree.cfg -> live_info Label.mapqui prend en argument un graphe de flot de contrôle ERTL et renvoie un dictionnaire qui associe à chaque étiquette une information du type suivant :
type live_info = { instr: Ertltree.instr; succ: Label.t list; (* successeurs *) mutable pred: Label.set; (* prédécesseurs *) defs: Register.set; (* définitions *) uses: Register.set; (* utilisations *) mutable ins: Register.set; (* variables vivantes en entrée *) mutable outs: Register.set; (* variables vivantes en sortie *) }On suggère d'utiliser localement une table de hachage associant à chaque étiquette une information de type live_info. On pourra procéder ainsi :
let print_set = Register.print_set let print_live_info fmt li = fprintf fmt "d={%a}@ u={%a}@ i={%a}@ o={%a}" print_set li.defs print_set li.uses print_set li.ins print_set li.outsAvec un programme comme
int main() { return 42; }on doit obtenir quelque chose comme
L5: alloc_frame --> L4 d={} u={} i={%r12, %rbx} o={%r12, %rbx} L4: mov %rbx #2 --> L3 d={#2} u={%rbx} i={%r12, %rbx} o={#2, %r12} L3: mov %r12 #3 --> L2 d={#3} u={%r12} i={#2, %r12} o={#2, #3} L2: mov $42 #1 --> L1 d={#1} u={} i={#2, #3} o={#1, #2, #3} L1: goto L10 d={} u={} i={#1, #2, #3} o={#1, #2, #3} L10: mov #1 %rax --> L9 d={%rax} u={#1} i={#1, #2, #3} o={#2, #3, %rax} L9: mov #2 %rbx --> L8 d={%rbx} u={#2} i={#2, #3, %rax} o={#3, %rax, %rbx} L8: mov #3 %r12 --> L7 d={%r12} u={#3} i={#3, %rax, %rbx} o={%r12, %rax, %rbx} L7: delete_frame --> L6 d={} u={} i={%r12, %rax, %rbx} o={%r12, %rax, %rbx} L6: return d={} u={%r12, %rax, %rbx} i={%r12, %rax, %rbx} o={}Tester ensuite avec des programmes plus complexes.