Vous êtes sur la page 1sur 54

Rapport de projet de fin dtudes

Valentin Robert
12 dcembre 2011

Sujet : Conception, mise en uvre et vrification dune analyse dalias pour CompCert, un compilateur C
formellement vrifi
Stagiaire : Valentin Robert
3me anne Informatique, option Gnie Logiciel
valentin.robert.2011@enseirb-matmeca.fr
Matre de stage : Xavier Leroy
Directeur de recherche INRIA
Responsable scientifique de lquipe-projet INRIA Gallium
xavier.leroy@inria.fr
01 39 63 55 61
Tuteur : Denis Barthou
Professeur
Membre de lquipe LaBRI Supports et algorithmes pour les applications numriques hautes
performances
denis.barthou@labri.fr
Priode de stage : 18 juillet 2011 - 16 dcembre 2011

Mots-cls : compilation, analyse statique, analyse dalias, analyse de pointeurs, preuves formelles

1
Table des matires
Remerciements 5

1 Introduction 6

2 INRIA 7
2.1 INRIA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 INRIA Paris-Rocquencourt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Lquipe-projet Gallium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 Contexte et problmatique 8
3.1 Contexte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2 Problmatique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3 tat de lart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.4 Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

4 Spcification et mise en uvre de lanalyse dalias 12


4.1 Description du langage intermdiaire RTL . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.2 Principe de lalgorithme de Kildall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.3 Dfinition du domaine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.4 La fonction de transfert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.5 Implmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.5.1 Reprsenter des ensembles infinis . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.5.2 Approximer des dictionnaires infinis . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.5.3 Rduire la complexit de limplmentation . . . . . . . . . . . . . . . . . . . . . 23
4.5.4 Amliorer lapproximation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

5 Preuve formelle 28
5.1 Preuves de programmes, Coq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.2 Principe de la preuve, proprits attendues . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.3 Invariants de preuve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.4 volution de labstracteur au cours de la preuve . . . . . . . . . . . . . . . . . . . . . . . 36

6 Mesures, tests et rsultats 38


6.1 Donnes numriques diverses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.2 tude du rsultat dune analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

7 Limites et amliorations 45

8 Conclusion 46

A Correspondances de notations 47

B Code Coq de la fonction de transfert 48

Glossaire 52

2
Table des figures
1 Planning du stage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2 Les diffrentes passes de CompCert C lassembleur . . . . . . . . . . . . . . . . . . . . 12
3 Un graphe de flot de contrle du langage RTL . . . . . . . . . . . . . . . . . . . . . . . . 13
4 Exemple de treillis de hauteur finie 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
5 Hirarchie des blocs abstraits et emplacements mmoire abstraits . . . . . . . . . . . . . . 20
6 Exemples de rsultats de la fonction de recherche dans le dictionnaire . . . . . . . . . . . 23
7 Exemple de rsultat de la fonction dajout dans le dictionnaire . . . . . . . . . . . . . . . 24
8 Problme lajout en labsence de cls du sommet de la hirarchie . . . . . . . . . . . . . 25
9 Exemple de dictionnaire hirarchique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
10 Principe de la preuve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
11 Rsultat de lanalyse sur lexemple, reprsent de manire incrmentale . . . . . . . . . . 41

Table des tableaux


1 Simple valuation du cot additionnel d lanalyse . . . . . . . . . . . . . . . . . . . . 38
2 Table de correspondance des notations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

Table des formules


1 Fonction de transfert, instructions RTL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2 Fonction de transfert, oprations arithmtiques . . . . . . . . . . . . . . . . . . . . . . . . 17
3 Fonction de transfert, fonctions intgres au compilateur . . . . . . . . . . . . . . . . . . 18
4 Fonction de transfert, calcul dadressage . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Table des fragments de code Coq


1 Type des blocs abstraits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2 Interface dun dictionnaire cls hirarchiques vers un treillis . . . . . . . . . . . . . . . 21
3 Proprit forte sur la fonction dajout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4 Exemple de la syntaxe dune proprit Coq . . . . . . . . . . . . . . . . . . . . . . . . . 28
5 Exemple de syntaxe alternative dune proprit Coq . . . . . . . . . . . . . . . . . . . . . 29
6 Proprit valsat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
7 Proprit regsat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
8 Proprit memsat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
9 Proprit ok_abs_mem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
10 Proprit ok_abs_genv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
11 Proprit ok_stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
12 Proprit satisfy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
13 Thorme satisfy_init . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

3
14 Encapsulation de funanalysis en cas dpuisement du carburant . . . . . . . . . . . . . . . 35
15 Thorme satisfy_step . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
16 Fonction de transfert, calcul dadressage . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
17 Fonction de transfert, oprations arithmtiques . . . . . . . . . . . . . . . . . . . . . . . . 49
18 Fonction de transfert, fonctions intgres au compilateur . . . . . . . . . . . . . . . . . . 50
19 Fonction de transfert, instructions RTL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

4
Remerciements
Je souhaite tout dabord remercier lensemble de lquipe-projet Gallium pour leur accueil chaleureux et
leurs conseils aviss qui mont permis de mpanouir et dapprendre beaucoup de choses durant ce stage,
que ce soit dans le cadre de mon projet ou sur des sujets plus vastes.
En particulier, je remercie mon matre de stage Xavier Leroy pour mavoir offert cette opportunit, encadr
au cours de ce stage, et appris certains rouages de Coq. Merci galement Alexandre Pilkiewicz et Franois
Pottier pour leurs prcieux conseils en Coq, Nicolas Pouillard pour nos discussions sur Haskell et Agda
et son support dans mes projets personnels, et Jonathan Protzenko pour ses conseils en LaTeX et pour
mavoir hberg quand cela fut ncessaire.
De chaleureux remerciements mes collgues et enseignants de lENSEIRB-MATMECA, notamment
Christelle Aroule, Cyril Roelandt et Paul Brauner pour leurs multiples contributions mon parcours scolaire
et extra-scolaire, et mes collgues et professeurs de lUniversit de Californie San Diego, en particulier
Zachary Tatlock, Ranjit Jhala, Sorin Lerner et Geoffrey M. Voelker.
Plus gnralement, merci aux personnes de lENSEIRB-MATMECA et dINRIA qui ont administr ce
stage.

5
1 Introduction
Lors de ma troisime anne en change lUniversit de Californie San Diego, jai grandement apprci
le fait dtre dans une cole qui soit la fois un laboratoire de recherche en informatique, et de pouvoir
discuter avec des doctorants et des professeurs de leur domaine de recherche, ainsi que de pouvoir assister
divers sminaires de prsentation de leurs travaux, ou de ceux dinvits.
Jai ainsi dcid de chercher un stage en laboratoire, afin de pouvoir mimmerger dans le milieu de la
recherche et pouvoir me faire une ide du travail de recherche au jour le jour et de lintrt que je pourrais
avoir effectuer une thse dans ce domaine. tant particulirement interess par les langages et systmes de
programmation, jai contact Xavier Leroy afin de savoir sil souhaitait me prendre en stage pour travailler
sur un des projets de recherche de lINRIA.
Jai donc eu la chance de voir cette demande accepte le 27 mars 2011, et ainsi dobtenir ce sujet de stage
sur la compilation vrifie. Cela tombait point nomm puisque je dbutais un cours de compilation, pour
lequel je devais faire un projet. Jai ainsi travaill pendant les mois prcdant le stage sous la supervision
de Sorin Lerner et en collaboration avec Zachary Tatlock et Ryan Kanoknukulchai sur un projet de super-
optimiseur pour CompCert. Sur cette priode, jai ainsi appris les bases de Coq de par mon projet, et ltat
de lart des optimisations dans les compilateurs de par mon cours, ce qui ma t dune grande aide au
dmarrage du stage, bien que Coq ne se matrise pas en quelques mois !
Ainsi, ce stage sest inscrit en continuation de mes cours, et ma permis dapprofondir mes connaissances
dans ce domaine que japprciais particulirement, et de mamliorer pour pouvoir ensuite continuer mon
travail personnel.

6
2 INRIA

2.1 INRIA
Cre en 1967 Rocquencourt, lINRIA sest depuis tendue et dispose prsent de 8 sites en France
(Rocquencourt, Rennes, Sophia Antipolis, Nancy, Grenoble, Bordeaux, Lille et Saclay).
Elle emploie plus de 4000 personnes, dont prs de 3500 scientifiques regroups en 171 quipes-projets, et
dispose dun budget annuel de 250 millions deuros.

2.2 INRIA Paris-Rocquencourt


Le centre de Rocquencourt emploie 620 personnes dont 470 scientifiques, regroups en 39 quipes de
recherche.
Les quipes du centre mnent des recherches dans des domaines aussi diversifis que les mathmatiques
appliques, lalgorithmique et les langages de programmation, les rseaux et systmes distribus, la cogni-
tique, ainsi que lapplication des technologies de linformation aux sciences de la vie et lenvironnement.

2.3 Lquipe-projet Gallium


Cre en 2006 en continuation de lquipe Cristal, lquipe-projet Gallium1 effectue sa recherche dans le
domaine de la conception, la formalisation et limplmentation des langages et systmes de programmation.
Cette recherche sappuie notamment sur deux projets clbres. Premirement Caml2 , langage fonctionnel
dvelopp depuis 1985, et notamment sa fameuse implmentation OCaml3 . Le second est CompCert4 , un
compilateur formellement vrifi (en Coq) pour un grand sous-ensemble du langage C.
Plus gnralement, les buts de lquipe couvrent entre autres ces quelques points :
concevoir des langages fonctionnels plus expressifs et de plus haut niveau, modulaires, composition-
nels et dots de smantiques formelles ;
faire voluer les systmes de types (polymorphisme dordre suprieur, meilleure infrence) ;
mieux dtecter les erreurs laide de systmes de types plus riches, et de meilleures analyses sta-
tiques ;
amliorer la compilation, autant sur le plan de lefficacit que sur celui de la correction ;
promouvoir les mthodes formelles appliques la preuve de programmes, notamment mcanise.
Mon projet de fin dtudes portait sur le compilateur CompCert.

1 http ://gallium.inria.fr
2 http ://caml.inria.fr
3 http ://caml.inria.fr/ocaml/index.en.html
4 http ://compcert.inria.fr

7
3 Contexte et problmatique

3.1 Contexte
On dsigne par le terme de compilateur5 un outil informatique permettant de traduire un code depuis un
langage source vers un langage cible, gnralement de plus bas niveau. Une proprit attendue dun tel outil
dans le domaine des langages de programmation est quil prserve la smantique du code compil : le code
excutable gnr la fin du processus doit se comporter conformment la smantique du langage source.
Cependant, on attend galement dun compilateur quil produise un code relativement efficace en ressources
(temps dexcution, mmoire consomme, taille de lexcutable). Cest pourquoi, la plupart des compila-
teurs effectuent des optimisations, cest--dire des transformations de programme, supposes smantique-
ment transparentes, mais faisant dcrotre un ou plusieurs de ces cots.
Malheureusement, ces optimisations sont souvent complexes, et sassurer quelles ne produisent pas de
bogues nest pas trivial dans le flot dexcution complexe dun compilateur [YCER11].
Dans le domaine des industries utilisant du logiciel critique (aviation, transports, banques, etc.), la certifi-
cation attendue des codes utiliss demande ce que des vrifications pousses soient effectues sur le code
source (vrification base de modles, de preuves de programme, ou danalyse statiques par exemple),
mais au final, ce code doit tre compil pour produire un excutable, et la correction du compilateur peut
ainsi mettre en pril tout ce travail en amont. Bien que des batteries de tests soient par la suite droules sur
le code gnr, ceux-ci ne peuvent pas tre exhaustifs sur des modles taille relle, de plus ils ne peuvent
que dmontrer la prsence de bogues, pas leur absence 6 . Pour ces raisons, il est souhaitable davoir un
compilateur formellement vrifi comme CompCert.
Cependant, concevoir et vrifier un tel outil est trs complexe, et moins de navoir pu prouver la validit
des optimisations quon dsire exploiter, on ne peut pas se permettre de les introduire dans le compilateur.
Aussi, on souhaite formaliser des mcanismes doptimisation standard afin de pouvoir en faire bnficier
un compilateur vrifi.

3.2 Problmatique
Dans le cadre de mon projet de fin dtudes, on ma charg de formaliser lanalyse dalias pour un sous-
ensemble formalis du langage C.
Le principe de cette analyse sappuie sur le fait que le langage C utilise des rfrences, cest--dire des
pointeurs vers des emplacements de la mmoire. En leur prsence, une analyse nave des variables du
programme se doit dtre trs prudente. Considrons le code :

5 Les mots en gras sont dfinis dans le glossaire.


6 Edsger W. Dijkstra

8
int *x, *y, z ;
/* ... */
*x = 42 ;
*y = z ;
z = *x + 1 ;

En labsence danalyse dalias, on ne peut rien supposer quant la valeur pointe par x la dernire ligne.
En effet, lcriture dans la valeur pointe par y la ligne prcdente empche toute supposition en labsence
dinformation sur ce que peut pointer y. Au contraire, si lon sait avec certitude que x et y ne sont pas des
alias, cest--dire quils ne peuvent pas pointer vers le mme emplacement mmoire, alors ce code peut tre
optimis en :

int *x, *y, z ;


/* ... */
*x = 42 ;
*y = z ;
z = 43 ;

Ainsi, au lieu dun drfrencement, suivi dune opration arithmtique, le compilateur pourra dsormais
aller jusqu simplifier la dernire ligne en un chargement dune valeur dans un registre. Lanalyse dalias
apporte donc une information supplmentaire pouvant tre exploite par les optimisations du compilateur
afin de dcouvrir de nouvelles opportunits, mises en vidence par le calcul densembles points disjoints
ou singletons.

3.3 tat de lart


Les analyses dalias, aussi appeles analyses de pointeurs se basent gnralement sur le principe des analyses
de flots de donnes (utilisant des graphes de flot de contrle), et peuvent tre catgorises selon divers
critres, dont les suivants :
Une analyse est dite intra-procdurale si elle examine chaque fonction sparment. Dans le cas
contraire, elle est dite inter-procdurale.
Une analyse est sensible au flot si elle calcule une information pour chaque arte du graphe de flot
de contrle. Ce type danalyse est plus coteux (notamment en mmoire) que la version insensible,
mais peut fournir des rsultats plus prcis.
Une analyse inter-procdurale est sensible au contexte si elle calcule une information diffrente en
fonction du graphe dappel. Le niveau de sensibilit doit tre ajust afin dassurer la terminaison pour
des fonctions rcursives et mutuellement rcursives.
Une analyse est sensible au chemin dexcution si elle calcule une information diffrente en fonction
du chemin dexcution. Par exemple, ltude des diffrentes branches dune structure conditionnelle
sera affine en fonction de linformation apporte par la condition quelles satisfont.
Une analyse est sensible aux champs si elle stocke une information diffrente pour les diffrents
champs des donnes structures du langage (enregistrements en Pascal, types struct en C).

9
Une analyse peut-tre de type may-alias si son rsultat indique ce vers quoi peut pointer une
variable, ou de type must-alias si son rsultat indique assurment ce quune variable pointe. La
dichotomie may/must existe galement pour dautres analyses.
Une analyse de pointeurs entirement sensible au flot ne passe pas lchelle, autant pour des raisons spa-
tiales que temporelles, sans avoir au moins appliqu des optimisations pralables. Cest pourquoi lanalyse
de pointeurs se base gnralement sur deux techniques insensibles au flot de donnes :
La mthode dAndersen [And94], dite base de sous-ensembles, calcule un ensemble de relations
de pointage pour lensemble du programme, et met jour une information de flot unique pour le
programme entier, de manire faible (cest--dire quune information ajoute nest jamais retire
par la suite, ce qui peut tre reprsent comme le calcul dun plus petit majorant dans un treillis
appropri).
La mthode de Steensgaard [Ste96], dite base dunification, restreint encore plus linformation
en nautorisant un pointeur abstrait ne pointer que vers une unique autre donne abstraite. Ainsi,
lorsquun pointeur obtient une deuxime cible, ces deux cibles sont unifies en une cible abstraite, et
leurs cibles respectives sont unifies rcursivement. Lalgorithme gagne ainsi la fois en complexit
temporelle et spatiale (lunification est plus rapide que la propagation itrative dAndersen, et le
graphe unifi est moins grand), au prix dune prcision diminue.
Ces analyses se basent sur une tude dun langage sous-ensemble de C, et bnficient donc de proprits
smantiques lies aux oprateurs du langage : laccs direct ou indirect un champ dune structure, laccs
dans un tableau, le type dune variable sont syntaxiquement reconnaissables. En pratique, elles prsupposent
galement quon travaille avec du code C correctement typ, dans la mesure du systme de types faible du
standard C, ce que lon ne souhaite pas axiomatiser. En outre, elles ne se soucient gnralement pas de traiter
des fonctionnalits du langage telles que les changements de type (cast) ou larithmtique des pointeurs.
Puisquil existe des codes quon souhaite compiler qui bricolent plus ou moins bon escient aves les types,
sans pour autant exhiber des comportements indfinis du standard, et utilisent des fonctionnalits telles que
celles dcrites linstant, on ne peut pas entirement se reposer sur ces travaux.
Au contraire, dautres approches [G+ 05], plus rares, essaient de raisonner sur un langage assembleur de bas
niveau, la smantique plus lgre : on ne considre que des registres, de la mmoire, et des oprations de
chargement et stockage. Cependant, ltat de lart des analyses non prouves considre les analyses intra-
procdurales comme dpasses, et emploie des algorithmes qui peuvent tre difficiles formaliser dans le
cadre de CompCert. Aussi, me suis-je inspir de ces travaux, et dautres rsultats de recherche rencontrs
durant ma phase dtat de lart, sans pour autant pouvoir adapter avec prcision un algorithme existant.

3.4 Cahier des charges


On souhaite donc :
une analyse intra-procdurale, ce qui semble dans un premier temps la fois plus simple mettre
en uvre et prouver, et est une tape ncessaire dmontrer la faisabilit du projet dune analyse
dalias formellement vrifie ;
une analyse sensible aux champs, ou plutt, dun point de vue de plus bas niveau, sensible la gra-
nularit dune unit mmoire (typiquement, un octet) : on souhaite en effet pouvoir obtenir des in-

10
formations sur les relations de pointage entre les champs de structures C, aussi on ne souhaite pas
considrer ces structures comme des blocs opaques mais bien comme des ensembles demplacements
mmoire ;
que cette analyse ait lieu ltape de compilation durant laquelle les optimisations sont effectues,
afin de ne pas devoir conserver et traduire le rsultat de lanalyse de linstant o elle est effectue
jusqu celui o elle est utilise (voir figure 2 page 12).

. Juil Aot Sept Oct Nov Dc

2 1 2 1 2 1 2 1 2 1

tat de lart
Prototype en OCaml
Dveloppement Coq
Tests, rapport

Fig. 1 : Planning du stage

11
4 Spcification et mise en uvre de lanalyse dalias

4.1 Description du langage intermdiaire RTL


Le processus de compilation de CompCert [Ler09a] fait intervenir de multiples transformations passant
par plusieurs langages intermdiaires (figure 2). Les optimisations sont gnralement faites sur un langage
nomm RTL (Register Transfer Language), dans lequel les variables du programme ont t remplaces par
des registres ou des accs mmoire, mais dont le code nest pas encore linaris [Ler09b].

side-effects out type elimination


Compcert C Clight C#minor
of expressions loop simplifications

stack allocation
Optimizations: constant prop., CSE, tail calls, (LCM)
of variables

CFG construction instruction


RTL expr. decomp.
CminorSel Cminor
selection
register allocation (Instruction scheduling)
(Iterated Register Coalescing)

linearization spilling, reloading


LTL LTLin calling conventions
Linear
of the CFG
layout of
stack frames
asm code
Asm generation
Mach

Fig. 2 : Les diffrentes passes de CompCert C lassembleur

Ce langage suppose notamment quil existe une infinit de registres, qui sont gnralement peu rutiliss
au sein dune fonction. Il nest toutefois pas sous forme SSA puisquun mme registre peut tre utilis dans
deux branches ou modifi plusieurs fois dans la mme branche. Ces registres sont utiliss pour reprsen-
ter les variables du programme source dont ladresse nest jamais prise. Au contraire, les variables dont
ladresse est rfrence sont places en mmoire dans la pile.
Le langage est assez rduit, et se compose des oprations suivantes :
Inop est lopration nulle.

Iop contient lensemble des oprations arithmtiques. Elle prend en entre une liste de registres, et crit
le rsultat dans un registre.
Iload permet de charger un morceau de mmoire dtermin par sa taille et adress par zro deux re-
gistres, et stocke le contenu dans un registre.
Istore stocke la valeur dun registre dans la mmoire, de manire analogue Iload.

Icall effectue un appel de fonction, et place la valeur retourne dans un registre.

12
Itailcall effectue un appel de fonction et retourne immdiatement la valeur retourne lappelant.

Ibuiltin permet dappeler une fonction traite spcialement par le compilateur. Celles-ci contiennent
notamment des fonctions built-in dans le compilateur, des oprations sur des variables volatiles,
ainsi que malloc, free et memcpy.
Icond effectue un saut conditionnel en fonction dune condition boolenne.

Ijumptable effectue un branchement multiple, en fonction de la valeur dun registre.

Ireturn termine la fonction courante et retourne une valeur la fonction appelante.

Il est galement intressant de considrer les spcificits du modle mmoire de CompCert. En effet, celui-ci
suppose le code source correct et en dduit des garanties fortes pour lanalyse :
En supposant labsence de drfrencement un indice hors des bornes correctes, on peut donner un
rsultat plus prcis lorsquun accs est effectu un indice inconnu dun bloc : en effet, on sait alors
que laccs seffectuera uniquement dans ce bloc, et pas arbitrairement dans la mmoire.
Le modle fait galement une sparation stricte entre les valeurs numriques et les pointeurs : il nest
ainsi pas possible de contrefaire un pointeur partir dune valeur numrique, tout comme il nest pas
possible dobtenir un pointeur en contournant les rgles de larithmtique des pointeurs.
Le modle offre galement des garanties sur la prservation des pointeurs : il est interdit dcrire une
valeur cheval sur la valeur dun pointeur, puis de drfrencer ce pointeur. Ainsi, un pointeur reste
valide et est dtruit si une criture laltre seulement partiellement.

4.2 Principe de lalgorithme de Kildall


On dsigne par graphe de flot de contrle un graphe orient dont les nuds reprsentent les oprations du
programme, et les arcs reprsentent les successions de ces oprations. La reprsentation intermdiaire en
RTL du compilateur CompCert reprsente chaque fonction sous cette forme.

Iop

Iload

Icond

.
Iop Ireturn

Fig. 3 : Un graphe de flot de contrle du langage RTL

Lalgorithme de Kildall [Kil73] est un algorithme standard permettant de rsoudre un systme dquations
ou dinquations sur un tel graphe de flot de contrle. Cette mthode consiste dfinir une fonction de trans-

13
fert pour chaque nud du programme, qui une information dentre (quelconque) associe une information
de sortie.
On dit que le problme est vers lavant si la fonction de transfert va dans le sens du graphe, et quil est en
arrire si la fonction de transfert va dans le sens contraire. Selon le problme rsoudre, on choisit lune
ou lautre approche.
Un exemple danalyse oriente vers larrire est lanalyse de vie des variables du programme : on souhaite
dterminer quelle variable peut encore tre utilise tout instant du programme. Pour cela, on part de la fin
du programme, et chaque utilisation dune variable, on la note comme vivante.
Pour lanalyse dalias, on procde vers lavant, puisqu partir de linformation en entre dune instruction,
on sait calculer linformation en sortie en fonction des effets de linstruction.
Lalgorithme consiste itrer lapplication des fonctions de transfert partir dune solution initiale jusqu
atteindre un point fixe qui est une solution du problme. Il convient cependant de prendre des prcautions
afin dassurer la terminaison dun tel algorithme : on se place gnralement dans un treillis de hauteur finie,
cest--dire un ensemble muni dune relation dordre partielle avec une borne suprieure et infrieure, et on
utilise des fonctions de transfert monotones, ce qui oblige lalgorithme terminer en un nombre dtapes
born par la hauteur du treillis, puisque chaque itration fait progresser le rsultat vers le sommet du treillis,
jusqu ce quon atteigne un point fixe.

{a,b,c}

{a,b} {a,c} {b,c}

{a} {b} {c}

Fig. 4 : Exemple de treillis de hauteur finie 3

Lalgorithme de Kildall prsent dans CompCert est cependant diffrent car il ne force pas la hauteur du
treillis tre finie. Ainsi, la terminaison est assure par un carburant, un entier strictement dcroissant
chaque itration. Lalgorithme peut ainsi chouer si le calcul ne termine pas, en puisant le carburant. En
pratique, il est donc fix une valeur assez importante pour que cela ne se produise pas, dans la mesure o
lon effectue des analyses de taille raisonnable.

4.3 Dfinition du domaine


On souhaite catgoriser lexcution lensemble de ce que peut pointer chaque registre, ainsi que lensemble
de ce que peut pointer chaque emplacement en mmoire. Dans le cas des registres, lespace de dpart est
fini (nombre de registres utiliss par la fonction), mais lespace darrive est potentiellement infini. Pour

14
les emplacements mmoire, la situation est pire puisque lespace de dpart est galement potentiellement
infini.
Ainsi, lanalyse va devoir travailler sur une approximation des ensembles de dpart et darrive afin de
manipuler des donnes finies.
On dfinit donc le type B des blocs abstraits comme :

Inductive B: Type :=
| Alloc: node -> B (* bloc allou au nud indiqu *)
| Allocs: B (* ensemble de tous les blocs allous *)
| Global: ident -> B (* bloc global indic *)
| Globals: B (* ensemble de tous les blocs globaux *)
| Other: B (* tous les autres blocs *)
| Stack: B (* bloc correspondant la pile *)
.

Fragment de code Coq 1 : Type des blocs abstraits

Ce type somme permet de catgoriser tout bloc lexcution en un nombre fini de rprsentations : ainsi,
tous les blocs concrets allous en un point dexcution sont projets sur le mme bloc abstrait, et tous les
blocs obtenus par des appels externes sont projets sur le bloc abstrait Other. La prsence des blocs abstraits
Allocs et Globals permet seulement de faciliter la reprsentation de lensemble des blocs Alloc et Global
respectivement.
On dfinit galement le type L := (B, int) des emplacements mmoire abstraits comme un type produit
dun bloc abstrait et dun entier mmoire reprsentant lindice de lemplacement dans le bloc en question.
Ainsi, on peut dfinir le type S des ensembles demplacements mmoire abstraits, puis les types R :=
reg S et M := L S des rsultats de notre analyse de flot de donnes, cest--dire une fonction
associant chaque registre (respectivement, chaque emplacement mmoire) un ensemble demplacements
mmoire vers lesquels ce premier peut pointer.

15
4.4 La fonction de transfert
Instructions RTL

La fonction de transfert Fop : (R, M) (R, M) est dfinie pour chaque opration du graphe de flot de
contrle ainsi :

FInop(succ) (r, m) = (r, m)


FIop(op,args,dst,succ) (r, m) = (Opop,args,dst (r, m), m)

(r R [dst 7 S m(x)], m) si chunk = M int32
FIload(chunk,addr,args,dst,succ) (r, m) = x@addr (args,r)

(r, m) sinon

(r, m M ( M [dst 7 r(src)])) si chunk = M int32
FIstore(chunk,addr,args,src,succ) (r, m) = dst@addr (args,r)

(r, m) sinon
FIcall(sign,fn,args,dst,succ) (r, m) = (r R [dst 7 S ], M )
FItailcall(sign,fn,args) (r, m) = (r, M )
FIbuiltin(ef,args,dst,succ) (r, m) = Bief,args,dst (r, m)
FIcond(cond,args,ifso,ifnot) (r, m) = (r, m)
FIjumptable(arg,tbl) (r, m) = (r, m)
FIreturn(res) (r, m) = (r, m)

Formules 1 : Fonction de transfert, instructions RTL

Une opration nulle (Inop) naffecte pas le rsultat.


Une opration arithmtique (Iop) naffecte pas la mmoire, seulement les registres (voir Formules 2 page 17).
Une lecture mmoire (Iload) naffecte pas la mmoire, mais seulement le registre destination, et ce uni-
quement lorsque lon charge un mot de 32 bits (taille dun pointeur). Dans ce cas, le registre destination
peut pointer vers ce que peut pointer le mot adress. La fonction @ est galement dcrite plus bas.
Une criture mmoire (Istore) naffecte pas les registres, mais affecte tous les emplacements mmoire
potentiellement adresss. Chacun deux peut alors pointer vers ce que pointait le registre source, en plus de
ce vers quoi il pointait avant.
la suite dun appel de fonction (Icall), le registre destination ainsi que lensemble de la mmoire peuvent
pointer vers tout. En effet, dans le cas dune analyse intra-procdurale, on ne dispose pas de plus dinfor-
mations.
Si lappel est terminal (Itaillcall), il en va de mme mais il ny a pas de registre destination, car la valeur
est directement rendue lappelant.

16
Bi traite les appels de fonctions externes au programme et est dcrite plus bas (Formules 3 page 18).
Pour les autres cas (sauts conditionnels et retour de fonction), rien nest altr.

Oprations arithmtiques

OpOlea(addr),args,dst (r, m) = r R [dst 7 @addr (args, r)]


OpOmove,[src],dst (r, m) = r R [dst 7 r(src)]
OpOsub,[src1,src2],dst (r, m) = r R [dst 7 unknown_offset(r(src1))]
Opop (r, m) = r pour les autres oprations

avec unknown_offset(s) = {(b, i)|o (b, o) s}irange(int)

Formules 2 : Fonction de transfert, oprations arithmtiques

Lopration Olea (Load Effective Address) permet de charger dans un registre une adresse obtenue par un
calcul dadressage. Ainsi, le registre destination peut pointer vers ce qui est adress.
Lopration Omove permet daffecter la valeur dun registre source un registre destination.
Lopration Osub permet de stocker la diffrence de deux registres source dans un registre destination. Cette
opration peut produire un pointeur valide par larithmtique des pointeurs : le premier oprande doit tre
un pointeur, et le second doit tre une valeur numrique. Puisquon ne connat pas la valeur de src2, on
considre lensemble des emplacements du bloc point.
Les autres oprations ne produisent que des valeurs numriques ne pouvant pas tre des pointeurs.

17
Fonctions intgres au compilateur

BiEF_external,args,dst (r, m) = (r R [dst 7 S ], m)


BiEF_builtin,args,dst (r, m) = (r R [dst 7 S ], m)
BiEF_vload,args,dst (r, m) = (r R [dst 7 S ], m)

BiEF_vstore,[rdst,rsrc],dst (r, m) = (r, m M ( [x 7 r(rsrc)]))
M
xr(rdst)

BiEF_malloc,args,dst (r, m) = (r R [dst 7 {(Alloc n, 0)}], m)


BiEF_free,args,dst (r, m) = (r, m)

BiEF_memcpy,[rdst,rsrc],dst,sz,al (r, m) = (r, m M ( [x 7 S ]))
M
xunknown_offset(r(rdst))

BiEF_annot,args,dst,text,targs (r, m) = (r, m)


BiEF_annot_val,[rval],dst,text,targ (r, m) = (r R [dst 7 r(rval)], m)

Formules 3 : Fonction de transfert, fonctions intgres au compilateur

Des appels de fonctions externes au programme (EF_external et EF_builtin) ne changent pas la mmoire,
et retournent une valeur inconnue dans le registre destination.
Une lecture volatile (EF_vload) retourne une valeur inconnue dans le registre destination.
Une criture volatile (EF_vstore) modifie lemplacement point par rdst, lui affectant la valeur contenue
dans rsrc. Selon le bloc point par ce dernier, il peut sagir dune criture relle en mmoire.
Une allocation dynamique (EF_malloc) retourne un pointeur vers lindice zro du bloc allou. Ce bloc est
abstrait ici en Allocn, o n reprsente le compteur du programme linstruction en question.
La libration dun bloc (EF_free) naffecte aucun pointeur.
La copie dun intervalle de mmoire (EF_memcpy) dun emplacement source vers un emplacement destina-
tion affecte de manire imprvisible le bloc destination.
Une annotation utilisateur qui ne contient pas de valeur (EF_annot) naltre rien, mais une annotation qui
en contient une (EF_annot_val) retourne la valeur dans le registre destination.

18
Calcul dadressage

La fonction @ calcule un ensemble demplacements mmoire pouvant tre points par un adressage donn,
en fonction du mode dadressage et des paramtres. Cette fonction dpend de larchitecture cible, ci dessous
un exemple pour x86 32 bits :

@Aindexed(o) ([r0], r) = {(b, i + o)|(b, i) r(r0)}


@Aindexed2(o) ([r1, r2], r) = unknown_offset(r(r1)) S unknown_offset(r(r2))
@Ascaled(sc,ofs) ([r0], r) = S (= S )
@Aindexed2scaled(sc,ofs) ([r1, r2], r) = unknown_offset(r(r1))
@Aglobal(s,ofs) ([], r) = {(Global s, ofs)}
@Abased(s,ofs) ([r0], r) = {(Global s, o)|o range(int)}
@Abasedscaled(sc,s,ofs) ([r0], r) = {(Global s, o)|o range(int)}
@Ainstack(ofs) ([], r) = {(Stack, of s)}

Formules 4 : Fonction de transfert, calcul dadressage

Pour le mode adress par un registre et un indice (Aindexed), on dcale simplement tous les emplacements
vers lesquels peut pointer ce registre.
Pour un adressage utilisant deux registres et un indice (Aindexed2), lexcution, lun des deux registres
devra contenir un pointeur, alors que lautre devra contenir un entier. Malheureusement, on ne sait pas
dterminer statiquement lequel jouera quel rle, aussi doit-on prendre en compte les deux cas. Pour chaque
cas, on retourne ainsi lensemble de tous les indices des blocs points par le registre qui joue le rle de
pointeur, puisquon ne peut pas non plus dterminer statiquement la valeur ou le domaine du registre qui
joue le rle de dcalage.
Le mode dadressage Ascaled est curieux, puisquil ne permet pas de calculer une adresse ! Il est notamment
exploit laide de linstruction Olea pour effectuer en une instruction une multiplication suivie dune
somme (par le calcul dadresse).
Le mode Aindexed2scaled calcule un pointeur partir de la valeur de r1.
Les modes dadressage globaux (Aglobal, Abased et Abasedscaled) calculent une adresse dans un bloc
global qui est statiquement fix. Pour le premier mode, lindice est aussi fix, aussi on retourne lempla-
cement abstrait correspondant. Pour les deux autres modes, lindice est dynamiquement calcul, aussi on
retourne tous les indices du bloc considr.
Enfin, le dernier mode dadressage permet de dsigner un indice dans la pile, fix statiquement (cest--dire,
un accs une variable locale dont ladresse est utilise dans la fonction), aussi on retourne lemplacement
correspondant du bloc abstrait Stack.

19
4.5 Implmentation
4.5.1 Reprsenter des ensembles infinis

Pour implmenter ces formules, il nous fallait tout dabord un moyen de reprsenter efficacement les en-
sembles dont il est fait mention dans les formules de la section prcdente. Notamment, si lon considre
un ensemble tel que {(Global s, o)|o range(int)}, on constate quil nous faut reprsenter ceci sous une
forme symbolique compacte : numrer les 232 valeurs du type int est hors de question.
En pratique, on ne dispose pas dune analyse de domaine numrique pour les valeurs du code, aussi on
souhaite seulement deux niveaux de granularit : soit celui dun indice, soit celui dun bloc entier. Aussi,
nous reprsenterons ces ensembles sous la forme dun couple compos dun ensemble de blocs abstraits
(granularit des blocs) qui reprsente lensemble de tous les indices du bloc en question, et dun ensemble
demplacements mmoire abstraits (granularit des indices).
Il en va de mme pour reprsenter lensemble des blocs globaux ou des blocs de sites dallocation, aussi on
peut dfinir une hirarchie des blocs abstraits comme celle dcrite en figure 5.

All

Allocs Globals

Alloc 1 ... Alloc ... Global g


... a Stack Other Global
... 1

. (Alloc 1, 0) ... (Alloc 1, i) (Stack, 0) ... (Stack, i) (Other, 0) ... (Other, i)(Global g, 0) ... (Global g, i)

Fig. 5 : Hirarchie des blocs abstraits et emplacements mmoire abstraits

Ainsi, pour reprsenter lensemble {(Global s, o)|o range(int)}, on utilise le couple ({Global s}, ).

4.5.2 Approximer des dictionnaires infinis

Le problme consiste maintenant reprsenter un type abstrait de donnes correspondant un dictionnaire


associant chacun des lments de lensemble prcdemment dcrit une information. Ce dictionnaire est
muni doprations get et add prservant les proprits de linterface suivante :

20
Module Type OMapInterface (O: OverlapInterface) (L: SEMILATTICE).
Parameter t: Type.
Parameter get: O.t -> t -> L.t.
Parameter add: O.t -> L.t -> t -> t.

Axiom get_add_same: forall k s m, L.ge (get k (add k s m)) s.


Axiom get_add: forall x y s m, L.ge (get x (add y s m)) (get x m).
Axiom get_add_overlap: forall x y s m,
O.overlap x y ->
L.ge (get x (add y s m)) s.
End OMapInterface.

Fragment de code Coq 2 : Interface dun dictionnaire cls hirarchiques vers un treillis

Le module O reprsente une hirarchie abstraite, dote dune proposition overlap indiquant si deux l-
ments sont en relation hirarchique (peu importe le sens). Le module L reprsente lensemble darrive,
quon suppose tre un treillis dot dune opration de calcul du plus petit majorant lub.
Les axiomes indiquent :
quen cherchant une cl aprs avoir ajout un ensemble s cette cl, on doit obtenir un ensemble
suprieur s (get_add_same).
quen cherchant une cl aprs avoir ajout une valeur quelconque une cl quelconque, on doit obtenir
un ensemble suprieur celui obtenu avant cet ajout (get_add).
quen cherchant une cl aprs avoir ajout un ensemble s une cl qui est en relation hirarchique
avec cette premire, on obtient un ensemble suprieur s (get_add_overlap).
Ces proprits se justifient aisment : supposons quon obtienne une information indiquant quun indice
inconnu du bloc Alloc a peut dsormais pointer vers un ensemble s. On ajoute cette information notre
rsultat. Pour autant, ne sachant pas quel indice particulier a t modifi, on ne peut pas faire fi de lin-
formation s dj prsente. On souhaite donc conserver une information qui soit suprieure s (axiome
get_add_same) et suprieure s (axiome get_add) pour le bloc Alloc a.

Supposons prsent quon demande linformation contenue lemplacement (Alloc a, 0). Cet empla-
cement fait partie du bloc Alloc a, et est donc potentiellement celui qui aurait d recevoir linformation
s si lanalyse avait pu tre plus prcise, il nous faut donc obtenir une information suprieure s pour cette
cl, qui est hirarchiquement infrieure la cl Alloc. De manire analogue, si on demande linformation
gnrale de tous les blocs allous grce la cl Allocs, on veut obtenir une information suprieure lin-
formation de chaque bloc Alloc. En particulier, une information suprieure s. Ainsi, pour tous les blocs
situs hirarchiquement en-dessous ou au-dessus du bloc assign, on souhaite obtenir un rsultat suprieur
linformation assigne.
Il est noter que linterface est volontairement relche. Par exemple, elle noblige pas ce que linforma-
tion lie une cl nayant aucune relation avec la cl assigne reste constante. Ainsi, une implmentation
lgitime de cette interface pourrait dgrader linformation dautres cls. Ceci a deux avantages. Dabord,
cela laisse plus de libert une implmentation de linterface, tout en restant assez fort pour prouver les

21
rsultats quon souhaite. Ensuite, cela facilite les preuves de conformit dune implmentation linterface,
puisque le rsultat prouver est plus faible. Par exemple, dans limplmentation la plus simple dcrite un
peu plus bas, la proprit suivante nest pas toujours vraie (L.eq dnote lgalit sur le treillis) :

Definition accurate_add: forall x y s m,


~ O.overlap x y -> L.eq (get x (add y s m)) (get x m).

Fragment de code Coq 3 : Proprit forte sur la fonction dajout

En revanche, linterface nautorise pas le raffinement de linformation lie une cl. On pourrait vouloir
rajouter pour cela une opration set qui effectue ce quon appelle une mise jour forte, dont le rsultat sur
une cl serait infrieur linformation stocke prcdemment pour cette cl. Cela serait en effet utile afin
de traiter les cas o lon sait avec certitude quun bloc abstrait correspondant un bloc concret unique va
tre cras. On sait cela sous les conditions suivantes :
ladresse laquelle seffectue lcriture doit tre statiquement value un emplacement mmoire
abstrait singleton : en effet, sil existe deux emplacements mmoire pouvant tre la destination de
lcriture, on ne peut affirmer quun de ces deux emplacements sera cras avec certitude.
le bloc abstrait de cet emplacement doit abstraire exactement un bloc concret cet instant de lexcu-
tion. Cest le cas des blocs de type Global et Stack, mais pas des blocs de type Alloc et Other, ainsi
que des blocs plus hauts dans la hirarchie. En effet, tous ceux-ci abstraient un ensemble de blocs
concrets, et en consquence un emplacement abstrait singleton tel que {(Alloc1, 0)} ne correspond
pas un unique emplacement concret (on ne pourrait laffirmer que si le site dallocation 1 se trouvait
avec certitude hors de toute boucle).
On ne sintresse pour linstant qu linterface prsente plus haut, sans se proccuper des mises jour
fortes. La manire la plus simple dimplmenter cette interface consiste implmenter un dictionnaire
paresseux sur cet espace de cl dont les oprations se comportent comme suit :
pour la recherche dune information associe une cl, on cherche la cl dans le dictionnaire et on
retourne lensemble associ. Si la cl nest pas prsente dans le dictionnaire, on cherche son parent
hirarchique, sil existe, et ainsi de suite (fig. 6).
lajout dune information pour une cl donne demande en revanche plus de travail : il faut parcourir
lensemble des cls, et pour toutes celles qui sont en hirarchie avec la cl ajoute, leur associer le plus
petit majorant de lensemble quelles contiennent et de lensemble ajout. Il faut aussi explicitement
ajouter la cl si elle ntait pas prsente, en initialisant son rsultat au plus petit majorant de la valeur
obtenue par sa recherche et de lensemble ajout.
Il subsiste une subtilit : que se passe-t-il en labsence des cls les plus hautes dans la hirarchie ? On peut
envisager deux alternatives.
Si lon suppose que les cls du sommet de la hirarchie peuvent tre absentes, il nous faut prciser
deux choses :
pour la recherche, si lon atteint le haut de la hirarchie sans succs, on retourne le minorant du
treillis .

22
(* Pseudo-notation : *)

M1 = { Block Allocs -> {A, B, C}


Block (Alloc 2) -> {B, C}
Loc (Alloc 2, 0) -> {C}
Block Stack -> {D} }
(* get (Loc (Alloc 2, 0)) M1 retourne {C}, cl prsente *)

M2 = { Block Allocs -> {A, B, C}


Block (Alloc 2) -> {B, C}
Block Stack -> {D} }
(* get (Loc (Alloc 2, 0)) M2 retourne {B, C}, premier parent trouv *)

Fig. 6 : Exemples de rsultats de la fonction de recherche dans le dictionnaire

pour lajout en revanche, on ne peut pas se permettre de ne rien faire : il nous faut toujours
sauvegarder linformation ajoute dans la cl la plus haute du treillis, faute de quoi on perd de
linformation, comme explicit dans la figure 8.
Puisque lalternative prcdente oblige rajouter les cls du sommet de la hirarchie ds le premier
ajout, une autre approche consiste les placer dans le dictionnaire ds le dbut, et conserver ce fait
comme un invariant :
pour la recherche, on est ainsi certain de toujours trouver une valeur en remontant dans la hi-
rarchie.
pour lajout, on na rien de plus faire que ce qui tait dcrit plus haut, et on a dsormais
la certitude que lajout dune cl prcdemment absente prend bien en compte linformation
accumule par son plus proche parent hirarchique.

4.5.3 Rduire la complexit de limplmentation

Cette structure partiellement nave ne savre tout de mme pas la plus efficace, pour plusieurs raisons :
dun point de vue algorithmique, lopration de recherche seffectue en une complexit linaire par
rapport la complexit de recherche dans le dictionnaire sous-jacent, puisquon demandera au plus un
nombre de cls fix par la hauteur de la hirarchie, mais lopration dajout est beaucoup plus coteuse
puisquelle doit parcourir lensemble des lments du dictionnaire, et potentiellement effectuer une
nouvelle insertion pour chacun deux.
dun point de vue de la preuve, lutilisation de plis sur la liste des lments du dictionnaire rend
compliques les preuves de conformit linterface. En effet, pour toute proprit, aprs avoir mon-
tr quune proprit est vraie un instant, soit au dbut, soit parce quil existe un lment dans le
dictionnaire qui ajoute cette proprit, il reste encore dmontrer que le reste du pli prserve cette
proprit.

23
M = {
Block Allocs -> {A, B, C}
Loc (Alloc 1, 0) -> {B}
Loc (Alloc 1, 1) -> {C}
Block (Alloc 2) -> {B, C}
Loc (Alloc 2, 0) -> {C}
Loc (Alloc 2, 1) -> {D}
Block Stack -> {A, E}
}

add (Block (Alloc 1)) {A, Z} M

M = {
Block Allocs -> {A, B, C, Z} anctre modifi
Block (Alloc 1) -> {A, Z} cl ajoute
Loc (Alloc 1, 0) -> {A, B, Z} descendant modifi
Loc (Alloc 1, 1) -> {A, C, Z} descendant modifi
Block (Alloc 2) -> {B, C}
Loc (Alloc 2, 0) -> {C}
Loc (Alloc 2, 1) -> {D}
Block Stack -> {A, E}
}

Fig. 7 : Exemple de rsultat de la fonction dajout dans le dictionnaire

En pratique, sur les quelques tests effectus, lanalyse ne sest pas montre particulirement lente : en
effet, tant que les fonctions restent assez courtes, elles manipulent ncessairement peu de valeurs, aussi il
est moins grave de parcourir tous les lments contenus dans le dictionnaire. Cependant, cela prsente des
risques pour passer lchelle, notamment sur des fonctions trs longues, ce qui peut tre le cas par exemple
avec du code gnr par des outils.
Aussi, on souhaite pouvoir changer cette implmentation que jai prouve par une implmentation plus
mme de nous satisfaire pour les deux points critiques mentionns ci-dessus. Cest ici que linterface, que
je navais pas mise en place auparavant, aide en dcouplant limplmentation des dictionnaires des preuves
effectues en aval.
Une ide de structure plus adapte serait la suivante : on pourrait calquer une hirarchie denregistrements
sur la hirarchie des cls, chacun de ces enregistrements comportant un ensemble de valeurs vers lequel la
cl situe cette hauteur hirarchique peut pointer, ainsi que deux dictionnaires, lun sur le domaine des
indices indiquant vers quelle valeur peut pointer chaque indice du bloc courant, et lautre sur le domaine
des cls de la strate hirarchique infrieure, pointant vers les enregistrements suivants (voir figure 9).
Les oprations simplmenteraient ainsi :
la recherche seffectue rcursivement depuis le sommet : tant quon nest pas sur la cl, on cherche
rcursivement dans le dictionnaire des sous-cls, si celui-ci existe. Si cette recherche russit, on re-
tourne le rsultat, sinon on retourne le rsultat contenu au niveau courant.
lajout dune information s une cl procde jusqu la cl en modifiant chaque cl parcourue pour
lui ajouter linformation s. Pour chaque cl sur le chemin qui nexistait pas, on la cre en prenant
linformation contenue dans son parent aprs ajout de s. La cl cherche est ensuite cre ou modifie

24
En l'absence de cls du sommet de la hirarchie :
M = {}
M' := add (Loc (Alloc 1, 0) S M
M' = { Loc (Alloc 1, 0) -> S}

get (Block Alloc) M'


retourne
FAUX !
En prsence de cls du sommet de la hirarchie :
M = { Block Allocs -> }
M' := add (Loc (Alloc 1, 0) S M
M' = { Block Allocs -> S,
Loc (Alloc 1, 0) -> S}
get (Block Alloc) M'
retourne S
CORRECT !

Fig. 8 : Problme lajout en labsence de cls du sommet de la hirarchie

de la mme faon, et si elle existait dj, on applique ensuite larbre des sous-cls une fonction qui
ajoute toutes celles-ci linformation s. Ainsi, toutes les cls en hirarchie avec la cl ajouter ont
effectivement t modifies.

4.5.4 Amliorer lapproximation

Il reste encore un souci que lon pourrait adresser avec une structure de donnes encore plus ad hoc :
lorsquune cl tait absente et quon souhaite lajouter avec une valeur s, on doit lui associer le plus petit
majorant de s et de la valeur obtenue par un get sur cette cl avant ajout, qui correspond la valeur de son
premier suprieur hirarchique prsent dans le dictionnaire.
Le problme est le suivant : parmi les lments prsents dans ce second ensemble, il en existe qui sont
ncessaires la correction, et qui rsultent dun ajout direct sur le bloc parent en question. Mais lautre
partie des lments provient de la propagation vers le haut effectue par add et ne concerne pas notre cl.
Lors de lajout de notre cl dans le dictionnaire, ce bruit vient sajouter linformation quon y stocke.
De par ce phnomne, plus une cl basse dans la hirarchie est ajoute tard, plus elle accumule de bruit li
aux modifications de ses surs et nices. On souhaiterait pouvoir filtrer ce bruit pour amliorer lapproxi-
mation, cest--dire satisfaire la proprit accurate_add dcrite plus haut. Cest possible en pratique en
associant toute cl deux ensembles plutt quun :
Le premier ensemble, que jappellerai mine, contient les valeurs qui ont t ajoutes directement la
cl en question.
Le second ensemble, que jappellerai noise, contient les valeurs qui ont t propages vers le haut
parce quelles ont t ajoutes un descendant de la cl en question.
En faisant cette distinction, il nous est possible de modifier limplmentation des fonctions get et set de

25
{ s: ptset
m: ptmap { s
m { s
m ...
Alloc 0
All Allocs

{ s
m { s

{ s
m ...
Globals
Alloc 1

{
(Alloc 1, 0)
s

(Alloc 1, 1)

{ s
m ...
Other
{ s

(Stack, 0)

{ s
m { s

Stack (Stack, 1)

Pour modier les cls en hirarchie avec Alloc 1 :


1) Parcourir et modier jusqu' Alloc 1 :
All, Allocs, Alloc 1
2) Appliquer la modication sur tout le sous-arbre m :
(Alloc 1, 0), (Alloc 1, 1)

Cot en parcours :
- 2 "nd" dans des arbres contenant peu d'indices
- 1 "map" sur un arbre ne contenant que des valeurs modier
(Prcdemment, 1 "map" sur toutes les cls existantes !)

Fig. 9 : Exemple de dictionnaire hirarchique

faon ce que le bruit ne soit pris en compte que l o il est ncessaire :


pour rechercher une cl donne dans le dictionnaire, on cherche tout dabord sa prsence. Si elle est
prsente, on retourne lunion de mine et noise. Si elle est absente, on retourne en revanche seulement
le mine de son parent (qui existera par construction).
pour ajouter une cl une information s dans le dictionnaire, il faut absolument modifier (et crer le
cas chant) les cls de ses anctres. Pour chacune delles :
si elle existe, on ajoute s son champ noise.
sinon, on la cre avec un mine gal celui de son parent et un noise gal s.
Une fois tous ses anctres crs, on peut soccuper de la cl elle-mme :
si elle existe, on ajoute s son champ mine.

26
sinon, on la cre avec un mine gal au plus petit majorant de s et du mine de son parent.
Il reste encore mettre jour toutes les cls descendantes de celle-ci, auxquelles on ajoute s leur
champ mine. Si le dictionnaire est cr de manire hirarchique, cela consiste utiliser une fonction
map sur ce dernier.

La preuve de conformit de cette implmentation linterface mentionne ci-dessus semble toutefois loin
dtre triviale, et na pas t faite.

27
5 Preuve formelle

5.1 Preuves de programmes, Coq


Afin de pouvoir vrifier mcaniquement des proprits relatives notre programme, on utilise lassistant
de preuves Coq. Celui-ci permet, dans une de ses utilisations les plus simples, de dfinir des programmes
dans un langage de programmation proche des ML, puis dnoncer des proprits sur ces programmes et
de les dmontrer en construisant de manire interactive un terme de preuve (les preuves sont en effet des
termes du langage).
Le langage utilis a tout de mme quelques spcificits : cest un langage fonctionnel pur (cest--dire sans
effets de bord), et la terminaison des fonctions doit tre assure. Pour ces raisons, on ne peut pas toujours
crire le programme quon souhaiterait sil impliquerait des effets ou une rcursivit non triviale.
La preuve des proprits seffectue de manire interactive : partir de la proprit que lon souhaite d-
montrer, Coq nous affiche les hypothses dont on dispose dans le contexte courant, et les sous-buts que lon
doit dmontrer. On peut alors indiquer, grce un langage de tactiques, la faon dont on souhaite procder
dans la preuve. Par exemple, on peut choisir dappliquer un lemme ou un axiome vers lavant, pour faire
progresser nos hypothses en direction de la proprit prouver, ou vers larrire si lon souhaite prouver
un but intermdiaire qui implique notre but.
On peut galement indiquer quon souhaite raisonner par induction sur une structure inductive, auquel
cas Coq soccupe de construire les cas de base et les cas inductifs prouver, et de crer les hypothses
dinduction adquates.
Par exemple, la preuve ci-dessous dmontre quune proprit P est vraie pour un terme fold_left f l s
(correspondant la rduction dune liste par un oprateur associatif gauche) si la proprit est vraie pour
le terme s et est prserve par application de f gauche :

Lemma fold_left_preserves_prop:
forall S F (P: S -> Prop) (f: S -> F -> S) l s,
P s ->
(forall x y, P x -> P (f x y)) ->
P (fold_left f l s).
Proof.
induction l; simpl; auto.
Qed.

Fragment de code Coq 4 : Exemple de la syntaxe dune proprit Coq

Par la suite, jutiliserai parfois une notation plus fonctionnelle des proprits, me permettant de nommer les
hypothses afin dy faire rfrence plus aisment. Ainsi, lnonc ci-dessus est moralement quivalent au
suivant :

28
Lemma fold_left_preserves_prop:
forall S F (P: S -> Prop) (f: S -> F -> S) l s
(INIT: P s)
(STEP: forall x y, P x -> P (f x y))
,
P (fold_left f l s).

Fragment de code Coq 5 : Exemple de syntaxe alternative dune proprit Coq

Les hypothses sont ainsi rellement des paramtres dont les types sont dpendants des valeurs dautres
paramtres. Le second style permet de donner un nom ces paramtres, alors que dans le premier style on
peut seulement les nommer lorsquon les introduit dans le cours de la preuve.

5.2 Principe de la preuve, proprits attendues


On suppose pour linstant que lalgorithme termine pour toutes les fonctions du programme tudi. Pour
chacune de ces fonctions, il retourne une structure associant chaque point du programme une seconde
structure, celle-ci associant chaque emplacement mmoire abstrait ainsi qu chaque registre un ensemble
demplacements mmoire vers lesquels celui-ci peut pointer cet endroit de lexcution.
On souhaite quune excution relle du programme ne sorte pas des limites fixes par cette approximation :
il ne doit pas exister de pointeur dont la destination nest pas incluse dans lapproximation de ce vers quoi
pointe sa source.
On introduit la notion dabstracteur demplacement mmoire comme une fonction partielle abs : block
-> option absblock des blocs concrets vers les blocs abstraits. On voudra videmment poser des restric-
tions sur la faon dont sont abstraits les blocs.
On dfinit la proprit valsat comme :

Definition valsat (v: val) (abs: abstracter) (s: ptset) :=


match v with
| Vptr b o =>
match abs b with
| Some ab => PTS.In (ab, o) s
| None => PTS.eq s PTS.top
end
| _ => True
end.

Fragment de code Coq 6 : Proprit valsat

Cette proprit dnote le fait quune valeur concrte (pouvant tre un entier, un nombre virgule flottante, ou
un pointeur) pointe dans un ensemble demplacements mmoire abstraits, conformment un abstracteur.

29
Si cette valeur nest pas un pointeur, cette proprit est trivialement vraie. Sinon, elle pointe vers un bloc b
un indice o, et lon souhaite alors que lemplacement abstrait correspond ce bloc et cet indice soit compris
dans lensemble s. Si, pour une raison quelconque, on ne sait pas abstraire le bloc en question, on demande
alors ce que lensemble s soit quivalent S .
Ainsi, tout moment de lexcution, on souhaite que la valeur contenue dans chaque registre satisfasse la
proprit valsat par rapport labstracteur courant et au rsultat associ ce registre par lanalyse :

Definition regsat (r: reg) (rs: regset) (abs: abstracter) (ptm: ptmap) :=
valsat rs#r abs (mpt (Reg r) ptm).

Fragment de code Coq 7 : Proprit regsat

Lon souhaite galement que pour tout emplacement mmoire concret valide en lecture, la valeur quil
contient satisfasse la mme proprit :

Definition memsat (b: block) (o: offset) (m: mem) (abs: abstracter) (ptm: ptmap) :=
forall v
(LOAD: Mem.loadv Mint32 m (Vptr b o) = Some v)
,
(match abs b with
| Some ab => valsat v abs (mpt (Loc (ab, o)) ptm)
| None => False
end).

Fragment de code Coq 8 : Proprit memsat

Le cas o on ne sait pas abstraire le bloc est impossible : en effet, on exigera par la suite que tout bloc valide
soit correctement abstrait, et par consquence, tout bloc depuis lequel un chargement russit lest aussi.
La preuve consiste alors montrer qu toute tape du programme, il existe un abstracteur tel que les
proprits regsat et memsat soient vrifies pour le rsultat de lanalyse cette tape. La reprsentation
graphique en figure 10 reprsente cette ide : tout moment de lexcution, reprsente gauche, lensemble
des relations de pointage concrtes, notes par des flches rouges, doit tre surapproxim par lensemble
des relations de pointage calcules par lanalyse reprsentes par des flches bleues. Cette approximation
est faite une projection par abstraction prs : pour toute paire dlments concrets x et y (x pouvant
tre un registre ou un emplacement, y tant un emplacement), si x pointe vers y lors de lexcution, alors
labstraction de x doit pointer vers labstraction de y dans le rsultat de lanalyse (labstraction dun registre
correspondant toujours ce mme registre).
Cependant, il nous faut introduire des proprits supplmentaires pour mener bien la preuve : celles-ci
constitueront un invariant qui renforcera nos hypothses, mais dont il faudra prouver quil est maintenu au
cours de la preuve.

30
Excution Abstraction Analyse
blocs concrets blocs abstraits

Stack

R1 R1

R2 R2
Global 1
R3 R3

R4 R4

R5 R5
Alloc 3
R6 R6

Alloc 3

Fig. 10 : Principe de la preuve


.

5.3 Invariants de preuve


Le choix des invariants est donc crucial : un invariant trop faible ne permettra pas de mener la preuve son
terme, alors quun invariant trop fort sera dur (voire impossible) maintenir. Ces invariants ont donc t
raffins au cours de la preuve pour trouver un bon compromis. La plupart de ces invariants cadrent notre
abstracteur afin davoir de bonnes proprits.
Le premier justifie le fait davoir choisi une fonction partielle (rendue totale par un type option) pour les
abstracteurs :

Definition ok_abs_mem (abs: abstracter) (m: mem) :=


forall b, abs b <> None <-> Mem.valid_block m b.

Fragment de code Coq 9 : Proprit ok_abs_mem

Cette proprit indique que tout bloc abstrait par labstracteur est un bloc valide pour la mmoire courante,
et rciproquement. Ceci nous permet dliminer dans la preuve la possibilit quun bloc nouvellement allou
puisse tre dj contenu dans labstracteur.
Le deuxime invariant force notre abstracteur abstraire les blocs globaux de manire approprie :

31
Definition ok_abs_genv (abs: abstracter) (ge: genv) :=
forall i b
(FIND: Genv.find_symbol ge i = Some b)
,
abs b = Some (Global i).

Fragment de code Coq 10 : Proprit ok_abs_genv

Il exprime que le bloc didentifiant i de lenvironnement global ge doit tre abstrait par Global i. Ainsi, on
sait que le rsultat de lanalyse pour le bloc abstrait en question est bien celui utilis pour valider la valeur
du bloc concret correspondant. Cet invariant est primordial afin de montrer que le rsultat de lanalyse est
correct lorsquon est confront une opration dont le mode dadressage est global.
Le troisime invariant permet doublier labstracteur dune fonction f lors de lappel dune fonction g, puis
den reconstruire un lorsque g termine tel quil satisfasse toujours les proprits dinvariance :

Inductive ok_stack (ge: genv) (b: block): list stackframe -> Prop :=
| ok_stack_nil:
ok_stack ge b nil
| ok_stack_cons: forall r f bsp osp pc rs stk ptmm abs
(STK: ok_stack ge b stk)
(MEM: forall b, abs b <> None -> b < b)
(GENV: ok_abs_genv abs ge)
(SP: abs bsp = Some Stack)
(RES: funanalysis f = Some ptmm)
(RSAT: forall r, regsat r rs abs (ptmm#pc))
(RET: PTS.eq (mpt (Reg r) ptmm#pc) PTS.top)
(MTOP: mem_at_top ptmm#pc)
,
ok_stack ge b (Stackframe r f (Vptr bsp osp) pc rs :: stk)

Fragment de code Coq 11 : Proprit ok_stack

Cette proprit de la pile prend deux paramtres : lenvironnement global du programme, et un bloc. Cest
une proprit inductive, trivialement satisfaite pour une pile vide. Dans le cas dune pile non vide, la pro-
prit exprime les faits suivants :
elle est inductivement vrifie sur le reste de la pile.
la tte de pile a la forme Stackframe r f (Vptr bsp osp) pc rs, correspondant au bloc dac-
tivation de la fonction appelante f. La proprit indique donc notamment que la valeur du pointeur
de pile est effectivement un pointeur.
il existe un abstracteur abs et un rsultat danalyse ptmm tels que :
labstracteur nest dfini que pour des blocs infrieurs b (le modle mmoire impliquant que
le bloc b a t allou avant le bloc b.

32
labstracteur satisfait lenvironnement global ge.
labstracteur abstrait le bloc dactivation vers le bloc abstrait Stack.
ptmm est le rsultat de lanalyse dalias sur la fonction f.
ltat des registres stock dans le bloc dactivation satisfait le rsultat linstruction suivant
lappel dans la fonction f, conformment abs.
le registre r, dans lequel sera plac la valeur retourne par la fonction appele, doit tre reli
lensemble S par le rsultat de lanalyse linstruction suivant lappel.
la mmoire doit tre relie lensemble S dans le rsultat de lanalyse linstruction suivant
lappel.
On peut noter que ces proprits ressemblent fortement aux proprits vraies pour la fonction courante, qui
seront dcrites ci-aprs. Sans cette proprit, il nous est trs dur de reconstruire un abstracteur en retour
de la fonction. Il faudrait pour cela pouvoir discriminer les blocs qui ont t allous depuis lappel et les
abstraire en Other, puis retrouver quels blocs taient allous quel endroit et restaurer les abstractions
correspondantes.
Une autre solution au problme consisterait empiler les abstracteurs au fur et mesure des appels dans
notre invariant, conservant ainsi une pile dabstracteurs en parallle de la pile dexcution. Cette solution est
cependant bien plus dsagrable maintenir dans la preuve. Puisque ce code na pas vocation tre extrait,
on choisit dutiliser la logique classique pour oublier la preuve constructive de lexistence de labstracteur,
et seulement conserver une proprit dexistence de ce dernier.
On peut prsent dcrire la proprit invariante de lexcution du programme, qui pour un rsultat danalyse
et un abstracteur donns dans un environnement indique que ltat courant dexcution satisfait ceux-ci :

33
Inductive satisfy (ge: genv) (ptmm: result) (abs: abstracter): state -> Prop :=
| satisfy_state: forall cs f bsp pc rs m ptm
(STK: ok_stack ge (Mem.nextblock m) cs)
(MEM: ok_abs_mem abs m)
(GENV: ok_abs_genv abs ge)
(SP: abs bsp = Some Stack)
(RES: funanalysis f = Some ptmm)
(PTM: ptm = ptmm#pc)
(WF: PTM.well_formed ptm)
(RSAT: forall r, regsat r rs abs ptm)
(MSAT: forall b o, memsat b o m abs ptm)
,
satisfy ge ptmm abs (State cs f (Vptr bsp Int.zero) pc rs m)
| satisfy_callstate: forall cs f args m
(MEM: ok_abs_mem abs m)
(STK: ok_stack ge (Mem.nextblock m) cs)
(GENV: ok_abs_genv abs ge)
,
satisfy ge ptmm abs (Callstate cs f args m)
| satisfy_returnstate: forall cs v m
(MEM: ok_abs_mem abs m)
(STK: ok_stack ge (Mem.nextblock m) cs)
(GENV: ok_abs_genv abs ge)
,
satisfy ge ptmm abs (Returnstate cs v m)
.

Fragment de code Coq 12 : Proprit satisfy

Cette proprit inductive considre les trois tapes diffrentes dexcution dun programme :
Ltat State cs f (Vptr bsp Int.zero) pc rs m est un tat en cours dexcution dans une
fonction f. cs constitue la pile, Vptr bsp Int.zero est le pointeur de pile, toujours situ lindice
zro du bloc de pile courant, pc est le compteur programme, qui est simplement un indice du graphe
de flot de contrle, rs reprsente ltat des registres, associant chaque registre une valeur numrique
ou un pointeur, et m reprsente ltat mmoire. Dans ces conditions :
lhypothse RES dfinit ptmm comme le rsultat de lanalyse de la fonction courante.
les hypothses RSAT et MSAT spcifient que tout registre et tout emplacement mmoire satisfait
respectivement regsat et memsat vues plus haut.
lhypothse WF nest pas lie au droulement de la preuve directement, elle indique seulement
que le rsultat de lanalyse doit correspondre certains critres pour tre valide. Cette hypothse
est uniquement place dans cet invariant parce quil est simple de la faire voyager avec lui.
les autres proprits ont t dcrites plus haut.

34
Ltat Callstate cs f args m correspond la transition vers un appel de la fonction f. Les infor-
mations de lappelant ont t stockes en tte de cs (notamment ltat des registres), les arguments de
lappel sont dans args et la mmoire dans m. Dans cette tape, on na seulement besoin des invariants
en rapport avec la mmoire et la pile.
Ltat Returnstate cs v m correspond un retour dappel de fonction. Ltat sera rtabli partir
de llment en tte de la pile cs, et v est la valeur retourne par la fonction (et qui sera place dans
un registre indiqu dans la pile). De mme, seules les proprits lies la mmoire et la pile sont
ncessaires dans un tel tat transitoire.
Enfin, les deux thormes quon souhaite dmontrer sont les suivants :

Theorem satisfy_init:
forall p st
(IS: initial_state p st)
,
exists abs, exists ptm, satisfy (Genv.globalenv p) ptm abs st.

Fragment de code Coq 13 : Thorme satisfy_init

Ce premier thorme sert montrer quil existe un abstracteur initial permettant de dmarrer la vrification
de lanalyse.
Afin de prouver ce thorme, il nous faut cependant assurer que lanalyse retourne un rsultat quelles que
soit les fonctions f du programme p. Ainsi, il faut prendre soin du cas o lanalyse choue en puisant le
carburant, auquel cas on renverra un rsultat trivialement vrai :

Definition safe_funanalysis f :=
match funanalysis f with
| Some res => res
| None => PTM.top
end.

Fragment de code Coq 14 : Encapsulation de funanalysis en cas dpuisement du carburant

Le second thorme permet de construire incrmentalement, partir du thorme prcdent, lexistence


dun abstracteur chaque tape de lexcution :

35
Theorem satisfy_step:
forall ge st t st ptm abs
(FAOK: forall f, {ff | funanalysis f = Some ff})
(STEP: step ge st t st)
(SAT: satisfy ge ptm abs st)
,
exists ptm, exists abs, satisfy ge ptm abs st.

Fragment de code Coq 15 : Thorme satisfy_step

Ce thorme indique que si lon connat un rsultat et une abstraction encadrant correctement ltat dun
programme dans ltat st, et que ce programme progresse correctement vers un tat st, alors on sait crer
un rsultat ptm et un abstracteur abs qui encadrent correctement le nouvel tat.
Bien que prsente sous cette forme, la proprit laisse penser quon peut utiliser nimporte quel rsultat
pour ptm, il ne faut pas oublier que la proprit satisfy force lunification de ce rsultat avec celui de
la fonction courante via lhypothse RES. On a prfr quantifier existentiellement cette valeur ici parce
que les tats Callstate et Returnstate ne contiennent pas explicitement de fonction, et se contentent du
rsultat qui prcdait dans lvaluation du programme.

5.4 volution de labstracteur au cours de la preuve


Il est intressant de regarder comment volue labstracteur, bien que ce rsultat soit peut tonnant.
Ainsi, pour toutes les instructions RTL lexception de EF_malloc, labstracteur nest pas modifi. Pour
ce dernier, il est modifi de la sorte :

exists (fun x => if zeq x b then Some (Alloc pc) else abs x).

Lancien abstracteur est conserv, mais le bloc b qui vient juste dtre allou est associ la valeur Alloc
pc, o pc reprsente videmment lindice de linstruction courante.

Un second cas o lon modifie labstracteur est lorsquon entre dans un appel de fonction. Dans ce cas, on
le modifie ainsi :

36
exists (fun b =>
if zeq b stk then Some Stack else
match abs b with
| Some ab => Some
(
match ab with
| Global i => Global i
| Globals => Globals
| _ => Other
end
)
| None => None
end
).

stk est le bloc dactivation qui vient dtre allou, il est donc abstrait en Stack. Tous les autres blocs
prcdemment valides sont envoys sur le bloc Other, lexception des blocs globaux qui sont prservs,
conformment nos invariants.
Enfin, quand on retourne dun appel de fonction, on jette labstracteur courant, et on dtruit la proprit
ok_stack afin dobtenir un abstracteur qui vrifie les proprits qui taient vraies juste avant lappel de
fonction. Il ne nous reste plus qu modifier celui-ci pour prendre en compte les blocs qui nexistaient pas
encore cet instant :

exists (fun b =>


match abs0 b with
| Some _ => abs0 b
| None => if (zlt b (Mem.nextblock m0)) then Some Other else None
end
).

Ici, on conserve toutes les abstractions existantes avant lappel, et on associe tous les blocs qui ont t
crs depuis (par construction, ceux qui sont infrieurs au prochain bloc de la mmoire) par Other. Les
autres blocs ne sont pas valides, donc pas abstraits.

37
6 Mesures, tests et rsultats

6.1 Donnes numriques diverses


3636 lignes de code Coq
dont 1397 lignes de spcification
* une moiti dfinissant lanalyse statique et son implmentation.
* lautre moiti dfinissant les proprits et les noncs des thormes.
dont 2239 lignes de preuves
* environ 1275 lignes pour les proprits des structures de donnes
* environ 964 lignes constituent la preuve principale
26 lignes dOCaml pour instrumenter lanalyse
216 lignes dOCaml pour afficher les rsultats de lanalyse
Temps ncessaire pour vrifier mes preuves (Intel Xeon Quad-Core W3565 3.20GHz) :
time make -j4 proof
[...]
real 2m20.103s
user 2m33.466s
sys 0m4.516s

Tab. 1 : Simple valuation du cot additionnel d lanalyse

Test (lignes de code) Compilation sans analyse (s) Compilation avec analyse (s)
chomp (370) 0.080 0.115
aes (1453) 0.160 0.448
raytracer (2560) 1.713 2.723
compression (4788) 0.985 1.215
spass (69073) 16.906 56.693

6.2 tude du rsultat dune analyse


Cette section prsente un code C, sa compilation en code RTL, et le rsultat dune excution de lanalyse
sur ce code, suivi des dtails expliquant ces rsultats.
Pour raliser ces tests, il a fallu modifier lgrement CompCert afin que les appels malloc soient correcte-
ment remplacs par des appels Ibuiltin EF_malloc, alors que la version actuelle les laisse sous la forme
dappels de fonction. Sans cette modification, lanalyse est beaucoup moins prcise puisquelle ne sait pas
reconnatre les oprations dallocation mmoire. Il serait possible de contourner ce problme autrement, en
dtectant les malloc pendant lanalyse, mais cela rendrait la preuve plus complexe inutilement.

38
Considrons donc le code C suivant. Celui-ci contient notamment des allocations dynamiques, une structure
conditionnelle affectant la mme variable deux valeurs distinctes dans deux branches distinctes, ainsi
quune boucle sur un tableau :

#include<stdlib.h>

#define SIZE 10

int main(int argc) {


int x, y, z, *p1, *p2, *p3, *p4, **t ;
x = argc ;

p1 = &x ;
p2 = malloc(sizeof(int)) ;
if (x < 0) {
p3 = p1 ;
} else {
p3 = malloc(sizeof(int)) ;
}
t = malloc(SIZE * sizeof(int)) ;
for (y = 0 ; y < SIZE ; y++)
{
t[y] = &z ;
}
p4 = t[0] ;
}

39
Ceci se traduit en pseudo-syntaxe RTL ainsi :

f(x1) {
23 : int32[stack(0)] = x1
22 : x9 = stack(0)
21 : x18 = 4
20 : x2 = builtin malloc(x18)
19 : x8 = x2
18 : x17 = int32[stack(0)]
17 : if (x17 <s 0) goto 13 else goto 16
16 : x16 = 4
15 : x3 = builtin malloc(x16)
14 : x7 = x3
goto 12
13 : x7 = x9
12 : x15 = 40
11 : x4 = builtin malloc(x15)
10 : x5 = x4
9 : x11 = 0
8 : nop
7 : if (x11 <s 10) goto 6 else goto 2
6 : x14 = stack(4)
5 : int32[x5 + x11 * 4 + 0] = x14
4 : x11 = x11 + 1
3 : goto 7
2 : x6 = int32[x5 + 0]
1 : return x13
}

Les accs mmoire sont dnots ici par la syntaxe s[o] o s reprsente la taille de laccs et o lindice. Le
graphe de flot de contrle est reprsent linaris, le flot tant dfini par des instructions goto, qui ne font
pas partie du langage RTL, puisque celui-ci est reprsent sous la forme dun graphe.
Le rsultat de lanalyse est donn en figure 11, et expliqu pas pas ci-aprs.

40
Globals , Other , R1
int32[stack(0)] = R1
Stack , (Stack, 0)
R9 = stack(0)
R9
R18 = 4

R2 = malloc(R18)
R2 (, {(Alloc20, 0)})
R8 = R2
R8 (, {(Alloc20, 0)})
R17 = int32[stack(0)]
R17
if (R17 <s 0)

R16 = 4

R3 = malloc(R16) R7 = R9
R3 (, {(Alloc15, 0)})
R7 = R3
R7 (, {(Alloc15, 0), (Stack, 0)})
R15 = 40

R4 = malloc(R15)
R4 (, {(Alloc11, 0)})
R5 = R4
R5 (, {(Alloc11, 0)})
R11 = 0

nop
Alloc 11 (, {(Stack, 4)}), Allocs (, {(Stack, 4)}), R14 (, {(Stack, 4)})
if (R11 <s 10)

R14 = stack(4) R6 = int32[R5]


R6 (, {(Stack, 4)})
int32[R5 + R11 * 4] = R14 return R13

. +1
R11 = R11

Fig. 11 : Rsultat de lanalyse sur lexemple, reprsent de manire incrmentale

41
Globals ->
Other ->
R1 ->

En entre de fonction, les blocs globaux et autres peuvent potentiellement pointer vers tout (en ralit, ils
ne peuvent pas pointer vers la pile, ni vers les blocs qui seront allous plus tard, mais cest plus complexe
dmontrer). De mme, le registre pass en paramtre la fonction peut ventuellement pointer vers tout.
prsent, je montrerai linstruction excute, dabord en C, puis en RTL, suivie du rsultat diffrentiel par
rapport au rsultat du nud prcdent, en vitant de rpter les relations qui nont pas chang.

x = argc ;

23 : Istore(chunk=Mint32, addr=Ainstack 0, args=[], src=R1, succ=22)


Stack ->
(Stack, 0) ->

En stockant argc dans la variable x, on crit en pile lindice 0 la valeur contenue dans le registre pass
en paramtre. Ainsi, cette case mmoire passe S , et il en va de mme pour le bloc reprsentant la pile
entire, tout indice confondu. Il serait ici intressant de savoir que largument de ligne de commande ne
peut pas tre un pointeur afin de ne pas perdre cette prcision ds le dpart.

p1 = &x ;

22 : Iop(op=Olea Ainstack 0, args=[], dest=R9, succ=21)


R9 -> ({}, {(Stack, 0)})

Il sagit ici dun simple rfrencement.

p2 = malloc(sizeof(int)) ;

21 : Iop(op=Ointconst 4, args=[], dest=R18, succ=20)


20 : Ibuiltin(ef=Malloc, args=[R18], dest=R2, succ=19)
R2 -> ({}, {(Alloc 20, 0)})
19 : Iop(op=Omove, args=[R2], dest=R8, succ=18)
R8 -> ({}, {(Alloc 20, 0)})

Lopration 21 naltre pas le rsultat. Lallocation dynamique en 20 cre pour sa part un bloc Alloc 20,
vers lequel pointe le registre destination correspondant la variable p2. Lopration 19 est un artefact sans
intrt d aux optimisations prcdant lanalyse.

if(x < 0)

42
18 : Iload(chunk=Mint32, addr=Ainstack 0, args=[], dest=R17, succ=17)
R17 ->
17 : Icond(args=[R17], ifso=13, ifnot=16)

On recharge x et on le compare 0. Rien de spcial ici, si ce nest le manque de prcision hrit de lins-
truction 23.

p3 = malloc(sizeof(int)) ;

16 : Iop(op=Ointconst 4, args=[], dest=R16, succ=15)


15 : Ibuiltin(ef=Malloc, args=[R16], dest=R3, succ=14)
R3 -> ({}, {(Alloc 15, 0)})
14 : Iop(op=Omove, args=[R3], dest=R7, succ=12)

Dans la branche de droite, une allocation dynamique similaire celle tudie prcdemment a lieu. Le
rsultat en sortie de 14 nest pas montr ici puisquil sera fusionn avec le rsultat en sortie de lautre
branche pour donner le rsultat en entre de 12.

p3 = p1 ;

13 : Iop(op=Omove, args=[R9], dest=R7, succ=12)

De mme, je ne montre pas ici le rsultat aprs 13 qui sera fusionn avec celui mentionn prcdemment
en entre de 12 :

t = malloc(SIZE * sizeof(int)) ;

R7 -> ({}, {(Alloc 15, 0), (Stack, 0)})


12 : Iop(op=Ointconst 40, args=[], dest=R15, succ=11)
11 : Ibuiltin(ef=Malloc, args=[R15], dest=R4, succ=10)
R4 -> ({}, {(Alloc 11, 0)})
10 : Iop(op=Omove, args=[R4], dest=R5, succ=9)
R5 -> ({}, {(Alloc 11, 0)})

On remarque ici lapparition simultane de deux emplacements mmoire pouvant tre points par le re-
gistre 7 en entre de linstruction 12. Ces deux pointeurs correspondent aux deux branches de la structure
conditionnelle, conformment ce quon peut attendre. La suite est une autre allocation dynamique.

for (y = 0 ; y < SIZE ; y++)

43
9 : Iop(op=Ointconst 0, args=[], dest=R11, succ=8)
8 : Inop(succ=7)
Alloc 11 -> ({}, {(Stack, 4)})
Allocs -> ({}, {(Stack, 4)})
R11 -> ({}, {})
R14 -> ({}, {(Stack, 4)})
7 : Icond(args=[R11], ifso=6, ifnot=2)

Ces oprations naltrent pas le rsultat, cependant on remarque beaucoup de changements partir de lins-
truction 8. En effet, cette instruction est un point de bouclage pour notre boucle for, et a donc accumul
par point fixe les rsultats du corps de la boucle. Nous allons voir plus bas quelles instructions ont ajout
chacune de ces relations.

t[y] = &z ;

6 : Iop(op=Olea Ainstack 4, args=[], dest=R14, succ=5)


5 : Istore(chunk=Mint32, addr=Aindexed2scaled (4, 0), args=[R5, R11], src=R14, succ=4)
4 : Iop(op=Olea Aindexed 1, args=[R11], dest=R11, succ=3)

Linstruction 6 avait donc rajout la relation R14 -> ({}, {(Stack, 4)}). Linstruction 5 est intressante :
en labsence dinformation sur la valeur de y, elle a ajout les relations Alloc 11 -> ({}, {(Stack, 4)})
et Allocs -> ({}, {(Stack, 4)}). Ainsi, cest tout le bloc allou qui peut pointer vers z. Si laffectation
avait eu la forme t[0] = &z ;, le rsultat aurait t beaucoup plus prcis, de la forme (Alloc 11, 0) ->
({}, {(Stack, 4)}).

Enfin, linstruction 4 correspond y++, et ajoute une relation R11 -> ({}, {}).

p4 = t[0] ;

2 : Iload(chunk=Mint32, addr=Aindexed 0, args=[R5], dest=R6, succ=1)


R6 -> ({}, {(Stack, 4)})
1 : Ireturn(r=Some R13)

Finalement, un chargement ladresse 0 depuis un pointeur situ dans le registre 5, qui cet instant peut
pointer vers ({}, {(Alloc 11, 0)}), et sachant que lintgralit du bloc Alloc 11 peut pointer vers
({}, {(Stack, 4)}) rsulte en lajout de la relation ({}, {(Stack, 4)}).

44
7 Limites et amliorations
mesure que le stage avanait, jai pu observer des limites apparatre dans mon dveloppement, en plus
de celles quon stait fixes dans le cahier des charges.
La premire limite vidente est le fait que lanalyse soit intra-procdurale : de fait, les rsultats sont peu
prcis et les approximations conservatrices. Une analyse inter-procdurale serait plus prcise, mais plus
coteuse et plus difficile prouver, et obligerait probablement changer le flot de contrle du compilateur,
qui traite pour linstant les fonctions du programme une par une de bout en bout, sparment.
Une seconde limite, cette fois-ci de mon implmentation, est labsence de mises jour fortes : lorsquon
sait quun registre ou un emplacement mmoire ne peut pointer que vers un emplacement singleton, et que
le bloc abstrait de cet emplacement abstrait un unique bloc concret, comme ce serait le cas pour les blocs
Global et Stack, une mise jour de la valeur pointe est certaine dcraser celle-ci, et lon pourrait vouloir
effectuer un remplacement plutt quun calcul de borne suprieure dans ce cas. Cependant, la preuve de
cette amlioration nest pas faite et demande plus de travail.
Une autre limite est cette fois de au modle de CompCert : il est en effet impossible de sparer strictement
les variables contenant des pointeurs des variables contenant des entiers. Ceci implique par exemple que
tous les paramtres passs la fonction qui ont un type entier sont traits conservativement en prsumant
quils puissent contenir des pointeurs, ce qui explique quen entre de fonction les registres des arguments
sont abstraits vers S . On pourrait imaginer remdier ce problme en ayant une analyse plus en amont
dinfrence de types qui nous indique quavec certitude, un registre ne peut pas contenir un pointeur. Cette
proprit est en effet dductible dans certains cas, par exemple si la valeur du registre est utilise comme
oprande dune multiplication.
Plusieurs amliorations de la preuve existante sont galement envisageables. On pourrait tout dabord s-
parer deux rsultats, pour les registres et la mmoire, comme je lai fait dans ce rapport. Ceci simplifierait
des preuves puisque chaque opration ne modifie gnralement que lune de ces deux ptmap.
La preuve pourrait aussi tre rendue plus robuste par endroits : certaines preuves sont en effet trs rptitives,
mais leffort demand pour les automatiser nest pas toujours rentable. Cependant, on paie parfois le prix
dune preuve trop manuelle lors dune refactorisation.

45
8 Conclusion
Le rsultat de mon stage est la dmonstration de faisabilit dune analyse dalias intra-procdurale, de bas
niveau, sensible au flot et aux champs, insensible au contexte et aux chemins dexcution, pour le langage
intermdiaire RTL du projet CompCert.
Jai effectu cette preuve par une mthode dite dinterprtation abstraite, cest--dire en effectuant une
projection de ltat rel dexcution sur un tat abstrait plus approximatif, et en assurant la validit des
rsultats sur cette surapproximation des tats possibles du systme.
Il reste prsent plusieurs chemins poursuivre : amliorer la preuve et limplmentation actuelles, affi-
ner les optimisations existantes en tenant compte des rsultats de lanalyse, et envisager des analyses plus
ambitieuses (inter-procdurale par exemple) ou plus efficaces (insensible au flot).
Ce projet de fin dtudes tait fort intressant pour plusieurs raisons : tout dabord, il ma permis de dcou-
vrir plus en dtail le travail de recherche, et lambiance de travail dans un laboratoire. Travailler avec les
personnes de lquipe Gallium tait particulirement enrichissant.
Ensuite, le sujet du projet lui-mme tait trs intressant, et en raccord avec les cours que javais suivis
prcdemment en France et aux tats-Unis, et il tait donc agrable de mettre lpreuve ces connaissances
sur un projet concret. Lapprentissage de Coq tait fort enrichissant, notamment par des erreurs de dbutant
que jai commises en cours de dveloppement et qui se sont rvles lorsque la preuve atteignait les recoins
pineux du langage RTL et mettait en exergue la faiblesse de mes invariants.
Si le script de preuve ainsi que limplmentation mritent encore des retouches, la preuve a dsormais t
tablie quil est possible de prouver une analyse dalias non triviale dans un compilateur vrifi, et jai
fourni une implmentation de rfrence.
lissue de ce stage, je candidate un poste de doctorant afin de pouvoir continuer travailler dans ce
domaine et explorer ce que peuvent apporter les mthodes formelles aux domaines du gnie logiciel et de la
compilation. En attendant les dcisions quant ma candidature lUniversit de Californie, San Diego, je
continuerai de travailler dans lquipe Gallium, cette fois-ci sur les dernires tapes du processus de com-
pilation : lassembleur et lditeur de liens. Je travaillerai valider le rsultat de ces tapes de compilation
en OCaml, puisque les oprations effectues ce stage manquent dune smantique formelle approprie
la preuve de proprits. Jai galement reu de mon matre de stage une proposition de thse porte sur la
formalisation dun domaine abstrait pour la preuve danalyses statiques de programmes, sujet qui sinscrit
assez bien dans la continuit du travail que jai effectu durant ce stage. Ainsi, je devrais pouvoir dmarrer
une thse dans une des quipes pour lesquelles je postule compter de septembre 2012.

46
A Correspondances de notations
Ci-dessous sont dcrites quelques correspondances de notation entre les conventions mathmatiques de ce
rapport, et le code Coq prsent dans ce rapport et dans le dveloppement :

Tab. 2 : Table de correspondance des notations

Notations mathmatiques Notations Coq Description


[x 7 s] reprsente la structure de donnes qui associe s
x et au reste (notation surcharge dont le type
est aisment infrable dans les contextes)
(b, o) s PTS.In (b, o) s appartenance dun emplacement abstrait un en-
semble
r(r0) (mpt (Reg r0) r) retourne lensemble que peut pointer r0 dans le r-
sultat danalyse r
rs#r0 retourne la valeur contenue dans le registre r0
S ptset type des ensembles demplacements mmoire
(r, m) ptm les deux rsultats sont regroups dans limplmen-
tation actuelle
R et M ptmap types des rsultats de lanalyse
B absblock type des blocs abstraits
abs abstracteur

47
B Code Coq de la fonction de transfert
La fonction de transfert dcrite en notations mathmatiques dans le rapport est donne dans sa forme Coq
ci-dessous.
Tout dabord, la fonction de calcul dadressage @ :

Definition mpt_addr addr args m :=


match addr, args with
| Aindexed o, r::nil =>
Some (shift_ptset (mpt (Reg r) m) o)
| Aindexed2 _, r1::r2::nil =>
Some (
PTS.lub
(unknown_offset (mpt (Reg r1) m))
(unknown_offset (mpt (Reg r2) m))
)
| Ascaled _ _, _::nil => Some PTS.bot
| Aindexed2scaled _ _, r::_::nil =>
Some (unknown_offset (mpt (Reg r) m))
| Aglobal i o, nil =>
Some (ABS.empty, ALS.singleton (Global i, o))
| Abased i _, _::nil
| Abasedscaled _ i _, _::nil =>
Some (ABS.singleton (Global i), ALS.empty)
| Ainstack o, nil =>
Some (ABS.empty, ALS.singleton (Stack, o))
| _, _ => None
end.

Fragment de code Coq 16 : Fonction de transfert, calcul dadressage

48
La fonction Op :

Definition transf_op op args dst m :=


match op with
| Olea addr =>
match mpt_addr addr args m with
| None => m (*!*)
| Some s => PTM.add (Reg dst) s m
end
| Omove =>
match args with
| r::nil => PTM.add (Reg dst) (mpt (Reg r) m) m
| _ => m (*!*)
end
| Osub =>
match args with
| r::_::nil => PTM.add (Reg dst) (unknown_offset (mpt (Reg r) m)) m
| _ => m (*!*)
end
| _ => m
end.

Fragment de code Coq 17 : Fonction de transfert, oprations arithmtiques

49
La fonction Bi :

Definition transf_builtin ef args dst m :=


match ef with
| EF_external _ sg => PTM.add (Reg dst) (ABS.singleton Globals, ALS.empty) m
| EF_builtin _ sg => PTM.add (Reg dst) (ABS.singleton Globals, ALS.empty) m
| EF_vload chunk => PTM.add (Reg dst) PTS.top m
| EF_vstore chunk =>
match args with
| r1 :: r2 :: nil =>
add_set_to_all_elts (mpt (Reg r2) m) (mpt (Reg r1) m) m
| _ => m
end
| EF_malloc =>
PTM.add (Reg dst) (ABS.empty, ALS.singleton (Alloc n, Int.zero)) m
| EF_free => m
| EF_memcpy _ _ =>
match args with
| rdst :: rsrc :: nil =>
add_set_to_all_elts PTS.top (unknown_offset (mpt (Reg rdst) m)) m
| _ => m (*!*)
end
| EF_annot _ _ => m
| EF_annot_val _ _ =>
match args with
| r1 :: nil => PTM.add (Reg dst) (mpt (Reg r1) m) m
| _ => m (*!*)
end
end

Fragment de code Coq 18 : Fonction de transfert, fonctions intgres au compilateur

50
Enfin, la fonction F :

Definition transf c n (m: ptmap) :=


match c!n with
| Some instr =>
match instr with
| Inop _ => m
| Iop op args dst succ => transf_op op args dst m
| Iload chunk addr args dst succ =>
if is_mint32_dec chunk
then
match mpt_addr addr args m with
| None => m (*!*)
| Some s =>
PTM.add (Reg dst) (lub_of_mpt_of_all_elts s m) m
end
else
PTM.add (Reg dst) PTS.bot m
| Istore chunk addr args src succ =>
if is_mint32_dec chunk
then
match mpt_addr addr args m with
| None => m (*!*)
| Some sdst =>
add_set_to_all_elts (mpt (Reg src) m) sdst m
end
else
m
| Icall sign fn args dst succ =>
PTM.add (Reg dst) PTS.top (top_mem m)
| Itailcall sign fn args => top_mem m
| Ibuiltin ef args dst succ => transf_builtin ef args dst m
| Icond cond args ifso ifnot => m
| Ijumptable arg tbl => m
| Ireturn _ => m
end
| None => m
end.

Fragment de code Coq 19 : Fonction de transfert, instructions RTL

51
Glossaire
alias
Se dit de deux variables faisant rfrence au mme objet.

bloc dactivation
Le bloc dactivation est un bloc en mmoire cr chaque appel de fonction non terminal, afin de
stocker des informations sur la fonction appelante durant le temps de lexcution de la fonction ap-
pele.

compilation
Transformation dun code depuis un langage source vers un autre langage.

dictionnaire
Structure de donnes permettant dassocier des valeurs des cls.
drfrencement
Action de retrouver lobjet dont ladresse a t rfrence.

graphe de flot de contrle


Graphe orient reprsentant un programme, trs utilis comme reprsentation intermdiaire dans les
compilateurs.

optimisation
Transformation dun code smantiquement transparente, en vue de rduire un cot.

pointeur
Un objet est un pointeur lorsque sa valeur est ladresse en mmoire dun autre objet.

registre
Un registre est un emplacement mmoire situ dans le processeur. Les registres sont trs rapides
accder, mais peu nombreux, aussi on souhaite les exploiter au maximum. Cependant, ils nont pas
dadresse, aussi il nexiste pas de pointeurs vers un registre.

smantique
La smantique dun langage de programmation est la description formelle, mathmatique, des pro-
prits attendues de lexcution dun code.
SSA
Reprsentation dun programme sous forme de graphe de flot de contrle dans lequel chaque variable
nest assigne quune unique fois, facilitant certains raisonnements.
statique
Se dit de toute chose effectue avant lexcution du programme.

52
treillis
Un treillis est un ensemble partiellement ordonn tel que tout couple dlments admet une borne
infrieure et une borne suprieure.

volatile
Une variable est dite volatile si elle doit tre traite de manire plus conservatrice par le compilateur
quune variable habituelle. Cest le cas de variables pouvant changer de valeur spontanment, par
exemple par laction dun processus concurrent ou parce que la variable est associe un priphrique.
Les oprations lies ce type de variables sont galement dites volatiles.

53
Rfrences
[And94] L. O. Andersen. Program analysis and specialization for the C programming language. PhD
thesis, University of Copenhagen, 1994.
[G+ 05] B. Guo et al. Practical and accurate low-level pointer analysis. In International Symposium on
Code Generation and Optimization (CGO05), 2005.
[Kil73] Gary A. Kildall. A unified approach to global program optimization. In 1st symposium Prin-
ciples of Programming Languages, pages 194206. ACM, 1973.
[Ler09a] Xavier Leroy. Formal verification of a realistic compiler. Communications of the ACM,
52(7) :107115, 2009.
[Ler09b] Xavier Leroy. A formally verified compiler back-end. Journal of Automated Reasoning,
43(4) :363446, 2009.
[Ste96] B. Steensgaard. Points-to analysis in almost linear time. In POPL 96 Proceedings of the 23rd
ACM SIGPLAN-SIGACT symposium on Principles of programming languages, 1996.
[YCER11] Xuejun Yang, Yang Chen, Eric Eide, and John Regehr. Finding and understanding bugs in c
compilers. In Proceedings of the 32nd ACM SIGPLAN conference on Programming language
design and implementation, PLDI 11, pages 283294, New York, NY, USA, 2011. ACM.

54

Vous aimerez peut-être aussi