Résumé – Analyse INFO0004 – Structure des langages de programmation fichier tex te Program m ation Analyse lex icale Analyse flux de jetons Génération de code ; liaison et exécution Com pilation Analyse syntax ique Justus H. Piater Liaison Interprétation Ex écution arbre de syntax e abstraite Analyse sém antique arbre annoté 2 / 51 Résumé – Génération de code Le code intermédiaire arbre annoté Program m ation Création de code interm édiaire Analyse pseudo- instructions en blocs élém entaires Am élioration Com pilation pseudo- instructions en blocs élém entaires Linkage Génération de code Interprétation Ex écution code assem bleur Am élioration code assem bleur Assem blage code binaire 3 / 51 Code intermédiaire Fibonacci • ressemble au code assembleur : • code linéaire • registres (nombre infini) • données brutes • indépendant d’une machine quelconque • pour effectuer des optimisations • pour réduire au minimum le code spécifique à la machine cible Le code intermédiaire // compute result – the nth Fibonacci number void main() { int n, fib0, fib1, temp, result; n = 8; fib0 = 0; fib1 = 1; while (n > 0) { temp = fib0; fib0 = fib1; fib1 = fib0 + temp; n = n – 1; } result = fib0; } 5 / 51 Le code intermédiaire Fibonacci : code intermédiaire Fibonacci : code intermédiaire (suite) Cf. UASM Store fib0, v4 Load v6, fib0 Load v7, temp v5 := v6 + v7 Store fib1, v5 Load v9, n LoadC v10, 1 v8 := v9 – v10 Store n, v8 Goto Block 1 Block 3: Load v14, fib0 Store result, v14 Block 0: LoadC v0, 8 Store n, v0 LoadC v1, 0 Store fib0, v1 LoadC v2, 1 Store fib1, v2 Block 1: Load v12, n LoadC v13, 0 v11 := v12 > v13 If v11 Goto Block 2 Goto Block 3 Block 2: Load v3, fib0 Store temp, v3 Load v4, fib1 Le code intermédiaire 6 / 51 7 / 51 Le code intermédiaire 8 / 51 Représenter le code intermédiaire Créer du code intermédiaire Chaque instruction du code intermédiaire est une structure de données qui encode : Formellement : spécification par une grammaire attribuée (attribute grammar) • le type de l’instruction (Load, :=, …) • le type de chaque opérande (registre virtuel, variable globale, variable locale, ou constante) • l’opérateur (le cas échéant) • des informations sémantiques supplémentaires – tout ce qui est utile pour l’amélioration et génération du code Pour nous : Très similaire à l’interprétation du code (voir les méthodes sémantiques) : Le code intermédiaire • On parcourt l’arbre de syntaxe abstraite; • à chaque nœud, on crée des instructions. En vue d’une amélioration du code par la suite, on ne réutilise jamais des registres ; un nouveau registre virtuel est prévu chaque fois. 9 / 51 Le code intermédiaire Les blocs élémentaires 10 / 51 Assignment Pour gérer des sauts, il faut insérer des étiquettes. BasicBlock* generate(const Assignment* s, BasicBlock* bb) { bb–>vec.push_back(new MStore(s–>target, generate(s–>source, bb))); return bb; } Le morceau de code entre une étiquette et le prochain saut constitue un bloc élémentaire (basic block). Nous allons couper le code en blocs élémentaires (pas essentiel ici, mais pour l’amélioration du code plus tard). • Il faut passer le bloc pour savoir où ajouter le code. • La source est un registre virtuel. • Il faut renvoyer le bloc parce qu’une instruction peut rompre le bloc élémentaire actuel. Ainsi, on crée un graphe de flot de contrôle (control flow graph). Note Cette implémentation fonctionne parce qu’ici, une expression ne rompt jamais le bloc élémentaire actuel. Le code intermédiaire 11 / 51 Le code intermédiaire 12 / 51 Binary Loop VirtualRegister* generate(const Binary* e, BasicBlock* bb) { VirtualRegister* target = newRegister(); bb–>vec.push_back(new MBinary(e–>op, target, generate(e–>term1, bb), generate(e–>term2, bb) )); return target; } BasicBlock* generate(const Loop* s, BasicBlock* bb) { BasicBlock* bbTest = basicBlocks–>getNewBasicBlock(); bb–>vec.push_back(new MGoto(bbTest)); BasicBlock* bbBodyBeg = basicBlocks–>getNewBasicBlock(); BasicBlock* bbBodyEnd = generate(s–>body, bbBodyBeg); bbBodyEnd–>vec.push_back(new MGoto(bbTest)); VirtualRegister* test = generate(s–>test, bbTest); BasicBlock* bbNext = basicBlocks–>getNewBasicBlock(); bbTest–>vec.push_back(new MCondGoto(test, bbBodyBeg)); bbTest–>vec.push_back(new MGoto(bbNext)); return bbNext; } Le code intermédiaire 13 / 51 La génération du code assembleur Le code intermédiaire 14 / 51 Allocations familières arbre annoté Création de code interm édiaire pseudo- instructions en blocs élém entaires Allocation des registres Allocation des variables locales Génération de code Allocation des variables globales Allocation des constantes code assem bleur La génération du code assembleur 16 / 51 Allocation des registres Allocation de ressources Problème : Nous avons un ensemble de tâches. Chaque tâche a une heure de début et une heure de fin avec . Chaque tâche doit être exécutée sur une machine ; une machine ne peut exécuter qu’une tâche à la fois. Deux tâches et ne sont pas en conflit si ou . Il s’agit d’exécuter toutes les tâches sur un nombre minimal de machines. Équivalent à la coloration de graphe (NP-hard) L’idée : Nœuds sont registres virtuels, arêtes connectent des nœuds simultanément actifs. Couleurs sont registres réels. Faisons-le pour Fibonacci en regardant les plages actives (live ranges) des registres virtuels… Simplification bloc-par-bloc : Sans plages actives inter-blocs élémentaires, un algorithme d’allocation de ressources convient. La génération du code assembleur 17 / 51 La génération du code assembleur Algorithme Correction Proposition : L’algorithme resourceAllocation() est correct. Temps d’exécution ? Algorithm resourceAllocation( ): Input: A set of tasks with start and end times and . Output: A minimal, nonconflicting allocation of machine for all tasks in . // minimal number of machines while do remove from the task with smallest if there is a machine with no task conflicting with then schedule task on machine else // add a new machine schedule task on machine La génération du code assembleur 18 / 51 Preuve (par contradiction) : Supposons que l’algorithme trouve une solution avec machines, alors qu’il existe une solution avec machines. Soit la première tâche allouée sur la machine . Quand l’algorithme traita la tâche , toutes les machines contenaient des tâches en conflict avec et aussi (par le même argument) entre elles, ce qui implique la présence dans de tâches en conflit entre elles. 19 / 51 La génération du code assembleur 20 / 51 Nombre insuffisant de registres ? • Déborder des registres vers la pile. • Avant d’allouer les registres, diviser la durée de vie d’un registre virtuel en plusieurs intervalles. Défi : Trivial : L’allocation des variables et constantes • Parcourir toutes les instructions intermédiaires pour créer le tableau de symboles (normalement c’est déjà fait lors de l’analyse syntaxique). • À chaque variable locale, affecter une adresse relative au frame pointer dans le cadre de pile. • À chaque variable et constante statique, affecter une adresse relative au début de la section des données statiques. Minimiser le coût des Store/Load. Trois registres réels suffisent toujours. On parlera plus tard des variables globales. La génération du code assembleur 21 / 51 Créer du code assembleur La génération du code assembleur 22 / 51 Créer du code assembleur (suite) Chaque instruction intermédiaire est traduite en une ou plusieurs instructions assembleur, en utilisant les registres réels / adresses des variables allouées. (On peut laisser tomber les petites constantes si elles sont toutes utilisées en tant qu’argument immédiat.) Cas spéciaux : • des constantes suffisamment petites pour les intégrer dans une instruction immédiate • des opérateurs du code intermédiaire sans équivalent en assembleur (toutes les variantes de <, >, immédiat ou non, existent-elles ?) On crée un bloc élémentaire à la fois, chacun précédé par son étiquette. Finalement, on crée les variables statiques et les constantes, chacune précédée par son étiquette. La génération du code assembleur 23 / 51 La génération du code assembleur 24 / 51 La machine Beta Fibonacci : UASM • architecture RISC, 32 bits Cf. IMF Toute opération sur des valeurs se fait entre registres. • 32 registres, dont : .include beta.uasm .options annotate R0 R27 R28 R29 R30 R31 BP LP SP XP .protect prologue: CMOVE(stack,SP) MOVE(SP,BP) ALLOCATE(5) block0: CMOVE(8, r7) |8 ST(r7, 12, BP) |n CMOVE(0, r6) |0 ST(r6, 8, BP) | fib0 CMOVE(1, r5) |1 ST(r5, 4, BP) | fib1 BR(block1) block1: valeur de retour base (frame) pointer linkage pointer stack pointer exception pointer toujours zéro • Pas de caches, délais, … La génération du code assembleur 25 / 51 Fibonacci : UASM (suite) 26 / 51 Fibonacci : UASM (suite) LD(BP, 12, r10) |n CMOVE(0, r9) |0 CMPLE(r10, r9, r11) XORC(r11, 1, r11) BT(r11, block2) BR(block3) block2: LD(BP, 8, r4) | fib0 ST(r4, 16, BP) | temp LD(BP, 4, r3) | fib1 ST(r3, 8, BP) | fib0 LD(BP, 8, r1) | fib0 LD(BP, 16, r15) | temp ADD(r1, r15, r2) ST(r2, 4, BP) | fib1 LD(BP, 12, r13) |n CMOVE(1, r12) |1 SUB(r13, r12, r14) ST(r14, 12, BP) |n La génération du code assembleur La génération du code assembleur BR(block1) block3: LD(BP, 8, r8) ST(r8, 0, BP) | fib0 | result epilogue: | don't clean up stack for debugging .breakpoint HALT() .unprotect stack: 27 / 51 La génération du code assembleur 28 / 51 Goto → BR, Load → LD L’assemblage • Expansion de macros • Remplacer les mnémoniques et les opérandes par le code binaire de la machine • Remplacer des étiquettes symboliques par des adresses • Parfois, amélioration du code. void generateCode(const MGoto* ii, ofstream& outfile) const { outfile << indent << "BR(block" << ii–>bb–>getId() << ")\n"; } void generateCode(const MLoad* ii, ofstream& outfile) const { outfile << indent << "LD(BP, " << VAR_SIZE * dict–>getOffset(*(ii–>source)) << ", r" << dict–>getOffset(*(ii–>target)) << ")\t| " << ii–>source–>id << "\n"; } La génération du code assembleur 29 / 51 La génération du code assembleur 30 / 51 Résumé – Liaison et exécution La liaison fichiers objet, librairies Program m ation Réadressage Analyse Résolution de nom s Com pilation Liaison Interprétation Ex écution fichier ex écutable Chargem ent processus Liaison dynam ique La liaison 32 / 51 La liaison 800 ... Exports call M(2300) Réadressage ... ... Code ... Code ... r1 := &M ... 3000 Réadressage 1800 X r1 := &L(1800) r2 := Y(3900) r3 := X(3300) 1500 1000 L: call M Données r1 := &M(2300) M 2300 Exports r1 := &L(1000) M: r2 := Y(400) r3 := X X: L: Y: 900 300 Données 500 M: 400 b. Code X M Recherche d’un nom non résolu dans les librairies Procédure récursive pour des objets des librairies Fichier objet ex écutable Imports M 1600 a. Imports 800 3. Fichiers objets réadressables 300 2. Placement des fichiers objet dans un ordre quelconque Réadressage (relocation) – insertion des bonnes adresses des destinations internes Résolution de noms – insertion des bonnes adresses des destinations externes 500 1. Une illustration Données X: Y: [Adapté de Scott 2000 Fig. 9.10] La liaison 33 / 51 La liaison Le contrôle de types Un exemple: fibonacci.cc • Par inclusion d’un symbole fictif qui représente une définition d’interface (p.ex., checksum) • Par codage de la signature d’une méthode dans son nom (name mangling) La liaison 34 / 51 // compute result – the nth Fibonacci number #include <cstdio> static int lastfib; int fibonacci(int n) { int fib0 = 0, fib1 = 1, temp; for (; n > 0; n––) { printf("%d iterations to go\n", n); temp = fib0; fib0 = fib1; fib1 = fib0 + temp; } return lastfib = fib0; } 35 / 51 La liaison 36 / 51 fibonacci.cc compilé, désassemblé 3e: 03 45 fc add 0xfffffffc(%ebp),%eax 41: 89 45 f8 mov %eax,0xfffffff8(%ebp) 44: ff 4d 08 decl 0x8(%ebp) 47: eb cb jmp 14 <_Z9fibonaccii+0x14> 49: 8b 45 fc mov 0xfffffffc(%ebp),%eax 4c: a3 00 00 00 00 mov %eax,0x0 51: c9 leave 52: c3 ret 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 18 sub $0x18,%esp 6: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp) d: c7 45 f8 01 00 00 00 movl $0x1,0xfffffff8(%ebp) 14: 83 7d 08 00 cmpl $0x0,0x8(%ebp) 18: 7f 02 jg 1c <_Z9fibonaccii+0x1c> 1a: eb 2d jmp 49 <_Z9fibonaccii+0x49> 1c: 83 ec 08 sub $0x8,%esp 1f: ff 75 08 pushl 0x8(%ebp) 22: 68 00 00 00 00 push $0x0 27: e8 fc ff ff ff call 28 <_Z9fibonaccii+0x28> 2c: 83 c4 10 add $0x10,%esp 2f: 8b 45 fc mov 0xfffffffc(%ebp),%eax 32: 89 45 f4 mov %eax,0xfffffff4(%ebp) 35: 8b 45 f8 mov 0xfffffff8(%ebp),%eax 38: 89 45 fc mov %eax,0xfffffffc(%ebp) 3b: 8b 45 f4 mov 0xfffffff4(%ebp),%eax La liaison 37 / 51 fibonacci.cc compilé, optimisé, désassemblé La liaison 38 / 51 fibonacci.cc compilé, optimisé, désassemblé (suite) 2f: 83 c4 10 add $0x10,%esp 32: 4b dec %ebx 33: 85 db test %ebx,%ebx 35: 7f e3 jg 1a <_Z9fibonaccii+0x1a> 37: 89 35 00 00 00 00 mov %esi,0x0 3d: 89 f0 mov %esi,%eax 3f: 8d 65 f4 lea 0xfffffff4(%ebp),%esp 42: 5b pop %ebx 43: 5e pop %esi 44: 5f pop %edi 45: 5d pop %ebp 46: c3 ret 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 57 push %edi 4: 56 push %esi 5: 53 push %ebx 6: 83 ec 0c sub $0xc,%esp 9: 8b 5d 08 mov 0x8(%ebp),%ebx c: be 00 00 00 00 mov $0x0,%esi 11: bf 01 00 00 00 mov $0x1,%edi 16: 85 db test %ebx,%ebx 18: 7e 1d jle 37 <_Z9fibonaccii+0x37> 1a: 83 ec 08 sub $0x8,%esp 1d: 53 push %ebx 1e: 68 00 00 00 00 push $0x0 23: e8 fc ff ff ff call 24 <_Z9fibonaccii+0x24> 28: 89 f0 mov %esi,%eax 2a: 89 fe mov %edi,%esi 2c: 8d 3c 38 lea (%eax,%edi,1),%edi La liaison fibonacci.cc compilé, désassemblé (suite) 39 / 51 La liaison 40 / 51 Tableaux de réadressage et de symboles Les différentes sections du fichier objet objdump -r fibonacci.o objdump -h fibonacci.o RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 0000001f R_386_32 .rodata.str1.1 00000024 R_386_PC32 printf 00000039 R_386_32 .bss Idx Name 0 .text 1 2 nm fibonacci.o 3 4 00000000 b lastfib U printf 00000000 T _Z9fibonaccii La liaison 5 41 / 51 Size VMA LMA File off Algn 00000047 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE .data 00000000 00000000 00000000 0000007c 2**2 CONTENTS, ALLOC, LOAD, DATA .bss 00000004 00000000 00000000 0000007c 2**2 ALLOC .rodata.str1.1 00000015 00000000 00000000 0000007c 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA .note.GNU-stack 00000000 00000000 00000000 00000091 2**0 CONTENTS, READONLY .comment 00000032 00000000 00000000 00000091 2**0 CONTENTS, READONLY La liaison 42 / 51 Le chargement 1. 2. 3. 4. Lancer un processus avec mémoire virtuelle S’il n’y est pas encore, charger le programme dans la mémoire Prévoir une petite pile Prévoir un petit tas Démarrer le programme à l’adresse zéro Le chargement 44 / 51 1. 2. 3. 4. 5. Lancer un processus sans mémoire virtuelle La liaison dynamique S’il n’y est pas encore, charger le programme dans la mémoire, à une adresse donnée Réadressage Prévoir la pile Prévoir le tas Démarrer le programme à l’adresse donnée Observation : Beaucoup de programmes et de processus partagent de grosses librairies. Idée : Créer une seule copie de ce code – les librairies partagées. Problème : Le même code peut se trouver à des différentes adresses pour différents processus ! Solution : PIC (Position-Independent Code) pour éviter le réadressage : • toutes les adresses relatives au PC • accès aux données non-partagées par un niveau supplémentaire d’indirection – le tableau de liaison Le chargement 45 / 51 Le chargement Une illustration 46 / 51 La liaison paresseuse Observation : m ain: *(sp+ N) := gp D Librairie partagée ... foo: - - call foo: liée dynam iquem ent gp := t9+ (E- D) t9 := *(gp+ A) ... Idée : Principe : jalr t9 gp := *(sp+ N) - - load X: ... t0 := *(gp+ F) E Code partagé (PIC) t0 := *t0 - - load X: ... t0 := *(gp+ C) t0 := *t0 - - load Y: ... t0 := *(gp+ G) t0 := *t0 - - load Y: t0 := *(gp+ B) gp(foo) F t0 := *t0 Tableau de liaison G gp(m ain) C B Beaucoup de liaisons ne sont jamais utilisées lors de la durée de vie d’un processus. Lier en cas de besoin. Lier des morceaux de code qui • appellent le lieur dynamique, qui • les remplacera par les vrais liens. A (un par processus) Données privées X: Y: (par processus) [Adapté de Scott 2000 Fig. 9.11] Le chargement 47 / 51 Le chargement 48 / 51 Résumé Résumé Génération de code : • code intermédiaire • code assembleur Liaison et chargement : • réadressage • résolution de noms • librairies partagées, PIC, liaison dynamique Résumé References M. Scott, Programming Language Pragmatics, Morgan Kaufmann 2000. Résumé 51 / 51 50 / 51