École Polytechnique INF564 – Compilation Jean-Christophe Filliâtre Cours 1 / 4 janvier 2017 Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 1 présentation du cours • cours le mercredi, 8h30–10h30 en salle Nicole-Reine Lepaute (1168) • transparents distribués • polycopié à venir • TD dans la foulée, 10h45–12h45 en salle Rosalind Franklin (1106) • machines Linux 64 bits toutes les infos sur le site web du cours (accessible depuis moodle) https://www.enseignement.polytechnique.fr/informatique/INF564/ questions ⇒ [email protected] Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 2 évaluation • un examen écrit • un projet = un mini compilateur C vers x86-64 • réalisé en TD, seul ou en binôme note finale = Jean-Christophe Filliâtre examen + projet 2 INF564 – Compilation 2016–2017 / cours 1 3 compilation schématiquement, un compilateur est un programme qui traduit un programme d’un langage source vers un langage cible, en signalant d’éventuelles erreurs langage source compilateur langage cible erreurs Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 4 compilation vers le langage machine quand on parle de compilation, on pense typiquement à la traduction d’un langage de haut niveau (C, Java, OCaml, ...) vers le langage machine d’un processeur (Intel Pentium, PowerPC, ...) % gcc -o sum sum.c source sum.c compilateur C (gcc) int main(int argc, char **argv) { int i, s = 0; for (i = 0; i <= 100; i++) s += i*i; −→ printf("0*0+...+100*100 = %d\n", s); } Jean-Christophe Filliâtre exécutable sum 00100111101111011111111111100000 10101111101111110000000000010100 10101111101001000000000000100000 10101111101001010000000000100100 10101111101000000000000000011000 10101111101000000000000000011100 10001111101011100000000000011100 ... INF564 – Compilation 2016–2017 / cours 1 5 langage cible dans ce cours, nous allons effectivement nous intéresser à la compilation vers de l’assembleur, mais ce n’est qu’un aspect de la compilation un certain nombre de techniques mises en œuvre dans la compilation ne sont pas liées à la production de code assembleur certains langages sont d’ailleurs • interprétés (Basic, COBOL, Ruby, Python, etc.) • compilés dans un langage intermédiaire qui est ensuite interprété (Java, OCaml, Scala, etc.) • compilés vers un autre langage de haut niveau • compilés à la volée Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 6 différence entre compilateur et interprète un compilateur traduit un programme P en un programme Q tel que pour toute entrée x, la sortie de Q(x) soit la même que celle de P(x) ∀P ∃Q ∀x... un interprète est un programme qui, étant donné un programme P et une entrée x, calcule la sortie s de P(x) ∀P ∀x ∃s... Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 7 différence entre compilateur et interprète dit autrement, le compilateur fait un travail complexe une seule fois, pour produire un code fonctionnant pour n’importe quelle entrée l’interprète effectue un travail plus simple, mais le refait sur chaque entrée autre différence : le code compilé est généralement bien plus efficace que le code interprété Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 8 exemple de compilation et d’interprétation source lilypond fichier PDF evince image << \chords { c2 c f2 c } \new Staff \relative c’ { \time 2/4 c4 c g’4 g a4 a g2 } \new Lyrics \lyricmode { twin4 kle twin kle lit tle star2 >> 42 C C F C twin kle twin kle lit tle star Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 9 qualité d’un compilateur à quoi juge-t-on la qualité d’un compilateur ? • à sa correction • à l’efficacité du code qu’il produit • à sa propre efficacité ”Optimizing compilers are so difficult to get right that we dare say that no optimizing compiler is completely error-free ! Thus, the most important objective in writing a compiler is that it is correct.” (Dragon Book, 2006) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 10 phases d’un compilateur typiquement, le travail d’un compilateur se compose • d’une phase d’analyse • reconnaı̂t le programme à traduire et sa signification • signale les erreurs et peut donc échouer (erreurs de syntaxe, de portée, de typage, etc.) • puis d’une phase de synthèse • production du langage cible • utilise de nombreux langages intermédiaires • n’échoue pas Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 11 phase d’analyse source ↓ analyse lexicale ↓ suite de lexèmes (tokens) ↓ analyse syntaxique ↓ arbre de syntaxe abstraite (AST ) ↓ analyse sémantique ↓ syntaxe abstraite + table des symboles Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 12 phase de synthèse syntaxe abstraite ↓ production de code (nombreuses phases) ↓ langage assembleur ↓ assembleur (as) ↓ langage machine ↓ éditeur de liens (ld) ↓ code exécutable Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 13 aujourd’hui assembleur Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 14 un peu d’arithmétique des ordinateurs un entier est représenté par n bits, conventionnellement numérotés de droite à gauche bn−1 bn−2 ... b1 b0 typiquement, n vaut 8, 16, 32, ou 64 les bits bn−1 , bn−2 , etc. sont dits de poids fort les bits b0 , b1 , etc. sont dits de poids faible Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 15 entier non signé bits = bn−1 bn−2 . . . b1 b0 n−1 X valeur = bi 2i i=0 bits 000. . .000 000. . .001 000. . .010 .. . valeur 0 1 2 .. . 111. . .110 111. . .111 2n − 2 2n − 1 exemple : 001010102 = 42 Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 16 entier signé : complément à deux le bit de poids fort bn−1 est le bit de signe bits = bn−1 bn−2 . . . b1 b0 n−2 X n−1 valeur = −bn−1 2 + bi 2i i=0 exemple : 110101102 = −128 + 86 = −42 Jean-Christophe Filliâtre INF564 – Compilation bits 100. . .000 100. . .001 .. . valeur −2n−1 −2n−1 + 1 .. . 111. . .110 111. . .111 000. . .000 000. . .001 000. . .010 .. . −2 −1 0 1 2 .. . 011. . .110 011. . .111 2n−1 − 2 2n−1 − 1 2016–2017 / cours 1 17 attention selon le contexte, on interprète ou non le bit bn−1 comme un bit de signe exemple : • 110101102 = −42 (8 bits signés) • 110101102 = 214 (8 bits non signés) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 18 opérations la machine fournit des opérations • opérations logiques, encore appelées bit à bit (AND, OR, XOR, NOT) • de décalage • arithmétiques (addition, soustraction, multiplication, etc.) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 19 opérations logiques opération exemple négation x NOT x 00101001 11010110 ET x y x AND y 00101001 01101100 00101000 OU x y x OR y 00101001 01101100 01101101 x y x XOR y 00101001 01101100 01000101 OU exclusif Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 20 opérations de décalages • décalage logique à gauche (insère des 0 de poids faible) ← bn−3 . . . b1 b0 0 0 ← • décalage logique à droite (insère des 0 de poids fort) → 0 0 bn−1 . . . b3 b2 → • décalage arithmétique à droite (réplique le bit de signe) → bn−1 bn−1 bn−1 . . . Jean-Christophe Filliâtre INF564 – Compilation b3 b2 → 2016–2017 / cours 1 21 un peu d’architecture très schématiquement, un ordinateur est composé • d’une unité de calcul (CPU), contenant • un petit nombre de registres entiers ou flottants • des capacités de calcul • d’une mémoire vive (RAM) • composée d’un très grand nombre d’octets (8 bits) 33 par exemple, 1 Go = 230 octets = 233 bits, soit 22 états possibles • contient des données et des instructions Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 22 un peu d’architecture RAM CPU %rip 0000056 %rax 0000012 %rbx 0000040 %rcx 0000022 %rdx 0000000 %rsi 0000000 ... l’accès à la mémoire coûte cher (à un milliard d’instructions par seconde, la lumière ne parcourt que 30 centimètres entre 2 instructions !) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 23 un peu d’architecture la réalité est bien plus complexe • plusieurs (co)processeurs, dont certains dédiés aux flottants • une ou plusieurs mémoires cache • une virtualisation de la mémoire (MMU) • etc. Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 24 principe d’exécution schématiquement, l’exécution d’un programme se déroule ainsi • un registre (%rip) contient l’adresse de l’instruction à exécuter • on lit un ou plusieurs octets à cette adresse (fetch) • on interprète ces bits comme une instruction (decode) • on exécute l’instruction (execute) • on modifie le registre %rip pour passer à l’instruction suivante (typiquement celle se trouvant juste après, sauf en cas de saut) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 25 principe d’exécution RAM CPU %rip 0000056 %rax 0000012 %rbx 0000040 %rcx 0000022 %rdx 0000000 %rsi 0000000 ... instruction : décodage : 48 c7 |{z} movq c0 |{z} %rax 2a | 00 00 {z 42 00 } i.e. mettre 42 dans le registre %rax Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 26 principe d’exécution là encore la réalité est bien plus complexe • pipelines • plusieurs instructions sont exécutées en parallèle • prédiction de branchement • pour optimiser le pipeline, on tente de prédire les sauts conditionnels Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 27 quelle architecture pour ce cours ? deux grandes familles de microprocesseurs • CISC (Complex Instruction Set) • • • • • beaucoup d’instructions beaucoup de modes d’adressage beaucoup d’instructions lisent / écrivent en mémoire peu de registres exemples : VAX, PDP-11, Motorola 68xxx, Intel x86 • RISC (Reduced Instruction Set) • peu d’instructions, régulières • très peu d’instructions lisent / écrivent en mémoire • beaucoup de registres, uniformes • exemples : Alpha, Sparc, MIPS, ARM on choisit x86-64 pour ce cours (les TD et le projet) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 28 l’architecture x86-64 Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 29 un (tout petit) peu d’histoire x86 une 1974 1978 1985 famille d’architectures compatibles Intel 8080 (8 bits) Intel 8086 (16 bits) Intel 80386 (32 bits) x86-64 une extension 64-bits 2000 introduite par AMD 2004 adoptée par Intel Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 30 l’architecture x86-64 • 64 bits • opérations arithmétiques, logique et de transfert sur 64 bits • 16 registres • %rax, %rbx, %rcx, %rdx, %rbp, %rsp, %rsi, %rdi, %r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15 • adressage de la mémoire sur 48 bits au moins (≥ 256 To) • nombreux modes d’adressage Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 31 assembleur x86-64 on ne programme pas en langage machine mais en assembleur l’assembleur fourni un certain nombre de facilités : • étiquettes symboliques • allocation de données globales le langage assembleur est transformé en langage machine par un programme appelé également assembleur (c’est un compilateur) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 32 environnement on utilise ici Linux et des outils GNU en particulier, on utilise l’assembleur GNU, avec la syntaxe AT&T sous d’autres systèmes, les outils peuvent être différents en particulier, l’assembleur peut utiliser la syntaxe Intel, différente Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 33 hello world .text .globl # des instructions suivent # rend main visible pour ld main main: movq call movq ret $message, %rdi puts $0, %rax # argument de puts # code de retour 0 .data # des données suivent .string "Hello, world!" # terminée par 0 message: Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 34 exécution assemblage > as hello.s -o hello.o édition de liens (gcc appelle ld) > gcc hello.o -o hello exécution > ./hello Hello, world! Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 35 désassembler on peut désassembler avec l’outil objdump > objdump -d hello.o 0000000000000000 <main>: 0: 48 c7 c7 00 00 00 00 7: e8 00 00 00 00 c: 48 c7 c0 00 00 00 00 13: c3 mov callq mov retq $0x0,%rdi c <main+0xc> $0x0,%rax on note • que les adresses de la chaı̂ne et de puts ne sont pas encore connues • que le programme commence à l’adresse 0 Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 36 désassembler on peut aussi désassembler l’exécutable > objdump -d hello 00000000004004f4 <main>: 4004f4: 48 c7 c7 20 10 60 00 4004fb: e8 f0 fe ff ff 400500: 48 c7 c0 00 00 00 00 400507: c3 mov callq mov retq $0x601020,%rdi 4003f0 <puts@plt> $0x0,%rax on observe maintenant • une adresse effective pour la chaı̂ne ($0x601020) • une adresse effective pour la fonction puts ($0x4003f0) • que le programme commence à l’adresse $0x4004f4 Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 37 boutisme on observe aussi que les octets de l’entier 0x601020 sont rangés en mémoire dans l’ordre 20, 10, 60, 00 on dit que la machine est petit-boutiste (en anglais little-endian) d’autres architectures sont au contraires gros-boutistes (big-endian) ou encore biboutistes (bi-endian) (référence : Les voyages de Gulliver de Jonathan Swift) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 38 gdb une exécution pas à pas est possible avec gdb (the GNU debugger ) > gcc -g hello.s -o hello > gdb hello GNU gdb (GDB) 7.1-ubuntu ... (gdb) break main Breakpoint 1 at 0x400524: file hello.s, line 4. (gdb) run Starting program: .../hello Breakpoint 1, main () at hello.s:4 4 movq $message, %rdi (gdb) step 5 call puts (gdb) info registers ... Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 39 Nemiver on peut aussi utiliser Nemiver (installé en salles infos) > nemiver hello Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 40 jeu d’instructions Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 41 registres 63 %rax 31 %eax 15 8 7 0 %ax %ah %al 63 %r8 31 %r8d 15 %r8w %rbx %ebx %bx %bh %bl %r9 %r9d %r9w %r9b %rcx %ecx %cx %ch %cl %r10 %r10d %r10w %r10b 87 0 %r8b %rdx %edx %dx %dh %dl %r11 %r11d %r11w %r11b %rsi %esi %si %sil %r12 %r12d %r12w %r12b %rdi %edi %di %dil %r13 %r13d %r13w %r13b %rbp %ebp %bp %bpl %r14 %r14d %r14w %r14b %rsp %esp %sp %spl %r15 %r15d %r15w %r15b Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 42 constantes, adresses, copies • chargement d’une constante dans un registre movq movq $0x2a, %rax $-12, %rdi # rax <- 42 • chargement de l’adresse d’une étiquette dans un registre movq $label, %rdi • copie d’un registre dans un autre movq Jean-Christophe Filliâtre %rax, %rbx # rbx <- rax INF564 – Compilation 2016–2017 / cours 1 43 arithmétique • addition de deux registres addq %rax, %rbx # rbx <- rbx + rax (de même, subq, imulq) • addition d’un registre et d’une constante addq $2, %rcx # rcx <- rcx + 2 %rbx # rbx <- rbx+1 %rbx # rbx <- -rbx • cas particulier incq (de même, decq) • négation negq Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 44 opérations logiques • non logique notq %rax # rax <- not(rax) %rbx, %rcx $0xff, %rcx %rax, %rax # rcx <- or(rcx, rbx) # efface les bits >= 8 # met à zéro • et, ou, ou exclusif orq andq xorq Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 45 décalages • décalage à gauche (insertion de zéros) salq salq $3, %rax %cl, %rbx # 3 fois # cl fois • décalage à droite arithmétique (copie du bit de signe) sarq $2, %rcx • décalage à droite logique (insertion de zéros) shrq $4, %rdx rolq rorq $2, %rdi $3, %rsi • rotation Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 46 taille des opérandes le suffixe q dans les instructions précédentes signifie une opération sur 64 bits (quad words) d’autres suffixes sont acceptés suffixe b w l q movb Jean-Christophe Filliâtre #octets 1 2 4 8 (byte) (word) (long ) (quad) $42, %ah INF564 – Compilation 2016–2017 / cours 1 47 taille des opérandes quand les tailles des deux opérandes diffèrent, il peut être nécessaire de préciser le mode d’extension movzbq movswl Jean-Christophe Filliâtre %al, %rdi %al, %rdi # avec extension de zéros # avec extension de signe INF564 – Compilation 2016–2017 / cours 1 48 accès à la mémoire une opérande entre parenthèses désigne un adressage indirect i.e. l’emplacement mémoire à cette adresse movq incq $42, (%rax) (%rbx) # mem[rax] <- 42 # mem[rbx] <- mem[rbx] + 1 note : l’adresse peut être une étiquette movq Jean-Christophe Filliâtre %rbx, (x) INF564 – Compilation 2016–2017 / cours 1 49 limitation la plupart des opérations n’acceptent pas plusieurs opérandes indirectes addq (%rax), (%rbx) Error: too many memory references for ‘add’ il faut donc passer par des registres movq addq Jean-Christophe Filliâtre (%rax), %rcx %rcx, (%rbx) INF564 – Compilation 2016–2017 / cours 1 50 adressage indirect indexé plus généralement, une opérande A(B, I , S) désigne l’adresse A + B + I × S où • A est une constante sur 32 bits signés • I vaut 0 si omis • S ∈ {1, 2, 4, 8} (vaut 1 si omis) movq -8(%rax,%rdi,4), %rbx Jean-Christophe Filliâtre # rbx <- mem[-8+rax+4*rdi] INF564 – Compilation 2016–2017 / cours 1 51 calcul de l’adresse effective l’opération lea calcule l’adresse effective correspondant à l’opérande A(B, I , S) leaq -8(%rax,%rdi,4), %rbx # rbx <- -8+rax+4*rdi note : on peut s’en servir pour faire seulement de l’arithmétique Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 52 drapeaux la plupart des opérations positionnent des drapeaux (flags) du processeur selon leur résultat drapeau ZF CF SF OF Jean-Christophe Filliâtre signification le résultat est 0 une retenue au delà du bit de poids fort le résultat est négatif débordement de capacité INF564 – Compilation 2016–2017 / cours 1 53 utilisation des drapeaux trois instructions permettent de tester les drapeaux • saut conditionnel (jcc) jne label • positionne à 1 (vrai) ou 0 (faux) (setcc) setge %bl • mov conditionnel (cmovcc) cmovl Jean-Christophe Filliâtre %rax, %rbx INF564 – Compilation suffixe e z ne nz s ns g ge l le a ae b be signification =0 6= 0 <0 ≥0 > signé ≥ signé < signé ≤ signé > non signé ≥ non signé < non signé ≤ non signé 2016–2017 / cours 1 54 comparaisons on peut positionner les drapeaux sans écrire le résultat quelque part, pour la soustraction et le ET logique cmpq %rbx, %rax # drapeaux de rax - rbx %rbx, %rax # drapeaux de and(rax, rbx) (attention au sens !) testq Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 55 saut inconditionnel • à une étiquette jmp label • à une adresse calculée jmp Jean-Christophe Filliâtre *%rax INF564 – Compilation 2016–2017 / cours 1 56 le défi de la compilation c’est de traduire un programme d’un langage de haut niveau vers ce jeu d’instructions en particulier, il faut • traduire les structures de contrôle (tests, boucles, exceptions, etc.) • traduire les appels de fonctions • traduire les structures de données complexes (tableaux, enregistrements, objets, clôtures, etc.) • allouer de la mémoire dynamiquement Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 57 appels de fonctions constat : les appels de fonctions peuvent être arbitrairement imbriqués ⇒ les registres peuvent ne pas suffire pour toutes les variables ⇒ il faut allouer de la mémoire pour cela les fonctions procèdent selon un mode last-in first-out, c’est-à-dire de pile Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 58 la pile pile ↓ la pile est stockée tout en haut, et croı̂t dans le sens des adresses décroissantes ; %rsp pointe sur le sommet de la pile ↑ données dynamiques (tas) données statiques les données dynamiques (survivant aux appels de fonctions) sont allouées sur le tas (éventuellement par un GC), en bas de la zone de données, juste au dessus des données statiques code ainsi, on ne se marche pas sur les pieds (note : chaque programme a l’illusion d’avoir toute la mémoire pour lui tout seul ; c’est l’OS qui crée cette illusion) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 59 manipulation de la pile • on empile avec pushq pushq pushq $42 %rax • on dépile avec popq popq popq %rdi (%rbx) exemple : pushq pushq pushq popq Jean-Christophe Filliâtre $1 $2 $3 %rax INF564 – Compilation %rsp → .. . 1 2 3 2016–2017 / cours 1 60 appel de fonction lorsqu’une fonction f (l’appelant ou caller ) souhaite appeler une fonction g (l’appelé ou callee), on ne peut pas se contenter de faire jmp g car il faudra revenir dans le code de f quand g aura terminé la solution consiste à se servir de la pile Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 61 appel de fonction deux instructions sont là pour ça l’instruction call g 1. empile l’adresse de l’instruction située juste après le call 2. transfert le contrôle à l’adresse g et l’instruction ret 1. dépile une adresse 2. y transfert le contrôle Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 62 appel de fonction problème : tout registre utilisé par g sera perdu pour f il existe de multiples manières de s’en sortir, mais en général on s’accorde sur des conventions d’appel Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 63 conventions d’appel • jusqu’à six arguments sont passés dans les registres %rdi, %rsi, %rdx, %rcx, %r8, %r9 • les autres sont passés sur la pile, le cas échéant • la valeur de retour est passée dans %rax • les registres %rbx, %rbp, %r12, %r13, %14 et %r15 sont callee-saved i.e. l’appelé doit les sauvegarder ; on y met donc des données de durée de vie longue, ayant besoin de survivre aux appels • les autres registres sont caller-saved i.e. l’appelant doit les sauvegarder si besoin ; on y met donc typiquement des données qui n’ont pas besoin de survivre aux appels • %rsp est le pointeur de pile, %rbp le pointeur de frame (optionnel) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 64 l’appel, en quatre temps il y a quatre temps dans un appel de fonction 1. pour l’appelant, juste avant l’appel 2. pour l’appelé, au début de l’appel 3. pour l’appelé, à la fin de l’appel 4. pour l’appelant, juste après l’appel s’organisent autour d’un segment situé au sommet de la pile appelé le tableau d’activation (en anglais stack frame) situé entre %rsp et %rbp Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 65 l’appelant, juste avant l’appel 1. passe les arguments dans %rdi,. . .,%r9, les autres sur la pile s’il y en a plus de 6 2. sauvegarde les registres caller-saved qu’il compte utiliser après l’appel (dans son propre tableau d’activation) 3. exécute call Jean-Christophe Filliâtre appelé INF564 – Compilation 2016–2017 / cours 1 66 l’appelé, au début de l’appel 1. sauvegarde %rbp puis le positionne, par exemple pushq movq %rbp %rsp, %rbp %rbp→ registres sauvés 2. alloue son tableau d’activation, par exemple subq variables locales $48, %rsp 3. sauvegarde les registres callee-saved dont il aura besoin ... argument 7 argument 8 adr. retour ancien %rbp %rsp→ ↓ %rbp permet d’atteindre facilement les arguments et variables locales, avec un décalage fixe quel que soit l’état de la pile Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 67 l’appelé, à la fin de l’appel 1. place le résultat dans %rax 2. restaure les registres sauvegardés 3. dépile son tableau d’activation et restaure %rbp avec leave qui équivaut à movq popq %rbp, %rsp %rbp 4. exécute ret Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 68 l’appelant, juste après l’appel 1. dépile les éventuels arguments 7, 8, ... 2. restaure les registres caller-saved, si besoin Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 69 récapitulation • une machine fournit • un jeu limité d’instructions, très primitives • des registres efficaces, un accès coûteux à la mémoire • la mémoire est découpée en • code / données statiques / tas (données dynamiques) / pile • les appels de fonctions s’articulent autour • d’une notion de tableau d’activation • de conventions d’appel Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 70 un exemple de compilation t(a,b,c){int d=0,e=a&~b&~c,f=1;if(a) for(f=0;d=(e-=d)&-e;f+=t(a-d,(b+d)*2, (c+d)/2));return f;}main(q){scanf("%d", &q);printf("%d\n",t(~(~0<<q),0,0));} Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 71 clarification int t(int a, int b, int c) { int d=0, e=a&~b&~c, f=1; if (a) for (f=0; d=(e-=d)&-e; f+=t(a-d, (b+d)*2, (c+d)/2)); return f; } int main() { int q; scanf("%d", &q); printf("%d\n", t(~(~0<<q), 0, 0)); } Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 72 clarification (suite) int t(int a, int b, int c) { int f=1; if (a) { int d, e=a&~b&~c; f = 0; while (d=e&-e) { f += t(a-d, (b+d)*2, (c+d)/2); e -= d; } } return f; } int main() { int q; scanf("%d", &q); printf("%d\n", t(~(~0<<q), 0, 0)); } Jean-Christophe Filliâtre INF564 – Compilation ce programme calcule le nombre de solutions du problème dit des n reines q q q q q q q q 2016–2017 / cours 1 73 comment ça marche ? • recherche par force brute (backtracking ) • entiers utilisés comme des ensembles : par ex. 13 = 0 · · · 011012 = {0, 2, 3} entiers 0 a&b a+b a-b ~a a&-a ~(~0<<n) a*2 a/2 Jean-Christophe Filliâtre ensembles ∅ a∩b a ∪ b, quand a ∩ b = ∅ a\ b, quand b ⊆ a {a {min(a)}, quand a 6= ∅ {0, 1, . . . , n − 1} {i + 1 | i ∈ a}, noté S(a) {i − 1 | i ∈ a ∧ i 6= 0}, noté P(a) INF564 – Compilation 2016–2017 / cours 1 74 justification de a&-a en complément à deux : -a = ~a+1 a = bn−1 bn−2 . . . bk 10 . . . 0 ~a = bn−1 bn−2 . . . bk 01 . . . 1 -a = bn−1 bn−2 . . . bk 10 . . . 0 a&-a = 0 0 . . . 010 . . . 0 exemple : a = 00001100 = 12 -a = 11110100 = −128 + 116 a&-a = 00000100 Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 75 clarification : version ensembliste int t(a, b, c) f ←1 if a 6= ∅ e ← (a\b)\c f ←0 while e 6= ∅ d ← min(e) f ← f + t(a\{d}, S(b ∪ {d}), P(c ∪ {d})) e ← e\{d} return f int queens(n) return t({0, 1, . . . , n − 1}, ∅, ∅) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 76 signification de a, b et c q q q ? ? ? ? ? ? ? ? q qqq Jean-Christophe Filliâtre q q q INF564 – Compilation q 2016–2017 / cours 1 77 intérêt de ce programme pour la compilation int t(int a, int b, int c) { int f=1; if (a) { int d, e=a&~b&~c; f = 0; while (d=e&-e) { f += t(a-d,(b+d)*2,(c+d)/2); e -= d; } } return f; } int main() { int q; scanf("%d", &q); printf("%d\n", t(~(~0<<q), 0, 0)); } Jean-Christophe Filliâtre INF564 – Compilation court, mais contient • un test (if) • une boucle (while) • une fonction récursive • quelques calculs c’est aussi une excellente solution au problème des n reines 2016–2017 / cours 1 78 compilation commençons par la fonction récursive t ; il faut • allouer les registres • compiler • le test • la boucle • l’appel récursif • les différents calculs Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 79 allocation de registres • a, b et c sont passés dans %rdi, %rsi et %rdx • le résultat est renvoyé dans %rax • les variables locales d, e et f seront stockées dans %r8, %rcx et %rax • en cas d’appel récursif, a, b, c, d, e et f auront besoin d’être sauvegardés, car ils sont tous utilisés après l’appel ⇒ sauvés sur la pile .. . adr. retour %rax (f) %rcx (e) %r8 (d) %rdx (c) %rsi (b) %rsp → %rdi (a) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 80 création/destruction du tableau d’activation t: subq ... addq ret Jean-Christophe Filliâtre $48, %rsp $48, %rsp INF564 – Compilation 2016–2017 / cours 1 81 compilation du test int t(int a, int b, int c) { int f=1; if (a) { ... } return f; } Jean-Christophe Filliâtre movq testq jz ... t return: addq ret INF564 – Compilation $1, %rax %rdi, %rdi t return $48, %rsp 2016–2017 / cours 1 82 cas général (a 6= 0) if (a) { int d, e=a&~b&~c; f = 0; while ... } xorq movq movq notq andq movq notq andq %rax, %rax %rdi, %rcx %rsi, %r9 %r9 %r9, %rcx %rdx, %r9 %r9 %r9, %rcx # f <- 0 # e <- a & ~b & ~c noter l’utilisation d’un registre temporaire %r9 (non sauvegardé) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 83 compilation de la boucle L1: while (expr) { body } L2: Jean-Christophe Filliâtre ... ... calcul de expr dans %rcx ... testq %rcx, %rcx jz L2 ... body ... jmp L1 ... INF564 – Compilation 2016–2017 / cours 1 84 compilation de la boucle il existe cependant une meilleure solution L1: while (expr) { body } L2: ... jmp L2 ... body ... ... expr ... testq %rcx, %rcx jnz L1 ainsi on fait un seul branchement par tour de boucle (mis à part la toute première fois) Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 85 compilation de la boucle while (d=e&-e) { ... } Jean-Christophe Filliâtre jmp loop body: ... loop test: movq movq negq andq testq jnz t return: ... INF564 – Compilation loop test %rcx, %r8 %rcx, %r9 %r9 %r9, %r8 %r8, %r8 # inutile loop body 2016–2017 / cours 1 86 compilation de la boucle (suite) while (...) { f += t(a-d, (b+d)*2, (c+d)/2); e -= d; } Jean-Christophe Filliâtre loop body: movq movq movq movq movq movq subq addq salq addq shrq call addq movq subq movq movq movq INF564 – Compilation %rdi, 0(%rsp) %rsi, 8(%rsp) %rdx, 16(%rsp) %r8, 24(%rsp) %rcx, 32(%rsp) %rax, 40(%rsp) %r8, %rdi %r8, %rsi $1, %rsi %r8, %rdx $1, %rdx t 40(%rsp), %rax 32(%rsp), %rcx 24(%rsp), %rcx 16(%rsp), %rdx 8(%rsp), %rsi 0(%rsp), %rdi # # # # # # a b c d e f # # # # # # f e -= d c b a 2016–2017 / cours 1 87 programme principal main: int main() { int q; scanf("%d", &q); ... } movq movq xorq call movq ... $input, %rdi $q, %rsi %rax, %rax scanf (q), %rcx .data input: .string "%d" q: .quad Jean-Christophe Filliâtre INF564 – Compilation 0 2016–2017 / cours 1 88 programme principal (suite) main: int main() { ... printf("%d\n", t(~(~0<<q), 0, 0)); } Jean-Christophe Filliâtre ... xorq notq salq notq xorq xorq call movq movq xorq call xorq ret INF564 – Compilation %rdi, %rdi %rdi %cl, %rdi %rdi %rsi, %rsi %rdx, %rdx t $msg, %rdi %rax, %rsi %rax, %rax printf %rax, %rax 2016–2017 / cours 1 89 optimisation ce code n’est pas optimal (par exemple, on crée inutilement un tableau d’activation quand a = 0) mais il est aussi bon que celui produit par gcc -O2 une différence fondamentale, cependant : on a écrit un code assembleur spécifique à ce programme, à la main, pas un compilateur ! Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 90 leçon • produire du code assembleur efficace n’est pas chose aisée (observer le code produit avec gcc -S -fverbose-asm ou encore ocamlopt -S) • maintenant il va falloir automatiser tout ce processus Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 91 pour en savoir plus lire • Computer Systems : A Programmer’s Perspective (R. E. Bryant, D. R. O’Hallaron) • son supplément PDF x86-64 Machine-Level Programming Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 92 la suite • TD 1 • compilation manuelle de programmes C • prochain cours • syntaxe abstraite Jean-Christophe Filliâtre INF564 – Compilation 2016–2017 / cours 1 93