Code intermédiaire

publicité
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
Téléchargement