INF564 – Compilation

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