Rapport de stage de DEA 
´Evaluation de la quantit´e de travail utile 
dans l’ex´ecution des programmes 
Benjamin Vidal 
Responsable de stage : Pierre Michaud 
Projet CAPS
Sujet de stage 
La recherche en architecture de processeur est confront´ee actuellement `a des contraintes qui 
rendent de plus en plus difficile l’augmentation des performances des processeurs. Ces contraintes 
sont multiples : consommation ´electrique, latence de propagation des signaux sur les connexions, 
temps et coˆut de developpement, etc. . . Pour esp´erer trouver d’´eventuelles solutions permettant 
d’augmenter les performances de mani`ere significative sur une large gamme d’applications, il faut 
trouver de nouveaux paradigmes d’architecture. Pour cela, il faut d’abord avoir une bonne compr´e-hension 
du comportement des programmes. 
Le sujet propos´e a pour but d’´evaluer la quantit´e de travail r´eellement utile dans l’ex´ecution des 
programmes. L’id´ee sous-jacente est que si une fraction importante de l’ex´ecution d’un programme 
consiste en du travail inutile, il peut ˆetre int´eressant de chercher un paradigme architectural per-mettant 
d’exploiter cette propri´et´e. 
Le probl`eme consiste `a donner une d´efinition de l’utilit´e d’un travail. Par exemple, dans la 
r´ef´erence [1], un r´esultat interm´ediaire est consid´er´e inutile s’il est ´ecrit dans un registre et est 
´ecras´e sans avoir ´et´e utilis´e. Dans la r´ef´erence [5], un store `a une adresse m´emoire est consid´er´e 
inutile s’il ´ecrit une valeur ´egale `a la valeur d´ej`a stock´ee `a cette adresse. Nous proposons d’´etudier 
une autre d´efinition, selon laquelle une instruction dynamique est consid´er´ee utile si 
– Elle produit un r´esultat ´emis en sortie du programme (ex. printf) 
– Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile 
– C’est un branchement dominant une instruction utile 
La partie recherche du stage consiste `a concevoir un algorithme efficace en temps et en m´emoire 
permettant d’´evaluer la quantit´e d’instructions dynamiques utiles. La partie mise-en-oeuvre consiste 
`a ´ecrire le programme correspondant, et `a l’utiliser pour obtenir des statistiques sur la fraction de 
travail utile, et d’autres statistiques, `a d´efinir, permettant de mieux appr´ehender le comportement 
des programmes. La mise en oeuvre se fera `a l’aide des outils d´evelopp´es au sein du projet CAPS. 
On travaillera sur des traces d’ex´ecution des programmes de la suite SPEC CPU2000. 
2
Remerciements 
Au cours de ce stage au sein de l’´equipe CAPS de l’IRISA, il m’a ´et´e possible de rencontrer 
un grand nombre de personnes qui m’ont aid´e `a comprendre le fonctionnement d’un laboratoire de 
recherche en informatique et surtout `a acqu´erir le recul n´ecessaire pour mieux appr´ehender le monde 
de l’architecture des microprocesseurs. Je voudrais donc remercier Ronan Amicel, Laurent Bertaux, 
Fran¸cois Bodin, Henri-Pierre Charles, Assia Djabelkhir, Romain Dolbeau, Antony Fraboulet, Karine 
Heydemann, Thierry Lafage, Antoine Monsifrot, Laurent Morin, Gilles Pokam, Olivier Rochecouste, 
Andr´e Seznec et ´Eric Toullec. 
Je tiens aussi `a remercier Yannos Sazeides (Enseignant `a l’universit´e de Chypre) avec qui j’ai eu 
l’occasion d’´echanger des id´ees sur la fa¸con d’´elaborer automatiquement un graphe de d´ependance 
de donn´ee `a partir de l’ex´ecution d’un programme. 
Et enfin je tiens `a remercier tr`es chaleureusement mon maˆıtre de stage, Pierre Michaud, qui m’a 
donn´e la libert´e de travail que j’aurais aim´e trouver tout au long de mon exp´erience universitaire 
et professionnelle et m’a permis ainsi de suivre les pistes que je souhaitais. Je tiens ´egalement `a le 
remercier pour tous les conseils qu’il a pu me donner concernant le monde de la recherche (publique 
ou priv´ee) et de m’avoir fait partager sa vision des choses sur de nombreux sujets. 
3
Table des mati`eres 
1 Bibliographie 9 
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 
1.2 Compilation et travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 
1.2.1 Vous avez dit « instructions inutiles » ? . . . . . . . . . . . . . . . . . . . . . 10 
1.2.2 Instructions statiques inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 
1.3 Premi`ere approche : 
Instructions inutiles d´etect´ees dynamiquement . . . . . . . . . . . . . . . . . . . . . . 11 
1.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 
1.3.2 Description du principe de d´etection et d’´elimination des instructions inutiles 11 
1.3.3 Id´ees d’impl´ementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 
1.3.4 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 
1.4 Deuxi`eme approche : 
´Ecritures silencieuses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 
1.4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 
1.4.2 Le ph´enom`ene d’´ecriture silencieuse . . . . . . . . . . . . . . . . . . . . . . . 13 
1.4.3 Les cons´equences de l’´elimination des ´ecritures silencieuses . . . . . . . . . . . 14 
1.4.4 Id´ees d’impl´ementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 
1.4.5 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 
4
1.5 Troisi`eme approche : 
Travail inutile global lors de l’ex´ecution d’un programme . . . . . . . . . . . . . . . . 16 
1.5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 
1.5.2 Evaluer l’utilit´e d’une instruction ? . . . . . . . . . . . . . . . . . . . . . . . . 16 
1.5.3 Mise en oeuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 
1.5.4 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 
1.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 
2 Compte rendu du stage 20 
2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 
2.1.1 Le travail inutile, qu’est ce que c’est ? . . . . . . . . . . . . . . . . . . . . . . 20 
2.1.2 Notre protocole de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 
2.2 La m´ethode utilis´ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 
2.2.1 L’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 
2.2.2 L’optimisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 
2.2.3 Le r´esultat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 
2.3 L’environnement de travail : Les choix de mise en oeuvre . . . . . . . . . . . . . . . . 29 
2.3.1 Les Outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 
2.3.2 L’instrumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 
2.3.3 Le choix de la Plateforme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 
2.3.4 SPARC : Le Meilleur des Mondes ? . . . . . . . . . . . . . . . . . . . . . . . . 32 
2.3.5 S’affranchir de la num´erotation des registres faite par Salto . . . . . . . . . . 34 
2.3.6 Les expressions r´eguli`eres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 
2.3.7 La gestion des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 
5
2.4 R´esultats & Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 
2.4.1 Les chiffres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 
2.4.2 Le doute. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 
2.4.3 La r´epartition du travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . 39 
2.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 
3 Annexes 46 
3.1 Petit historique du stage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 
3.2 A propos de la description machine Salto du Sparc . . . . . . . . . . . . . . . . . . . 47 
3.2.1 Gestion des instructions Save et Restore . . . . . . . . . . . . . . . . . . . . . 47 
3.2.2 L’instruction call & link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 
3.2.3 L’instruction addx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 
3.2.4 Un d´etail : les instructions nop, ba et bn . . . . . . . . . . . . . . . . . . . . . 47 
3.3 R´esultat de l’´evaluation du travail inutile sur un exemple simple . . . . . . . . . . . 49 
3.3.1 Code source en C de l’exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 49 
3.3.2 Code source en assembleur Sparc de l’exemple . . . . . . . . . . . . . . . . . 49 
3.3.3 Identifiant d’instruction statique . . . . . . . . . . . . . . . . . . . . . . . . . 51 
3.3.4 Trace d’ex´ecution dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 
3.3.5 Graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . . . . . . . 56 
3.3.6 Trace d’ex´ecution dynamique 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 57 
3.4 Exemple de donn´ees stock´ees en cours d’ex´ecution . . . . . . . . . . . . . . . . . . . 59 
3.5 Code source du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 
6
Table des figures 
1.1 Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es 
que par des instructions inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 
2.1 La structure de donn´ee d’un noeud du graphe . . . . . . . . . . . . . . . . . . . . . . 24 
2.2 Exemple de graphe g´en´er´e par l’algorithme 1 & 3 . . . . . . . . . . . . . . . . . . . . 25 
2.3 Du code source en langage de haut niveau au graphe de d´ependance de donn´ee 
dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 
2.4 Instrumentation de code source en assembleur . . . . . . . . . . . . . . . . . . . . . . 30 
2.5 Principe de l’instrumentation faite par le programme d’´evaluation de la quantit´e de 
travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 
2.6 Le principe de la fenˆetre de registres du Sparc . . . . . . . . . . . . . . . . . . . . . . 33 
2.7 Quantit´e d’instructions assembleurs inutiles lors de l’ex´ecution de gzip dans diff´erentes 
conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 
2.8 Mise en ´evidence d’un probl`eme d’impl´ementation par divergence du flot de contrˆole 38 
2.9 ´Evolution de la quantit´e de travail inutile en fonction du temps . . . . . . . . . . . . 40 
3.1 Graphe d’exemple g´en´er´e par l’utilitaire « dot » . . . . . . . . . . . . . . . . . . . . . 56 
3.2 Les structures de donn´ees utilis´ees par le programme pour construire le graphe de 
d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 
7
Liste des Algorithmes 
1 Construction du graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . 23 
2 Parcours du graphe (Noeud) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 
3 D´etection des instructions inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 
8
Chapitre 1 
Bibliographie 
1.1 Introduction 
Aujourd’hui, pour am´eliorer les performances d’un programme, il ne suffit plus seulement d’ajou-ter 
du mat´eriel dans un syst`eme donn´e. Il faut avant tout ´etudier le comportement de ce programme 
afin d’adapter au mieux les ajouts qui doivent ˆetre faits au syst`eme. De ce constat, les architectes 
des microprocesseurs ont tir´e des id´ees aujourd’hui fondamentales (tels les diff´erents niveaux de 
m´emoires caches qui exploitent la propri´et´e de localit´e temporelle et spatiale d’acc`es aux donn´ees 
dans les programmes). 
En ce sens, certains travaux de recherche s’int´eressent aujourd’hui au probl`eme du travail ef-fectu 
´e inutilement par un microprocesseur. Ils mettent en ´evidence une quantit´e non n´egligeable 
de travail inutile. Dans cette bibliographie, trois approches principales de travail inutile ont ´et´e 
retenues. 
1. Une instruction produisant un r´esultat jamais utilis´e par une autre instruction est consid´er´ee 
comme inutile (approche de « l’instruction morte » retenue par l’article [1]). 
2. Une instruction d’´ecriture est consid´er´ee comme inutile si cette derni`ere ne modifie pas l’´etat 
de la m´emoire (i.e. la mˆeme valeur est ´ecrite `a la mˆeme adresse m´emoire) (approche de 
« l’´ecriture silencieuse » retenue dans de nombreux articles [5, 3, 7]). 
3. Une instruction est consid´er´ee comme utile si elle produit un r´esultat en sortie (affichage d’un 
r´esultat par exemple) ou qu’elle est elle mˆeme utile `a une instruction utile (approche retenue 
pour le stage). 
Apr`es un bref tour d’horizon des travaux d´ej`a effectu´es dans le domaine au niveau des compila-teurs, 
chacun des trois aspects d´ecrits ci-dessus du travail inutile sera d´evelopp´e dans un paragraphe 
de cette bibliographie. S’en suivra un paragraphe de discussion sur la possibilit´e de mˆeler ces deux 
approches pour essayer d’obtenir une coop´eration compilation/ex´ecution dans l’´elimination des 
instructions inutiles. 
9
1.2 Compilation et travail inutile 
1.2.1 Vous avez dit « instructions inutiles » ? 
Il peut paraˆıtre surprenant au premier abord d’entendre parler de travail inutile dans un pro-gramme. 
En effet, `a partir du moment ou le programmeur demande d’effectuer un travail `a la 
machine, (encore que celui-ci ne soit pas infaillible. . .) ce travail doit avoir une utilit´e (au sens 
informatique du terme bien entendu. . .). Cependant, au del`a du programmeur, il existe toute une 
chaˆıne de m´ecanismes permettant de passer du langage de haut niveau (i.e. langage de program-mation 
classique) au code machine ex´ecutable. Ainsi ce programme va passer par toute sortes de 
transformations qui vont introduire du travail inutile. De plus, il est possible de trouver, dans la 
fa¸con dont sont con¸cus les programmes, du travail inutile (redondance de calculs par exemple). 
1.2.2 Instructions statiques inutiles 
Pour commencer, il est important de rappeler ce que sont les instructions statiques et les ins-tructions 
dynamiques. Une instruction statique est une instruction telle qu’on peut la trouver dans 
le code source d’un programme. Une instruction dynamique est une instance d’instruction statique. 
A chaque instruction statique peut correspondre plusieurs instructions dynamiques (autant que de 
fois o`u l’on ex´ecute cette instruction statique). 
Exemple simple : 
pour i de 1 à n faire 
t[i] := 0; 
fpour 
Instruction statique : t[i] := 0 
Instructions dynamiques associées : t[1] := 0, t[2] := 0, …, t[n] := 0 
Dans l’exemple suivant, il est important de noter que si n n’est pas fix´e lors de la compilation, la 
seule connaissance du compilateur est l’instruction statique. Il ne pourra donc pas, `a priori se servir 
de la valeur de n `a des fins d’optimisation. Supposons maintenant qu’un programme ne soit compos´e 
que des instructions de l’exemple et n’affiche aucun r´esultat. Le compilateur peut en d´eduire que 
l’ensemble du travail `a effectuer pour ex´ecuter cette boucle est inutile. Cependant, il suffit d’ajouter 
une instruction qui utilise t[m] en lecture (m ´etant un param`etre d’entr´ee du programme inconnu 
`a la compilation) pour que, potentiellement, l’ensemble du travail de la boucle devienne utile. En 
effet, le compilateur ne sachant pas quelle case du tableau t va ˆetre acc´ed´e, il est oblig´e de consid´erer 
que l’ensemble de la boucle fournit du travail utile. 
Il existe de nombreuses autres mani`eres d’´eliminer du travail inutile lors de la compilation [2, 4] 
que nous n’aborderons pas ici car seul l’aspect d´ecrit ci-dessus se rapproche des travaux vis´es dans 
cette ´etude. 
Dans la suite de cette bibliographie, nous ne nous int´eresserons qu’aux instructions inutiles 
dynamiques (i.e. qui ne peuvent pas ˆetre d´etect´ees par le compilateur puisqu’elles d´ependent de 
valeurs d’entr´ees du programme non connues au moment de la compilation). 
10
1.3 Premi`ere approche : 
Instructions inutiles d´etect´ees dynamiquement 
1.3.1 Introduction 
Les auteurs de l’article [1] se sont aper¸cus que le r´earrangement des instructions fait par les com-pilateurs 
lors des phases d’optimisations cr´e´e des instructions inutiles. En effet, comme le montre 
leurs r´esultats, une compilation faite sans optimisations montre un niveau faible d’instructions in-utiles 
alors qu’une compilation avec un fort niveau d’optimisation montre un taux d’instructions 
inutiles relativement ´elev´e (parfois sup´erieur `a 10 %). Cependant, malgr´e ce travail effectu´e inuti-lement, 
il est bon de rappeler que globalement, le temps d’ex´ecution de ces programmes diminue 
(i.e. on a bien l’effet d´esir´e). La question qui vient alors est : 
« Comment conserver ces optimisations tout en r´eduisant le travail inutile qui leur est associ´e ? » 
1.3.2 Description du principe de d´etection et d’´elimination des instructions 
inutiles 
Dans un premier temps, l’important est d’analyser les instructions ex´ecut´ees inutilement afin 
de savoir comment les d´etecter. Les auteurs de l’article [1] se sont ainsi aper¸cus que les instructions 
dynamiques inutiles ´etaient tr`es souvent des instances d’un nombre r´eduit d’instructions statiques. 
Ces instructions statiques sont appel´ees des instructions partiellement inutiles. En marquant ces 
instructions particuli`eres comme ´etant propices `a g´en´erer des instructions dynamiques inutiles, 
il est possible de ne faire un traitement particulier que sur ces derni`eres afin de savoir si une 
instance pr´ecise sera r´eellement inutile. Lors de l’ex´ecution, pour chaque instance d’une instruction 
partiellement inutile, une estimation de l’utilit´e de cette instruction dynamique sera faite. De cette 
estimation d´ecoulera son ex´ecution ou non. Dans le cas d’une mauvaise pr´ediction, un m´ecanisme 
de r´ecup´eration permet de lancer l’ex´ecution de cette instruction au moment ou l’on apprend que 
la pr´ediction est ´erron´ee. 
1.3.3 Id´ees d’impl´ementation 
Les auteurs de l’article [1] ont donn´e quelques id´ees d’impl´ementation qui pourraient ˆetre mises 
en oeuvre pour la d´etection de ce type d’instructions. La plus simple consiste `a m´emoriser dans un 
cache totalement associatif les instructions statiques ayant d´ej`a g´en´er´e des r´esultats inutiles par le 
pass´e. Du fait qu’un faible nombre de ces instructions g´en`erent un grand nombre des instructions 
dynamiques inutiles, ce cache permettra de « suspecter » la prochaine instance d’une instruction 
statique ayant d´ej`a g´en´er´e des r´esultats inutiles. 
Par la suite, lors de la d´etection d’une instruction dynamique « suspect´ee » d’ˆetre inutile, son 
ex´ecution sera suspendue jusqu’au « verdict » final permettant de savoir si il ´etait juste de la sus-pecter. 
Si tel est le cas, cette instruction ne sera pas ex´ecut´ee, dans le cas contraire, cette instruction 
sera ex´ecut´ee ajoutant ainsi un surcoˆut dˆu au retard d’ex´ecution pris par cette instruction. Il est 
11
donc tr`es important d’avoir une estimation la plus fine possible afin d’´eviter ce genre de cas et afin 
d’augmenter le nombre d’instruction inutiles suspect´ees `a juste titre. Pour cela, des optimisations 
sont propos´ees : utilisation de l’information de flot de contrˆole et ajout d’un compteur deux bits `a 
saturation principalement. 
Il est important de noter que ces impl´ementations ne tiennent pas compte des r´esultats calcul´es 
qui ne servent qu’`a des instructions inutiles. Autrement dit, cette impl´ementation ne prend pas en 
compte le caract`ere transitif que peuvent avoir certaines instructions inutiles. 
Instruction 
produisant un résultat R 
Instruction 
utilisant R et produisant R’ 
Instruction 
utilisant R’ et produisant R’’ 
Si R’’ est un résultat inutile R’ et R auront été produits inutilement 
Fig. 1.1 – Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es que 
par des instructions inutiles 
1.3.4 Conclusion sur cette approche 
En conclusion, nous pouvons dire que les auteurs de l’article [1] ont mis en ´evidence une quantit´e 
non n´egligeable de travail inutile mˆeme si elle reste, aujourd’hui, difficile `a exploiter. En effet, dans 
un environnement o`u les ressources sont peu limit´ees, l’efficacit´e de l’impl´ementation d´ecrite ci-dessus 
offre des gains en performance n´egligeables. En revanche, dans des conditions de ressources 
plus limit´ees, les gains peuvent atteindre 10 % d’am´elioration des performances. De plus le fait 
d’ex´ecuter moins d’instructions permet une diminution de la charge des Unit´es Arithm´etiques et 
Logiques (UAL) et de la consommation ´electrique relativement importante. D’apr`es les auteurs, un 
m´ecanisme mat´eriel diminuant l’impact des instructions inutiles sur la performance et la consom-mation 
´electrique permettrait d’appliquer des optimisations de code plus pouss´ees `a la compilation. 
12
1.4 Deuxi`eme approche : 
´Ecritures silencieuses 
1.4.1 Introduction 
D’apr`es les auteurs de l’article [5], il existe principalement deux types d’´ecritures silencieuses. 
D’une part les mises `a jours de valeurs silencieuses (qui ne changent pas l’´etat de la m´emoire dans 
laquelle elles ´ecrivent) et d’autre part les ´ecritures silencieuses stochastiques qui mettent `a jour la 
m´emoire de mani`ere pr´evisible. Dans la suite de ce chapitre, nous nous concentrerons sur les mises 
`a jour de valeurs silencieuses et parlerons, par abus de langage, d’´ecritures silencieuses pour les 
d´esigner. 
1.4.2 Le ph´enom`ene d’´ecriture silencieuse 
Au vu de la d´efinition de ce qu’est une ´ecriture silencieuse, il parait difficile de croire que ces 
instructions puissent avoir un impact n´egatif important sur les performances d’un programme. 
Pourtant, les articles sur le sujet montrent que souvent plus de 30 % des ´ecritures sont silencieuses 
dans les applications test´ees. En effet, il existe de nombreux cas o`u, lors du parcours des ´el´ements 
d’un tableau, les modifications apport´ees par ce parcours ne concernent qu’un petit nombre des 
´el´ements de ce tableau. 
Exemples simples : 
pour i de 1 à n faire 
b := (b & t[i]); 
fpour 
pour i de 1 à n faire 
t[i] := (t[i] & b); 
fpour 
(a) (b) (c) 
t étant un tableau de booléens 
b étant un booléen 
l'opération & étant un "ET" logique 
pour i de 1 à n faire 
t[i] := t[i] + e(i); 
fpour 
t étant un tableau d'entiers 
e étant une fonction 
Dans l’exemple (a), nous pouvons voir que pour chaque case du tableau t dont le bool´een est `a 
vrai, l’ex´ecution du corps de la boucle ne produit aucun travail utile (la mˆeme valeur sera r´e-´ecrite 
dans b). Dans l’exemple (b), le simple fait que b soit ´egal `a vrai entraˆıne une inutilit´e de l’ensemble 
de la boucle. Dans l’exemple (c), lorsque "(i) renvoi z´ero, le corps de la boucle peut-ˆetre consid´er´e 
comme inutile. Un autre cas assez fr´equent de travail inutile est celui o`u un tableau est initialis´e 
apr`es avoir ´et´e utilis´e une premi`ere fois. Si lors de la premi`ere utilisation de ce tableau, toutes ses 
valeurs n’ont pas ´et´e modifi´ees, il est inutile de r´e-initialiser l’ensemble des cases de ce tableau. 
Il existe d’autres situations dans lesquelles un grand nombre d’´ecritures silencieuses peuvent ˆetre 
observ´ees : lors de l’appel d’un sous-programme, si les registres sauvegard´es n’ont pas ´et´e utilis´es 
dans ce sous-programme, leur restauration sera inutile. Ce mˆeme ph´enom`ene peut-ˆetre observ´e lors 
de la sauvegarde/restauration de contexte d’un processus par un syst`eme d’exploitation. 
13
1.4.3 Les cons´equences de l’´elimination des ´ecritures silencieuses 
Au del`a du gain ´evident que provoquerait un m´ecanisme fiable de suppression des ´ecritures 
silencieuses, un tel syst`eme permettrait ´egalement de supprimer une certaine quantit´e de travail 
assez importante li´ee `a ces instructions. En premier lieu, les informations de contrˆole li´ees `a ces 
instructions ne sont plus n´ecessaires (Ex : si une s´erie d’´ecritures silencieuses se trouve dans une 
boucle, il est inutile d’ex´ecuter la boucle). De plus, lors de l’ex´ecution d’une instruction de range-ment 
en m´emoire, tout un m´ecanisme lourd de rapatriement de la ligne de cache concern´ee vers la 
m´emoire est mis en place (´ecriture de la donn´ee dans le cache, marquage de la ligne de cache comme 
´etant modifi´ee puis, lors du chargement de nouvelles donn´ees dans cette ligne de cache, ´ecriture 
de l’ancienne ligne de cache consid´er´ee comme modifi´ee en m´emoire). De fait, la suppression d’une 
´ecriture ´evite d’avoir `a passer par toute ces op´erations d’acc`es `a la m´emoire tr`es coˆuteuses. Comme 
expliqu´e dans l’article [3], cette remarque prend encore plus d’importance dans un syst`eme multi-processeur 
puisque `a chaque ´ecriture m´emoire est associ´e un message d’invalidation `a destination 
des autres processeurs provoquant un d´efaut de cache lors du prochain acc`es `a ces donn´ees. . . Il 
est ´egalement important de noter que si certaines ´ecritures ne sont pas effectu´ees, de fait, certaines 
d´ependances de donn´ees n’existent plus. De cette fa¸con, le processeur n’est plus oblig´e d’attendre 
que ces valeurs soient ´ecrites pour pouvoir les utiliser. Le rendement du pipeline du processeur est 
alors am´elior´e. 
1.4.4 Id´ees d’impl´ementation 
Les auteurs de l’article [5] ont propos´e une impl´ementation basique permettant de supprimer 
les ´ecritures silencieuses. Cette impl´ementation consiste `a remplacer toute les op´erations de ran-gement 
en m´emoire par trois op´erations : Chargement de l’ancienne valeur pr´esente en m´emoire, 
comparaison avec la valeur qui doit y ˆetre ´ecrite et enfin, dans le cas o`u ces deux valeurs ne seraient 
pas ´egales, ´ecriture de la nouvelle valeur en m´emoire. Cette m´ethode est sˆure et permet de d´etecter 
l’ensemble des ´ecritures silencieuses. De plus, les lectures pouvant ˆetre servies en parall`eles, il peut 
ˆetre int´eressant de remplacer les ´ecritures par des lectures suivies de comparaisons. Cependant, 
dans la mesure ou le nombre d’´ecritures silencieuses ne repr´esente pas la majorit´e des ´ecritures 
m´emoire, les auteurs ont ajout´e une « impl´ementation parfaite » dans laquelle un m´ecanisme per-met 
de savoir si une ´ecriture va ˆetre utile et, dans ce cas, n’effectuera que l’´ecriture en m´emoire 
sans avoir `a comparer la nouvelle valeur `a la valeur pr´ec´edente. 
D’autres id´ees d’impl´ementations apparaissent ´egalement dans l’article [5] comme par exemple 
la possibilit´e que la ligne de cache ne soit pas marqu´ee comme modifi´ee lorsqu’elle re¸coit une ´ecriture 
silencieuse ´evitant ainsi d’avoir `a propager l’´ecriture en m´emoire centrale (avantage principal de 
l’´elimination des ´ecriture silencieuses). 
L’impl´ementation retenue pour les simulations faites par les auteurs de [5] est la premi`ere 
propos´ee avec pour caract´eristique suppl´ementaire que seules les ´ecritures mises en attente vont 
subir une v´erification de leur utilit´e. Ce qui veut dire qu’une ´ecriture survenant `a un moment o`u au 
moins un port d’´ecriture de la m´emoire est disponible sera servie avant que la v´erification de son 
utilit´e n’ai pu ˆetre faite. De cette fa¸con, les performances des ´ecritures ne sont jamais d´egrad´ees 
puisque le m´ecanisme n’agit que sur la file d’attente des ´ecritures afin de la r´eduire. 
14
1.4.5 Conclusion sur cette approche 
Les auteurs de l’article [5] ont mis en ´evidence une grande quantit´e de travail inutile `a travers les 
´ecritures silencieuses. En effet, les proportions d’´ecritures silencieuses obtenues lors des tests sont 
parfois tr`es importantes et laissent penser qu’elles pourraient avoir une influence tr`es importante 
sur les performances, notamment dans les syst`emes dont la purge des lignes de cache en m´emoire est 
un goulet d’´etranglement. Les auteurs mettent ´egalement en avant la r´eduction du trafic sur le bus 
d’un syst`eme multiprocesseur `a m´emoire partag´ee qui est souvent un point critique dans ce type de 
syst`emes (ce trafic limite le nombre de processeurs sur un mˆeme bus). D’autres travaux ´elargissant le 
th`eme de l’´ecriture silencieuse ont ´egalement ´et´e pr´esent´es comme celui sur les ´ecritures silencieuses 
temporaires [6] consid´erant que si une valeur en m´emoire est modifi´ee puis remise `a son ancienne 
valeur et qu’aucune lecture ne soit intervenue sur la valeur transitoire, elle peut-ˆetre consid´er´ee 
comme silencieuse. Ce mod`ele semble bien s’adapter aux cas d´ecrits ci-dessus de sauvegarde et 
de restauration de contexte fr´equents (appel de sous-programmes, passage d’un processus `a un 
autre. . .). 
15
1.5 Troisi`eme approche : 
Travail inutile global lors de l’ex´ecution d’un programme 
1.5.1 Introduction 
Dans cette approche, la probl´ematique est un peu diff´erente de celle vue dans les deux premiers 
paragraphes. En effet, le but de ces deux approches ´etait de d´etecter (soit par pr´evision, soit de 
mani`ere dynamique) une cat´egorie d’instructions inutiles afin d’´eviter leur ex´ecution « au vol ». 
Dans l’approche retenue pour le stage, il s’agit d’abord de regarder quelle est la quantit´e de travail 
inutile de fa¸con globale (essayer d’´evaluer l’ensemble du travail fait inutilement par un programme) 
afin d’avoir ensuite une id´ee du type de comportement ou d’application ex´ecut´e par un processeur 
qui produit le plus de travail inutile. De cette fa¸con, si certains r´esultats montrent une quantit´e non 
n´egligeable de travail inutile dans certains types d’applications, il sera ensuite possible d’´etudier 
pourquoi ce travail inutile est si important et si il peut ˆetre ´evit´e d’une mani`ere ou d’une autre. 
1.5.2 Evaluer l’utilit´e d’une instruction ? 
La m´ethode retenue ici pour ´evaluer l’utilit´e d’une instruction est assez simple : Une valeur qui 
est affich´ee en sortie d’un programme est consid´er´ee comme un r´esultat utile. Toute instruction 
ayant servi `a calculer ce r´esultat est une instruction utile. Ainsi, les instructions utiles `a un r´esultat 
peuvent ˆetre repr´esent´ees par un arbre de d´ependance entre ces derni`eres dont la racine est le 
r´esultat lui-mˆeme et chaque noeud repr´esente les instructions utiles au calcul de ce r´esultat (aussi 
bien les instructions de rangement/r´ecup´eration en m´emoire, de calcul et de branchement). Les 
feuilles seront alors les valeurs d’entr´ees (param`etres fix´es lors de la compilation ou de l’ex´ecution) 
du programme. En regroupant l’ensemble des arbres ainsi obtenus pour chaque r´esultat en un graphe 
orient´e dont les sources sont les r´esultats et les puits sont les valeurs d’entr´ee du programme, il 
est possible d’identifier quelles sont les instructions r´eellement utiles au programme. En effet, les 
instructions et les valeurs d’entr´ees inutiles au programme n’appartiendront pas `a ce graphe et 
seront ainsi mises en ´evidence. 
16
Exemple simple : 
a := lire(); 
b := VRAI; 
c := 0; 
si a=0 faire 
b := FAUX; 
c := 5; 
fsi 
si b alors écrire(c) 
sinon écrire(a) 
Exemple de graphe d’exécution si a vaut 0 : 
écrire(a) 
nécessite b 
b := FAUX 
branchement 
correspondant 
Test a=0 
nécessite a 
nécessite a 
a := lire() 
Valeur d’entrée 
du programme 
a := lire() 
Valeur d’entrée 
du programme 
c := 5 
c := 0 
b := VRAI 
Instructions exécutées 
inutilement 
Exemple de graphe d’exécution si a vaut 1 : 
nécessite b nécessite c 
Test a=0 
nécessite a 
a := lire() 
Valeur d’entrée 
du programme 
écrire(c) 
c := 0 
Valeur d’entrée 
du programme 
Aucune instruction n’est 
exécutée inutilement 
b := VRAI 
Valeur d’entrée 
du programme 
Cet exemple permet de mettre en ´evidence le fait que selon les valeurs d’entr´ees du programme, 
il peut y avoir du travail inutile ou pas. De plus, il met en lumi`ere (dans le cas o`u a vaut 1) le 
fait que le test a=0 correspondant au branchement du « si » doit ˆetre pris en compte comme ´etant 
du travail utile puisque de ce branchement vont d´ependre les instructions qui vont suivre (Nous 
pouvons dire que ces instructions « exigent » l’ex´ecution de ce branchement et donc du test qui 
permet de savoir si ce branchement doit ˆetre pris). 
Cette m´ethode d’identification des instructions inutiles semble parfaite (mˆeme si elle ne prend 
pas en compte certaines ´ecritures silencieuses). Cependant, elle n´ecessite le d´eroulement complet du 
programme afin de savoir si oui ou non une instruction dynamique du programme sera utile pour 
un r´esultat final. De fait, cette m´ethode ne peut pas ˆetre utilis´ee directement pour ´eliminer « au 
vol » les instructions inutiles. En revanche, elle permet d’exhiber de nombreux cas d’instructions 
inutiles que les autres m´ethodes ne d´etectent pas. Par exemple, le cas d’une instruction inutile par 
transitivit´e mis en ´evidence figure 1.1 sera d´etect´e par cette m´ethode. 
1.5.3 Mise en oeuvre 
Comme d´ecrit dans le sujet de stage, la mise en oeuvre de cette approche du travail inutile consis-tera 
`a ´elaborer un programme permettant de d´etecter les instructions dynamiques inutiles, d’apr`es 
la d´efinition donn´ee ci-dessus, afin de faire des statistiques sur la quantit´e de travail inutile dans un 
ensemble de programmes `a tester. Diff´erentes cat´egories de travail inutile pourront ´egalement ˆetre 
mises en ´evidence (Ex : chargements inutiles, rangements en m´emoire inutiles, calculs inutiles. . .). 
Une fois les tests effectu´es, un travail de regroupement des applications test´ees selon les r´esultats 
pourra ˆetre fait afin de d´egager, ´eventuellement, des « motifs » de comportements permettant en-suite 
de savoir quelles applications sont le plus concern´ees par quel type de travail inutile. Nous 
pouvons imaginer, `a partir de l`a, que des ´ebauches de solutions mat´erielles et/ou logicielles ne soient 
17
trouv´ees pour r´eduire cette quantit´e de travail inutile. Cependant, l’objet du stage reste celui-ci : 
« Concevoir et ´ecrire un programme permettant de calculer la quantit´e de travail inutile dans un 
programme particulier apr`es son ex´ecution » . 
Dans ce sens, le travail `a effectuer en stage sera, dans un premier temps, de r´efl´echir `a la mani`ere 
de d´etecter quelles sont les instructions qui ont ´et´e ex´ecut´ees inutilement lorsque l’ex´ecution d’un 
programme sera termin´ee (algorithme de construction puis d’exploration du graphe de d´ependance 
des instructions d´ecrit dans cette section). Ces r´esultats devront ensuite ˆetre mis en forme afin de 
d´egager des statistiques sur la quantit´e de travail inutile (pourcentage d’instructions inutiles) et 
sur la nature de ces instructions (de quel type d’instructions s’agit-il ?). Une fois cet algorithme 
impl´ement´e, il sera int´eressant de le tester sur diff´erent type de programme afin de savoir quelle 
est la quantit´e de travail r´eellement inutile (d’apr`es la d´efinition donn´ee en introduction de cette 
section) dans ces programmes. 
1.5.4 Conclusion sur cette approche 
En conclusion nous pouvons dire que l’approche retenue pour le stage est une d´emarche scien-tifique 
exp´erimentale permettant de savoir quelle est la proportion globale de travail inutile dans 
un programme. Si les r´esultats r´ev`elent une grande quantit´e de travail inutile, de nombreuses 
ouvertures paraissent possibles : D´etection de ces instructions grˆace `a des compilateurs « intel-ligents 
», d´etection de ces instructions « au vol » (approche d´ej`a retenue par [1]), coop´eration 
compilateur/mat´eriel ou encore ajout de nouvelles instructions afin de faciliter leur d´etection. 
18
1.6 Conclusion 
En conclusion, nous pouvons dire que plusieurs mani`eres d’´eliminer le travail inutile ont d´ej`a ´et´e 
abord´ees (tant dans le domaine de la compilation qu’en architecture). En effet, lors de la compila-tion, 
une certaine quantit´e de travail inutile peut d´ej`a ˆetre supprim´ee (en fonction des informations 
que le compilateur peut exploiter). Cependant, nous avons ´egalement vu que certaines optimisa-tions 
de ces mˆemes compilateurs g´en`erent des instructions inutiles. De fait, diff´erentes m´ethodes ont 
´et´e propos´ees pour ´eliminer ce travail inutile lors de l’ex´ecution (instructions dynamiques inutiles). 
Une autre approche int´eressante consistait `a ´eliminer les ´ecritures silencieuses, une autre forme de 
travail inutile. 
Apr`es ce tour d’horizon global, il est assez difficile de savoir de mani`ere pr´ecise quelle est la 
quantit´e de travail inutile effectu´ee par un microprocesseur lors de l’ex´ecution d’un programme. 
C’est `a cette question que va tenter de r´epondre le travail `a venir en stage. . . 
19
Chapitre 2 
Compte rendu du stage 
2.1 Introduction 
2.1.1 Le travail inutile, qu’est ce que c’est ? 
Le travail inutile est une notion difficile `a cerner. Il existe diff´erentes approches pour traiter le 
probl`eme du travail effectu´e inutilement par un programme. Tout d’abord, le travail inutile peut- 
ˆetre de nature statique (d´etectable et supprimable lors de la compilation) ou de nature dynamique 
(visible uniquement lors de l’ex´ecution). L’´elimination du travail inutile statique est d´ej`a bien 
connue et fait partie int´egrante de toute chaˆıne de compilation optimis´ee digne de ce nom. Ici nous 
nous int´eresserons seulement au travail inutile de nature dynamique puisque ce dernier n’est pas 
exploit´e par les processeurs ou les langages de programmation actuels. 
Une fois le cadre du travail inutile dynamique pos´e, il est n´ecessaire de se donner une d´efinition 
pr´ecise du travail inutile afin de pouvoir en ´evaluer la quantit´e lors de l’ex´ecution d’un programme 
sur un jeu de donn´ees particulier de fa¸con automatique. Cette d´efinition, dans un premier temps 
tr`es large, a ´et´e restreinte pour des raisons d’impl´ementation. 
La d´efinition prise comme base de d´epart `a cette ´evaluation ´etait la suivante : 
Tout travail qui ne sert, ni directement, ni indirectement, `a produire un r´esultat est jug´e inutile. 
De fa¸con plus pr´ecise : 
Une instruction dynamique est consid´er´ee comme utile si 
- Elle produit un r´esultat ´emis en sortie du programme (ex : affichage `a l’´ecran). 
- Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile. 
- C’est un branchement dominant une instruction utile. 
20
Exemple simple : 
instruction 1; 
si booléen faire 
instruction 2; 
instruction 3; 
fpour 
instruction 4; 
Dans cet exemple, le branchement conditionnel « domine » les instructions 2 et 3. Si le bool´een 
est vrai et que les instructions 2 et 3 sont inutiles, alors on peut consid´erer le branchement comme 
inutile. 
Note 
Par abus de langage, dans la suite de ce document, nous d´esignerons toutes les instructions de 
transfert de contrˆole (branchement conditionnels et inconditionnels, sauts, appels de fonctions. . .) 
par l’expression « instruction de branchement ». 
En regardant cette d´efinition de plus pr`es, un probl`eme se pose dans le cas g´en´eral : Supposons 
que l’instruction 2 soit un « store » qui range une valeur `a une adresse m´emoire, que l’instruction 
4 soit un « load » et que le bool´een soit `a faux. Dans ce cas, si l’instruction 4 est consid´er´ee 
comme utile et si les deux acc`es pointent sur la mˆeme adresse m´emoire, alors le branchement devra 
ˆetre consid´er´e comme utile. Cependant, l’adresse de l’acc`es `a la m´emoire qui aurait pu ˆetre fait 
par l’instruction 2 n’est pas connue puisque cette instruction n’a pas ´et´e ex´ecut´ee. A cause de ce 
type d’acc`es `a la m´emoire (dont l’adresse acc´ed´ee n’est connue qu’`a l’ex´ecution) nous avons du 
faire l’hypoth`ese conservatrice suivante : Toutes les instructions de branchements sont consid´er´ees 
comme utiles. 
Ce qui nous donne la d´efinition suivante : 
Une instruction dynamique est consid´er´ee comme utile si 
- Elle produit un r´esultat ´emis en sortie du programme (ex : affichage `a l’´ecran). 
- Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile. 
- C’est un branchement. 
Note 
Par abus de langage, dans la suite de ce document, nous utiliserons le mot « ressource » pour 
d´esigner soit un registre soit un emplacement m´emoire. 
Partant de cette nouvelle d´efinition, l’objectif ´etait de construire un graphe de d´ependance de 
donn´ee en reliant les instructions lisant une ressource `a la derni`ere instruction ayant ´ecrit dans cette 
mˆeme ressource. De cette fa¸con, lorsqu’une ressource apparaˆıt comme utile (lorsque sa valeur est 
´ecrite en sortie ou qu’elle est utilis´ee par une instruction de branchement), il devient possible, en 
parcourant les arcs de ce graphe, de trouver toutes les instructions qui ont ´et´e utiles pour produire 
21
ce r´esultat (le r´esultat est un graphe ressemblant `a la figure 2.2 page 25). 
2.1.2 Notre protocole de test 
A partir de cette d´efinition, nous avons essay´e de mesurer la quantit´e de travail inutile dans des 
petits programmes d’exemple, puis, une fois ces exemples valid´es, nous avons test´e notre protocole 
pour mesurer le travail inutile sur un programme plus cons´equent et surtout n’ayant pas ´et´e con¸cu 
dans le but d’en mesurer la quantit´e de travail inutile. Pour faire nos tests, nous avons choisi 
l’utilitaire de compression/d´ecompression de donn´ees gzip. 
22
2.2 La m´ethode utilis´ee 
2.2.1 L’algorithme 
Pour construire notre graphe de d´ependance de donn´ee, nous avons choisi d’instrumenter chaque 
instruction assembleur afin de contrˆoler de fa¸con pr´ecise les entr´ees et les sorties de chacune d’elles. 
Les entr´ees ´etant les op´erandes d’une instruction (les ressources ´etant lues par l’instruction) et les 
sorties ´etant les r´esultats d’une instruction (les ressources ´etant ´ecrites par l’instruction). 
Dans le cas g´en´eral, notre impl´ementation de la d´etection de d´ependance de donn´ee peut se 
r´esumer par l’algorithme 1. 
Algorithme 1: Construction du graphe de d´ependance de donn´ee 
Entr´ee : Programme dont on veut ´evaluer la quantit´e de travail inutile. 
Donn´ees `a fournir en entr´ee `a ce programme. 
Sortie : Graphe de d´ependance de donn´ee dynamique. 
1 pour chaque instruction ex´ecut´ee faire 
2 Cr´eer une repr´esentation interne de cette instruction; 
3 L’ajouter `a la liste des instructions dynamiques ex´ecut´ees; 
{Cette repr´esentation interne contient des informations concernant l’instruction : son 
num´ero dynamique, le fichier source auquel elle appartient, son num´ero statique dans ce 
fichier, son type. . . } 
4 pour chaque op´erande de l’instruction {ressource lue} faire 
5 Lire dans la table des ressources quelle est la derni`ere instruction qui a ´ecrit dans 
cette ressource; 
6 Cr´eer un lien de d´ependance {arc dans le graphe} entre l’instruction courante et la 
derni`ere instruction ayant ´ecrit dans la ressource en question; 
{Associe `a l’op´erande le num´ero de l’instruction dynamique qui l’a produit} 
fin 
7 pour chaque r´esultat de l’instruction {ressource ´ecrite} faire 
8 Ecrire dans la table des ressources que l’instruction courante a modifi´ee l’´etat de 
cette ressource; 
fin 
fin 
Une fois cet algorithme ex´ecut´e sur un programme particulier, il est possible de connaˆıtre les 
d´ependances directes entre les instructions grˆace aux arcs construits mais aussi les d´ependances 
indirectes grˆace aux chemins form´es par des suites d’arcs dans le graphe (le graphe de la figure 2.2 
page 25 en est un exemple). 
En ajoutant `a l’algorithme pr´ec´edent une condition dans la boucle principale permettant de 
parcourir le graphe construit lorsqu’on rencontre une instruction de sortie (affichage), il devient 
23
possible de connaˆıtre les instructions utiles lors de l’ex´ecution d’un programme (d’apr`es la premi`ere 
d´efinition donn´ee ci-dessus) (cf. algorithme 3). 
Proc´edure Parcours du graphe (Noeud) 
1 si le noeud est marqu´e inutile alors 
2 Marquer ce noeud {repr´esentant une instruction} comme ´etant utile; 
pour chaque noeud op´erande de ce noeud faire 
3 Appeler la proc´edure Parcours du graphe (Noeud op´erande); 
fin 
fin 
Algorithme 3: D´etection des instructions inutiles 
Entr´ee : Graphe de d´ependance de donn´ee dynamique. 
Sortie : Quantit´e d’instructions dynamiques inutiles. 
Localisation de ces instructions dans le code source. 
pour chaque noeud du graphe faire 
si le noeud repr´esente une instruction de sortie ou de branchement alors 
Appeler la proc´edure Parcours du graphe (Noeud); 
fin 
fin 
Numéro d'instruction dynamique 
Identificateur d'instruction statique 
Type de l'instruction 
Nombre d'opérandes 
Liste des instructions ayant écrit en dernier dans les opérandes 
Fig. 2.1 – La structure de donn´ee d’un noeud du graphe 
Un point int´eressant de cet algorithme, dont nous nous sommes rendu compte une fois l’impl´e-mentation 
op´erationnelle, est qu’il permet de d´etecter les acc`es fait en lecture `a une zone m´emoire 
non initialis´ee pr´ealablement. Par exemple, si un tableau de taille n est d´eclar´e et initialis´e, le fait 
de tenter d’acc´eder `a l’adresse de la zone m´emoire n+1, qui n’a donc pas ´etait initialis´ee, provoque 
une incoh´erence dans l’algorithme puisqu’il est impossible de trouver la derni`ere instruction ayant 
´ecrit dans cette zone m´emoire (ligne 5 dans l’algorithme 1 page pr´ec´edente). De cette fa¸con, il est 
possible de trouver, lors de l’ex´ecution, une erreur d’acc`es `a la m´emoire. 
24
39 
40 38 
36 
37 
35 
34 
3 
14 
33 
0 
13 
25 
24 
32 
31 
30 
29 
28 
26 
27 
23 
17 
16 
22 
21 
20 19 
18 
15 
9 
12 11 
8 
10 
7 
2 
6 
5 
4 
1 
Dans ce graphe, les noeuds sont ´etiquet´es par les num´eros dynamiques des instructions (ordre 
d’ex´ecution). Les noeuds en gris sont des instructions inutiles alors que les noeuds en noir sont des 
instructions utiles. 
Les instructions dynamiques 4, 12 et 20 sont issues d’une seule et mˆeme instruction statique de 
rangement en m´emoire dont seulement une instance est utile : l’instruction dynamique num´ero 12. 
Fig. 2.2 – Exemple de graphe g´en´er´e par l’algorithme 1 & 3 
25
2.2.2 L’optimisation 
Le gros probl`eme de cette approche est que le graphe devient tr`es rapidement ´enorme, mˆeme 
avec des programmes de petite taille. En effet, ´etant donn´e qu’il faut conserver des informations 
concernant chaque instruction dynamique jusqu’`a la fin de l’ex´ecution, seule une ex´ecution ayant 
un nombre r´eduit d’instructions dynamiques peut ˆetre envisag´ee (aux alentours de 300 000 dans 
notre impl´ementation). 
Nous avons donc tent´e de r´eduire le graphe au maximum en ´eliminant au cours de l’ex´ecution 
les informations qui ne nous ´etaient plus n´ecessaires. 
Ce qui est fait. . . 
Dans un premier temps, pour r´eduire ce graphe et donc augmenter la taille des programmes 
testables, nous avons d´ecid´e de supprimer `a la vol´ee les informations concernant les instructions 
« certifi´ees » utiles. Ces informations ´etant le noeud repr´esentant cette instruction et les arcs sor-tant 
de celle-ci. Ces informations ne sont plus d’aucune utilit´e une fois que le parcours de l’arbre 
dont cette instruction est la racine est effectu´e. Il est alors possible de les supprimer sans perdre 
d’information utile `a notre calcul de quantit´e de travail inutile. Cette m´ethode permet, `a mesure 
que le programme se d´eroule et envoi des informations en sortie, (affichage. . .) de r´eduire le graphe. 
Il est alors d’autant plus r´eduit que la quantit´e de travail utile est importante (sur nos tests, le gain 
r´eel en occupation m´emoire est d’un facteur trois `a quatre ce qui permet de tester des programmes 
d´epassant le million d’instructions dynamiques sans avoir un temps d’ex´ecution r´edhibitoire). 
La courbe de l’occupation m´emoire de l’algorithme au cours du temps devient alors identique 
(`a quelques d´etails d’impl´ementation pr`es) `a la courbe repr´esentant la quantit´e de travail inutile 
cumul´e (figure 2.9 page 40). 
Quelques petites optimisations ont aussi ´etaient apport´ees au programme concernant le temps 
d’ex´ecution. Bien que ce facteur ne soit pas le point crucial de notre algorithme, il semblait 
int´eressant de s’y pencher pour ´eviter d’avoir des dur´ees de tests trop importantes. 
Nous avons par exemple, `a la ligne 1 de la proc´edure Parcours du graphe page 24, supprim´e le 
parcours d’une branche lorsque cette derni`ere poss`ede comme racine une instruction utile. En effet, 
si tel est le cas, cela signifie que cette branche a d´ej`a ´et´e enti`erement explor´ee et qu’il est inutile de 
la parcourir `a nouveau. 
Ce qu’il reste `a faire. . . 
Dans un second temps, il est int´eressant de voir que si une instruction ´ecrit dans une ou plu-sieurs 
ressources, puis que ces ressources sont de nouveau ´ecrites sans ˆetre lues entre temps, les 
informations concernant cette instruction inutile ne nous serviront jamais puisque cette instruction 
ne sera jamais rendue utile (notion de « valeur morte »). De cette fa¸con, il est possible, ici encore, 
26
de r´eduire notre graphe en supprimant les noeuds repr´esentant ce type d’instructions ainsi que leurs 
arcs sortants. 
Une mani`ere simple d’impl´ementer un tel m´ecanisme serait de consid´erer que chaque instruction 
inutile est un objet et que les ressources (registres et zones m´emoire) sont des moyens d’acc´eder `a ces 
objets. Si une instruction est accessible depuis au moins une ressource, alors il n’est pas possible 
de supprimer les informations concernant cette instruction. En revanche, si aucune ressource ne 
« r´ef´erence » l’objet, alors cet objet est inaccessible depuis les ressources et le restera jusqu’`a la fin 
de l’ex´ecution du programme. Cet objet peut donc ˆetre supprim´e (noeud ainsi que ses arcs sortants). 
Cette m´ethode s’apparente `a un syst`eme de ramasse-miettes comme il est souvent mis en place dans 
un environnement d’ex´ecution pour lib´erer des zones m´emoires n’´etant plus r´ef´erenc´ees par aucun 
pointeur. 
2.2.3 Le r´esultat 
La figure 2.3 page suivante est un exemple simple permettant de comprendre comment est 
construit le graphe de d´ependance de donn´ee `a partir du code assembleur du programme dont on 
veut ´evaluer la quantit´e de travail inutile. 
Dans la figure 2.3 page suivante, les noeuds sources sont les instruction utiles par hypoth`ese (en 
caract`ere gras). Ces instructions sont soit des instructions de sortie (print %valeur dans l’exemple) 
soit des instructions de branchement (bne boucle dans l’exemple). Une fois ces instructions jug´ees 
comme ´etant utile au programme, nous pouvons appliquer la d´efinition r´ecursive permettant de 
trouver toutes les instructions ayant servi `a produire les valeurs utiles `a ces instructions. Ainsi, 
dans notre exemple, l’instruction dynamique num´ero 18 (print %valeur) poss`ede comme entr´ee le 
registre %valeur. Il est donc n´ecessaire de trouver la derni`ere instruction ayant ´ecrit dans ce registre. 
Cette instruction est l’instruction dynamique num´ero 17 (load [@tab+2],%valeur). Ainsi de suite 
r´ecursivement, l’instruction dynamique num´ero 17 poss`ede comme entr´ee la seconde case du tableau 
rang´e `a l’adresse m´emoire @tab dont la derni`ere ´ecriture a ´et´e faite par l’instruction dynamique 
num´ero 8 et ainsi de suite jusqu’`a n’arriver qu’`a des instructions n’ayant aucune entr´ee (copie 
d’une constante dans un registre (mov 1,%indice dans l’exemple), instruction d’entr´ee au clavier 
par l’utilisateur. . .). 
De cette mani`ere, en parcourant le graphe, il est possible d’identifier le travail utile. Les ins-tructions 
n’´etant accessible depuis aucune des sources (instructions de sorties ou de branchement) 
sont identifi´ees comme ´etant du travail inutile (instructions dynamiques 2, 3, 12 et 13 dans notre 
exemple). 
Grˆace `a cet exemple, nous avons mis en ´evidence un cas simple comportant peu de travail inutile. 
En revanche, il est facile de prendre conscience de l’importance que peut atteindre ce travail inutile 
d`es lors que le traitement `a l’int´erieur d’une boucle du type de celle pr´esent´ee dans l’exemple devient 
important. En effet, dans notre exemple, seule la multiplication par 10 et le rangement en m´emoire 
sont inutiles mais si la valeur `a ranger dans le tableau avait ´et´e un calcul effectu´e par une fonction 
comportant 10 000 instructions, les chiffres auraient ´et´e diff´erents. De mˆeme, si la taille du tableau 
avait ´et´e de 10 000 cases dont seulement une aurait ´et´e utilis´ee, la quantit´e de travail inutile aurait 
´et´e beaucoup plus importante. En revanche si, dans notre exemple, les trois cases du tableau avaient 
27
1: mov 1,%indice 
boucle: 
2: mul %indice,10,%valeur 
3: store %valeur,[%indice+@tab-1] 
4: add %indice,1,%indice 
5: cmp 4,%indice 
6: bne boucle 
7: load [@tab+2],%valeur 
8: print %valeur 
Code en assembleur RISC 
(code statique) 
Dépendances de données permettant d’identifier le travail 
utile (arcs du graphe parcourus par l’algorithme). 
Dépendance de donnée n’étant pas parcourues par 
l'algorithme. 
Trace d’exécution des instructions 
(dynamique) 
1ère itération de la boucle 
2ème itération de la boucle 
3ème itération de la boucle 
Numéro 
Dynamique 
Numéro 
Statique 
123456789 
10 
11 
12 
13 
14 
15 
16 
17 
18 
123456234562345678 
n Numéro d’instruction statique utile par essence 
(instructions de sorties ou de branchement). 
n Numéro d’instruction statique utile après parcours du 
graphe de dépendance (définition récursive). 
n Numéro d’instruction dynamique (indique l’ordre 
d’éxécution des instructions dans le temps) 
n 
Numéro d’instruction statique jugés comme étant inutile 
d’après la définition (instruction n’appartenant pas au 
graphe de dépendance de données). 
Compilation 
Pour i de 1 à 3 faire 
t[i] := i*10; 
Finpour 
Ecrire (t[2]); 
Code source original 
Fig. 2.3 – Du code source en langage de haut niveau au graphe de d´ependance de donn´ee dynamique 
´et´e affich´ees (instruction print), la quantit´e de travail inutile aurait ´et´e nulle. 
Un exemple plus complet montrant dans le d´etail comment l’algorithme a ´et´e impl´ement´e est 
en annexe (figure 3.2 page 59). 
28
2.3 L’environnement de travail : Les choix de mise en oeuvre 
2.3.1 Les Outils 
Pour la mise au point de notre programme d’´evaluation de la quantit´e de travail inutile, plusieurs 
outils ont ´et´e mis `a contribution : 
- Salto : Salto est une biblioth`eque de fonctions permettant d’analyser du code source en 
assembleur pour en extraire les informations s´emantiques sous une forme exploitable en C++. 
Ces informations peuvent ˆetre de diff´erentes natures : Il est possible de connaˆıtre le d´ecoupage 
en blocs de base du code source, les ressources utilis´ees par une instruction pr´ecise (dans notre 
cas, ce qui nous int´eresse sont les acc`es `a la m´emoire et aux diff´erents registres). De plus, 
une des fonctionnalit´e indispensable `a notre r´ealisation disponible dans Salto est la possibilit´e 
d’instrumenter le code source (figure 2.4 page suivante). 
- Le compilateur GCC pour processeur Sparc : Compilateur C/C++ gratuit sous licence GNU. 
- Le compilateur CC pour processeur Sparc : Compilateur C propri´etaire de Sun disponible 
seulement pour la plateforme Sparc. 
- Les expressions r´eguli`eres en C. 
2.3.2 L’instrumentation 
L’instrumentation c’est quoi ? 
L’instrumentation d’instruction est un m´ecanisme qui consiste `a ins´erer des instructions suppl´e-mentaires 
entre les instructions du code source d’un programme d´ej`a ´etabli afin « d’ausculter » ce 
dernier. Les informations qu’il est possible de r´ecup´erer par ce m´ecanisme sont de nature dynamique 
puisque les instructions rajout´ees par instrumentation sont ex´ecut´ees autant de fois que le sont les 
instructions appartenant au code source d’origine. Prenons comme exemple le cas d’une boucle 
dont le corps est ex´ecut´e n fois, alors le code rajout´e par instrumentation du code source d’origine 
dans le corps de cette boucle sera lui aussi ex´ecut´e n fois. Ceci permet de savoir de fa¸con pr´ecise 
quelles sont les instructions statiques qui ont ´et´e ex´ecut´ees par le processeur s´equentiellement. De 
plus, l’instrumentation permet de r´ecup´erer d’autres informations dynamiques comme les adresses 
des acc`es `a la m´emoire. 
Dans l’exemple de la figure 2.4 page suivante, le code source original est instrument´e afin de 
r´ecup´erer la valeur du r´esultat produit par l’instruction `a instrumenter et pour le traiter dans la 
fonction fct (tmp ´etant par exemple un registre de d´ebuggage dont le code source original ne fait 
jamais usage mais qui peut ˆetre utilis´e par la fonction fct pour effectuer son traitement). 
29
mov r1,r2 
add r2,3,r2 
store r2,[a0] 
Code source 
mov r1,r2 
mov r2,tmp 
call fct 
add r2,3,r2 
mov r2,tmp 
call fct 
store r2,[a0] 
load [a0],tmp 
call fct 
Code source 
instrumenté 
instrumentation 
Fig. 2.4 – Instrumentation de code source en assembleur 
Note 
Dans l’exemple de la figure 2.4, les instructions sont instrument´ees apr`es leur ex´ecution, ce 
qui n’est pas le cas dans notre impl´ementation : l’instrumentation se trouve avant l’ex´ecution de 
l’instruction pour des raisons de suivi des branchements (pour pouvoir instrumenter correctement 
les branchements, il est n´ecessaire de placer le code d’instrumentation avant ceux-ci). 
L’instrumentation dans notre programme 
L’instrumentation, dans le cas g´en´eral, permet d’ins´erer des instructions dans un programme. A 
partir de ce concept simple, nous avons d´ecid´e d’utiliser l’instrumentation pour ins´erer des appels 
de fonctions (´ecrites en C et compil´ees par ailleurs). De fait, les appels de fonctions en assembleur 
ne faisant pas de sauvegarde des registres globaux (accessible de n’importe o`u dans le programme). 
Il nous a fallu ajouter `a cette instrumentation une sauvegarde de contexte avant l’appel `a cette 
fonction puis une restauration apr`es (figure 2.5 page suivante). Mais ce n’est pas tout : Chaque 
instruction assembleur ayant un nombre variable d’op´erandes et de r´esultats, il nous a fallu ajouter 
une instruction d’appel `a une fonction pour chaque op´erande et pour chaque r´esultat. De plus, 
chaque acc`es `a la m´emoire n´ecessitant la r´ecup´eration de l’adresse de cet acc`es, il nous a fallu 
r´ecup´erer des informations sur la valeur des registres utilis´es par l’instruction `a instrumenter afin 
de savoir quelle ´etait l’adresse de cet acc`es m´emoire. 
L’instrumentation d’une instruction dans notre programme d’´evaluation de la quantit´e de travail 
inutile peut-ˆetre r´esum´ee en sept phases : 
– Sauvegarde : Une phase de sauvegarde du contexte (registres globaux, d´ecalage de la fenˆetre 
de registres. . .). 
– D´ebut : Une phase de cr´eation de la structure de donn´ee repr´esentant une instruction (fi-gure 
2.1 page 24). 
– Op´erandes : Une phase permettant de cr´eer les arcs vers les instructions ayant ´ecrit en 
dernier dans les ressources op´erandes de l’instruction. 
30
inst1 
inst2 
inst3 
Code source 
sauvegarde 
call fct 
restauration 
inst1 
sauvegarde 
call fct 
restauration 
inst2 
sauvegarde 
call fct 
restauration 
inst3 
Code source 
instrumenté 
instrumentation 
Fig. 2.5 – Principe de l’instrumentation faite par le programme d’´evaluation de la quantit´e de 
travail inutile 
– Milieu : Une phase, utile seulement pour les instruction « save » et « restore », permettant 
de mettre `a jour le niveau de la fenˆetre de registres. 
– R´esultats : Une phase permettant de mettre `a jour l’´etat des ressources en fonction des 
r´esultats produits par l’instruction. 
– Fin : Une phase permettant d’´evaluer l’utilit´e de l’instruction courante. Si cette derni`ere est 
utile, on parcour son arbre de d´ependance de donn´ee. 
– Restauration : Une phase de restauration du contexte. 
2.3.3 Le choix de la Plateforme 
Pour choisir notre plateforme de travail, nous avons pris en compte plusieurs param`etres. Nous 
avions le choix entre le jeu d’instruction x86 (CISC) et le jeu d’instruction Sparc (RISC). En premier 
lieu, l’outil mis `a notre disposition (Salto) semblait plus adapt´e `a un jeu d’instruction r´eduit. 
En effet, Salto ´etant bas´e sur la reconnaissance des instructions assembleur par des expressions 
r´eguli`eres, il est tr`es difficile de supporter un jeu d’instruction aussi vaste que le x86 d’Intel (CISC). 
De fait, le support d’un tel jeu d’instruction par Salto est apparu comme ´etant insuffisant. De plus, 
le travail `a effectuer ´etant, entre autre, d’identifier les acc`es `a la m´emoire, un jeu d’instruction 
r´eduit avec seulement une instruction pour le chargement et une pour le rangement en m´emoire 
nous est apparu plus simple `a manipuler. Cependant, le Sparc poss`ede quelques inconv´enients qui, 
nous le verrons plus loin, ne nous ont pas facilit´e la tˆache. Nous avons donc d´ecid´e de travailler 
avec un jeu d’instruction RISC pleinement support´e par Salto : le Sparc de Sun. 
31
2.3.4 SPARC : Le Meilleur des Mondes ? 
Le Sparc est une architecture RISC assez classique ce qui signifie que le nombre d’instructions 
est assez r´eduit, qu’elles ont toutes la mˆeme taille (quatre octets pour le Sparc) et que, chose 
relativement importante pour notre impl´ementation, les instructions d’acc`es `a la m´emoire sont au 
nombre de deux (load pour charger une valeur de la m´emoire vers un registre et store pour ranger 
une valeur d’un registre vers la m´emoire). Tout aurait ´et´e parfait si la description du Sparc s’´etait 
arrˆet´ee l`a. . . 
- Le delay slot est une des particularit´es de l’architecture Sparc. Il s’agit d’ex´ecuter l’instruc-tion 
qui suit imm´ediatement une instruction de branchement avant que cette instruction de 
branchement ne modifie le flot de contrˆole de l’ex´ecution du programme. Pour r´esumer, l’ins-truction 
qui se trouve dans le delay slot d’un branchement (elle se trouve juste apr`es dans le 
code source) se comporte comme si elle se trouvait avant le branchement pour ce qui est du 
contrˆole mais comme si elle ´etait apr`es pour ce qui est des donn´ees. 
- L’annul bit est une autre particularit´e « amusante » du Sparc qui est en relation directe avec le 
delay slot. Lorsqu’une instruction se trouve dans le delay slot d’un branchement dont l’annul 
bit est activ´e, (be,a : la virgule et le a indique que l’annul bit est activ´e) cette instruction ne 
s’ex´ecute que lorsque le branchement est pris. Dans le cas contraire, l’instruction se trouvant 
dans le delay slot n’est pas ex´ecut´ee (on dit qu’elle est annul´ee). 
- La fenˆetre de registres tournante est une fa¸con de pallier `a la lenteur des acc`es `a la pile lors du 
passage de param`etres `a une fonction. En effet, le Sparc se propose de r´esoudre le probl`eme 
du passage de param`etres `a une fonction de mani`ere originale au moyen de cette fenˆetre de 
registres tournante. Il s’agit de ranger les valeurs que l’on souhaite passer en param`etres `a 
la fonction qui va ˆetre appel´ee dans des registres sp´eciaux nomm´es registres de sortie (%o0 
`a %o5) qui, une fois la fonction appel´ee, seront renomm´es en registres d’entr´ee (%i0 `a %i5) 
(cf. figure 2.6 page suivante). De cette fa¸con, la fonction appel´ee peut se servir de ces valeurs 
sans qu’elles n’aient ´et´e recopi´ees dans une quelconque pile (`a l’exception du cas o`u la taille 
de la fenˆetre de registres tournante n’est pas suffisante). 
Le delay slot 
Dans notre impl´ementation, nous avons du prendre en compte cette particularit´e de l’archi-tecture 
Sparc. En effet, afin d’instrumenter les instructions se trouvant dans le delay slot d’un 
branchement, nous avons du utiliser diff´erentes techniques consistant `a « remonter » notre instru-mentation 
de ce type d’instructions avant le branchement. Ceci `a conduit `a pas mal de probl`emes 
de coh´erence entre la repr´esentation des instructions cr´e´ees par l’instrumentation et les instructions 
r´eellement ex´ecut´ees. 
Mais le cas o`u le delay slot nous a pos´e le plus de probl`eme est celui o`u il nous a fallu ajouter des 
sauts afin d’´eviter l’ex´ecution de certaines instructions. En effet, dans ce cas, si l’instruction que l’on 
veut « sauter » se trouve dans le delay slot d’un branchement, il faut dupliquer cette instruction 
des branchement : mettre dans le code source une version normale avec l’instruction qui se trouvait 
dans son delay slot dans le code source d’origine et une seconde version du mˆeme branchement 
contenant un nop dans son delay slot. De cette fa¸con, selon que l’instruction se trouvant dans le 
32
delay slot de ce branchement doit ˆetre ex´ecut´ee ou non, le flot de contrˆole est aiguill´ee vers l’une 
ou l’autre des deux versions de ce branchement. Il faut ensuite faire converger les deux versions en 
un mˆeme point repr´esentant la suite du programme. 
La fenˆetre de registres du Sparc 
La fenˆetre de registre tournante est une des particularit´e du processeur Sparc. Nous allons en 
expliquer rapidement le principe afin d’exposer la mani`ere dont nous avons trait´e cette particularit´e 
dans notre programme. 
Registres de sortie 
%o0 à %o7 
Registres locaux 
%l0 à %l7 
Registres d’entrée 
%i0 à %i7 
Registres accessibles 
au niveau n+1 
niveau n 
Registres de sortie 
%o0 à %o7 
Registres locaux 
%l0 à %l7 
Registres d’entrée 
%i0 à %i7 
Désigne les mêmes 
registres physiques 
Registres de sortie 
%o0 à %o7 
Registres locaux 
%l0 à %l7 
Registres d’entrée 
%i0 à %i7 
niveau n+2 
Désigne les mêmes 
registres physiques 
L’instruction « save » permet de passer d’un niveau n `a un niveau n+1 (utilis´e g´en´eralement comme 
une sauvegarde de contexte avec en plus la possibilit´e de passer des param`etres d’un contexte `a 
l’autre au niveau de la zone de recouvrement de la fenˆetre n et n+1). 
L’instruction « restore » permet de passer d’un niveau n `a un niveau n-1 (utilis´e g´en´eralement 
comme une restauration de contexte avec en plus la possibilit´e de passer des r´esultats d’un contexte 
`a l’autre au niveau de la zone de recouvrement de la fenˆetre n et n-1). 
Fig. 2.6 – Le principe de la fenˆetre de registres du Sparc 
Lorsque le nombre de registres utilis´es d´epasse le nombre de registres physiques r´eellement 
pr´esents dans le processeur, un m´ecanisme invisible pour l’utilisateur utilise une pile en m´emoire 
pour sauvegarder la fenˆetre de registres la plus ancienne et r´eutiliser ainsi cette derni`ere comme 
une nouvelle fenˆetre vierge. De cette fa¸con, le nombre de registres virtuellement utilisables par 
l’utilisateur n’est limit´e que par la taille de la pile et non par la taille du fichier de registres dans le 
processeur. Cette fenˆetre est souvent repr´esent´ee, dans les diff´erentes documentations sur le Sparc, 
de fa¸con circulaire pour montrer que la plus ancienne fenˆetre et la plus r´ecente peuvent se recouvrir 
si le nombre de fenˆetres disponibles dans le fichier de registres est insuffisant pour l’ex´ecution d’un 
programme donn´e. 
33
2.3.5 S’affranchir de la num´erotation des registres faite par Salto 
Les registres du Sparc s’organisent en deux parties. D’une part, des registres dit globaux qui se 
comportent de mani`ere classique, et d’autre part une fenˆetre de registres coulissante comme d´ecrit 
sur la figure 2.6 page pr´ec´edente. Pour les registres globaux, nous avons utilis´e la num´erotation 
propos´ee par Salto qui convenait tout `a fait ´etant donn´e qu’un nom de registre d´esigne toujours le 
mˆeme registre physique. 
En revanche, pour la fenˆetre de registres coulissante, nous avons du mettre en place notre propre 
syst`eme d’identification de registres : 
En effet, Salto ´etant un outil qui travaille sur du code assembleur, il lui est impossible d’avoir 
acc`es `a des informations concernant l’ex´ecution du programme (les seules informations disponible 
au niveau de Salto sont les informations statiques sur le programme). De fait, les informations 
que nous donne Salto concernant les acc`es aux registres sont les noms de ces registres. Il lui est 
impossible de connaˆıtre le niveau de la fenˆetre de registre en un point donn´e du code et donc de 
d´esigner un registre physique de mani`ere unique. Afin d’identifier de mani`ere unique chacun des 
registres physiques, il a donc fallu s’affranchir du syst`eme de num´erotation des registres propos´e 
par Salto pour le remplacer par un calcul fait de mani`ere dynamique (au cours de l’ex´ecution du 
programme) permettant de savoir `a quel registre physique correspondait chaque acc`es `a un registre 
appartenant `a la fenˆetre de registre courante. 
Pour ce faire, nous avons instrument´e les instructions save qui d´ecalent la fenˆetre de registre 
vers le haut et les instructions restore qui d´ecalent la fenˆetre de registre vers le bas (cf. figure 2.6 
page pr´ec´edente). De cette fa¸con, il est possible de tenir `a jour une variable globale indiquant le 
niveau actuel de la fenˆetre de registre. En utilisant ce niveau comme d´ecalage par rapport `a un 
point de r´ef´erence, (la premi`ere fenˆetre disponible lors du lancement du programme) il est possible 
de savoir dans quelle fenˆetre de registre seront fait les acc`es aux registres de la fenˆetre courante 
indiqu´es par Salto. 
Grˆace `a cette m´ethode, il est possible d’identifier de mani`ere unique chacun des registres d’une 
fenˆetre pr´ecise mˆeme si celle-ci peut avoir ´et´e sauvegard´ee dans la pile par manque de place dans 
le fichier de registres. Les acc`es `a ces registres restent ce qu’ils sont puisque cette op´eration est 
transparente pour l’utilisateur du processeur (en l’occurrence notre programme). 
2.3.6 Les expressions r´eguli`eres 
L’utilisation des expressions r´eguli`eres est particuli`erement utile pour analyser une chaˆıne de 
caract`eres ayant un motif fixe et une partie variable. C’est justement le cas d’une instruction 
assembleur dont la partie fixe est le mn´emonique de cette instruction et dont les parties variables 
sont les arguments. 
De cette fa¸con, nous avons utilis´e les expressions r´eguli`eres pour « d´ecouper » les instructions 
load et store en plusieurs parties : le mn´emonique d’une part (permettant de connaˆıtre la taille de 
l’acc`es `a la m´emoire : octet, mot, double mot ou quadruple mot) et les arguments d’autre part. Une 
34
fois chaque argument r´ecup´er´e, il est possible d’acc´eder aux valeurs contenues dans les registres 
et aux ´eventuelles constantes. Il est donc possible de connaˆıtre de fa¸con exacte `a quelle adresse 
m´emoire va acc´eder l’instruction et `a combien d’octets elle va acc´eder. 
De plus, les expressions r´eguli`eres nous ont ´et´e utiles pour r´ecup´erer les ´etiquettes des appels 
de fonctions afin de savoir si ces fonctions ´etaient d´efinies en local (dans le code source du pro-gramme 
´etudi´e) ou si elles appartenaient `a une biblioth`eque externe au programme (stdio en C 
par exemple). 
2.3.7 La gestion des fonctions 
Une fois notre d´efinition impl´ement´ee et test´ee, il reste encore de nombreux probl`emes `a r´esoudre 
pour pouvoir confronter cet algorithme au « monde r´eel » (`a des programmes classiques tel que 
gzip). En effet, ce mod`ele th´eorique serait parfait si l’ensemble du code source assembleur d’une 
application ´etait visible par le programme d’´evaluation. Or les programmes classiques utilisent des 
fonctions d´efinies dans des biblioth`eques dont on n’a pas le code source (Un programme en C 
travaillant sur des fichiers utilisera par exemple stdio pour lire et ´ecrire dans un fichier). Afin de 
r´egler cette difficult´e, nous avons du utiliser l’options de compilation -SO de cc permettant de savoir 
quels sont les registres du Sparc et les adresses m´emoires utilis´ees dans la pile comme param`etres 
lors de l’appel `a une fonction. Ainsi, il nous a ´et´e possible de rajouter « artificiellement » des arcs 
de d´ependance entre l’appel `a une fonction d´efinie dans une biblioth`eque externe (call printf 
par exemple) et les param`etres pass´es `a cette fonction. Pour cette raison, cc pour Sparc est apparu 
comme ´etant id´eal dans notre protocole de test. 
Afin de r´esoudre ce probl`eme, les fonctions appel´ees par le programme ´etudi´e ont ´et´e class´ees 
en trois cat´egories, chacune correspondant `a un traitement particulier `a faire pour les prendre en 
compte correctement : 
Les fonctions « internes » 
Les fonctions internes sont les fonctions dont le code source est disponible (peut-ˆetre utilis´e par 
notre programme d’´evaluation). Les appels `a ce type de fonctions peuvent donc ˆetre trait´es comme 
de simple branchements inconditionnels puisque les arcs du graphe de d´ependance peuvent tr`es bien 
relier une instruction appartenant `a cette fonction `a une instruction appartenant au programme 
appelant. Le graphe de d´ependance de donn´ee n’est donc pas interrompu par un tel appel de 
fonction. 
Les fonctions « externes » utilisant des pointeurs 
Les fonctions externes sont les fonctions dont le code source n’est pas disponible (code source 
d’une biblioth`eque d’entr´ee/sortie par exemple). Dans ces cas l`a, il est n´ecessaire de consid´erer que 
les param`etres pass´es `a la fonction sont lus par l’instruction d’appel de la fonction et que le r´esultat 
35
est ´ecrit par cette instruction. 
Cependant, dans le cas g´en´eral, il est n´ecessaire de d´etourner l’appel `a une telle fonction pour 
faire ajouter « artificiellement » des arcs de d´ependance de donn´ee pour traiter les donn´ees qui 
vont ˆetre lues et ´ecrites `a l’int´erieur de cette fonction. En effet, notre programme est incapable 
de connaˆıtre le type des param`etres d’une fonction (si il s’agit d’entiers, de pointeurs. . .) et donc, 
de savoir si une fonction d´efinie dans une biblioth`eque dont on n’a pas le code source acc`ede ou 
non `a une zone m´emoire dont l’adresse est un de ses param`etres. De plus, mˆeme en sachant qu’un 
param`etre est un pointeur, rien ne dit si la fonction dont on n’a pas le code source va y acc´eder 
en lecture, en ´ecriture ou pire encore, `a combien d’´el´ements elle va acc´eder ! (Il ne faut pas oublier 
qu’en C les tableaux sont implicites et que, par cons´equent, on ne connaˆıt pas leur taille simplement 
en connaissant l’adresse de leur premier ´el´ement). Afin de r´egler cette difficult´e, nous avons choisi de 
d´etourner les appels `a ces fonctions (au nombre de trente cinq dans gzip) afin de leur faire ex´ecuter 
du code permettant de simuler leur comportement en mati`ere d’acc`es `a la m´emoire. Ces fonctions 
sont, le plus souvent des fonctions d’entr´ees/sorties (ex : read, write, fflush. . .), des fonctions de 
lecture/´ecriture dans des chaˆınes de caract`eres (ex : strcat, strcpy, strcmp. . .) ou des fonctions 
de lecture/´ecriture de zones m´emoire en g´en´eral (ex : memset, memcpy, memcmp. . .). 
Exemple simple : 
char *my_strcpy(char *c1, const char *c2) 
{ 
/* On simule un acc`es en lecture `a une zone m´emoire d´ebutant `a l’adresse 
contenue dans le pointeur c2 et de longueur strlen(c2)+1 (taille de la 
cha^ıne de caract`ere `a lire avec son terminateur) */ 
instrumentationEntreeMemoire((int)c2, strlen(c2)+1); 
/* On simule un acc`es en ´ecriture `a une zone m´emoire d´ebutant `a l’adresse 
contenue dans le pointeur c1 et de longueur strlen(c2)+1 (taille de la 
cha^ıne de caract`ere `a copier avec son terminateur) */ 
instrumentationSortieMemoire((int)c1, strlen(c2)+1); 
/* On retourne la valeur retourn´ee par la vraie fonction strcpy */ 
return strcpy(c1, c2); 
} 
Les fonctions « externes » n’utilisant pas de pointeurs 
Les fonctions externes n’utilisant pas de pointeurs sont des fonctions dites externes d’apr`es la 
d´efinition ci-dessus `a la diff´erence qu’elles ne font pas d’acc`es `a la m´emoire `a partir de pointeur. 
Elles peuvent donc ˆetre trait´ees simplement en consid´erant que les param`etres pass´es `a la fonction 
sont lus et que le r´esultat est ´ecrit. 
36
2.4 R´esultats & Analyse 
2.4.1 Les chiffres. . . 
Une fois l’´evaluation de la quantit´e de travail inutile effectu´ee de cette mani`ere, nous obtenons 
les chiffres suivants (cf. figure 2.7). 
gzipa gzipb gzipc 
O0d 11.03 % 9.53 % 11.03 % 
O1 12.05 % 10.73 % 16.44 % 
O2 11.40 % 10.28 % 16.17 % 
O3 12.77 % 12.10 % 21.83 % 
O4 12.66 % 12.55 % 23.35 % 
O5 12.66 % 12.55 % 23.35 % 
gunzipe gunzipf gunzipg 
O0 1.09 % 1.14 % 0.21 % 
O1 2.94 % 3.10 % 0.29 % 
O2 3.51 % 4.28 % 0.41 % 
O3 10.01 % 10.51 % 0.47 % 
O4 9.83 % 10.26 % 0.52 % 
O5 9.83 % 10.26 % 0.52 % 
aCompression d’un fichier texte (RTF) de 2127 octets avec gzip (fort taux de compression (facteur : 1.9)) 
bCompression d’un fichier image (GIF) de 1704 octets avec gzip (faible taux de compression (facteur : 1.4)) 
cRe-compression d’un fichier compress´e par gzip de 1506 octets avec gzip (taux de compression nul (facteur : 0.98)) 
dSeuls ces r´esultats ont ´et´e valid´es avec le programme de v´erification pour des raisons d’impl´ementation 
eD´ecompression du fichier texte de 2127 octets 
fD´ecompression du fichier image de 1704 octets 
gD´ecompression du fichier compress´e deux fois par gzip de 1506 octets 
Conditions de test : gzip version 1.2.4 recompil´e avec cc de Sun pour le processeur Sparc version 
v8plus. Ces chiffres s’entendent en ne comptant pas les ´eventuelles instructions nop qui se trouvent 
dans le delay slot de certaines instructions de branchement. 
Fig. 2.7 – Quantit´e d’instructions assembleurs inutiles lors de l’ex´ecution de gzip dans diff´erentes 
conditions 
2.4.2 Le doute. . . 
Introduction 
Ces chiffres n’´etant qu’une ´evaluation, rien ne permet de dire avec certitude que ce travail est 
effectivement inutile. D’autant plus que l’impl´ementation laisse souvent apparaˆıtre des failles que 
l’on n’imagine pas lorsqu’on raisonne de fa¸con abstraite sur les d´ependances de donn´ees (la gestion 
des fonctions dont on n’a pas le code source en est un exemple). Afin de valider notre programme 
et d’avoir la certitude que le travail inutile ´evalu´e en ´etait bien, nous avons mis au point un second 
programme r´e-ex´ecutant exactement le mˆeme programme de test (gzip dans notre cas) sur le mˆeme 
jeu de donn´ees (avec le mˆeme fichier en entr´ee) en n’ex´ecutant pas les instructions qui avaient ´et´e 
jug´ees comme ´etant inutiles lors de la premi`ere ex´ecution. Ainsi, si la seconde ex´ecution donne 
rigoureusement le mˆeme r´esultat que la premi`ere (le mˆeme fichier compress´e dans le cas de gzip), 
nous pouvons dire que les deux ex´ecutions sont ´equivalentes et que, par cons´equent, les instructions 
qui n’ont pas ´et´e ex´ecut´ees lors de la seconde ex´ecution n’´etaient effectivement pas utiles. 
37
somewhere : 
sub r3,r4,r5 
... 
mov r1,r2 
add r2,3,r2 
cmp 0,r2 
be somewhere 
store r2,(a0) 
... 
Code source 
mov r1,r2 
add r2,3,r2 
cmp 0,r2 
be somewhere 
sub r3,r4,r5 
... 
Trace dynamique de la 
première exécution 
(détection du travail inutile) 
mov r1,r2 
add r2,3,r2 
cmp 0,r2 
be somewhere 
store r2,(a0) 
... 
Trace dynamique de la 
deuxième exécution (non 
exécution du travail inutile) 
L'instruction add est jugée inutile : 
elle ne sera donc pas exécutée 
lors de la seconde exécution 
Fig. 2.8 – Mise en ´evidence d’un probl`eme d’impl´ementation par divergence du flot de contrˆole 
Note 
Reproduire une ex´ecution `a l’identique ne fonctionne que sur un programme d´eterministe : deux 
ex´ecutions successives sur un mˆeme jeu de donn´ee doivent s’ex´ecuter rigoureusement de la mˆeme 
mani`ere. De fait, il serait complexe de traiter des programmes utilisant des fonctions de tirages 
al´eatoires ou conservant des informations d’une ex´ecution sur l’autre (cache dans un fichier par 
exemple). Ce n’est pas le cas de gzip ce qui nous a permis de mener nos tests de fa¸con correcte 
sur ce programme. 
L’int´erˆet 
Un aspect tr`es int´eressant de l’utilisation de ce programme de v´erification est qu’il a permis 
d’affiner le programme d’´evaluation de la quantit´e de travail inutile. En effet, en observant les 
divergences entre la premi`ere et la seconde ex´ecution (figure 2.8), il a ´et´e possible de trouver les 
points faibles du programme d’´evaluation de la quantit´e de travail inutile et de les consolider afin de 
rendre les deux ex´ecutions ´equivalentes. Par exemple, lorsque la seconde ex´ecution du programme 
de test divergeait de la premi`ere (un branchement pris alors qu’il n’aurait pas du par exemple), cela 
signifiait qu’une instruction utile au flot de contrˆole avait ´etait jug´ee comme ´etant inutile `a tort. De 
cette mani`ere, il a ´et´e possible de trouver les incorrections du programme d’´evaluation du travail 
inutile et surtout de mettre en lumi`ere les lacunes d’une impl´ementation trop « na¨ıve » par rapport 
aux appels de fonctions d´efinies dans des biblioth`eques dont le code source n’est pas disponible. 
Dans l’exemple de la figure 2.8, le branchement be (branch if equal) est pris lors de la premi`ere 
ex´ecution alors qu’il n’est pas pris lors de la seconde, ce qui entraˆıne une incoh´erence entre les deux 
38
ex´ecutions qui ne sont alors plus ´equivalentes. Ceci se produit en raison d’un mauvais jugement 
port´e sur l’instruction add. En effet, celle-ci est utile au bon d´eroulement du programme alors 
qu’elle a ´et´e jug´ee comme ne l’´etant pas par le programme de d´etection du travail inutile. Grˆace `a 
ce syst`eme, il est facile de corriger les incorrections et impr´ecisions que comporte le programme de 
d´etection du travail inutile. Par processus incr´emental, il est alors possible de corriger ces erreurs 
une `a une jusqu’`a l’obtention d’un programme qui ne juge inutile que du travail r´eellement inutile 
(Ce qui ne prouve pas pour autant qu’il d´etecte tout le travail inutile que peut comporter un 
programme). 
La M´ethode 
Pour pouvoir mettre au point ce deuxi`eme programme, il faut tout d’abord que le programme 
d’´evaluation de la quantit´e de travail inutile laisse une trace des instructions inutiles dans un 
fichier qui sera utilis´e par le programme de v´erification. Pour la mise au point de ce programme de 
v´erification, Salto a, l`a encore, ´et´e sollicit´e afin d’instrumenter chaque instruction. Lorsque cette 
instruction est jug´ee inutile (d’apr`es la trace de la premi`ere ex´ecution) alors cette instruction est 
saut´ee au moyen d’un jump d’une valeur constante puisque, dans les processeurs Sparc, toutes les 
instructions ont une taille de quatre octets (« merci » les jeux d’instructions RISC). De cette fa¸con, 
il est assez facile de « sauter » une instruction lorsqu’elle apparaˆıt dans la trace des instructions 
inutiles. 
Conclusion 
Ce programme `a permis, sur des exemples simples, de v´erifier que la quantit´e de travail inutile 
´evalu´ee ´etait bien du travail inutile quelque soit le niveau d’optimisation utilis´e et les cas de figure 
rencontr´es. Cependant, par manque de temps, nous n’avons r´eussi `a le faire fonctionner que sur gzip 
compil´e avec un niveau d’optimisation de 0. N´eanmoins, cette v´erification nous `a permis d’accroˆıtre 
la confiance en nos r´esultats (figures 2.7 page 37 et 2.9 page suivante). 
2.4.3 La r´epartition du travail inutile 
Une fois les informations sur le travail inutile lors de l’ex´ecution d’un programme r´ecup´er´ees, 
il est n´ecessaire de les organiser afin de pouvoir analyser d’ou provient ce travail inutile. Dans 
un premier temps, nous avons essay´e de voir `a quelles instructions statiques correspondaient nos 
instructions dynamiques inutiles. Nous avons trouv´e, sans surprise ´etant donn´e les r´esultats de 
l’article [1], que seul un petit nombre d’instructions statiques ´etaient concern´ees. Ce qui signifie 
que la plupart des instructions dynamiques inutiles sont concentr´ees sur un nombre d’instructions 
statiques r´eduit (de l’ordre de 12.4 % des instructions statiques totales g´en`erent au moins une 
instance dynamique inutile). Une fois ces instructions statiques en assembleur identifi´ees, nous 
avons cherch´e `a « remonter », lorsque c’´etait possible, au code source en C correspondant afin 
de mieux comprendre la raison pour laquelle ce travail est jug´e comme ´etant inutile par notre 
d´efinition. 
39
108876.0 
90730.0 
72584.0 
54438.0 
36292.0 
18146.0 
0.0 
Compression d’un fichier RTF de 2127 octets 
Algorithme GZIP compile avec cc et un niveau d’optimisation de 0 
0.0 268685.0 537370.0 806055.0 1074740.0 
(a) Sans optimisations de compilation 
53388.0 
44490.0 
35592.0 
26694.0 
17796.0 
8898.0 
0.0 
Compression d’un fichier RTF de 2127 octets 
Algorithme GZIP compile avec cc et un niveau d’optimisation de 5 
0.0 105695.0 211390.0 317085.0 422780.0 
(b) Avec optimisations de compilation 
Abscisse : Num´ero d’instruction dynamiques : repr´esente le temps ´ecoul´e en nombre d’instructions 
Ordonn´ee : Quantit´e d’instructions inutiles (cumul´ees) 
Fig. 2.9 – ´Evolution de la quantit´e de travail inutile en fonction du temps 
Travail inutile algorithmique 
Introduction En observant les courbes de la figure 2.9 on s’aper¸coit que l’algorithme de gzip, 
dans nos conditions de test, se d´ecompose en plusieurs phases g´en´erant chacune des quantit´es de 
travail inutile diff´erentes. En premier lieu, il est int´eressant de noter que la phase d’initialisation 
comporte une grande proportion de travail inutile (presque 50 % avec un niveau d’optimisation 
de 0 et plus de 50 % avec un niveau de 5). Ensuite, vient une courte phase durant laquelle aucun 
travail inutile n’est pr´esent (quelque soit le niveau d’optimisation). 
Vient ensuite un ensemble de phases que nous appellerons le coeur de l’algorithme durant lequel 
on observe une quantit´e de travail inutile moyen non n´egligeable avec un niveau d’optimisation de 
0 (de l’ordre de 6,9 %) et plus important encore avec un niveau d’optimisation de 5 (de l’ordre de 
9,9 %). 
La phase d’initialisation Dans certains cas, le travail inutile semble ˆetre d’origine algorith-mique. 
En effet, en observant le code source en C de gzip, il apparaˆıt parfois du travail inutile qu’il 
serait simple d’´eviter en modifiant une petite partie du code. De fait, nous pouvons dire que ce 
travail inutile est inh´erent `a la fa¸con dont l’algorithme de gzip est impl´ement´e. 
De plus, ´etant donn´e une tr`es forte proportion de travail inutile durant la phase d’initialisation 
des structures de donn´ees utiles `a l’algorithme de gzip (aux alentours de 50%), il semble raisonnable 
de penser qu’une tr`es grande partie de ces structures de donn´ees sont initialis´ees puis jamais utilis´ees 
ou r´eutilis´ees pour ˆetre ´ecrites (ce qui g´en`ere des valeurs mortes). Ce type de travail inutile semble 
40
ˆetre r´eellement inh´erent `a l’algorithme et non du `a une mauvaise impl´ementation de celui-ci. 
Exemple simple : 
Dans la fonction local void gen codes (tree, max code), on observe que le code source 
suivant est inutile la plupart du temps (lors de l’ex´ecution sur un fichier de test) : 
for (bits = 1; bits <= MAX_BITS; bits++) { 
next_code[bits] = code = (code + bl_count[bits-1]) << 1; 
} 
L’affectation dans le tableau next code est inutile 52 fois sur 60 dans l’exemple test´e (le nombre 
d’it´erations de la boucle est MAX BITS et est ´egal `a 60). Le fait que cette affectation soit inutile un 
certain nombre de fois engendre qu’une partie des calculs fait dans la boucle devient inutile. Ce 
qui nous donne, pour l’ensemble de la boucle, un nombre de 824 instructions inutiles pour 1440 au 
total (soit une proportion de 57 %). 
Etant donn´e ces r´esultats, il est int´eressant de se pencher sur le cas de l’initialisation des 
structures de donn´ees en g´en´eral. En effet, le premier r´eflexe d’un programmeur, lorsqu’il d´eclare 
une structure de donn´ee (tableau, liste. . .) est de l’initialiser pour ´eviter, par la suite, d’y faire 
un acc`es en lecture sans y avoir pr´ealablement rang´e une valeur. Or ce r´eflexe de programmation 
est probablement ce que nous observons ici ´etant donn´e que les structures de donn´ees de gzip 
n’´echappent apparemment pas `a cette r`egle. 
Le coeur de l’algorithme Au coeur de l’algorithme, nous observons diff´erents cas d’instructions 
dynamiques inutiles. Parfois, nous observons que le travail inutile est du `a l’initialisation de va-riables 
locales dont le contenu est, la plupart du temps, r´e-´ecrit avant d’ˆetre lu. Parfois, il s’agit de 
param`etres pass´es `a une fonction et qui ne servent que dans certaines conditions. Et enfin, un cas 
assez fr´equent ´egalement est celui des variables globales qui sont maintenues `a jour de fa¸con inutile. 
En effet, si une telle variable refl`ete une valeur lors du dernier passage dans une certaine fonction, 
il est possible que cette fonction soit appel´ee plusieurs fois sans que cette valeur n’ai ´et´e lue entre 
temps. 
Exemples : 
L’affectation prev match = match start ; peut-ˆetre inutile car la seule utilisation de la variable 
prev match en lecture est le cas suivant : 
if (prev_length >= MIN_MATCH && match_length <= prev_length) { 
check_match(strstart-1, prev_match, prev_length); 
flush = ct_tally(strstart-1-prev_match, prev_length - MIN_MATCH); 
... 
} 
41
Ce qui signifie que lorsque la condition ci-dessus sera fausse, l’affectation de la variable prev match 
sera inutile (c’est le cas 78 fois 82 dans notre test). En supposant que le calcul de la valeur de la 
variable match start puisse ˆetre coˆuteux, et que cette variable ne soit pas r´e-utilis´ee en lecture 
entre temps, on prend conscience de la port´ee que peut avoir le travail inutile. 
Note 
Dans l’exemple pr´ec´edent, il est int´eressant de noter que le d´eplacement de l’instruction d’affec-tation 
prev match = match start ; dans le corps de la conditionnelle aurait suffit `a ´eliminer 
ce travail inutile (dans la mesure o`u on ne fait pas d’´ecriture dans match start entre temps). 
En effet, la variable prev match n’´etant utilis´ee que dans ce bloc, il est inutile de faire cette 
affectation si la condition n’est pas vraie. 
Une macro un peu particuli`ere a ´egalement retenu notre attention. Elle se trouve au coeur de 
l’algorithme de compression, dans la fonction deflate(). Il s’agit de la macro INSERT STRING qui 
ins`ere une chaˆıne de caract`ere dans la liste des chaˆınes de caract`eres qu’utilise gzip pour trouver 
les chaˆınes les plus fr´equemment pr´esentes dans le fichier `a compresser. 
Voici le code de cette macro apr`es passage du pr´e-processeur : 
((ins_h = (((ins_h)<<((15+3-1)/3)) ^ 
( window[(strstart) + 3-1])) & 
((unsigned)(1<<15)-1)), 
prev[(strstart) & (0x8000-1)] = hash_head = (prev+0x8000)[ins_h], 
(prev+0x8000)[ins_h] = (strstart)); 
Pour des raisons de lisibilit´e, nous avons r´e-´ecrit ce code : 
ins_h = ( ins_h<<5 ^ window[strstart+2] & (unsigned)(1<<15)-1 ); 
hash_head = (prev+0x8000)[ins_h]; 
prev[strstart & (0x8000-1)] = hash_head; 
(prev+0x8000)[ins_h] = strstart; 
Dans cette macro, qui se trouve au coeur de l’algorithme de compression, les deux derni`eres 
instructions (remplissage du tableau prev) se trouvent ˆetre tr`es souvent inutile (77 fois sur 82 dans 
notre exemple). Ceci tend `a montrer que l’algorithme de compression utilis´e par gzip contient, par 
nature, du travail inutile. 
Travail inutile introduit par le compilateur. . . 
. . . lors des phases d’optimisation de compilation On observe ´egalement que la version 
compil´ee avec un niveau d’optimisation de 0 pr´esente une quantit´e globale de travail inutile moins 
important (proportionnellement) `a la version compil´ee avec un niveau d’optimisation de 5. De plus, 
42
l’´ecart entre les deux versions s’accentue dans le coeur de l’algorithme. En effet, durant la phase 
d’initialisation, les deux versions se comportent `a peu pr`es de la mˆeme fa¸con (aux alentours de 
50 % de travail inutile) alors que dans le coeur de l’ex´ecution, la version non optimis´ee comporte en 
moyenne 6.9 % de travail inutile `a comparer aux 9.9 % observ´e dans le cas de la version optimis´ee. 
Ce ph´enom`ene avait d´ej`a ´et´e constat´e dans l’article [1] mais uniquement au sujet des valeurs mortes. 
. . . du au jeu d’instruction du processeur Cette ´etude n’est absolument pas exhaustive sur 
les diverses causes que peut avoir le travail inutile. Cependant, mˆeme si cet aspect n’a pu ˆetre 
explor´e pour des raisons de temps, il parait raisonnable de penser qu’une partie du travail inutile 
pourrait avoir ´et´e introduit en raison des contraintes impos´ees par le jeu d’instructions utilis´e. En 
effet, dans un jeu d’instruction RISC (comme le Sparc) une instruction de haut niveau (en langage 
C par exemple) peut ˆetre convertie par le compilateur en une suite tr`es importante d’instructions 
comme en une seule. Ceci d´epend de l’´eloignement de cette instruction en langage C par rapport aux 
instructions disponibles dans le jeu d’instruction assembleur utilis´e. A contrario, un jeu d’instruction 
CISC (comme le x86) aura des instructions assembleur plus proche des instructions en langage de 
haut niveau. De cette fa¸con, les proportions d’instructions assembleur inutiles peuvent ne pas ˆetre 
identiques aux proportions d’instructions inutiles de haut niveau (en langage C par exemple). 
De plus, certaines optimisations de compilation effectuant un r´e-ordonnancement des instructions 
assembleur, il est parfois difficile de savoir quel ensemble d’instructions assembleur repr´esente quelle 
instruction de haut niveau. 
Conclusion 
En conclusion, nous pouvons dire que les proportions de travail inutile trouv´ees se rattachent 
majoritairement au travail inutile pr´esent dans l’algorithme en langage de haut niveau. De fait, une 
piste qui pourrait ˆetre int´eressante pour r´eduire ce travail inutile serait de signaler au programmeur, 
lors des premi`eres ex´ecutions d’un prototype de programme, que certaines parties de l’algorithme 
g´en`erent une grande quantit´e de travail inutile et que, par cons´equent, une r´e-´ecriture en prenant en 
compte cet ´etat de fait pourrait ´eviter ce travail. Il est mˆeme possible d’imaginer un outil proposant 
au programmeur une ´ebauche de solution pour l’aider `a restructurer une partie de son code afin 
d’´eviter ce travail inutile. Cependant, ce type d’outils ne peut rien pour aider `a ´eliminer le travail 
inutile intrins`eque `a l’algorithme. 
43
2.5 Conclusion 
Cette ´etude est, en premier lieu, une ´etude permettant de comprendre un ph´enom`ene, `a priori, 
contre intuitif : Le travail inutile. Pour ce faire, nous nous sommes bas´e sur des r´esultats existants 
qui ont d´ej`a ´et´e publi´es et qui montre que le travail inutile existe bel et bien dans des programmes 
classiques. 
Le but de ce stage ´etait d’´elargir les d´efinitions donn´ees dans ces articles afin d’avoir une 
id´ee du travail inutile global qui peut se trouver dans un programme. Cette ´etude, contrairement 
`a celles cit´ees ci-contre, n’avait pas pour but de trouver un moyen d’exploiter ce travail inutile 
pour en r´eduire l’impact sur le temps d’ex´ecution ou la consommation ´electrique mais seulement 
de comprendre ce ph´enom`ene et de savoir pourquoi ce travail inutile est pr´esent (est-ce du au 
compilateur ?, au programmeur ?. . .). 
En conclusion, nous pouvons dire que cette ´etude `a permis de confirmer l’existence du travail 
inutile et de comprendre, en partie, d’o`u il provient. 
44
Bibliographie 
[1] G. Sohi A. Butt. Dynamic dead-instruction detection and elimination. ASPLOS X, October 
2002. 
[2] Jeffrey D. Ullman Alfred V. Aho, Ravi Sethi. Compilers : Principles, Techniques and Tools. 
Addison-Wesley, 1986. 
[3] Gordon B. Bell. Characterization of silent stores. Submitted in partial fulfillment of the M.S. 
Degree in Electrical and Computer Engineering, May 2001. 
[4] F. Bodin. Cours d’optimisation : Transformer pour la performance. Septembre 2002. 
[5] M. Lipasti K. Lepak, G. Bell. Silent stores and store value locality. IEEE Transactions on 
Computers, 50(11), November 2001. 
[6] Mikko H. Lipasti Kevin M. Lepak. Temporally silent stores. ASPLOS X, October 2002. 
[7] Kevin M. Lepak. Silent stores for free : Reducing the cost of store verification. Submitted in 
partial fulfillment of the M.S. Degree in Electrical and Computer Engineering, December 2000. 
[8] Charles N. Fischer Milo M. Martin, Amir Roth. Exploiting dead value information. Proceedings 
of Micro-30, December 1997. 
45
Chapitre 3 
Annexes 
3.1 Petit historique du stage. . . 
11/2002 : Elaboration de la bibliographie : Recherche et lecture d’articles sur le travail inutile. 
12/2002 : Elaboration de la bibliographie : R´edaction du rapport bibliographique et r´eflexion 
autour de la partie personnelle `a ajouter (Troisi`eme approche du chapitre Bibliographie). 
02/2003 : D´ebut du stage : Prise en main de l’environnement de travail (C++, gcc, cc, concept 
g´en´eraux de compilation. . .) et de l’outils Salto mis `a disposition par l’´equipe. 
03/2003 : Elaboration du squelette de l’application d’´evaluation du travail inutile et mise au 
point d’un programme simple permettant de construire le graphe de d´ependance de donn´ee 
en utilisant l’outil Salto. 
04/2003 : Impl´ementation et test sur gzip du programme d’´evaluation de la quantit´e de travail 
inutile et mise au point du programme de v´erification que ce travail est bien inutile. 
05/2003 : Test du protocole, optimisation, collecte des r´esultats, et ´ecriture du rapport de stage. 
06/2003 : Fin du rapport et pr´esentation orale du travail effectu´e en stage 
46
3.2 A propos de la description machine Salto du Sparc 
3.2.1 Gestion des instructions Save et Restore 
Les instructions save et restore du Sparc poss`edent trois arguments (g´en´eralement save 
%sp,constante,%sp et restore %g0,%g0,%g0). Ces deux instructions font glisser la fenˆetre de 
registre dans le sens positif pour save (cr´eation d’un nouveau contexte) et dans le sens n´egatif 
pour restore (restauration de l’ancien contexte). De plus, ces instructions se comportent comme 
l’instruction add `a un d´etail pr`es : les registres lus (deux premiers arguments) sont lus dans l’an-cienne 
fenˆetre de registres alors que le registre ´ecrit (troisi`eme argument) est ´ecrit dans la nouvelle 
fenˆetre (apr`es la cr´eation ou la restauration du contexte). Or la description machine Salto du Sparc 
consid`ere que les trois registres pass´es en argument des instructions save et restore sont lus alors 
que le troisi`eme est ´ecrit (erreur invisible dans la mesure ou save et restore ´ecrivent aussi tous les 
registres de la fenˆetre. . . sauf lorsqu’on utilise comme troisi`eme argument un registre n’appartenant 
pas `a la fenˆetre (registre global par exemple)). 
3.2.2 L’instruction call & link 
L’instruction call sert `a faire un appel de fonction. Cette instruction effectue deux actions : 
elle branche `a l’adresse pass´ee en param`etre (constante sur 30 bits) et elle sauvegarde la valeur 
du PC au moment de son ex´ecution dans le registre %o7 (registre 15 dans le manuel du Sparc), 
permettant ainsi `a la fonction appel´ee de revenir `a la suite dans le code source une fois la fonction 
ex´ecut´ee au moyen de l’instruction ret. Or cette deuxi`eme fonction n’est pas vue par la description 
machine Salto du Sparc. Il faut donc rajouter une ´ecriture dans le registre %o7 dans la d´efinition 
de l’instruction call pour la rendre correcte. 
3.2.3 L’instruction addx 
L’instruction addx `a le mˆeme effet que l’instruction add `a ceci prˆet qu’elle utilise le registre des 
codes conditions pour ajouter, si elle est positionn´ee, la retenue de celui-ci (bit carry) au r´esultat 
de l’addition. Or dans la description machine Salto du Sparc, l’instruction addx est vue comme 
´etant ´equivalente `a l’instruction addcc qui, elle, positionne le bit carry en fonction des arguments 
pass´es `a l’instruction addcc. Donc, l’instruction addx lit le registre des codes condition alors que 
l’instruction addcc le modifie. L’instruction addx n’est utilis´ee par les compilateurs que lorsqu’on 
active les optimisations de compilation. 
3.2.4 Un d´etail : les instructions nop, ba et bn 
D’apr`es la description machine Salto du Sparc, l’instruction nop (No op´eration) consommerait 
un registre en entr´ee (dont le num´ero d’identification dans la description machine est 39). Or, 
d’apr`es la documentation sur le Sparc, l’instruction nop ne fait aucune action donc elle ne devrait 
47
pas utiliser de ressources en entr´ee. De plus, les instructions ba (Branch always) et bn (Branch 
never) utilisent, d’apr`es la description machine du Sparc, le registre des codes condition en entr´ee. 
Or, si les instructions de branchement conditionnel utilisent en effet le registre des codes condition, 
les instructions ba et bn peuvent ˆetre consid´er´ees comme des instructions de branchement incon-ditionnel 
puisque le fait que le branchement ai lieu ne d´epend pas des bits activ´es dans le registre 
des codes condition. 
48
3.3 R´esultat de l’´evaluation du travail inutile sur un exemple 
simple 
3.3.1 Code source en C de l’exemple 
1 #include <stdio.h> 
2 
3 
4 int main(void) 
5 { 
6 int tab[3],i; 
7 
8 
9 for(i=0; i<3; i++) tab[i]=i*10; 
10 
11 printf("%d",tab[1]); 
12 
13 exit(0); 
14 } 
3.3.2 Code source en assembleur Sparc de l’exemple 
Ce code est g´en´er´e par le compilateur cc avec les options ”-SO” et ”-XO0” (niveau d’optimisation 
´egal `a 0). 
! FILE exemple.c 
! 1 !#include <stdio.h> 
! 4 !int main(void) 
! 5 !{ 
! 
! SUBROUTINE main 
! 
! OFFSET SOURCE LINE LABEL INSTRUCTION 
.global main 
main: 
/* 000000 5 */ save %sp,-120,%sp 
! 6 ! int tab[3],i; 
! 9 ! for(i=0; i<3; i++) tab[i]=i*10; 
49
.L90: 
/* 0x0004 9 */ or %g0,0,%g2 
/* 0x0008 */ st %g2,[%fp-20] 
/* 0x000c */ ld [%fp-20],%g3 
/* 0x0010 */ cmp %g3,3 
/* 0x0014 */ bl .L95 
/* 0x0018 */ nop 
/* 0x001c */ ba .L94 
/* 0x0020 */ nop 
.L95: 
.L92: 
/* 0x0024 9 */ ld [%fp-20],%g2 
/* 0x0028 */ sll %g2,2,%g3 
/* 0x002c */ add %g3,%g2,%g4 
/* 0x0030 */ sll %g4,1,%o2 
/* 0x0034 */ ld [%fp-20],%o3 
/* 0x0038 */ sll %o3,2,%o4 
/* 0x003c */ add %fp,-16,%o3 
/* 0x0040 */ st %o2,[%o4+%o3] ! volatile 
/* 0x0044 */ ld [%fp-20],%o4 
/* 0x0048 */ add %o4,1,%o5 
/* 0x004c */ st %o5,[%fp-20] 
/* 0x0050 */ ld [%fp-20],%o7 
/* 0x0054 */ cmp %o7,3 
/* 0x0058 */ bl .L92 
/* 0x005c */ nop 
/* 0x0060 */ ba .L96 
/* 0x0064 */ nop 
.L96: 
! 10 ! printf("%dn",tab[1]); 
.L94: 
/* 0x0068 10 */ sethi %hi(.L97),%g2 
/* 0x006c */ add %g2,%lo(.L97),%g3 
/* 0x0070 */ or %g0,%g3,%g4 
/* 0x0074 */ or %g0,%g4,%o0 
/* 0x0078 */ ld [%fp-12],%o2 
/* 0x007c */ or %g0,%o2,%o1 
/* 0x0080 */ call printf ! params = %o0 %o1 ! Result = 
/* 0x0084 */ nop 
! 11 ! 
! 12 ! exit(0); 
/* 0x0088 12 */ or %g0,0,%o3 
/* 0x008c */ or %g0,%o3,%o0 
/* 0x0090 */ call exit ! params = %o0 ! Result = 
50
/* 0x0094 */ nop 
/* 0x0098 */ ba .L89 
/* 0x009c */ nop 
.L89: 
/* 0x00a0 */ ret ! Result = 
/* 0x00a4 */ restore %g0,%g0,%g0 
/* 0x00a8 0 */ .type main,2 
/* 0x00a8 0 */ .size main,(.-main) 
/* 0x00a8 0 */ .global __fsr_init_value 
/* 0x00a8 */ __fsr_init_value=0 
3.3.3 Identifiant d’instruction statique 
Ce fichier texte est obtenu apr`es passage de Salto sur le code source en assembleur de notre 
exemple. Le num´ero statique permettant d’identifier une instruction se trouve entre crochets. 
Salto rev. 1.4.2beta1 (built with g++ on sun4u) 
Copyright (C) 1997 Inria, France 
Machine description file ok. 
CFG (0) : 
BB (0) : 
INST (0) [1] : save %o6,-120,%o6 in : 55 out : 55 
BB (1) : 
INST (0) [2] : or %g0,0,%g2 in : 41 out : 43 
INST (1) [3] : st %g2,[%i6-20] in : 71 in : 43 out : 113 
INST (2) [4] : ld [%i6-20],%g3 in : 71 in : 113 out : 44 
INST (3) [5] : subcc %g3,3,%g0 in : 44 out : 74 out : 41 
INST (4) [6] : bl .L95 in : 74 
BB (2) : 
INST (0) [7] : nop in : 39 
INST (1) [8] : ba .L94 in : 74 
BB (3) : 
INST (0) [9] : nop in : 39 
BB (4) : 
INST (0) [10] : ld [%i6-20],%g2 in : 71 in : 113 out : 43 
INST (1) [11] : sll %g2,2,%g3 in : 43 out : 44 
INST (2) [12] : add %g3,%g2,%g4 in : 44 in : 43 out : 45 
INST (3) [13] : sll %g4,1,%o2 in : 45 out : 51 
INST (4) [14] : ld [%i6-20],%o3 in : 71 in : 113 out : 52 
INST (5) [15] : sll %o3,2,%o4 in : 52 out : 53 
INST (6) [16] : add %i6,-16,%o3 in : 71 out : 52 
INST (7) [17] : st %o2,[%o4+%o3] in : 53 in : 52 in : 51 out : 113 
INST (8) [18] : ld [%i6-20],%o4 in : 71 in : 113 out : 53 
INST (9) [19] : add %o4,1,%o5 in : 53 out : 54 
51
INST (10) [20] : st %o5,[%i6-20] in : 71 in : 54 out : 113 
INST (11) [21] : ld [%i6-20],%o7 in : 71 in : 113 out : 56 
INST (12) [22] : subcc %o7,3,%g0 in : 56 out : 74 out : 41 
INST (13) [23] : bl .L92 in : 74 
BB (5) : 
INST (0) [24] : nop in : 39 
INST (1) [25] : ba .L96 in : 74 
BB (6) : 
INST (0) [26] : nop in : 39 
BB (7) : 
INST (0) [27] : sethi %hi(.L97),%g2 out : 43 
INST (1) [28] : add %g2,%lo(.L97),%g3 in : 43 out : 44 
INST (2) [29] : or %g0,%g3,%g4 in : 41 in : 44 out : 45 
INST (3) [30] : or %g0,%g4,%o0 in : 41 in : 45 out : 49 
INST (4) [31] : ld [%i6-12],%o2 in : 71 in : 113 out : 51 
INST (5) [32] : or %g0,%o2,%o1 in : 41 in : 51 out : 50 
BB (8) : 
INST (0) [33] : call printf out : 56 
BB (9) : 
INST (0) [34] : nop in : 39 
INST (1) [35] : or %g0,0,%o3 in : 41 out : 52 
INST (2) [36] : or %g0,%o3,%o0 in : 41 in : 52 out : 49 
BB (10) : 
INST (0) [37] : call exit out : 56 
BB (11) : 
INST (0) [38] : nop in : 39 
INST (1) [39] : ba .L89 in : 74 
BB (12) : 
INST (0) [40] : nop in : 39 
BB (13) : 
INST (0) [41] : jmpl %i7+8,%g0 in : 72 out : 41 
BB (14) : 
INST (0) [42] : restore %g0,%g0,%g0 in : 41 in : 41 out : 41 
3.3.4 Trace d’ex´ecution dynamique 
La trace d’ex´ecution dynamique de ce programme de test est donn´ee par le programme d’´evaluation 
de la quantit´e de travail inutile. 
I.Dynamic 66 -- I.Static 38 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 65 -- I.Static 37 -- File 1 -- utile -- D´epend de 64 
I.Dynamic 64 -- I.Static 36 -- File 1 -- utile -- D´epend de 63 
I.Dynamic 63 -- I.Static 35 -- File 1 -- utile 
I.Dynamic 62 -- I.Static 34 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 61 -- I.Static 33 -- File 1 -- utile -- D´epend de 58,60 
52
I.Dynamic 60 -- I.Static 32 -- File 1 -- utile -- D´epend de 59 
I.Dynamic 59 -- I.Static 31 -- File 1 -- utile -- D´epend de 0,30,30,30,30 
I.Dynamic 58 -- I.Static 30 -- File 1 -- utile -- D´epend de 57 
I.Dynamic 57 -- I.Static 29 -- File 1 -- utile -- D´epend de 56 
I.Dynamic 56 -- I.Static 28 -- File 1 -- utile -- D´epend de 55 
I.Dynamic 55 -- I.Static 27 -- File 1 -- utile 
I.Dynamic 54 -- I.Static 26 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 53 -- I.Static 25 -- File 1 -- utile -- D´epend de 50 
I.Dynamic 52 -- I.Static 24 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 51 -- I.Static 23 -- File 1 -- utile -- D´epend de 50 
I.Dynamic 50 -- I.Static 22 -- File 1 -- utile -- D´epend de 49 
I.Dynamic 49 -- I.Static 21 -- File 1 -- utile -- D´epend de 0,48,48,48,48 
I.Dynamic 48 -- I.Static 20 -- File 1 -- utile -- D´epend de 0,47 
I.Dynamic 47 -- I.Static 19 -- File 1 -- utile -- D´epend de 46 
I.Dynamic 46 -- I.Static 18 -- File 1 -- utile -- D´epend de 0,33,33,33,33 
I.Dynamic 45 -- I.Static 17 -- File 1 -- INUTILE -- D´epend de 43,44,41 
I.Dynamic 44 -- I.Static 16 -- File 1 -- INUTILE -- D´epend de 0 
I.Dynamic 43 -- I.Static 15 -- File 1 -- INUTILE -- D´epend de 42 
I.Dynamic 42 -- I.Static 14 -- File 1 -- INUTILE -- D´epend de 0,33,33,33,33 
I.Dynamic 41 -- I.Static 13 -- File 1 -- INUTILE -- D´epend de 40 
I.Dynamic 40 -- I.Static 12 -- File 1 -- INUTILE -- D´epend de 39,38 
I.Dynamic 39 -- I.Static 11 -- File 1 -- INUTILE -- D´epend de 38 
I.Dynamic 38 -- I.Static 10 -- File 1 -- INUTILE -- D´epend de 0,33,33,33,33 
I.Dynamic 37 -- I.Static 24 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 36 -- I.Static 23 -- File 1 -- utile -- D´epend de 35 
I.Dynamic 35 -- I.Static 22 -- File 1 -- utile -- D´epend de 34 
I.Dynamic 34 -- I.Static 21 -- File 1 -- utile -- D´epend de 0,33,33,33,33 
I.Dynamic 33 -- I.Static 20 -- File 1 -- utile -- D´epend de 0,32 
I.Dynamic 32 -- I.Static 19 -- File 1 -- utile -- D´epend de 31 
I.Dynamic 31 -- I.Static 18 -- File 1 -- utile -- D´epend de 0,18,18,18,18 
I.Dynamic 30 -- I.Static 17 -- File 1 -- utile -- D´epend de 28,29,26 
I.Dynamic 29 -- I.Static 16 -- File 1 -- utile -- D´epend de 0 
I.Dynamic 28 -- I.Static 15 -- File 1 -- utile -- D´epend de 27 
I.Dynamic 27 -- I.Static 14 -- File 1 -- utile -- D´epend de 0,18,18,18,18 
I.Dynamic 26 -- I.Static 13 -- File 1 -- utile -- D´epend de 25 
I.Dynamic 25 -- I.Static 12 -- File 1 -- utile -- D´epend de 24,23 
I.Dynamic 24 -- I.Static 11 -- File 1 -- utile -- D´epend de 23 
I.Dynamic 23 -- I.Static 10 -- File 1 -- utile -- D´epend de 0,18,18,18,18 
I.Dynamic 22 -- I.Static 24 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 21 -- I.Static 23 -- File 1 -- utile -- D´epend de 20 
I.Dynamic 20 -- I.Static 22 -- File 1 -- utile -- D´epend de 19 
I.Dynamic 19 -- I.Static 21 -- File 1 -- utile -- D´epend de 0,18,18,18,18 
I.Dynamic 18 -- I.Static 20 -- File 1 -- utile -- D´epend de 0,17 
I.Dynamic 17 -- I.Static 19 -- File 1 -- utile -- D´epend de 16 
I.Dynamic 16 -- I.Static 18 -- File 1 -- utile -- D´epend de 0,3,3,3,3 
I.Dynamic 15 -- I.Static 17 -- File 1 -- INUTILE -- D´epend de 13,14,11 
I.Dynamic 14 -- I.Static 16 -- File 1 -- INUTILE -- D´epend de 0 
I.Dynamic 13 -- I.Static 15 -- File 1 -- INUTILE -- D´epend de 12 
53
I.Dynamic 12 -- I.Static 14 -- File 1 -- INUTILE -- D´epend de 0,3,3,3,3 
I.Dynamic 11 -- I.Static 13 -- File 1 -- INUTILE -- D´epend de 10 
I.Dynamic 10 -- I.Static 12 -- File 1 -- INUTILE -- D´epend de 9,8 
I.Dynamic 9 -- I.Static 11 -- File 1 -- INUTILE -- D´epend de 8 
I.Dynamic 8 -- I.Static 10 -- File 1 -- INUTILE -- D´epend de 0,3,3,3,3 
I.Dynamic 7 -- I.Static 7 -- File 1 -- nop -- D´epend de 0 
I.Dynamic 6 -- I.Static 6 -- File 1 -- utile -- D´epend de 5 
I.Dynamic 5 -- I.Static 5 -- File 1 -- utile -- D´epend de 4 
I.Dynamic 4 -- I.Static 4 -- File 1 -- utile -- D´epend de 0,3,3,3,3 
I.Dynamic 3 -- I.Static 3 -- File 1 -- utile -- D´epend de 0,2 
I.Dynamic 2 -- I.Static 2 -- File 1 -- utile 
I.Dynamic 1 -- I.Static 1 -- File 1 -- utile -- D´epend de 0 
I.Dynamic 0 -- I.Static 0 -- File 0 -- utile 
Table des registres fixes : 
Registre 39 : Modifi´e par l’instruction 0 et lu depuis par 7 instruction(s) 
Registre 43 : Modifi´e par l’instruction 55 et lu depuis par 1 instruction(s) 
Registre 44 : Modifi´e par l’instruction 56 et lu depuis par 1 instruction(s) 
Registre 45 : Modifi´e par l’instruction 57 et lu depuis par 1 instruction(s) 
Registre 74 : Modifi´e par l’instruction 50 et lu depuis par 2 instruction(s) 
Table des registres tournants (niveau actuel : 1) : 
Registre 17 : Modifi´e par l’instruction 0 et lu depuis par 22 instruction(s) 
Registre 32 : Modifi´e par l’instruction 65 et lu depuis par 0 instruction(s) 
Registre 33 : Modifi´e par l’instruction 1 et lu depuis par 0 instruction(s) 
Registre 34 : Modifi´e par l’instruction 47 et lu depuis par 1 instruction(s) 
Registre 35 : Modifi´e par l’instruction 46 et lu depuis par 1 instruction(s) 
Registre 36 : Modifi´e par l’instruction 63 et lu depuis par 1 instruction(s) 
Registre 37 : Modifi´e par l’instruction 59 et lu depuis par 1 instruction(s) 
Registre 38 : Modifi´e par l’instruction 60 et lu depuis par 1 instruction(s) 
Registre 39 : Modifi´e par l’instruction 64 et lu depuis par 1 instruction(s) 
Liste des adresses m´emoires : 
Adresse -4261820 : Modifi´e par l’instruction 48 et lu depuis par 13 instruction(s) 
Adresse -4261819 : Modifi´e par l’instruction 48 et lu depuis par 1 instruction(s) 
Adresse -4261818 : Modifi´e par l’instruction 48 et lu depuis par 1 instruction(s) 
Adresse -4261817 : Modifi´e par l’instruction 48 et lu depuis par 1 instruction(s) 
Adresse -4261816 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) 
Adresse -4261815 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) 
Adresse -4261814 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) 
Adresse -4261813 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) 
Adresse -4261812 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) 
Adresse -4261811 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) 
Adresse -4261810 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) 
Adresse -4261809 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) 
Adresse -4261808 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) 
Adresse -4261807 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) 
Adresse -4261806 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) 
54
Adresse -4261805 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) 
Calcul prenant en compte les "nop" : 
Nombre d’instructions inutiles : 16/66 soit 24.242424 % 
Nombre d’instructions utiles : 44/66 soit 66.666664 % 
Nombre de nop : 6/66 soit 9.090909 % 
Calcul ne prenant pas en compte les "nop" : 
Nombre d’instructions inutiles : 16/60 soit 26.666666 % 
Nombre d’instructions utiles : 44/60 soit 73.333336 % 
Nombre d’ocurences d’instructions inutiles pour une instruction statique : 
1 : 0/1 
2 : 0/1 
3 : 0/1 
4 : 0/1 
5 : 0/1 
6 : 0/1 
7 : 0/1 
8 : 
9 : 
10 : 2/3 
11 : 2/3 
12 : 2/3 
13 : 2/3 
14 : 2/3 
15 : 2/3 
16 : 2/3 
17 : 2/3 
18 : 0/3 
19 : 0/3 
20 : 0/3 
21 : 0/3 
22 : 0/3 
23 : 0/3 
24 : 0/3 
25 : 0/1 
26 : 0/1 
27 : 0/1 
28 : 0/1 
29 : 0/1 
30 : 0/1 
31 : 0/1 
32 : 0/1 
33 : 0/1 
34 : 0/1 
55
35 : 0/1 
36 : 0/1 
37 : 0/1 
38 : 0/1 
3.3.5 Graphe de d´ependance de donn´ee 
Les noeuds de ce graphe sont ´etiquet´es avec les num´eros dynamiques des instructions. Les noeuds 
en gris repr´esente les instructions inutiles alors que les noeuds en noir repr´esente les instructions 
utiles. 
65 
64 1 
63 
0 
61 
58 60 
57 59 
30 
28 
29 
26 
53 
56 
55 
50 
49 
51 
48 
47 
46 
45 
43 
33 
32 
44 
41 
42 
40 
39 
38 
36 
35 
34 
31 
18 
17 
27 
25 
24 
23 
21 
20 
19 
16 
3 
2 
15 
13 
14 
11 
12 
10 
9 
8 
6 
5 
4 
Fig. 3.1 – Graphe d’exemple g´en´er´e par l’utilitaire « dot » 
56
3.3.6 Trace d’ex´ecution dynamique 2 
La trace d’ex´ecution dynamique de ce programme est donn´ee par le programme de v´erification 
que ce travail est bien inutile (Seconde ex´ecution avec le mˆeme jeu de donn´ees). 
I.Dynamic 1 -- I.Static 1 -- File 1 
I.Dynamic 2 -- I.Static 2 -- File 1 
I.Dynamic 3 -- I.Static 3 -- File 1 
I.Dynamic 4 -- I.Static 4 -- File 1 
I.Dynamic 5 -- I.Static 5 -- File 1 
I.Dynamic 6 -- I.Static 6 -- File 1 -- Branch 
I.Dynamic 7 -- I.Static 7 -- File 1 
I.Dynamic 8 -- I.Static 10 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 9 -- I.Static 11 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 10 -- I.Static 12 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 11 -- I.Static 13 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 12 -- I.Static 14 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 13 -- I.Static 15 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 14 -- I.Static 16 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 15 -- I.Static 17 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 16 -- I.Static 18 -- File 1 
I.Dynamic 17 -- I.Static 19 -- File 1 
I.Dynamic 18 -- I.Static 20 -- File 1 
I.Dynamic 19 -- I.Static 21 -- File 1 
I.Dynamic 20 -- I.Static 22 -- File 1 
I.Dynamic 21 -- I.Static 23 -- File 1 -- Branch 
I.Dynamic 22 -- I.Static 24 -- File 1 
I.Dynamic 23 -- I.Static 10 -- File 1 
I.Dynamic 24 -- I.Static 11 -- File 1 
I.Dynamic 25 -- I.Static 12 -- File 1 
I.Dynamic 26 -- I.Static 13 -- File 1 
I.Dynamic 27 -- I.Static 14 -- File 1 
I.Dynamic 28 -- I.Static 15 -- File 1 
I.Dynamic 29 -- I.Static 16 -- File 1 
I.Dynamic 30 -- I.Static 17 -- File 1 
I.Dynamic 31 -- I.Static 18 -- File 1 
I.Dynamic 32 -- I.Static 19 -- File 1 
I.Dynamic 33 -- I.Static 20 -- File 1 
I.Dynamic 34 -- I.Static 21 -- File 1 
I.Dynamic 35 -- I.Static 22 -- File 1 
I.Dynamic 36 -- I.Static 23 -- File 1 -- Branch 
I.Dynamic 37 -- I.Static 24 -- File 1 
I.Dynamic 38 -- I.Static 10 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 39 -- I.Static 11 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 40 -- I.Static 12 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 41 -- I.Static 13 -- File 1 -- Non ex´ecut´ee 
57
I.Dynamic 42 -- I.Static 14 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 43 -- I.Static 15 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 44 -- I.Static 16 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 45 -- I.Static 17 -- File 1 -- Non ex´ecut´ee 
I.Dynamic 46 -- I.Static 18 -- File 1 
I.Dynamic 47 -- I.Static 19 -- File 1 
I.Dynamic 48 -- I.Static 20 -- File 1 
I.Dynamic 49 -- I.Static 21 -- File 1 
I.Dynamic 50 -- I.Static 22 -- File 1 
I.Dynamic 51 -- I.Static 23 -- File 1 -- Branch 
I.Dynamic 52 -- I.Static 24 -- File 1 
I.Dynamic 53 -- I.Static 25 -- File 1 -- Branch 
I.Dynamic 54 -- I.Static 26 -- File 1 
I.Dynamic 55 -- I.Static 27 -- File 1 
I.Dynamic 56 -- I.Static 28 -- File 1 
I.Dynamic 57 -- I.Static 29 -- File 1 
I.Dynamic 58 -- I.Static 30 -- File 1 
I.Dynamic 59 -- I.Static 31 -- File 1 
I.Dynamic 60 -- I.Static 32 -- File 1 
I.Dynamic 61 -- I.Static 33 -- File 1 -- Branch 
I.Dynamic 62 -- I.Static 34 -- File 1 
I.Dynamic 63 -- I.Static 35 -- File 1 
I.Dynamic 64 -- I.Static 36 -- File 1 
I.Dynamic 65 -- I.Static 37 -- File 1 -- Branch 
I.Dynamic 66 -- I.Static 38 -- File 1 
58
3.4 Exemple de donn´ees stock´ees en cours d’ex´ecution 
Instruction dynamique : 
Instruction statique : 
Numéro de fichier : 
Type de l'instruction : 
n 
mf 
add 
Instruction dynamique : 
Instruction statique : 
Numéro de fichier : 
Type de l'instruction : 
n+1 
m+1 
f 
store 
Instruction dynamique : 
Instruction statique : 
Numéro de fichier : 
Type de l'instruction : 
n+2 
m’ 
f 
bra 
Instruction dynamique : 
Instruction statique : 
Numéro de fichier : 
Type de l'instruction : 
n+3 
m’+1 
f 
load 
Instruction dynamique : 
Instruction statique : 
Numéro de fichier : 
Type de l'instruction : 
n+4 
m’+2 
f 
mul 
Liste chainée des instructions dynamiques 
(représente l'ordre d'exécution) 
%g0 
%g1 
%g2 
%g3 
%g4 
%g5 
%g6 
%g7 
Table des registres globaux 
%o0 
%o1 
%o2 
%o3 
%o4 
%o5 
%o6 
%o7 
%l0 
%l1 
%l2 
%l3 
%l4 
%l5 
%l6 
%l7 
%o0 
%o1 
%o2 
%o3 
%o4 
%o5 
%o6 
%o7 
%i0 
%i1 
%i2 
%i3 
%i4 
%i5 
%i6 
%i7 
%l0 
%l1 
%l2 
%l3 
%l4 
%l5 
%l6 
%l7 
Table de la fenêtre de registres 
%i0 
%i1 
%i2 
%i3 
%i4 
%i5 
%i6 
%i7 
0x0000 
0x0001 
0x0002 
0x0003 
0x0004 
0x0005 
0x0006 
0x0007 
… 
Table des adresses mémoires 
Pointeur sur l’instruction dynamique précédente (structure de la liste chainée) 
Pointeur sur les instructions ayant produits les opérandes de l’instruction 
Pointeur sur la dernière instruction ayant écrit dans la ressource 
Fig. 3.2 – Les structures de donn´ees utilis´ees par le programme pour construire le graphe de 
d´ependance de donn´ee 
59
3.5 Code source du programme 
60
12 mai 03 13:40 instrumentation.h Page 1/2 
/* Défini le nom du fichier contenant les fonction définies localement par rappo 
rt au programme (pas dans une bibliothèques) */ 
#define NOM_FICHIER_FONCTIONS_INTERNES "fonctions.txt" 
/* Défini le nom du fichier contenant les fonction définies hors du programme (b 
ibliothèques) mais non redéfinies dans le fichier redefinition.c */ 
#define NOM_FICHIER_FONCTIONS_EXTERNES_SPECIALES "fonctions_externes_speciales.txt" 
void appelDeFonctionInstDebut(BB *bb, int position, unsigned int *nbInstructions 
Ajoutees, int numeroInst, int typeInst, char *chaineAnnulBit); 
void appelDeFonctionInstMilieu(BB *bb, int position, unsigned int *nbInstruction 
sAjoutees, char *chaineAnnulBit); 
void appelDeFonctionInstFin(BB *bb, int position, unsigned int *nbInstructionsAj 
outees, char *chaineAnnulBit); 
void appelDeFonctionReg(BB *bb, int position, unsigned int *nbInstructionsAjoute 
es, int es, int indentificateurRessource, char *chaineAnnulBit); 
int tailleAccesMemoire(INST *inst); 
void appelDeFonctionMem(BB *bb, INST *inst, int position, unsigned int *nbInstru 
ctionsAjoutees, int es, char *acces1, char *acces2, char *chaineAnnulBit, int ta 
illeAccesMemoire); 
void appelDeFonctionCopierInstDelay(BB *bb, int position, unsigned int *nbInstru 
ctionsAjoutees); 
void appelDeFonctionEchangerInstDelay(BB *bb, int position, unsigned int *nbInst 
ructionsAjoutees); 
int rechercheChaine(char *chaine, FILE *fichier); 
int estPresent(char *motif, char *chaine); 
int typeInstruction(INST *inst); 
void operandeMemoire(char *instruction, char *acces1, char *acces2); 
// Fonction permettant de sauvagarder le contexte du programme (registres généra 
ux et codes conditions) afin de ne pas 
// intervenir sur les valeurs des registres et codes conditions utilisés par le 
programme 
void sauvegardeContexte(BB *bb, int position, unsigned int *nbInstructionsAjoute 
es); 
// Fonction permettant de restaurer le contexte du programme (registres généraux 
et codes conditions) afin de ne pas 
// intervenir sur les valeurs des registres et codes conditions utilisés par le 
programme 
void restaurationContexte(BB *bb, int position, unsigned int *nbInstructionsAjou 
tees); 
int registreSalto(char *acces); 
void blancSuivant(char **ptr); 
// Insère le code permettant d’instrumenter en fonction des entrées produites pa 
r inst 
void appelDeFonctionsEntrees(INST *inst, BB *bb, int position, unsigned int *nbI 
nstructionsAjoutees, char *chaineAnnulBit); 
// Ajout de l’instrumentation représentant les entrées fictives des call externe 
s 
void appelDeFonctionsEntreesAppelExterne(INST *inst, BB *bb, int position, unsig 
ned int *nbInstructionsAjoutees); 
// Insère le code permettant d’instrumenter en fonction des sorties produites pa 
r inst 
void appelDeFonctionsSorties(INST *inst, BB *bb, int position, unsigned int *nbI 
nstructionsAjoutees, char *chaineAnnulBit); 
// Ajout de l’instrumentation représentant les sorties fictives des call externe 
s 
void appelDeFonctionsSortiesAppelExterne(INST *inst, BB *bb, int position, unsig 
ned int *nbInstructionsAjoutees); 
void instrumenter(INST *inst, BB *bb, BB *bbSuivant, int position, unsigned int 
*nbInstructionsAjoutees, int numeroInst); 
void ajouterCommentaireAppelExterne(INST *inst); 
void Salto_hook(); 
12 mai 03 13:40 instrumentation.h Page 2/2 
void Salto_init_hook(int argc, char *argv[]); 
void Salto_end_hook(); 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.h 1/37
03 jun 03 16:11 instrumentation.cc Page 1/23 
#include <stdio.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <fstream> 
#include <iostream> 
#include <sys/types.h> 
#include <regex.h> 
#include <stdlib.h> 
#include <string.h> 
#include "salto.h" 
#include "instrumentation.h" 
#include "instrument.h" 
#define FICHIER_SOURCE_ASSEMBLEUR ".s$" 
#define REPERTOIRE_FICHIER_INSTRUMENTES "instrumente/" 
#define IN 0 
#define OUT 1 
#define EXP_REGISTRE "^%[golisf][0−7p]$" 
#define EXP_MEMOIRE "[[−+%a−zA−Z0−9_.()]+]" 
#define EXP_FONCTION "[ ]+call[ ]+" 
#define EXP_SAVE "[ ]+save[ ]+" 
#define EXP_RESTORE "[ ]+restore[ ]+" 
#define EXP_ANNUL_BIT ",a" 
#define EXP_LOAD "ld[usd]?[bh]?" 
#define EXP_STORE "st[bhd]?" 
#define ETIQUETTE_FONCTION_NOP "f_nop" 
#define PREFIXE_FONCTIONS_EXTERNES "my_" 
#define SAVE "save %sp,−136,%sp" 
#define RESTORE "restore %g0,%g0,%g0" 
#define NOP "nop" 
// Pointeur sur le fichier dans lequel sera écrit le code instrumenté 
FILE *fichierSInstrumente; 
// Pointeur sur le fichier contenant le code original (non instrumenté) 
FILE *fichierSOriginal; 
// Permet d’identifier de manière unique le fichier en cours de traitement 
unsigned char numeroFichier; 
void appelDeFonctionInstDebut(BB *bb, int position, unsigned int *nbInstructions 
Ajoutees, int numeroInst, int typeInst, char *chaineAnnulBit) 
{ 
char chaine[20],tmp[100]; 
// On empile un paramètre de type entier à passer à la fonction (équivaut à m 
ov typeInst,%o0) 
sprintf(chaine,"or %%g0,%d,%%o0",typeInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Si la constante numeroInst à ranger dans %o1 peut être codée sur 13 bits ( 
Imprimé par Benjamin Vidal 
03 jun 03 16:11 instrumentation.cc Page 2/23 
i.e. est entre −4096 et 4095) 
if(numeroInst <= 4095) 
{ 
// On empile un paramètre de type entier à passer à la fonction (équivaut à 
mov numeroInst,%o1) 
sprintf(chaine,"or %%g0,%d,%%o1",numeroInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
} 
else 
{ 
// On empile ce même paramètre mais en deux fois (les 22 premiers bits du r 
egistre d’abbord) 
sprintf(chaine,"sethi %%hi(%d),%%o1",numeroInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Puis les 10 derniers bits ensuite 
sprintf(chaine,"or %%o1,%%lo(%d),%%o1",numeroInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
} 
// On empile un paramètre de type entier à passer à la fonction (équivaut à m 
ov numeroFichier,%o1) 
sprintf(chaine,"or %%g0,%d,%%o2",numeroFichier); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Si l’instruction que l’on instrumente est un branchement avec un annulBit 
(ex : bl,a ...) on insère une instruction qui 
// va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de 
l’instruction se trouvant dans le DelaySlot 
// sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être 
fait statiquement) 
if (chaineAnnulBit != NULL) 
{ 
INST *inst_br_nop; 
inst_br_nop = newAsm(NOP); 
inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi 
t)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); 
(*nbInstructionsAjoutees)++; 
strcpy(tmp,"b "); 
} 
else strcpy(tmp,"call "); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_DEB 
UT_INST))); 
(*nbInstructionsAjoutees)++; 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
void appelDeFonctionInstMilieu(BB *bb, int position, unsigned int *nbInstruction 
sAjoutees, char *chaineAnnulBit) 
{ 
mercredi 18 juin 2003 instrumentation.cc 2/37
03 jun 03 16:11 instrumentation.cc Page 3/23 
char tmp[100]; 
// Si l’instruction que l’on instrumente est un branchement avec un annulBit 
(ex : bl,a ...) on insère une instruction qui 
// va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de 
l’instruction se trouvant dans le DelaySlot 
// sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être 
fait statiquement) 
if (chaineAnnulBit != NULL) 
{ 
INST *inst_br_nop; 
inst_br_nop = newAsm(NOP); 
inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi 
t)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); 
(*nbInstructionsAjoutees)++; 
strcpy(tmp,"b "); 
} 
else strcpy(tmp,"call "); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_MIL 
IEU_INST))); 
(*nbInstructionsAjoutees)++; 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
void appelDeFonctionInstFin(BB *bb, int position, unsigned int *nbInstructionsAj 
outees, char *chaineAnnulBit) 
{ 
char tmp[100]; 
// Si l’instruction que l’on instrumente est un branchement avec un annulBit 
(ex : bl,a ...) on insère une instruction qui 
// va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de 
l’instruction se trouvant dans le DelaySlot 
// sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être 
fait statiquement) 
if (chaineAnnulBit != NULL) 
{ 
INST *inst_br_nop; 
inst_br_nop = newAsm(NOP); 
inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi 
t)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); 
(*nbInstructionsAjoutees)++; 
strcpy(tmp,"b "); 
} 
else strcpy(tmp,"call "); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_FIN 
_INST))); 
(*nbInstructionsAjoutees)++; 
Imprimé par Benjamin Vidal 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
void appelDeFonctionReg(BB *bb, int position, unsigned int *nbInstructionsAjoute 
es, int es, int indentificateurRessource, char *chaineAnnulBit) 
{ 
char chaine[20],tmp[100]; 
// On empile le paramètre de type entier à passer à la fonction (équivaut à m 
ov %d,%o0) 
sprintf(chaine,"or %%g0,%d,%%o0",indentificateurRessource); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Si l’instruction que l’on instrumente est un branchement avec un annulBit 
(ex : bl,a ...) on insère une instruction qui 
// va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de 
l’instruction se trouvant dans le DelaySlot 
// sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être 
fait statiquement) 
if (chaineAnnulBit != NULL) 
{ 
INST *inst_br_nop; 
inst_br_nop = newAsm(NOP); 
inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi 
t)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); 
(*nbInstructionsAjoutees)++; 
strcpy(tmp,"b "); 
} 
else strcpy(tmp,"call "); 
// Appel de la fonction définie "ailleurs" (fichier instrument.c) 
// Si le paramètre de l’instruction à instrumentée est un paramètre d’entrée 
if (es==IN) 
{ 
// On appelle la fonction de traitement spécifique aux paramètres d’entrée 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_I 
N_REG))); 
(*nbInstructionsAjoutees)++; 
} 
// Si le paramètre de l’instruction a instrumenter est un paramètre de sortie 
else 
{ 
// On appelle la fonction de traitement spécifique aux paramètres de sortie 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_O 
UT_REG))); 
(*nbInstructionsAjoutees)++; 
} 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
03 jun 03 16:11 instrumentation.cc Page 4/23 
mercredi 18 juin 2003 instrumentation.cc 3/37
03 jun 03 16:11 instrumentation.cc Page 5/23 
int tailleAccesMemoire(INST *inst) 
{ 
regex_t *preg = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch[nmatch]; 
// Compilation de l’expression régulière permettant de détecter si une instruc 
tion est un load 
if (regcomp(preg, EXP_LOAD, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_LOAD""n"); 
exit(4); 
} 
// Si l’instruction est un load 
if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) != REG_NOMATCH) 
{ 
regfree(preg); 
// Si le dernier caractère de l’expression est un b, alors le load est un lo 
ad byte 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’b’) return 1; 
// Si le dernier caractère de l’expression est un h, alors le load est un lo 
ad half−word 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’h’) return 2; 
// Si le dernier caractère de l’expression est un d et que ce caractère est 
en deuxième position, alors le load est un load word 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’d’ && pmatch[0].rm_eo == 3) return 
4; 
// Si le dernier caractère de l’expression est un d et que ce caractère est 
en troisième position, alors le load est un load double−word 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’d’ && pmatch[0].rm_eo == 4) return 
8; 
fprintf(STDERR,"Impossible de reconnaitre la taille des données lues par l’instruction "%s"n",inst−> 
unparse()); 
fprintf(STDERR,"Fonction tailleAccesMemoire du fichier instrumentation.ccn"); 
exit(7); 
} 
// Compilation de l’expression régulière permettant de détecter si une instruc 
tion est un store 
if (regcomp(preg, EXP_STORE, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_STORE""n"); 
exit(4); 
} 
// Si l’instruction est un store 
if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) != REG_NOMATCH) 
{ 
regfree(preg); 
// Si le dernier caractère de l’expression est un b, alors le store est un s 
tore byte 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’b’) return 1; 
// Si le dernier caractère de l’expression est un h, alors le store est un s 
tore half−word 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’h’) return 2; 
// Si le dernier caractère de l’expression est un t, alors le store est un s 
tore word 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’t’) return 4; 
// Si le dernier caractère de l’expression est un d, alors le store est un s 
tore double−word 
if(inst−>unparse()[pmatch[0].rm_eo−1] == ’d’) return 8; 
03 jun 03 16:11 instrumentation.cc Page 6/23 
fprintf(STDERR,"Impossible de reconnaitre la taille des données lues par l’instruction "%s"n",inst−> 
unparse()); 
fprintf(STDERR,"Fonction tailleAccesMemoire du fichier instrumentation.ccn"); 
exit(7); 
} 
} 
void appelDeFonctionMem(BB *bb, INST *inst, int position, unsigned int *nbInstru 
ctionsAjoutees, int es, char *acces1, char *acces2, char *chaineAnnulBit, int ta 
illeAccesMemoire) 
{ 
char chaine[200],tmp[100]; 
// * Sauvegarde de la valeur stockée dans %i0 dans la pile afin de pouvoir la 
récupérer par la suite 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("st %i0,[%sp+124]")); 
(*nbInstructionsAjoutees)++; 
// Cette instruction permet de récupérer l’état des registres tels qu’ils éta 
it au moment 
// ou l’appel de l’instruction instrumentée était iminent (on descend d’un cr 
an le contexte) 
restaurationContexte(bb, position, nbInstructionsAjoutees); 
// Insertion de l’instruction permettant de faire passer le résultat du calcu 
l au contexte supérieure 
// (fenêtre de registre placée au dessus) 
sprintf(chaine,"add %s,%s,%%o0",acces1,acces2); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Puis on repasse dans le contexte supérieur 
sauvegardeContexte(bb, position, nbInstructionsAjoutees); 
// En fin on récupère le résultat calculé par l’addition pour le passer en pa 
ramètre à la fonction 
// qui va être appellée (mov %i0,%o0) 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("or %g0,%i0,%o0")); 
(*nbInstructionsAjoutees)++; 
// * Restauration de la valeur stockée dans %o0 (valeur qui avait été sauvega 
rdée par "st %i0,[%sp+124]") 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("ld [%sp+124],%i0")); 
(*nbInstructionsAjoutees)++; 
// On range dans %o1 la taille de l’acces mémoire effectué (nombre d’octets l 
us ou écrits par l’instruction 
sprintf(chaine,"or %%g0,%d,%%o1",tailleAccesMemoire); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Si l’instruction que l’on instrumente est un branchement avec un annulBit 
(ex : bl,a ...) on insère une instruction qui 
// va inhiber l’effet du "call suivant" lorsque l’annulation de l’exécution d 
e l’instruction se trouvant dans le DelaySlot 
// sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être 
fait statiquement) 
if (chaineAnnulBit != NULL) 
{ 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.cc 4/37
03 jun 03 16:11 instrumentation.cc Page 7/23 
INST *inst_br_nop; 
inst_br_nop = newAsm(NOP); 
inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi 
t)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); 
(*nbInstructionsAjoutees)++; 
strcpy(tmp,"b "); 
} 
else strcpy(tmp,"call "); 
// Appel de la fonction définie "ailleurs" (fichier instrument.c) 
// Si le paramètre de l’instruction à instrumentée est un paramètre d’entrée 
if (es==IN) 
{ 
// On appelle la fonction de traitement spécifique aux paramètres d’entrée 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_I 
N_MEM))); 
(*nbInstructionsAjoutees)++; 
} 
// Si le paramètre de l’instruction à instrumentée est un paramètre de sortie 
else 
{ 
// On appelle la fonction de traitement spécifique aux paramètres de sortie 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_O 
UT_MEM))); 
(*nbInstructionsAjoutees)++; 
} 
// On comble le delay slot de l’appel de fonction précédent par un nop 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
void appelDeFonctionCopierInstDelay(BB *bb, int position, unsigned int *nbInstru 
ctionsAjoutees) 
{ 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("call copierInstDelay")); 
(*nbInstructionsAjoutees)++; 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
void appelDeFonctionEchangerInstDelay(BB *bb, int position, unsigned int *nbInst 
ructionsAjoutees) 
{ 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("call echangerInstDelay")); 
(*nbInstructionsAjoutees)++; 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
int rechercheChaine(char *chaine, FILE *fichier) 
{ 
03 jun 03 16:11 instrumentation.cc Page 8/23 
char chaine2[100]; 
while (!feof(fichier)) 
{ 
fscanf(fichier,"%s",chaine2); 
if(!strcmp(chaine,chaine2)) return 1; 
} 
return 0; 
} 
int estPresent(char *motif, char *chaine) 
{ 
int i; 
regex_t *preg = new regex_t(); 
size_t nmatch = 10; 
regmatch_t pmatch[nmatch]; 
// Compilation de l’expression régulière 
if (regcomp(preg, motif, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "%s"n",motif); 
exit(4); 
} 
// Exécution de l’expression régulière et renvoi du résultat en fonction 
if (regexec(preg, chaine, nmatch, pmatch, 0) == REG_NOMATCH) 
{ 
regfree(preg); 
return 0; 
} 
for(i=0; i<nmatch && pmatch[i].rm_so!=−1; i++); 
regfree(preg); 
return i; 
} 
int typeInstruction(INST *inst) 
{ 
int i,j; 
char tmp[100]; 
regex_t *preg = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch[nmatch]; 
FILE *fichier; 
// Si l’instruction est une instruction "save", on retourne la valeur T_SAVE 
if (estPresent(EXP_SAVE,inst−>unparse())) return T_SAVE; 
// Si l’instruction est une instruction "restore", on retourne la valeur T_RES 
TORE 
if (estPresent(EXP_RESTORE,inst−>unparse())) return T_RESTORE; 
// Compilation de l’expression régulière permettant de détecter si une instruc 
tion est une instruction de sortie 
// (printf entre autre) 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.cc 5/37
03 jun 03 16:11 instrumentation.cc Page 9/23 
if (regcomp(preg, EXP_FONCTION, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_FONCTION""n"); 
exit(4); 
} 
// Si l’instruction est un call 
if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) != REG_NOMATCH) 
{ 
// On récupère dans la chaine tmp l’étiquette à laquelle on va brancher en e 
xécutant le call 
for(i=0,j=pmatch[0].rm_eo; j<strlen(inst−>unparse()); j++) 
if((inst−>unparse())[j]!=’ ’ && (inst−>unparse())[j]!=’t’) tmp[i++]=(inst− 
>unparse())[j]; 
tmp[i] = ’0’; 
regfree(preg); 
fichier = fopen(NOM_FICHIER_FONCTIONS_INTERNES, "r"); 
if(fichier == NULL) 
{ 
fprintf(STDERR,"Problème lors de l’ouverture du fichier ""NOM_FICHIER_FONCTIONS_INTERNE 
S"" "); 
fprintf(STDERR,"(fonction typeInstruction dans le fichier instrumentation.cc)n"); 
exit(11); 
} 
// On recherche dans notre base de nom de fonctions définies localement si l 
a fonction désignée par l’étiquette 
// fait partie du code qui à était instrumenté ou non 
if(rechercheChaine(tmp,fichier)) 
{ 
fclose(fichier); 
return T_APPEL_INTERNE; 
} 
fclose(fichier); 
fichier = fopen(NOM_FICHIER_FONCTIONS_EXTERNES_SPECIALES,"r"); 
if(fichier == NULL) 
{ 
fprintf(STDERR,"Problème lors de l’ouverture du fichier ""NOM_FICHIER_FONCTIONS_EXTERNE 
S_SPECIALES"" "); 
fprintf(STDERR,"(fonction typeInstruction dans le fichier instrumentation.cc)n"); 
exit(11); 
} 
if(rechercheChaine(tmp,fichier)) 
{ 
fclose(fichier); 
return T_APPEL_EXTERNE_SPECIAL; 
} 
fclose(fichier); 
return T_APPEL_EXTERNE; 
} 
// Si l’instruction est un "nop", on retourne la valeur T_NOP 
if (inst−>isNop()) return T_NOP; 
// Si l’instruction est un branchement, on retourne la valeur T_BRANCHEMENT 
if (inst−>isCTI()) 
if(estPresent(EXP_ANNUL_BIT,inst−>unparse())) 
Imprimé par Benjamin Vidal 
03 jun 03 16:11 instrumentation.cc Page 10/23 
return T_BRANCHEMENT_ANNUL_BIT; 
else return T_BRANCHEMENT; 
// Si l’instruction est une instruction "ld", on retourne la valeur T_LOAD 
if (estPresent(EXP_LOAD,inst−>unparse())) return T_LOAD; 
// Si l’instruction est une instruction "st", on retourne la valeur T_STORE 
if (estPresent(EXP_STORE,inst−>unparse())) return T_STORE; 
return T_AUTRE; 
} 
void operandeMemoire(char *instruction, char *acces1, char *acces2) 
{ 
int i,j; 
char tmp[10]; 
regex_t *preg_mem = new regex_t(); 
regex_t *preg_signe = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch_mem[nmatch]; 
regmatch_t pmatch_signe[nmatch]; 
// Compilation de l’expression régulière permettant de détecter un acces mémoi 
re d’une instruction 
if (regcomp(preg_mem, EXP_MEMOIRE, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_MEMOIRE""n"); 
exit(4); 
} 
if (regcomp(preg_signe, "[−+]", REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "[−+]"n"); 
exit(4); 
} 
if (regexec(preg_mem, instruction, nmatch, pmatch_mem, 0) == REG_NOMATCH) 
{ 
fprintf(STDERR,"Erreur lors de l’exécution de l’expression régulière ""EXP_MEMOIRE"" : impossible d 
e "); 
fprintf(STDERR,"trouver un motif correspondant à cette expression dans l’instruction "%s"n",instru 
ction); 
exit(4); 
} 
if (regexec(preg_signe, instruction, nmatch, pmatch_signe, 0) == REG_NOMATCH) 
{ 
// On récupère la chaine de caractère correspondant au nom du registre auque 
l on accède 
for(i=0,j=pmatch_mem[0].rm_so+1; j<pmatch_mem[0].rm_eo−1; j++) acces1[i++]=i 
nstruction[j]; 
acces1[i] = ’0’; 
strcpy(acces2,"0"); 
} 
else 
{ 
// On récupère la chaine de caractère correspondant au nom du premier regist 
re auquel on accède 
for(i=0,j=pmatch_mem[0].rm_so+1; j<pmatch_signe[0].rm_so; j++) acces1[i++]=i 
mercredi 18 juin 2003 instrumentation.cc 6/37
03 jun 03 16:11 instrumentation.cc Page 11/23 
nstruction[j]; 
acces1[i] = ’0’; 
i=0; 
// Si le signe reconnu par l’expression régulière [−+] est un moins, on le r 
ejoute en début de chaine 
// possible seulement si le deuxième acces est une constante (ex : ld [%i6−6 
0],%o0) 
if (instruction[pmatch_signe[0].rm_so] == ’−’) acces2[i++] = ’−’; 
// On récupère la chaine de caractère correspondant au nom du deuxième regis 
tre auquel on accède ou à la constante 
for(j=pmatch_signe[0].rm_eo; j<pmatch_mem[0].rm_eo−1; j++) 
acces2[i++] = instruction[j]; 
acces2[i] = ’0’; 
} 
regfree(preg_mem); 
regfree(preg_signe); 
} 
// Fonction permettant de sauvagarder le contexte du programme (registres généra 
ux et codes conditions) afin de ne pas 
// intervenir sur les valeurs des registres et codes conditions utilisés par le 
programme 
void sauvegardeContexte(BB *bb, int position, unsigned int *nbInstructionsAjoute 
es) 
{ 
INST *inst_ccr; 
char *lectureCodesConditions = "trd %ccr,%l0n"; 
// "Empilement" du contexte du programme (création d’un contexte intermédiaire 
artificiel entre l’exécution du 
// programme et l’exécution des fonctions d’instrumentation du code. Ce contex 
te permet de travailler avec 
// les registres %o[0−5] afin de faire passer les paramètres aux fonctions d’i 
nstrumentations. 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(SAVE)); 
(*nbInstructionsAjoutees)++; 
inst_ccr = newAsm(NOP); 
inst_ccr−>addAttribute(UNPARSE_ATT, lectureCodesConditions, strlen(lectureCode 
sConditions)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_ccr); 
(*nbInstructionsAjoutees)++; 
// Sauvegarde en mémoire (dans la pile) des registres globaux 
for(int i=1; i<=4; i++) 
{ 
char tmp[20]; 
// Sauvegarde du registre (%gi) (ex : "st %g1,[%sp+92]") 
sprintf(tmp,"st %%g%d,[%%sp+%d]",i,88+(4*i)); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); 
(*nbInstructionsAjoutees)++; 
} 
} 
// Fonction permettant de restaurer le contexte du programme (registres généraux 
et codes conditions) afin de ne pas 
// intervenir sur les valeurs des registres et codes conditions utilisés par le 
03 jun 03 16:11 instrumentation.cc Page 12/23 
programme 
void restaurationContexte(BB *bb, int position, unsigned int *nbInstructionsAjou 
tees) 
{ 
INST *inst_ccr; 
char *ecritureCodesConditions = "twr %l0,%ccrn"; 
// Récupération des registres globaux depuis la mémoire (depuis la pile) 
for(int i=1; i<=4; i++) 
{ 
char tmp[20]; 
// Restauration du registre (%gi) (ex : "ld [%sp+92],%g1") 
sprintf(tmp,"ld [%%sp+%d],%%g%d",88+(4*i),i); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); 
(*nbInstructionsAjoutees)++; 
} 
inst_ccr = newAsm(NOP); 
inst_ccr−>addAttribute(UNPARSE_ATT, ecritureCodesConditions, strlen(ecritureCo 
desConditions)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_ccr); 
(*nbInstructionsAjoutees)++; 
// "Dépilement" du contexte du programme 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(RESTORE)); 
(*nbInstructionsAjoutees)++; 
} 
int registreSalto(char *acces) 
{ 
regex_t *preg = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch[nmatch]; 
// Compilation de l’expression régulière permettant de détecter un registre 
if (regcomp(preg, EXP_REGISTRE, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_REGISTRE""n"); 
exit(4); 
} 
if (regexec(preg, acces, nmatch, pmatch, 0) == REG_NOMATCH) 
{ 
regfree(preg); 
return 0; 
} 
regfree(preg); 
// Traitement des cas particuliers : %sp et %fp (correspondant à %o6 et %i6) 
if (!strcmp(acces,"%sp")) return ID_REG_SALTO_O+6; 
if (!strcmp(acces,"%fp")) return ID_REG_SALTO_I+6; 
// Traitement du cas général 
switch (acces[1]) 
{ 
case ’g’ : return ID_REG_SALTO_G+atoi(&(acces[2])); 
case ’o’ : return ID_REG_SALTO_O+atoi(&(acces[2])); 
case ’l’ : return ID_REG_SALTO_L+atoi(&(acces[2])); 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.cc 7/37
03 jun 03 16:11 instrumentation.cc Page 13/23 
case ’i’ : return ID_REG_SALTO_I+atoi(&(acces[2])); 
default : fprintf(STDERR,"Passage impossible : fonction registreSalto dans instrumentation.ccn") 
; 
} 
} 
void blancSuivant(char **ptr) 
{ 
while ((*ptr)[0]!=’ ’ && (*ptr)[0]!=’t’ && (*ptr)[0]!=’n’ && (*ptr)[0]!=’0’) ( 
*ptr)++; 
while ((*ptr)[0]==’ ’ || (*ptr)[0]==’t’ || (*ptr)[0]==’n’) (*ptr)++; 
} 
// Insère le code permettant d’instrumenter en fonction des entrées produites pa 
r inst 
void appelDeFonctionsEntrees(INST *inst, BB *bb, int position, unsigned int *nbI 
nstructionsAjoutees, char *chaineAnnulBit) 
{ 
ResourceDataBase &rdb = xxx_server −> GetResT(); 
res_ref* in; 
ResId_T identificateurRessource; 
// Ajout de l’instrumentation représentant les entrées de l’instruction fourni 
e par salto (lectures) 
for (int i=0; i < inst−>numberOfInput(); i++) 
{ 
in = inst−>getInput(i); 
identificateurRessource = in−>get_res_id(); 
// Traitement fait pour chaque opérandes d’entrée 
switch ((rdb.get_res(identificateurRessource))−>getType()) 
{ 
case MEMORY_RTYPE : 
char acces1[100],acces2[100]; 
operandeMemoire(inst−>unparse(), acces1, acces2); 
appelDeFonctionMem(bb, inst, position, nbInstructionsAjoutees, IN, acces 
1, acces2, chaineAnnulBit, tailleAccesMemoire(inst)); 
break; 
case REGISTER_RTYPE : 
appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, identificat 
eurRessource, chaineAnnulBit); 
break; 
default : 
fprintf(STDERR,"Passage impossible : fonction appelDeFonctionsEntrees dans instrumentation.ccn" 
); 
} 
} 
} 
// Ajout de l’instrumentation représentant les entrées fictives des call externe 
s 
void appelDeFonctionsEntreesAppelExterne(INST *inst, BB *bb, int position, unsig 
ned int *nbInstructionsAjoutees) 
{ 
char acces[20]; 
char *ptr = (char *)(inst−>attributeValue(IN,COMMENT_ATT)); 
int identificateurRessource; 
03 jun 03 16:11 instrumentation.cc Page 14/23 
sscanf(ptr,"%s",acces); 
// Tant qu’on n’a pas atteint la fin de la chaine de caractère contenant les e 
ntrées effectuées par le call traité 
while (ptr[0] != ’0’) 
{ 
blancSuivant(&ptr); 
identificateurRessource = registreSalto(acces); 
if(identificateurRessource != 0) 
appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, identificateu 
rRessource, NULL); 
else 
{ 
char tmp[20],acces1[100],acces2[100]; 
strcpy(tmp,"["); 
strcat(tmp,acces); 
strcat(tmp,"]"); 
operandeMemoire(tmp, acces1, acces2); 
appelDeFonctionMem(bb, inst, position, nbInstructionsAjoutees, IN, acces1, 
acces2, NULL, 4); 
} 
sscanf(ptr,"%s",acces); 
} 
// Les fonctions définies hors du fichier local font toujours un accès en lect 
ure à %o6 (pointeur de pile) 
// et à %o7 (adresse de retour) 
/*appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, ID_REG_SALTO_O+ 
6, NULL); 
appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, ID_REG_SALTO_O+7, 
NULL);*/ 
} 
// Insère le code permettant d’instrumenter en fonction des sorties produites pa 
r inst 
void appelDeFonctionsSorties(INST *inst, BB *bb, int position, unsigned int *nbI 
nstructionsAjoutees, char *chaineAnnulBit) 
{ 
ResourceDataBase &rdb = xxx_server −> GetResT(); 
res_ref* out; 
int identificateurRessource; 
// Ajout de l’instrumentation représentant les sorties de l’instruction fourni 
e par salto (écritures) 
for (int i=0; i < inst−>numberOfOutput(); i++) 
{ 
out = inst−>getOutput(i); 
identificateurRessource = out−>get_res_id(); 
// Traitement fait pour chaque opérandes de sortie 
switch ((rdb.get_res(identificateurRessource))−>getType()) 
{ 
case MEMORY_RTYPE : 
char acces1[100],acces2[100]; 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.cc 8/37
03 jun 03 16:11 instrumentation.cc Page 15/23 
operandeMemoire(inst−>unparse(), acces1, acces2); 
appelDeFonctionMem(bb, inst, position, nbInstructionsAjoutees, OUT, acce 
s1, acces2, chaineAnnulBit, tailleAccesMemoire(inst)); 
break; 
case REGISTER_RTYPE : 
appelDeFonctionReg(bb, position, nbInstructionsAjoutees, OUT, identifica 
teurRessource, chaineAnnulBit); 
break; 
default : 
fprintf(STDERR,"Passage impossible : fonction appelDeFonctionsSorties dans instrumentation.ccn" 
); 
} 
} 
} 
// Ajout de l’instrumentation représentant les sorties fictives des call externe 
s 
void appelDeFonctionsSortiesAppelExterne(INST *inst, BB *bb, int position, unsig 
ned int *nbInstructionsAjoutees) 
{ 
char *acces = (char *)(inst−>attributeValue(OUT,COMMENT_ATT)); 
ResId_T identificateurRessource = registreSalto(acces); 
if(identificateurRessource == 0) 
{ 
fprintf(STDERR,"Erreur : la chaine "%s" n’est pas reconnue comme étant un registre par la fonction ",a 
cces); 
fprintf(STDERR,"registreSalto(char*) (fonction appelDeFonctionsSortiesAppelExterne dans instrumentatio 
n.cc)n"); 
exit(9); 
} 
appelDeFonctionReg(bb, position, nbInstructionsAjoutees, OUT, identificateurRe 
ssource, NULL); 
} 
void instrumenter(INST *inst, BB *bb, BB *bbSuivant, int position, unsigned int 
*nbInstructionsAjoutees, int numeroInst) 
{ 
static unsigned char flagDelaySlot = 0; 
// Si l’instruction est une instruction d’appel externe, on remplace "call tar 
tampion" par "call my_tartempion" 
// pour détourner l’appel "normal" à la fonction de la librairie en un appel à 
une fonction redéfinie permettant de 
// rendre compte des accès mémoires fait par ces fonctions 
if(typeInstruction(inst) == T_APPEL_EXTERNE) 
{ 
char chaine[100],chaine2[100], *tmp; 
sscanf(inst−>unparse(),"%s %s",chaine,chaine2); 
strcpy(chaine, "tcall "PREFIXE_FONCTIONS_EXTERNES); 
// Allocation d’une nouvelle chaine de caractère contenant la nouvelle repré 
sentation de l’instruction 
tmp = (char *)malloc((strlen(chaine)+strlen(chaine2)+2)*sizeof(char)); 
strcpy(tmp,chaine); 
Imprimé par Benjamin Vidal 
03 jun 03 16:11 instrumentation.cc Page 16/23 
strcat(tmp,chaine2); 
strcat(tmp,"n"); 
inst−>addAttribute(UNPARSE_ATT, tmp, strlen(tmp)+1); 
} 
// Si on n’a pas à faire à une instruction se trouvant dans un DelaySlot 
if(flagDelaySlot == 0) 
// Si l’instruction n’est pas un branchement, alors, on instrumente cette in 
struction normalement 
if(!inst−>isCTI()) 
{ 
sauvegardeContexte(bb, position, nbInstructionsAjoutees); 
appelDeFonctionInstDebut(bb, position, nbInstructionsAjoutees, numeroInst, 
typeInstruction(inst), NULL); 
appelDeFonctionsEntrees(inst, bb, position, nbInstructionsAjoutees, NULL); 
if(typeInstruction(inst)==T_SAVE || typeInstruction(inst)==T_RESTORE) 
appelDeFonctionInstMilieu(bb, position, nbInstructionsAjoutees, NULL); 
appelDeFonctionsSorties(inst, bb, position, nbInstructionsAjoutees, NULL); 
appelDeFonctionInstFin(bb, position, nbInstructionsAjoutees, NULL); 
restaurationContexte(bb, position, nbInstructionsAjoutees); 
} 
// Si l’instruction courante est un branchement, alors, on l’instrumente ain 
si que son DelaySlot 
else 
{ 
char *chaineAnnulBit = (char *)malloc(100*sizeof(char)); 
INST *instDelaySlot = bbSuivant−>getAsm(0); 
/*fprintf(STDERR,"Inst <%s>tttDelay <%s>n",inst−>unparse(),instDelaySl 
ot−>unparse());*/ 
// On met le flag à 1 pour indiquer que l’instrumentation de l’instruction 
se trouvant dans le DelaySlot 
// du branchement que l’on est en train de traiter a déjà été instrumentée 
flagDelaySlot = 1; 
sauvegardeContexte(bb, position, nbInstructionsAjoutees); 
// On traite le début de l’instruction de branchement 
appelDeFonctionInstDebut(bb, position, nbInstructionsAjoutees, numeroIns 
t, typeInstruction(inst), NULL); 
// On traite les lectures de l’instruction de branchement 
appelDeFonctionsEntrees(inst, bb, position, nbInstructionsAjoutees, NULL 
); 
// On traite les écritures de l’instruction de branchement 
appelDeFonctionsSorties(inst, bb, position, nbInstructionsAjoutees, NULL 
); 
appelDeFonctionCopierInstDelay(bb, position, nbInstructionsAjoutees); 
// Si le branchement est un brachement avec annul_bit (ex : bl,a ... 
) alors on rempli la chaine de caractère 
// chaineAnnulBit en conséquence (on y met les instructions pour tra 
iter ce cas correctement) 
if(typeInstruction(inst) == T_BRANCHEMENT_ANNUL_BIT) 
mercredi 18 juin 2003 instrumentation.cc 9/37
03 jun 03 16:11 instrumentation.cc Page 17/23 
{ 
int i; 
char tmp[20]; 
regex_t *preg = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch[nmatch]; 
if (regcomp(preg, EXP_ANNUL_BIT, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_ANNUL 
_BIT""n"); 
exit(4); 
} 
if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) == REG_NOMAT 
CH) 
{ 
fprintf(STDERR,"Erreur lors de l’exécution de l’expression régulière ""EXP_ANNUL_B 
IT"" :n"); 
fprintf(STDERR,"Impossible de trouver l’expression régulière dans l’instruction "%s"n 
",inst−>unparse()); 
exit(4); 
} 
regfree(preg); 
for(i=0; i<pmatch[0].rm_eo; i++) 
tmp[i] = (inst−>unparse())[i]; 
tmp[i] = ’0’; 
strcpy(chaineAnnulBit,"trd %pc,%o7n"); 
strcat(chaineAnnulBit,"tadd %o7,16,%o7n"); 
strcat(chaineAnnulBit,"twr %l0,%ccrn"); 
strcat(chaineAnnulBit,tmp); 
strcat(chaineAnnulBit," "ETIQUETTE_FONCTION_NOP"n"); 
} 
else chaineAnnulBit = NULL; 
// On traite le début de l’instruction qui se trouve dans le DelaySl 
ot 
appelDeFonctionInstDebut(bb, position, nbInstructionsAjoutees, numer 
oInst+1, typeInstruction(instDelaySlot), chaineAnnulBit); 
// On traite les lectures de l’instruction se trouvant dans le Delay 
Slot du branchement 
appelDeFonctionsEntrees(instDelaySlot, bb, position, nbInstructionsA 
joutees, chaineAnnulBit); 
appelDeFonctionInstMilieu(bb, position, nbInstructionsAjoutees, chai 
neAnnulBit); 
// On traite les écritures de l’instruction se trouvant dans le Dela 
ySlot du branchement 
appelDeFonctionsSorties(instDelaySlot, bb, position, nbInstructionsA 
joutees, chaineAnnulBit); 
// On traite la fin de l’instruction qui se trouve dans le DelaySlot 
appelDeFonctionInstFin(bb, position, nbInstructionsAjoutees, chaineA 
nnulBit); 
appelDeFonctionEchangerInstDelay(bb, position, nbInstructionsAjoutees); 
// Si l’instruction est un call externe, on lui ajoute comme entrées et 
sorties les paramètres consommés 
// et produits par cette fonction 
if(typeInstruction(inst) == T_APPEL_EXTERNE || typeInstruction(inst) == 
T_APPEL_EXTERNE_SPECIAL) 
Imprimé par Benjamin Vidal 
03 jun 03 16:11 instrumentation.cc Page 18/23 
{ 
switch (inst−>numberOfAttributes(COMMENT_ATT)) 
{ 
case 1 : 
appelDeFonctionsEntreesAppelExterne(inst, bb, position, nbInstruct 
ionsAjoutees); 
break; 
case 2 : 
appelDeFonctionsEntreesAppelExterne(inst, bb, position, nbInstruct 
ionsAjoutees); 
appelDeFonctionsSortiesAppelExterne(inst, bb, position, nbInstruct 
ionsAjoutees); 
break; 
default : 
fprintf(STDERR,"Passage impossible : fonction instrumenter dans instrumentation.ccn"); 
} 
} 
// On traite la fin de l’instruction de branchement seulment si on n’a p 
as à faire à un appel externe interceptées par les 
// fonctions redéfinies dans redefinition.c 
if(typeInstruction(inst)!=T_APPEL_EXTERNE) appelDeFonctionInstFin(bb, po 
sition, nbInstructionsAjoutees, NULL); 
appelDeFonctionEchangerInstDelay(bb, position, nbInstructionsAjoutees); 
restaurationContexte(bb, position, nbInstructionsAjoutees); 
} 
// Si l’instruction se trouve dans un DelaySlot, on remet le flag correspondan 
t à 0 
else flagDelaySlot = 0; 
} 
void ajouterCommentaireAppelExterne(INST *inst) 
{ 
int i,j; 
char etiquette[100], ligneCourante[200]; 
regex_t *preg_fct = new regex_t(); 
regex_t *preg_etiquette = new regex_t(); 
regex_t *preg_params = new regex_t(); 
regex_t *preg_result = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch[nmatch]; 
regmatch_t pmatch_params[nmatch]; 
regmatch_t pmatch_result[nmatch]; 
// Compilation de l’expression régulière permettant de détecter si une instruc 
tion est une fonction 
if (regcomp(preg_fct, EXP_FONCTION, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_FONCTION""n"); 
exit(4); 
} 
regexec(preg_fct, inst−>unparse(), nmatch, pmatch, 0); 
// On récupère dans la chaine etiquette l’étiquette associée au call traité 
for(i=0,j=pmatch[0].rm_eo; j<strlen(inst−>unparse()); j++) 
mercredi 18 juin 2003 instrumentation.cc 10/37
03 jun 03 16:11 instrumentation.cc Page 19/23 
if((inst−>unparse())[j]!=’ ’ && (inst−>unparse())[j]!=’t’) etiquette[i++]=(i 
nst−>unparse())[j]; 
etiquette[i] = ’0’; 
if (regcomp(preg_etiquette, etiquette, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "%s"n",etiquette); 
exit(4); 
} 
if (regcomp(preg_params, "[ ]+! params =[ ]+", REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "[ t]+! params =[ t]+"n"); 
exit(4); 
} 
if (regcomp(preg_result, "[ ]+! Result =[ ]+", REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "[ t]+! Result =[ t]+"n"); 
exit(4); 
} 
// On cherche, dans le fichier assembleur original, la ligne contenant l’appel 
à la fonction traitée 
do 
{ 
char c; 
i=0; 
do 
{ 
c = fgetc(fichierSOriginal); 
ligneCourante[i++] = c; 
} 
while(c != ’n’); 
ligneCourante[i] = ’0’; 
} 
while (regexec(preg_fct, ligneCourante, nmatch, pmatch, 0) == REG_NOMATCH || 
regexec(preg_etiquette, ligneCourante, nmatch, pmatch, 0) == REG_NOMATC 
H || 
regexec(preg_params, ligneCourante, nmatch, pmatch_params, 0) == REG_NO 
MATCH || 
regexec(preg_result, ligneCourante, nmatch, pmatch_result, 0) == REG_NO 
MATCH || 
feof(fichierSOriginal)); 
regfree(preg_fct); 
regfree(preg_etiquette); 
regfree(preg_params); 
regfree(preg_result); 
if(feof(fichierSOriginal)) 
{ 
fprintf(STDERR,"Erreur : fin du fichier .s original atteinte "); 
fprintf(STDERR,"(fonction ajouterCommentaireAppelExterne dans instrumentation.cc)n"); 
exit(7); 
} 
// Ici, la variable ligneCourante contient la ligne du fichier assembleur corr 
espondant à l’appel 
// de la fonction traitée (qui contient donc aussi en commentaire les paramètr 
es de cette fonction) 
char liste[100],*tmp; 
03 jun 03 16:11 instrumentation.cc Page 20/23 
// On récupère la liste des paramètres d’entrée 
for(i=0,j=pmatch_params[0].rm_eo; j<pmatch_result[0].rm_so; j++) liste[i++]=li 
gneCourante[j]; 
liste[i] = ’0’; 
// Puis on la range dans les attributs à mettre en commentaire dans le fichier 
généré 
// NOTE : ces commentaires servent aussi par la suite à identifier quels sont 
les accès fait par la fonction externe 
tmp = (char *)malloc((strlen(liste)+1)*sizeof(char)); 
strcpy(tmp,liste); 
inst−>addAttribute(COMMENT_ATT, tmp, strlen(liste)+1); 
// On récupère la liste contenant le résultat (paramètre de sortie) 
for(i=0,j=pmatch_result[0].rm_eo; ligneCourante[j]!=’!’ && 
ligneCourante[j]!=’ ’ && 
ligneCourante[j]!=’t’ && 
ligneCourante[j]!=’n’; j++) liste[i++]=lign 
eCourante[j]; 
liste[i] = ’0’; 
// Puis, si la chaine n’est pas vide, on la range dans les attributs à mettre 
en commentaire dans le fichier généré 
// NOTE : ces commentaires servent aussi par la suite à identifier quels sont 
les accès fait par la fonction externe 
if(liste[0] != ’0’) 
{ 
tmp = (char *)malloc((strlen(liste)+1)*sizeof(char)); 
strcpy(tmp,liste); 
inst−>addAttribute(COMMENT_ATT, tmp, strlen(tmp)+1); 
} 
} 
void Salto_hook() 
{ 
CFG *proc; 
BB *bb,*bbSuivant; 
INST *inst; 
int numeroInst; 
unsigned int nbInstructionsAjoutees; 
// Parcours du programme permettant de récupérer les paramètres des fonctions 
définies à 
// l’extérieur des fichiers .s locaux 
for (int i=0; i < numberOfCFG(); i++) 
{ 
proc = getCFG(i); 
for (int j=0; j < proc−>numberOfBB(); j++) 
{ 
bb = proc−>getBB(j); 
for (int k=0; k < bb−>numberOfAsm(); k++) 
{ 
inst = bb−>getAsm(k); 
if(typeInstruction(inst) == T_APPEL_EXTERNE || 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.cc 11/37
03 jun 03 16:11 instrumentation.cc Page 21/23 
typeInstruction(inst) == T_APPEL_EXTERNE_SPECIAL) ajouterCommentaireA 
ppelExterne(inst); 
} 
} 
} 
// Parcours du programme permettant d’insérer le code d’instrumentation des in 
structions 
numeroInst = 0; 
for (int i=0; i < numberOfCFG(); i++) 
{ 
proc = getCFG(i); 
for (int j=0; j < proc−>numberOfBB(); j++) 
{ 
bb = proc−>getBB(j); 
bbSuivant = proc−>getBB(j+1); 
int nbAsm = bb−>numberOfAsm(); 
nbInstructionsAjoutees = 0; 
for (int k=0; k < nbAsm; k++) 
{ 
numeroInst++; 
inst = bb−>getAsm(k+nbInstructionsAjoutees); 
instrumenter(inst, bb, bbSuivant, k, &nbInstructionsAjoutees, numeroInst 
); 
} 
} 
} 
// Parcours du programme permettant d’insérer le code pour l’initialisation de 
s variables globales 
// et d’affichage de ces variables en fin d’exécution du programme. 
for (int i=0; i < numberOfCFG(); i++) 
{ 
proc = getCFG(i); 
if(!strcmp(proc−>getName(),"main")) 
{ 
// Insertion d’un appel à la procédure initialisant les variables globales 
(proc−>getBB(0))−>insertAsm(0, newAsm("call initVariablesGlobales")); 
(proc−>getBB(0))−>insertAsm(1, newAsm(NOP)); 
for (int j=0; j < proc−>numberOfBB(); j++) 
{ 
bb = proc−>getBB(j); 
for (int k=0; k < bb−>numberOfAsm(); k++) 
{ 
inst = bb−>getAsm(k); 
// Insertion d’un appel à la procédure d’affichage des structures de d 
onnées juste avant l’instruction "call exit" 
if(estPresent("call",inst−>unparse()) && estPresent("exit",inst−>unparse( 
))) 
{ 
bb−>insertAsm(k, newAsm("call afficherSdd")); 
bb−>insertAsm(k+1, newAsm(NOP)); 
break; 
} 
} 
} 
break; 
} 
} 
// Envoi du code instrumenté vers la sortie standard 
produceCode(fichierSInstrumente); 
} 
void Salto_init_hook(int argc, char *argv[]) 
{ 
int i,j,k; 
char nomFichierSortie[100]; 
Imprimé par Benjamin Vidal 
// Récupération dans la ligne de commande entrée par l’utilisateur du nom du f 
ichier original 
for(i=1; i<argc && !estPresent("−i",argv[i]); i++); 
if(i == argc−1) 
{ 
fprintf(STDERR,"Erreur, votre ligne de commande ne comporte pas l’option "−i"n"); 
exit(6); 
} 
// On ouvre le fichier .s original à traiter pour pouvoir s’en servir dans sal 
to_hook() 
fichierSOriginal = fopen(argv[i+1],"r"); 
if(fichierSOriginal == NULL) 
{ 
fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",argv[i+1]); 
fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation.cc)n"); 
exit(11); 
} 
nomFichierSortie[0]=’0’; 
strcat(nomFichierSortie,REPERTOIRE_FICHIER_INSTRUMENTES); 
strcat(nomFichierSortie,argv[i+1]); 
// On ouvre le fichier .s instrumenté en écriture à traiter pour pouvoir s’en 
servir dans salto_hook() 
fichierSInstrumente = fopen(nomFichierSortie,"w"); 
if(fichierSInstrumente == NULL) 
{ 
fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",nomFichierSortie); 
fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation2.cc)n"); 
exit(11); 
} 
// Recherche de l’expression "−−" dans la ligne de commande 
for(; i<argc && !estPresent("−−",argv[i]); i++); 
// Si cette expression n’est pas présente, on ne numérote pas les fichiers 
if(i == argc−1) numeroFichier = 0; 
else numeroFichier=atoi(argv[i+1]); 
} 
03 jun 03 16:11 instrumentation.cc Page 22/23 
mercredi 18 juin 2003 instrumentation.cc 12/37
void Salto_end_hook() 
{ 
exit(0); 
} 
03 jun 03 16:11 instrumentation.cc Page 23/23 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation.cc 13/37
16 jun 03 17:02 instrument.h Page 1/1 
#define STDERR stdout 
#define TRACE_INSTRUCTIONS_INUTILES "../trace_inutile.txt" 
#define TRACE_INSTRUCTIONS_STATIQUES_INUTILES "../trace_statiques_inutile.txt" 
#define TRACE_EVOLUTION_INUTILES "../evolution_inutile.txt" 
#define TRACE_VALEURS_MORTES "../evolution_valeurs_mortes.txt" 
/* Défini le nom de la fonction à appeler à chaque début de bloc de base */ 
#define NOM_FCT_IN_MEM "instrumentationEntreeMemoire" 
#define NOM_FCT_OUT_MEM "instrumentationSortieMemoire" 
#define NOM_FCT_IN_REG "instrumentationEntreeRegistre" 
#define NOM_FCT_OUT_REG "instrumentationSortieRegistre" 
#define NOM_FCT_DEBUT_INST "instrumentationInstructionDebut" 
#define NOM_FCT_MILIEU_INST "instrumentationInstructionMilieu" 
#define NOM_FCT_FIN_INST "instrumentationInstructionFin" 
#define NOM_FCT_INST "instrumentationInstruction" 
/* Défini les différentes valeur que peut prendre la variable 
typeInstruction dans la structure instruction */ 
#define T_APPEL_EXTERNE 0 
#define T_APPEL_EXTERNE_SPECIAL 1 
#define T_APPEL_INTERNE 2 
#define T_BRANCHEMENT 3 
#define T_BRANCHEMENT_ANNUL_BIT 4 
#define T_SAVE 5 
#define T_RESTORE 6 
#define T_LOAD 7 
#define T_STORE 8 
#define T_NOP 9 
#define T_AUTRE 10 
/* Identifiant Salto du registre %g0 */ 
#define ID_REG_SALTO_G 41 
/* Identifiant Salto du registre %o0 */ 
#define ID_REG_SALTO_O 49 
/* Identifiant Salto du registre %l0 */ 
#define ID_REG_SALTO_L 57 
/* Identifiant Salto du registre %i0 */ 
#define ID_REG_SALTO_I 65 
/* Décalage, en nombre de registres, entre une fenêtre et la suivante */ 
#define OFFSET_FENETRE 16 
/* A virer dans la version optimisée */ 
#define MAX_NB_INST_DYNAMIQUE 10000000 
void instrumentationInstructionDebut(int typeInstruction, int numeroInst, int nu 
meroFichier); 
void instrumentationInstructionMilieu(void); 
void instrumentationInstructionFin(void); 
void copierInstDelay(void); 
void echangerInstDelay(void); 
void instrumentationEntreeRegistre(int identificateurRessource); 
void instrumentationSortieRegistre(int identificateurRessource); 
void instrumentationEntreeMemoire(int adresseMemoire, int nbOctetsLus); 
void instrumentationSortieMemoire(int adresseMemoire, int nbOctetsEcrits); 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument.h 14/37
18 jun 03 17:55 instrument.c Page 1/13 
#include <stdio.h> 
#include <stdlib.h> 
#include "instrument.h" 
#define NB_REGISTRES 115 
#define NB_REGISTRES_TOURNANTS 100000 
/* MAX_NB_OPERANDES doit etre égal au maximum d’opérandes que peuvent avoir les 
instruction +1 ! */ 
#define MAX_NB_OPERANDES 150 
/* Défini les différentes valeur que peuvent prendre les variables utiliteInst[i 
], i allant de 0 à MAX_NB_INST_DYNAMIQUE.*/ 
#define INUTILE 0 
#define UTILE 1 
#define NOP 2 
#define GENERER_FICHIER_DOT 1 
#define FICHIER_TRACE_DOT "graphe_dependances.dot" 
#define MAX_NB_INST_STATIQUES 11000 
#define MAX_INST_STATIQUES_TOTAL 250000 
#define NB_FICHIERS 15 
typedef unsigned char flag; 
typedef struct instru { 
unsigned char numeroFichier; 
unsigned int numeroStatique; 
unsigned int numeroDynamique; 
flag typeInstruction; 
struct instru *origineOperandes[MAX_NB_OPERANDES]; 
struct instru *precedent; 
} instruction; 
typedef struct mem { 
int adresse; 
unsigned int nbLecture; 
instruction *derniereEcriture; 
struct mem *suivant; 
} elementMemoire; 
/* Définition des registres généraux */ 
instruction *tableRegistres[NB_REGISTRES]; 
unsigned int tableLectureRegistres[NB_REGISTRES]; 
/* Définition des registres tournants */ 
instruction *tableRegistresTournants[NB_REGISTRES_TOURNANTS]; 
unsigned int tableLectureRegistresTournants[NB_REGISTRES_TOURNANTS]; 
unsigned int niveauFenetreRegistre = 0; 
/* Définition des zones mémoires (point d’entrée dans la liste chainée) */ 
elementMemoire *adresseInitiale = NULL; 
/* Définition des instructions (point d’entrée dans la liste chainée et tableau 
18 jun 03 17:55 instrument.c Page 2/13 
de flag) */ 
flag utiliteInst[MAX_NB_INST_DYNAMIQUE]; 
flag valeursMortes[MAX_NB_INST_DYNAMIQUE]; 
instruction *instInitiale; 
instruction *instCourante = NULL; 
instruction *instCouranteDelay = NULL; 
unsigned int occurencesInutileInstStatiques[MAX_NB_INST_STATIQUES][NB_FICHIERS]; 
unsigned int occurencesInstStatiques[MAX_NB_INST_STATIQUES][NB_FICHIERS]; 
unsigned int allocation=0; 
/* Utile uniquement pour la version donnant la proportion d’instruction statique 
s inutiles */ 
int cptInstStatiquesTraitees; 
void initVariablesGlobales(void) 
{ 
int i,j; 
/* Création d’une instruction initiale fictive sur laquelle vont pointer celle 
s qui 
utilisent des registres déjà initialisés lors du lancement du programme */ 
instInitiale = malloc(sizeof(instruction)); 
allocation++; 
if(instInitiale == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument.c)n"); 
exit(10); 
} 
instInitiale−>numeroStatique = 0; 
instInitiale−>numeroDynamique = 0; 
instInitiale−>numeroFichier = 0; 
for(i=0; i<MAX_NB_OPERANDES; i++) instInitiale−>origineOperandes[i] = NULL; 
instInitiale−>typeInstruction = T_AUTRE; 
instInitiale−>precedent = NULL; 
instCourante = instInitiale; 
for(i=0; i<NB_REGISTRES; i++) 
{ 
tableRegistres[i] = instInitiale; 
tableLectureRegistres[i] = 0; 
} 
for(i=0; i<NB_REGISTRES_TOURNANTS; i++) 
{ 
tableRegistresTournants[i] = instInitiale; 
tableLectureRegistresTournants[i] = 0; 
} 
for(i=0; i<MAX_NB_INST_DYNAMIQUE; i++) 
{ 
utiliteInst[i] = INUTILE; 
valeursMortes[i] = 0; 
} 
for(i=0; i<MAX_NB_INST_STATIQUES; i++) 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument.c 15/37
18 jun 03 17:55 instrument.c Page 3/13 
for(j=0; j<NB_FICHIERS; j++) 
{ 
occurencesInstStatiques[i][j]=0; 
occurencesInutileInstStatiques[i][j]=0; 
} 
} 
void remonterArbre(instruction *inst) 
{ 
int i; 
/* Si on n’a pas encore parcouru l’arbre de dépendance de l’instuction inst, 
on le parcours pour pour positionner les flag d’utilité utiliteInst[inst−>n 
umeroDynamique] */ 
if(utiliteInst[inst−>numeroDynamique]!=UTILE) 
{ 
utiliteInst[inst−>numeroDynamique] = UTILE; 
for(i=0; inst−>origineOperandes[i]!=NULL; i++) remonterArbre(inst−>origineOp 
erandes[i]); 
} 
} 
insert(float *tableauInstStatiqueInutiles, float valeur) 
{ 
int i,j,k; 
for(i=0; tableauInstStatiqueInutiles[i]>valeur; i++); 
for(k=cptInstStatiquesTraitees; k!=i; k−−) tableauInstStatiqueInutiles[k]=tabl 
eauInstStatiqueInutiles[k−1]; 
tableauInstStatiqueInutiles[i] = valeur; 
cptInstStatiquesTraitees++; 
} 
/* Fonction de debug permettant d’afficher la liste chainée des instructions, 
la table des registres et enfin le pourcentage d’instructions utile et inutil 
es */ 
void afficherSdd(void) 
{ 
instruction *i; 
elementMemoire *a; 
int j,k,cptUtile=0,cptInutile=0,cptNop=0,cptInstStatique=0,cptInstStatiqueOccu 
rencesInutile=0, 
cptLoadInutile=0,cptLoad=0,cptStoreInutile=0,cptStore=0,cptMortes=0,nbTotalIns 
tructions; 
unsigned int instInutileArtificiel; 
float tableauInstStatiqueInutiles[MAX_INST_STATIQUES_TOTAL]; 
/* Définition du fichier contenant la trace (numéro) des instructions inutiles 
) */ 
FILE *traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"w"); 
FILE *traceInstructionsStatiquesInutiles = fopen(TRACE_INSTRUCTIONS_STATIQUES_ 
INUTILES,"w"); 
FILE *fichierEvolutionQtValeursMortes = fopen(TRACE_VALEURS_MORTES,"w"); 
FILE *fichierDot; 
if(GENERER_FICHIER_DOT) 
{ 
18 jun 03 17:55 instrument.c Page 4/13 
fichierDot = fopen(FICHIER_TRACE_DOT,"w"); 
if(fichierDot==NULL) 
{ 
fprintf(stderr,"Problème lors de l’ouverture du fichier ""FICHIER_TRACE_DOT"" "); 
fprintf(stderr,"(fonction afficherSdd dans le fichier instrument.c)n"); 
exit(11); 
} 
fprintf(fichierDot,"digraph G {n"); 
} 
if(traceInstructionsInutiles==NULL) 
{ 
fprintf(stderr,"Problème lors de l’ouverture du fichier ""TRACE_INSTRUCTIONS_INUTILES"" " 
); 
fprintf(stderr,"(fonction afficherSdd dans le fichier instrument.c)n"); 
exit(11); 
} 
/* Affichage des dépendances entre les instructions dynamiques 
(stockées dans la liste chainée d’instructions dynamiques) */ 
for(i=instCourante; i!=NULL; i=i−>precedent) 
{ 
if(GENERER_FICHIER_DOT && utiliteInst[i−>numeroDynamique]!=NOP) 
{ 
if(utiliteInst[i−>numeroDynamique]==INUTILE) 
fprintf(fichierDot,"t%d [color=".0 .0 .8",fontcolor=".0 .0 .8"];n", i−>numeroDynamiq 
ue); 
} 
fprintf(stdout,"I.Dynamic %dt−− I.Static %dt−− File %d",i−>numeroDynamique,i−>numeroS 
tatique,i−>numeroFichier); 
if(utiliteInst[i−>numeroDynamique]==UTILE) fprintf(stdout,"t−− utile"); 
else if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(stdout,"t−− INUTILE 
"); 
else if(utiliteInst[i−>numeroDynamique]==NOP) fprintf(stdout,"t−− nop"); 
if(i−>origineOperandes[0]!=NULL) 
{ 
fprintf(stdout,"t−− Dépend de %d",(i−>origineOperandes[0])−>numeroDynamique) 
; 
if(GENERER_FICHIER_DOT && utiliteInst[i−>numeroDynamique]!=NOP) 
{ 
fprintf(fichierDot,"t%d −> %d", i−>numeroDynamique, (i−>origineOperandes 
[0])−>numeroDynamique); 
if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(fichierDot," [color=". 
0 .0 .8"]"); 
fprintf(fichierDot,";n"); 
} 
} 
for(j=1; j<MAX_NB_OPERANDES; j++) 
if(i−>origineOperandes[j]!=NULL) 
{ 
fprintf(stdout,",%d",(i−>origineOperandes[j])−>numeroDynamique); 
if(GENERER_FICHIER_DOT && utiliteInst[i−>numeroDynamique]!=NOP) 
{ 
fprintf(fichierDot,"t%d −> %d", i−>numeroDynamique, (i−>origineOperand 
es[j])−>numeroDynamique); 
if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(fichierDot," [color 
=".0 .0 .8"]"); 
fprintf(fichierDot,";n"); 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument.c 16/37
18 jun 03 17:55 instrument.c Page 5/13 
} 
} 
fprintf(stdout,"n"); 
if(utiliteInst[i−>numeroDynamique]==INUTILE) 
{ 
/* Ecriture dans le fichier traceInstructionsInutiles du numéro des instru 
ctions dynamiques inutiles */ 
fprintf(traceInstructionsInutiles,"%un",i−>numeroDynamique); 
occurencesInutileInstStatiques[i−>numeroStatique][i−>numeroFichier]++; 
} 
occurencesInstStatiques[i−>numeroStatique][i−>numeroFichier]++; 
switch (i−>typeInstruction) 
{ 
case T_LOAD : 
cptLoad++; 
if(utiliteInst[i−>numeroDynamique]==INUTILE) cptLoadInutile++; 
break; 
case T_STORE : 
cptStore++; 
if(utiliteInst[i−>numeroDynamique]==INUTILE) cptStoreInutile++; 
} 
} 
fclose(traceInstructionsInutiles); 
if(GENERER_FICHIER_DOT) 
{ 
fprintf(fichierDot,"}"); 
fclose(fichierDot); 
} 
/* Affichage de la table des registres fixes avec les informations qui lui son 
t relatives */ 
fprintf(stdout,"nTable des registres fixes :n"); 
for(j=0; j<NB_REGISTRES; j++) 
{ 
if(tableRegistres[j]−>numeroDynamique != 0 || tableLectureRegistres[j] != 0) 
{ 
fprintf(stdout,"Registre numéro %dt",j); 
fprintf(stdout,": Modifié par l’instruction %dt",tableRegistres[j]−>numeroDynamique) 
; 
fprintf(stdout,"et lu depuis par %d instruction(s)n",tableLectureRegistres[j]); 
} 
} 
/* Affichage de la table des registres tournants avec les informations qui lui 
sont relatives */ 
fprintf(stdout,"nTable des registres tournants (niveau actuel : %d) :n",niveauFenetreRegistre); 
for(j=0; j<NB_REGISTRES_TOURNANTS; j++) 
{ 
if(tableRegistresTournants[j]−>numeroDynamique != 0 || tableLectureRegistres 
Tournants[j] != 0) 
{ 
fprintf(stdout,"Registre tournant numéro %dt",j); 
fprintf(stdout,": Modifié par l’instruction %dt",tableRegistresTournants[j]−>numeroD 
ynamique); 
fprintf(stdout,"et lu depuis par %d instruction(s)n",tableLectureRegistresTournants[j 
Imprimé par Benjamin Vidal 
18 jun 03 17:55 instrument.c Page 6/13 
]); 
} 
} 
/* Affichage de la liste chainée contenant les adresses mémoire */ 
fprintf(stdout,"nListe des adresses mémoires :n"); 
for(a=adresseInitiale; a!=NULL; a=a−>suivant) 
{ 
fprintf(stdout,"Adresse mémoire %dt",a−>adresse); 
fprintf(stdout,": Modifié par l’instruction %dt",a−>derniereEcriture−>numeroDynamique) 
; 
fprintf(stdout,"et lu depuis par %d instruction(s)n",a−>nbLecture); 
} 
/* On compte le nombre d’instructions utiles et inutiles afin de l’afficher */ 
for(j=0; j<instCourante−>numeroDynamique; j++) 
{ 
switch (utiliteInst[j]) 
{ 
case UTILE : cptUtile++; break; 
case INUTILE : cptInutile++; break; 
case NOP : cptNop++; break; 
default : fprintf(stderr,"Passage impossible : fonction afficherSdd dans instrument.cn" 
); 
} 
if(valeursMortes[j] == 0) cptMortes++; 
if(valeursMortes[j] == 0 && utiliteInst[j] != INUTILE) 
{ 
fprintf(stderr,"Instruction %d !!!!n",j); 
} 
if(j%20 == 0) fprintf(fichierEvolutionQtValeursMortes,"%dt%dn",j,cptMortes) 
; 
} 
fclose(fichierEvolutionQtValeursMortes); 
nbTotalInstructions = instCourante−>numeroDynamique; 
fprintf(stdout,"nCalcul prenant en compte les "nop" :n"); 
fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", 
cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi 
ons); 
fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%n", 
cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) 
; 
fprintf(stdout,"Nombre de nop : %d/%d soit %f %%nn", 
cptNop,nbTotalInstructions,(float)(cptNop*100)/(float)nbTotalInstructions); 
nbTotalInstructions = instCourante−>numeroDynamique − cptNop; 
fprintf(stdout,"nCalcul ne prenant pas en compte les "nop" :n"); 
fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", 
cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi 
ons); 
fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%nn", 
cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) 
; 
fprintf(stdout,"Nombre d’instructions dont le résultat est mort : %d/%d soit %f %%nn", 
cptMortes,nbTotalInstructions,(float)(cptMortes*100)/(float)nbTotalInstruction 
s); 
fprintf(stdout,"nnNombre d’ocurences d’instructions inutiles pour une instruction statique : n"); 
mercredi 18 juin 2003 instrument.c 17/37
18 jun 03 17:55 instrument.c Page 7/13 
tableauInstStatiqueInutiles[0] = 0.0; 
/* Lignes de la matrice */ 
for(k=0; k<MAX_NB_INST_STATIQUES; k++) 
{ 
if(k==0) 
for(j=1; j<NB_FICHIERS; j++) 
fprintf(stdout," t%d",j); 
else 
{ 
fprintf(stdout,"n%d :t",k); 
/* Colonnes de la matrice */ 
for(j=1; j<NB_FICHIERS; j++) 
{ 
if(occurencesInstStatiques[k][j]!=0) 
{ 
fprintf(stdout,"%u/%ut",occurencesInutileInstStatiques[k][j],occurence 
sInstStatiques[k][j]); 
cptInstStatique++; 
if(occurencesInutileInstStatiques[k][j]!=0) cptInstStatiqueOccurencesI 
nutile++; 
insert(tableauInstStatiqueInutiles,(float)occurencesInutileInstStatiqu 
es[k][j]/(float)occurencesInstStatiques[k][j]); 
} 
else fprintf(stdout," t"); 
} 
} 
} 
for(j=0; j<cptInstStatiquesTraitees; j++) 
fprintf(traceInstructionsStatiquesInutiles,"%dt%fn",cptInstStatiquesTraitee 
s,tableauInstStatiqueInutiles[j]); 
fclose(traceInstructionsStatiquesInutiles); 
fprintf(stdout,"nNombre d’instructions statique ayant des occurences inutiles / Nombre d’instructions stati 
ques utilisées : %d/%d soit %f %%n", 
cptInstStatiqueOccurencesInutile, cptInstStatique, (float)(cptInstStatiqueOccu 
rencesInutile*100)/(float)cptInstStatique); 
fprintf(stdout,"nNombre de load inutiles / Nombre de load dynamique total : %d/%d soit %f %%n", 
cptLoadInutile,cptLoad,(float)(cptLoadInutile*100)/(float)cptLoad); 
fprintf(stdout,"nNombre de store inutiles / Nombre de store dynamique total : %d/%d soit %f %%n", 
cptStoreInutile,cptStore,(float)(cptStoreInutile*100)/(float)cptStore); 
fprintf(stdout,"Nombre de noeuds total dans le graphe : %dn",allocation); 
} 
/* Création de la représentation des instructions dynamiques en mémoire */ 
/* !!!!! Attention, cette fonction est différente selon qu’on utilise le program 
me sur Sparc ou x86 (DelaySlot) */ 
void instrumentationInstructionDebut(int typeInstruction, int numeroInst, int nu 
meroFichier) 
{ 
int i; 
instruction *tmp = malloc(sizeof(instruction)); 
Imprimé par Benjamin Vidal 
18 jun 03 17:55 instrument.c Page 8/13 
allocation++; 
if(tmp == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction instrumentationInstructionDebut dans le fichier instrument.c)n"); 
exit(10); 
} 
if(instCourante−>numeroDynamique >= MAX_NB_INST_DYNAMIQUE) 
{ 
fprintf(stderr,"Nombre maximum d’instruction dépassé, veulliez augmenter la valeur de la constante ") 
; 
fprintf(stderr,"MAX_NB_INST_DYNAMIQUE dans le fichier instrument.cn"); 
exit(1); 
} 
tmp−>numeroStatique = numeroInst; 
tmp−>numeroFichier = numeroFichier; 
tmp−>typeInstruction = typeInstruction; 
/* On passe pour la première fois dans cette fonction */ 
if(instCourante == NULL) 
{ 
tmp−>numeroDynamique = 1; 
tmp−>precedent = instInitiale; 
} 
else 
{ 
tmp−>numeroDynamique = instCourante−>numeroDynamique+1; 
tmp−>precedent = instCourante; 
} 
for(i=0; i<MAX_NB_OPERANDES; i++) tmp−>origineOperandes[i] = NULL; 
instCourante = tmp; 
/*fprintf(stderr,"Début statique %d dynamique %d fichier %d >",numeroInst,tmp− 
>numeroDynamique,tmp−>numeroFichier);*/ 
} 
/* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u 
n DelaySolt (Sparc) */ 
void instrumentationInstructionMilieu(void) 
{ 
/* Si l’instruction est un save, on incrémente le niveau de la fenetre de regi 
stres */ 
if(instCourante−>typeInstruction == T_SAVE) niveauFenetreRegistre++; 
/* Si c’est un restore, on décrémente le niveau de la fenetre de registres */ 
else if(instCourante−>typeInstruction == T_RESTORE) niveauFenetreRegistre−−; 
} 
void instrumentationInstructionFin(void) 
{ 
/* Si l’instruction courante est un branchement quelconque, un save ou un rest 
ore, on la marque comme utile 
et on marque comme utile toute les instructions dont elle dépend (appel réc 
ursif à remonterArbre) */ 
if(instCourante−>typeInstruction == T_BRANCHEMENT || 
instCourante−>typeInstruction == T_BRANCHEMENT_ANNUL_BIT || 
mercredi 18 juin 2003 instrument.c 18/37
18 jun 03 17:55 instrument.c Page 9/13 
instCourante−>typeInstruction == T_SAVE || 
instCourante−>typeInstruction == T_RESTORE || 
instCourante−>typeInstruction == T_APPEL_INTERNE || 
instCourante−>typeInstruction == T_APPEL_EXTERNE || 
instCourante−>typeInstruction == T_APPEL_EXTERNE_SPECIAL) remonterArbre(ins 
tCourante); 
if(instCourante−>typeInstruction == T_NOP) utiliteInst[instCourante−>numeroDyn 
amique] = NOP; 
/*fprintf(stderr,"< Fin statique %d dynamique %d fichier %dn",instCourante−>n 
umeroStatique,instCourante−>numeroDynamique,instCourante−>numeroFichier);*/ 
} 
/* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u 
n DelaySolt (Sparc) */ 
void copierInstDelay(void) 
{ 
instCouranteDelay = instCourante; 
} 
/* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u 
n DelaySolt (Sparc) */ 
void echangerInstDelay(void) 
{ 
instruction *tmp = instCourante; 
instCourante = instCouranteDelay; 
instCouranteDelay = tmp; 
} 
/* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o 
u à des registres (en entrée) */ 
/* !!!!! Attention, cette fonction est différente selon qu’on utilise le program 
me sur Sparc ou x86 (registres) */ 
void instrumentationEntreeRegistre(int identificateurRessource) 
{ 
int i; 
unsigned int identificateurRegistreTournant; 
/* identificateurRessource numéro ID_REG_SALTO_G = trou noir (%g0) */ 
if(identificateurRessource != ID_REG_SALTO_G) 
{ 
/* Si le registre accédé fait partie de la fenêtre de registres tournante */ 
if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE 
G_SALTO_I+7) 
{ 
identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet 
reRegistre*OFFSET_FENETRE); 
if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) 
{ 
fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de 
"); 
fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); 
exit(1); 
} 
tableLectureRegistresTournants[identificateurRegistreTournant]++; 
Imprimé par Benjamin Vidal 
18 jun 03 17:55 instrument.c Page 10/13 
for(i=0; (instCourante−>origineOperandes)[i]!=NULL; i++) 
if (i >= MAX_NB_OPERANDES) 
{ 
fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente 
r la valeur de "); 
fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); 
exit(8); 
} 
instCourante−>origineOperandes[i] = tableRegistresTournants[identificateur 
RegistreTournant]; 
} 
/* Sinon, on le considère comme un registre fixe */ 
else 
{ 
tableLectureRegistres[identificateurRessource]++; 
for(i=0; (instCourante−>origineOperandes)[i]!=NULL; i++) 
if (i >= MAX_NB_OPERANDES) 
{ 
fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente 
r la valeur de "); 
fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); 
exit(8); 
} 
instCourante−>origineOperandes[i] = tableRegistres[identificateurRessource 
]; 
} 
} 
} 
/* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o 
u à des registres (en sortie) */ 
/* !!!!! Attention, cette fonction est différente selon qu’on utilise le program 
me sur Sparc ou x86 (registre %g0) */ 
void instrumentationSortieRegistre(int identificateurRessource) 
{ 
unsigned int identificateurRegistreTournant; 
/* Mise à jour du compteur de résultat : le registre identificateurRessource e 
st considéré comme un résultat 
de l’instruction instCourante */ 
valeursMortes[instCourante−>numeroDynamique]++; 
/* Si le registre dans lequel on écrit est le numéro 41 (correspondant à %g0), 
on n’en tient pas compte car ce 
registre est un trou noir (quelque soit ce qui y est écrit, on lit toujours 
la valeur 0 dans ce registre) */ 
if(identificateurRessource != ID_REG_SALTO_G) 
{ 
/* Si le registre accédé fait partie de la fenêtre de registres tournante */ 
if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE 
G_SALTO_I+7) 
{ 
identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet 
reRegistre*OFFSET_FENETRE); 
if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) 
{ 
fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de 
mercredi 18 juin 2003 instrument.c 19/37
18 jun 03 17:55 instrument.c Page 11/13 
"); 
fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); 
exit(1); 
} 
if(tableLectureRegistresTournants[identificateurRegistreTournant] == 0) 
valeursMortes[(tableRegistresTournants[identificateurRegistreTournant])− 
>numeroDynamique]−−; 
tableRegistresTournants[identificateurRegistreTournant] = instCourante; 
tableLectureRegistresTournants[identificateurRegistreTournant] = 0; 
} 
/* Sinon, on le considère comme un registre fixe */ 
else 
{ 
if(tableLectureRegistres[identificateurRessource] == 0) 
valeursMortes[(tableRegistres[identificateurRessource])−>numeroDynamique 
]−−; 
tableRegistres[identificateurRessource] = instCourante; 
tableLectureRegistres[identificateurRessource] = 0; 
} 
} 
} 
void lectureMemoireOctet(int adresseMemoire) 
{ 
int i; 
elementMemoire *a; 
for(a=adresseInitiale; a==NULL || adresseMemoire>a−>adresse; a=a−>suivant) 
if(a==NULL) 
{ 
/*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’instr 
uction %d (fichier %d)n", 
adresseMemoire, instCourante−>numeroStatique, instCourante−>numeroFichier) 
;*/ 
return; 
} 
if(adresseMemoire != a−>adresse) 
{ 
/*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’instruc 
tion %d (fichier %d)n", 
adresseMemoire, instCourante−>numeroStatique, instCourante−>numeroFichier);* 
/ 
return; 
} 
for(i=0; (instCourante−>origineOperandes)[i]!=NULL; i++) 
if (i >= MAX_NB_OPERANDES) 
{ 
fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmenter la va 
leur de "); 
fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); 
exit(8); 
} 
instCourante−>origineOperandes[i] = a−>derniereEcriture; 
a−>nbLecture++; 
} 
18 jun 03 17:55 instrument.c Page 12/13 
void instrumentationEntreeMemoire(int adresseMemoire, int nbOctetsLus) 
{ 
int offset; 
for(offset=0; offset<nbOctetsLus; offset++) lectureMemoireOctet(adresseMemoire 
+offset); 
} 
void ecritureMemoireOctet(int adresseMemoire) 
{ 
elementMemoire *nouveau, *tmp = adresseInitiale; 
/* Mise à jour du compteur de résultat : la case mémoire adresseMemoire est co 
nsidéré comme un résultat 
de l’instruction instCourante */ 
valeursMortes[instCourante−>numeroDynamique]++; 
/* Cas ou la liste chainée est vide : premier accès en écriture à la mémoire * 
/ 
if(adresseInitiale == NULL) 
{ 
adresseInitiale = malloc(sizeof(elementMemoire)); 
if(adresseInitiale == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); 
exit(10); 
} 
adresseInitiale−>adresse = adresseMemoire; 
adresseInitiale−>nbLecture = 0; 
adresseInitiale−>derniereEcriture = instCourante; 
adresseInitiale−>suivant = NULL; 
} 
else 
{ 
/* Cas ou l’insertion de données concerne le premier élément de la liste cha 
inée */ 
if(adresseMemoire <= adresseInitiale−>adresse) 
{ 
if(adresseInitiale−>adresse == adresseMemoire) 
{ 
if(adresseInitiale−>nbLecture == 0) 
valeursMortes[(adresseInitiale−>derniereEcriture)−>numeroDynamique]−−; 
adresseInitiale−>nbLecture = 0; 
adresseInitiale−>derniereEcriture = instCourante; 
} 
else 
{ 
nouveau = malloc(sizeof(elementMemoire)); 
if(nouveau == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); 
exit(10); 
} 
nouveau−>adresse = adresseMemoire; 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument.c 20/37
18 jun 03 17:55 instrument.c Page 13/13 
nouveau−>nbLecture = 0; 
nouveau−>derniereEcriture = instCourante; 
nouveau−>suivant = adresseInitiale; 
adresseInitiale = nouveau; 
} 
} 
/* Cas général d’insertion d’un élément dans la liste chainée */ 
else 
{ 
/* On parcours la liste chainée jusqu’a trouver l’endroit ou il faut insér 
er la donnée */ 
while(tmp−>suivant != NULL && adresseMemoire >= tmp−>suivant−>adresse) t 
mp = tmp−>suivant; 
/* Si l’adresse mémoire à déjà était accédée par le passé, on modifie le p 
ointeur sur 
l’instruction qui a écrit dernièrement dans cette zone mémoire */ 
if(tmp−>adresse == adresseMemoire) 
{ 
if(tmp−>nbLecture == 0) 
valeursMortes[(tmp−>derniereEcriture)−>numeroDynamique]−−; 
tmp−>nbLecture = 0; 
tmp−>derniereEcriture = instCourante; 
} 
/* Sinon, on créé une nouvelle cellule représentant cette zone mémoire et 
on l’insère dans la liste chainée */ 
else 
{ 
nouveau = malloc(sizeof(elementMemoire)); 
if(nouveau == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); 
exit(10); 
} 
nouveau−>adresse = adresseMemoire; 
nouveau−>nbLecture = 0; 
nouveau−>derniereEcriture = instCourante; 
nouveau−>suivant = tmp−>suivant; 
tmp−>suivant = nouveau; 
} 
} 
} 
} 
void instrumentationSortieMemoire(int adresseMemoire, int nbOctetsEcrits) 
{ 
int offset; 
for(offset=0; offset<nbOctetsEcrits; offset++) ecritureMemoireOctet(adresseMem 
oire+offset); 
} 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument.c 21/37
18 jun 03 14:19 instrument_optim.c Page 1/10 
#include <stdio.h> 
#include <stdlib.h> 
#include "instrument.h" 
#define NB_REGISTRES 115 
#define NB_REGISTRES_TOURNANTS 100000 
/* MAX_NB_OPERANDES doit etre égal au maximum d’opérandes que peuvent avoir les 
instruction +1 ! */ 
#define MAX_NB_OPERANDES 1400 
/* Défini les différentes valeur que peuvent prendre les variables utiliteInst[i 
], i allant de 0 à MAX_NB_INST_DYNAMIQUE.*/ 
#define INUTILE 0 
#define UTILE 1 
#define NOP 2 
typedef unsigned char flag; 
struct donnees_instruction { 
unsigned char numeroFichier; 
unsigned int numeroStatique; 
unsigned int numeroDynamique; 
flag typeInstruction; 
flag valeurMorte; 
struct instru *origineOperandes[MAX_NB_OPERANDES]; 
}; 
typedef struct instru { 
flag utiliteInst; 
struct donnees_instruction *donnees; 
struct instru *precedent; 
} instruction; 
typedef struct mem { 
int adresse; 
unsigned int nbLecture; 
instruction *derniereEcriture; 
struct mem *suivant; 
} elementMemoire; 
/* Définition des registres généraux */ 
instruction *tableRegistres[NB_REGISTRES]; 
unsigned int tableLectureRegistres[NB_REGISTRES]; 
/* Définition des registres tournants */ 
instruction *tableRegistresTournants[NB_REGISTRES_TOURNANTS]; 
unsigned int tableLectureRegistresTournants[NB_REGISTRES_TOURNANTS]; 
unsigned int niveauFenetreRegistre = 0; 
/* Définition des zones mémoires (point d’entrée dans la liste chainée) */ 
elementMemoire *adresseInitiale = NULL; 
/* Définition des instructions (point d’entrée dans la liste chainée et tableau 
de flag) */ 
instruction *instInitiale; 
instruction *instCourante = NULL; 
18 jun 03 14:19 instrument_optim.c Page 2/10 
instruction *instCouranteDelay = NULL; 
unsigned int numeroDynamiqueCourant; 
int cptUtile = 0, cptInutile = 0, cptNop = 0; 
unsigned int allocation=0; 
void initVariablesGlobales(void) 
{ 
int i; 
struct donnees_instruction *donnees = malloc(sizeof(struct donnees_instruction 
)); 
allocation++; 
numeroDynamiqueCourant=0; 
/* Création d’une instruction initiale fictive sur laquelle vont pointer celle 
s qui 
utilisent des registres déjà initialisés lors du lancement du programme */ 
instInitiale = malloc(sizeof(instruction)); 
if(donnees == NULL || instInitiale == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument.c)n"); 
exit(10); 
} 
donnees−>numeroFichier = 0; 
donnees−>numeroStatique = 0; 
donnees−>numeroDynamique = numeroDynamiqueCourant; /* 0 ici */ 
donnees−>typeInstruction = T_AUTRE; 
donnees−>valeurMorte = 0; 
for(i=0; i<MAX_NB_OPERANDES; i++) donnees−>origineOperandes[i] = NULL; 
instInitiale−>utiliteInst = INUTILE; 
instInitiale−>donnees = donnees; 
instInitiale−>precedent = NULL; 
instCourante = instInitiale; 
for(i=0; i<NB_REGISTRES; i++) 
{ 
tableRegistres[i] = instInitiale; 
tableLectureRegistres[i] = 0; 
} 
for(i=0; i<NB_REGISTRES_TOURNANTS; i++) 
{ 
tableRegistresTournants[i] = instInitiale; 
tableLectureRegistresTournants[i] = 0; 
} 
} 
/* Fonction de debug permettant d’afficher la liste chainée des instructions, 
la table des registres et enfin le pourcentage d’instructions utile et inutil 
es */ 
void afficherSdd(void) 
{ 
instruction *i; 
elementMemoire *a; 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument_optim.c 22/37
18 jun 03 14:19 instrument_optim.c Page 3/10 
int j,nbTotalInstructions,cptInutile2=0,cptMortes=0,cptMortes2=0; 
/* Définition du fichier contenant la trace (numéro) des instructions inutiles 
) */ 
FILE *traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"w"); 
FILE *fichierEvolutionQtTravailInutile = fopen(TRACE_EVOLUTION_INUTILES,"w"); 
FILE *fichierEvolutionQtValeursMortes = fopen(TRACE_VALEURS_MORTES,"w"); 
/* Affichage des dépendances entre les instructions dynamiques (stockées dans 
la liste chainée d’instructions dynamiques) */ 
/* Version optimisée : ne sont disponible que les données sur les instructions 
inutiles */ 
fprintf(stdout,"Liste des instructions dynamiques inutiles :n"); 
for(i=instCourante; i!=NULL; i=i−>precedent) 
{ 
if(i−>utiliteInst==INUTILE) 
{ 
struct donnees_instruction *d = i−>donnees; 
fprintf(stdout,"I.Dynamic %dt−− I.Static %dt−− File %dn",d−>numeroDynamique,d−>num 
eroStatique,d−>numeroFichier); 
/*if(d−>origineOperandes[0]!=NULL) fprintf(stdout,"t−− Dépend de %d",(d−> 
origineOperandes[0])−>donnees−>numeroDynamique); 
for(j=1; j<MAX_NB_OPERANDES; j++) 
if(d−>origineOperandes[j]!=NULL) fprintf(stdout,",%d",(d−>origineOperand 
es[j])−>donnees−>numeroDynamique); 
fprintf(stdout,"n");*/ 
/* Ecriture dans le fichier traceInstructionsInutiles du numéro des instru 
ctions dynamiques inutiles */ 
fprintf(traceInstructionsInutiles,"%un",d−>numeroDynamique); 
cptInutile++; 
if(d−>valeurMorte == 0) cptMortes++; 
} 
} 
fclose(traceInstructionsInutiles); 
for(i=instCourante,j=numeroDynamiqueCourant; i!=NULL; i=i−>precedent,j−−) 
{ 
if(i−>utiliteInst == INUTILE) 
{ 
cptInutile2++; 
if(i−>donnees−>valeurMorte == 0) cptMortes2++; 
} 
if(j%20 == 0) 
{ 
fprintf(fichierEvolutionQtTravailInutile,"%dt%dn",j,cptInutile−cptInutile 
2); 
fprintf(fichierEvolutionQtValeursMortes,"%dt%dn",j,cptMortes−cptMortes2); 
} 
} 
fclose(fichierEvolutionQtTravailInutile); 
fclose(fichierEvolutionQtValeursMortes); 
nbTotalInstructions = cptUtile + cptInutile + cptNop; 
fprintf(stdout,"nCalcul prenant en compte les "nop" :n"); 
fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", 
cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi 
ons); 
fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%n", 
18 jun 03 14:19 instrument_optim.c Page 4/10 
cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) 
; 
fprintf(stdout,"Nombre de nop : %d/%d soit %f %%nn", 
cptNop,nbTotalInstructions,(float)(cptNop*100)/(float)nbTotalInstructions); 
nbTotalInstructions = cptUtile + cptInutile; 
fprintf(stdout,"nCalcul ne prenant pas en compte les "nop" :n"); 
fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", 
cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi 
ons); 
fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%nn", 
cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) 
; 
fprintf(stdout,"Nombre de noeuds dans le graphe : %dn",allocation); 
} 
void remonterArbre(instruction *inst) 
{ 
int i; 
/* Si on n’a pas encore parcouru l’arbre de dépendance de l’instuction inst, 
on le parcours pour pour positionner les flag d’utilité utiliteInst[inst−>n 
umeroDynamique] */ 
if(inst−>utiliteInst == INUTILE) 
{ 
inst−>utiliteInst = UTILE; 
cptUtile++; 
for(i=0; inst−>donnees−>origineOperandes[i]!=NULL; i++) remonterArbre(inst−> 
donnees−>origineOperandes[i]); 
/* On libère la zone mémoire allouée au champ de donnée lorsqu’on est sur qu 
e l’instruction est utile 
(ce champ de donnée ne nous sert plus à rien dans ce cas la) */ 
free(inst−>donnees); 
allocation−−; 
inst−>donnees = NULL; 
} 
} 
/* Création de la représentation des instructions dynamiques en mémoire */ 
/* !!!!! Attention, cette fonction est différente selon qu’on utilise le program 
me sur Sparc ou x86 (DelaySlot) */ 
void instrumentationInstructionDebut(int typeInstruction, int numeroInst, int nu 
meroFichier) 
{ 
int i; 
instruction *inst = malloc(sizeof(instruction)); 
struct donnees_instruction *d = malloc(sizeof(struct donnees_instruction)); 
allocation++; 
if(inst==NULL || d==NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction instrumentationInstructionDebut dans le fichier instrument.c)n"); 
exit(10); 
} 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument_optim.c 23/37
d−>numeroFichier = numeroFichier; 
d−>numeroStatique = numeroInst; 
d−>numeroDynamique = ++numeroDynamiqueCourant; 
d−>typeInstruction = typeInstruction; 
if(typeInstruction == T_NOP) cptNop++; 
for(i=0; i<MAX_NB_OPERANDES; i++) d−>origineOperandes[i] = NULL; 
inst−>utiliteInst = INUTILE; 
inst−>donnees = d; 
inst−>precedent = instCourante; 
instCourante = inst; 
/*fprintf(stderr,"Début inst statique %d, inst dynamique %d fichier %dn",nume 
roInst,tmp−>numeroDynamique,tmp−>numeroFichier);*/ 
} 
/* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u 
n DelaySolt (Sparc) */ 
void instrumentationInstructionMilieu(void) 
{ 
/* Si l’instruction est un save, on incrémente le niveau de la fenetre de regi 
stres */ 
if(instCourante−>donnees−>typeInstruction == T_SAVE) niveauFenetreRegistre++; 
/* Si c’est un restore, on décrémente le niveau de la fenetre de registres */ 
else if(instCourante−>donnees−>typeInstruction == T_RESTORE) niveauFenetreRegi 
stre−−; 
} 
void instrumentationInstructionFin(void) 
{ 
/*fprintf(stderr,"Fin inst statique numéro %d, inst dynamique numéro %dn",ins 
tCourante−>numeroStatique,instCourante−>numeroDynamique);*/ 
/* Si l’instruction courante est un branchement quelconque, un save ou un rest 
ore, on la marque comme utile 
et on marque comme utile toute les instructions dont elle dépend (appel réc 
ursif à remonterArbre) */ 
if(instCourante−>donnees−>typeInstruction == T_BRANCHEMENT || 
instCourante−>donnees−>typeInstruction == T_BRANCHEMENT_ANNUL_BIT || 
instCourante−>donnees−>typeInstruction == T_SAVE || 
instCourante−>donnees−>typeInstruction == T_RESTORE || 
instCourante−>donnees−>typeInstruction == T_APPEL_INTERNE || 
instCourante−>donnees−>typeInstruction == T_APPEL_EXTERNE || 
instCourante−>donnees−>typeInstruction == T_APPEL_EXTERNE_SPECIAL) remonter 
Arbre(instCourante); 
/* Si l’instruction courante est un nop, on la marque comme tel et on libère l 
’espace mémoire qui avait était alloué 
pour sa représentation interne dans le programme */ 
else if(instCourante−>donnees−>typeInstruction == T_NOP) 
{ 
instCourante−>utiliteInst = NOP; 
free(instCourante−>donnees); 
allocation−−; 
instCourante−>donnees = NULL; 
} 
} 
18 jun 03 14:19 instrument_optim.c Page 5/10 
18 jun 03 14:19 instrument_optim.c Page 6/10 
/* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u 
n DelaySolt (Sparc) */ 
void copierInstDelay(void) 
{ 
instCouranteDelay = instCourante; 
} 
/* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u 
n DelaySolt (Sparc) */ 
void echangerInstDelay(void) 
{ 
instruction *tmp = instCourante; 
instCourante = instCouranteDelay; 
instCouranteDelay = tmp; 
} 
/* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o 
u à des registres (en entrée) */ 
/* !!!!! Attention, cette fonction est différente selon qu’on utilise le program 
me sur Sparc ou x86 (registres) */ 
void instrumentationEntreeRegistre(int identificateurRessource) 
{ 
int i; 
unsigned int identificateurRegistreTournant; 
/* identificateurRessource numéro ID_REG_SALTO_G = trou noir (%g0) */ 
if(identificateurRessource != ID_REG_SALTO_G) 
{ 
/* Si le registre accédé fait partie de la fenêtre de registres tournante */ 
if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE 
G_SALTO_I+7) 
{ 
identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet 
reRegistre*OFFSET_FENETRE); 
if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) 
{ 
fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de 
"); 
fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); 
exit(1); 
} 
tableLectureRegistresTournants[identificateurRegistreTournant]++; 
for(i=0; (instCourante−>donnees−>origineOperandes)[i]!=NULL; i++) 
if (i >= MAX_NB_OPERANDES) 
{ 
fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente 
r la valeur de "); 
fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); 
exit(8); 
} 
(instCourante−>donnees−>origineOperandes)[i] = tableRegistresTournants[ide 
ntificateurRegistreTournant]; 
} 
/* Sinon, on le considère comme un registre fixe */ 
else 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument_optim.c 24/37
18 jun 03 14:19 instrument_optim.c Page 7/10 
{ 
tableLectureRegistres[identificateurRessource]++; 
for(i=0; (instCourante−>donnees−>origineOperandes)[i]!=NULL; i++) 
if (i >= MAX_NB_OPERANDES) 
{ 
fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente 
r la valeur de "); 
fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); 
exit(8); 
} 
(instCourante−>donnees−>origineOperandes)[i] = tableRegistres[identificate 
urRessource]; 
} 
} 
} 
/* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o 
u à des registres (en sortie) */ 
/* !!!!! Attention, cette fonction est différente selon qu’on utilise le program 
me sur Sparc ou x86 (registre %g0) */ 
void instrumentationSortieRegistre(int identificateurRessource) 
{ 
unsigned int identificateurRegistreTournant; 
/* Mise à jour du compteur de résultat : le registre identificateurRessource e 
st considéré comme un résultat 
de l’instruction instCourante */ 
instCourante−>donnees−>valeurMorte++; 
/* Si le registre dans lequel on écrit est le numéro 41 (correspondant à %g0), 
on n’en tient pas compte car ce 
registre est un trou noir (quelque soit ce qui y est écrit, on lit toujours 
la valeur 0 dans ce registre) */ 
if(identificateurRessource != ID_REG_SALTO_G) 
{ 
/* Si le registre accédé fait partie de la fenêtre de registres tournante */ 
if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE 
G_SALTO_I+7) 
{ 
identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet 
reRegistre*OFFSET_FENETRE); 
if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) 
{ 
fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de 
"); 
fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); 
exit(1); 
} 
if(tableLectureRegistresTournants[identificateurRegistreTournant] == 0 && 
tableRegistresTournants[identificateurRegistreTournant]−>utiliteInst == 
INUTILE) 
tableRegistresTournants[identificateurRegistreTournant]−>donnees−>val 
eurMorte−−; 
tableRegistresTournants[identificateurRegistreTournant] = instCourante; 
tableLectureRegistresTournants[identificateurRegistreTournant] = 0; 
} 
/* Sinon, on le considère comme un registre fixe */ 
else 
{ 
Imprimé par Benjamin Vidal 
if(tableLectureRegistres[identificateurRessource] == 0 && 
tableRegistres[identificateurRessource]−>utiliteInst == INUTILE) 
tableRegistres[identificateurRessource]−>donnees−>valeurMorte−−; 
tableRegistres[identificateurRessource] = instCourante; 
tableLectureRegistres[identificateurRessource] = 0; 
} 
} 
} 
void lectureMemoireOctet(int adresseMemoire) 
{ 
int i; 
elementMemoire *a; 
/*static elementMemoire *lecturePrecedente = NULL;*/ 
/* Si l’adresse accédée est consécutive à la précédente, alors on initialise a 
à lecturePrecedente−>suivant */ 
/* Sinon, on parcourt la liste chainée pour trouver l’élément mémoire corespon 
dant */ 
/*if((lecturePrecedente−>adresse)+1 == adresseMemoire) a=lecturePrecedente−>su 
ivant; 
else*/ 
for(a=adresseInitiale; a==NULL || adresseMemoire>a−>adresse; a=a−>suivant) 
if(a==NULL) 
{ 
/*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’ins 
truction %d (fichier %d)n", 
adresseMemoire, instCourante−>donnees−>numeroStatique, instCourante−>don 
nees−>numeroFichier);*/ 
return; 
} 
if(adresseMemoire != a−>adresse) 
{ 
/*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’instruc 
tion %d (fichier %d)n", 
adresseMemoire, instCourante−>donnees−>numeroStatique, instCourante−>donnees 
−>numeroFichier);*/ 
return; 
} 
for(i=0; (instCourante−>donnees−>origineOperandes)[i]!=NULL; i++) 
if (i >= MAX_NB_OPERANDES) 
{ 
fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmenter la va 
leur de "); 
fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); 
exit(8); 
} 
(instCourante−>donnees−>origineOperandes)[i] = a−>derniereEcriture; 
a−>nbLecture++; 
/*lecturePrecedente = a;*/ 
} 
18 jun 03 14:19 instrument_optim.c Page 8/10 
mercredi 18 juin 2003 instrument_optim.c 25/37
18 jun 03 14:19 instrument_optim.c Page 9/10 
void instrumentationEntreeMemoire(int adresseMemoire, int nbOctetsLus) 
{ 
int offset; 
for(offset=0; offset<nbOctetsLus; offset++) lectureMemoireOctet(adresseMemoire 
+offset); 
} 
void ecritureMemoireOctet(int adresseMemoire) 
{ 
elementMemoire *nouveau, *tmp = adresseInitiale; 
/* Mise à jour du compteur de résultat : la case mémoire adresseMemoire est co 
nsidéré comme un résultat 
de l’instruction instCourante */ 
instCourante−>donnees−>valeurMorte++; 
/* Cas ou la liste chainée est vide : premier accès en écriture à la mémoire * 
/ 
if(adresseInitiale == NULL) 
{ 
adresseInitiale = malloc(sizeof(elementMemoire)); 
if(adresseInitiale == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); 
exit(10); 
} 
adresseInitiale−>adresse = adresseMemoire; 
adresseInitiale−>nbLecture = 0; 
adresseInitiale−>derniereEcriture = instCourante; 
adresseInitiale−>suivant = NULL; 
} 
else 
{ 
/* Cas ou l’insertion de données concerne le premier élément de la liste cha 
inée */ 
if(adresseMemoire <= adresseInitiale−>adresse) 
{ 
if(adresseInitiale−>adresse == adresseMemoire) 
{ 
if(adresseInitiale−>nbLecture == 0 && (adresseInitiale−>derniereEcriture 
)−>utiliteInst == INUTILE) 
(adresseInitiale−>derniereEcriture)−>donnees−>valeurMorte−−; 
adresseInitiale−>nbLecture = 0; 
adresseInitiale−>derniereEcriture = instCourante; 
} 
else 
{ 
nouveau = malloc(sizeof(elementMemoire)); 
if(nouveau == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); 
exit(10); 
} 
nouveau−>adresse = adresseMemoire; 
nouveau−>nbLecture = 0; 
nouveau−>derniereEcriture = instCourante; 
18 jun 03 14:19 instrument_optim.c Page 10/10 
nouveau−>suivant = adresseInitiale; 
adresseInitiale = nouveau; 
} 
} 
/* Cas général d’insertion d’un élément dans la liste chainée */ 
else 
{ 
/* On parcours la liste chainée jusqu’a trouver l’endroit ou il faut insér 
er la donnée */ 
while(tmp−>suivant != NULL && adresseMemoire >= tmp−>suivant−>adresse) t 
mp = tmp−>suivant; 
/* Si l’adresse mémoire à déjà était accédée par le passé, on modifie le p 
ointeur sur 
l’instruction qui a écrit dernièrement dans cette zone mémoire */ 
if(tmp−>adresse == adresseMemoire) 
{ 
if(tmp−>nbLecture == 0 && (tmp−>derniereEcriture)−>utiliteInst == INUTIL 
E) 
(tmp−>derniereEcriture)−>donnees−>valeurMorte−−; 
tmp−>nbLecture = 0; 
tmp−>derniereEcriture = instCourante; 
} 
/* Sinon, on créé une nouvelle cellule représentant cette zone mémoire et 
on l’insère dans la liste chainée */ 
else 
{ 
nouveau = malloc(sizeof(elementMemoire)); 
if(nouveau == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); 
exit(10); 
} 
nouveau−>adresse = adresseMemoire; 
nouveau−>nbLecture = 0; 
nouveau−>derniereEcriture = instCourante; 
nouveau−>suivant = tmp−>suivant; 
tmp−>suivant = nouveau; 
} 
} 
} 
} 
void instrumentationSortieMemoire(int adresseMemoire, int nbOctetsEcrits) 
{ 
int offset; 
for(offset=0; offset<nbOctetsEcrits; offset++) ecritureMemoireOctet(adresseMem 
oire+offset); 
} 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument_optim.c 26/37
19 mai 03 17:16 instrumentation2.cc Page 1/9 
#include <stdio.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <fstream> 
#include <iostream> 
#include <sys/types.h> 
#include <regex.h> 
#include <stdlib.h> 
#include <string.h> 
#include "salto.h" 
#include "instrument.h" 
#define FICHIER_SOURCE_ASSEMBLEUR ".s$" 
#define REPERTOIRE_FICHIER_INSTRUMENTES "instrumente2/" 
#define SAVE "save %sp,−136,%sp" 
#define RESTORE "restore %g0,%g0,%g0" 
#define NOP "nop" 
#define OFFSET_INSTRUMENTATION "16" 
#define OFFSET_INSTRUMENTATION_2 "28" 
#define OFFSET_INSTRUMENTATION_DELAY_SLOT "64" 
#define OFFSET_2_INSTRUMENTATION_DELAY_SLOT "44" 
#define OFFSET_INSTRUMENTATION_ANNUL_BIT "76" 
#define EXP_ANNUL_BIT ",a[ ]+" 
#define ETIQUETTE_FONCTION_NOP "f_nop" 
// Pointeur sur le fichier dans lequel sera écrit le code instrumenté 
FILE *fichierSInstrumente; 
// Pointeur sur le fichier contenant le code original (non instrumenté) 
FILE *fichierSOriginal; 
// Permet d’identifier de manière unique le fichier en cours de traitement 
unsigned char numeroFichier; 
// Fonction permettant de sauvagarder le contexte du programme (registres généra 
ux et codes conditions) afin de ne pas 
// intervenir sur les valeurs des registres et codes conditions utilisés par le 
programme 
void sauvegardeContexte(BB *bb, int position, unsigned int *nbInstructionsAjoute 
es) 
{ 
INST *instCCR; 
char *lectureCodesConditions = "trd %ccr,%l0n"; 
// "Empilement" du contexte du programme (création d’un contexte intermédiaire 
artificiel entre l’exécution du 
// programme et l’exécution des fonctions d’instrumentation du code. Ce contex 
te permet de travailler avec 
// les registres %o[0−5] afin de faire passer les paramètres aux fonctions d’i 
nstrumentations. 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(SAVE)); 
19 mai 03 17:16 instrumentation2.cc Page 2/9 
(*nbInstructionsAjoutees)++; 
instCCR = newAsm(NOP); 
instCCR−>addAttribute(UNPARSE_ATT, lectureCodesConditions, strlen(lectureCodes 
Conditions)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, instCCR); 
(*nbInstructionsAjoutees)++; 
// Sauvegarde en mémoire (dans la pile) des registres globaux 
for(int i=1; i<=4; i++) 
{ 
char tmp[20]; 
// Sauvegarde du registre (%gi) (ex : "st %g1,[%sp+92]") 
sprintf(tmp,"st %%g%d,[%%sp+%d]",i,88+(4*i)); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); 
(*nbInstructionsAjoutees)++; 
} 
} 
// Fonction permettant de restaurer le contexte du programme (registres généraux 
et codes conditions) afin de ne pas 
// intervenir sur les valeurs des registres et codes conditions utilisés par le 
programme 
void restaurationContexte(BB *bb, int position, unsigned int *nbInstructionsAjou 
tees) 
{ 
INST *instCCR; 
char *ecritureCodesConditions = "twr %l0,%ccrn"; 
// Récupération des registres globaux depuis la mémoire (depuis la pile) 
for(int i=1; i<=4; i++) 
{ 
char tmp[20]; 
// Restauration du registre (%gi) (ex : "ld [%sp+92],%g1") 
sprintf(tmp,"ld [%%sp+%d],%%g%d",88+(4*i),i); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); 
(*nbInstructionsAjoutees)++; 
} 
instCCR = newAsm(NOP); 
instCCR−>addAttribute(UNPARSE_ATT, ecritureCodesConditions, strlen(ecritureCod 
esConditions)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, instCCR); 
(*nbInstructionsAjoutees)++; 
// "Dépilement" du contexte du programme 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(RESTORE)); 
(*nbInstructionsAjoutees)++; 
} 
int estPresent(char *motif, char *chaine) 
{ 
int i; 
regex_t *preg = new regex_t(); 
size_t nmatch = 10; 
regmatch_t pmatch[nmatch]; 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation2.cc 27/37
19 mai 03 17:16 instrumentation2.cc Page 3/9 
// Compilation de l’expression régulière 
if (regcomp(preg, motif, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "%s"n",motif); 
exit(4); 
} 
// Exécution de l’expression régulière et renvoi du résultat en fonction 
if (regexec(preg, chaine, nmatch, pmatch, 0) == REG_NOMATCH) 
{ 
regfree(preg); 
return 0; 
} 
for(i=0; i<nmatch && pmatch[i].rm_so!=−1; i++); 
regfree(preg); 
return i; 
} 
int typeInstruction(INST *inst) 
{ 
if(inst−>isCTI()) 
if(estPresent(EXP_ANNUL_BIT,inst−>unparse())) return T_BRANCHEMENT_ANNUL_BIT 
; 
else return T_BRANCHEMENT; 
return T_AUTRE; 
} 
void appelDeFonctionInst(INST *inst, BB *bb, int position, unsigned int *nbInstr 
uctionsAjoutees, int numeroInst) 
{ 
static unsigned char flagDelaySlot=0; 
char chaine[20],tmp[100], *lecturePC = "trd %pc,%o1n"; 
static INST *dernierBranchement; 
if(!flagDelaySlot) sauvegardeContexte(bb, position, nbInstructionsAjoutees); 
// On empile un paramètre de type entier à passer à la fonction (équivaut à mo 
v typeInst,%o0) 
sprintf(chaine,"or %%g0,%d,%%o0",typeInstruction(inst)); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Si la constante numeroInst à ranger dans %o1 peut être codée sur 13 bits (i 
.e. est entre −4096 et 4095) 
if(numeroInst <= 4095) 
{ 
// On empile un paramètre de type entier à passer à la fonction (équivaut à 
mov numeroInst,%o1) 
sprintf(chaine,"or %%g0,%d,%%o1",numeroInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
} 
else 
{ 
// On empile ce même paramètre mais en deux fois (les 22 premiers bits du re 
Imprimé par Benjamin Vidal 
19 mai 03 17:16 instrumentation2.cc Page 4/9 
gistre d’abbord) 
sprintf(chaine,"sethi %%hi(%d),%%o1",numeroInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Puis les 10 derniers bits ensuite 
sprintf(chaine,"or %%o1,%%lo(%d),%%o1",numeroInst); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
} 
// On empile un paramètre de type entier à passer à la fonction (équivaut à mo 
v numeroFichier,%o2) 
sprintf(chaine,"or %%g0,%d,%%o2",numeroFichier); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); 
(*nbInstructionsAjoutees)++; 
// Si le branchement est un brachement avec annul_bit (ex : bl,a ... 
) alors on rempli la chaine de caractère 
// chaineAnnulBit en conséquence (on y met les instructions pour tra 
iter ce cas correctement) 
if(flagDelaySlot && typeInstruction(dernierBranchement)==T_BRANCHEME 
NT_ANNUL_BIT) 
{ 
int i; 
char *chaineAnnulBit = (char *)malloc(100*sizeof(char)); 
regex_t *preg = new regex_t(); 
size_t nmatch = 1; 
regmatch_t pmatch[nmatch]; 
INST *inst_br_nop; 
if (regcomp(preg, EXP_ANNUL_BIT, REG_EXTENDED)) 
{ 
fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_ANNUL 
_BIT""n"); 
exit(4); 
} 
if (regexec(preg, dernierBranchement−>unparse(), nmatch, pmatch, 0 
) == REG_NOMATCH) 
{ 
fprintf(STDERR,"Erreur lors de l’exécution de l’expression régulière ""EXP_ANNUL_B 
IT"" :n"); 
fprintf(STDERR,"Impossible de trouver l’expression régulière dans l’instruction "%s"n 
",dernierBranchement−>unparse()); 
exit(4); 
} 
regfree(preg); 
for(i=0; i<pmatch[0].rm_eo; i++) 
tmp[i] = (dernierBranchement−>unparse())[i]; 
tmp[i] = ’0’; 
strcpy(chaineAnnulBit,"trd %pc,%o7n"); 
strcat(chaineAnnulBit,"tadd %o7,"OFFSET_INSTRUMENTATION_2",%o7n"); 
strcat(chaineAnnulBit,"twr %l0,%ccrn"); 
strcat(chaineAnnulBit,tmp); 
strcat(chaineAnnulBit," "ETIQUETTE_FONCTION_NOP"n"); 
mercredi 18 juin 2003 instrumentation2.cc 28/37
19 mai 03 17:16 instrumentation2.cc Page 5/9 
inst_br_nop = newAsm(NOP); 
inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chai 
neAnnulBit)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); 
(*nbInstructionsAjoutees)++; 
strcpy(tmp,"b "); 
} 
else strcpy(tmp,"call "); 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_INST 
))); 
(*nbInstructionsAjoutees)++; 
// On ajoute un nop afin de combler le delay slot 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
if(flagDelaySlot && typeInstruction(dernierBranchement)==T_BRANCHEME 
NT_ANNUL_BIT) 
{ 
INST *instLecturePC; 
instLecturePC = newAsm(NOP); 
instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lectur 
ePC)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); 
(*nbInstructionsAjoutees)++; 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o1+" 
OFFSET_INSTRUMENTATION_ANNUL_BIT",%g0")); 
(*nbInstructionsAjoutees)++; 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
} 
if(!flagDelaySlot) 
{ 
if(!inst−>isCTI()) 
{ 
INST *instLecturePC; 
instLecturePC = newAsm(NOP); 
instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lecturePC)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); 
(*nbInstructionsAjoutees)++; 
// A ce stade, %o0 contient le resultat de la fonction NOM_FCT_INST et %o1 
contient le PC à l’instant t−1 
// (instruction précédente à celle−ci) 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("add %o1,%o0,%o0")); 
(*nbInstructionsAjoutees)++; 
Imprimé par Benjamin Vidal 
19 mai 03 17:16 instrumentation2.cc Page 6/9 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o0+"OFFSET_IN 
STRUMENTATION",%g0")); 
(*nbInstructionsAjoutees)++; 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
restaurationContexte(bb, position, nbInstructionsAjoutees); 
} 
else 
{ 
// On met le flagDelaySlot à 1 pour signaler que la prochaine instruction 
se trouvera dans 
// le DelaySlot de celle−ci 
flagDelaySlot=1; 
// On conserve dans une variable statique un pointeur sur l’instruction en 
cours de traitement 
dernierBranchement = inst−>copy(); 
} 
} 
else 
{ 
// On traite l’instruction se trouvant dans le DelaySlot du précédent CTI (b 
ranchement) 
INST *instLecturePC, *instBranchement; 
// On remet le flagDelaySlot à zéro pour le prochain passage 
flagDelaySlot=0; 
instLecturePC = newAsm(NOP); 
instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lecturePC)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); 
(*nbInstructionsAjoutees)++; 
// Dans le registre %o0, se trouve 0 si on n’a pas à annuler l’instruction e 
t 52 si on doit l’annuler (inutile) 
// subcc %o0,0,%g0 <=> cmp %o0,0 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("subcc %o0,0,%g0")); 
(*nbInstructionsAjoutees)++; 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("be,a f_nop")); 
(*nbInstructionsAjoutees)++; 
// On utilise un jmpl dans le delay slot du brnz,a pour pouvoir brancher sur 
une adresse contenue dans un registre 
// (impossible si l’on utilise brnz,a directement car il faut lui fournir un 
e étiquette) 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o1+"OFFSET_INST 
RUMENTATION_DELAY_SLOT",%g0")); 
(*nbInstructionsAjoutees)++; 
// A mon avis, ce nop n’est jamais exécuté puisque l’instruction se trouvant 
dans un delay slot d’une instruction 
// se trouvant elle même dans un delay slot n’est jamais exécutée... 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
restaurationContexte(bb, position, nbInstructionsAjoutees); 
mercredi 18 juin 2003 instrumentation2.cc 29/37
19 mai 03 17:16 instrumentation2.cc Page 7/9 
// Insertion d’une instruction équivalente à celle représentant le brancheme 
nt dont on est en train de traiter 
// le DelaySlot 
bb−>insertAsm(*nbInstructionsAjoutees+position, dernierBranchement); 
(*nbInstructionsAjoutees)++; 
// On insère un nop dans le Delay Slot puisque l’instruction se trouvant nor 
malement dans le delay slot de ce 
// branchement est jugée inutile 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
// On lit le PC 
instLecturePC = newAsm(NOP); 
instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lecturePC)+1); 
bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); 
(*nbInstructionsAjoutees)++; 
// Et on branche après sur la suite du programme en sautant le branchement e 
t son delay slot dans le cas utile 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o1+"OFFSET_2_IN 
STRUMENTATION_DELAY_SLOT",%g0")); 
(*nbInstructionsAjoutees)++; 
bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); 
(*nbInstructionsAjoutees)++; 
restaurationContexte(bb, position, nbInstructionsAjoutees); 
} 
} 
void instrumenter(INST *inst, BB *bb, BB *bbSuivant, int position, unsigned int 
*nbInstructionsAjoutees, int numeroInst) 
{ 
static unsigned char flagDelaySlot = 0; 
// Si on n’a pas à faire à une instruction se trouvant dans un DelaySlot 
if(!flagDelaySlot) 
// Si l’instruction n’est pas un branchement, alors, on instrumente cette in 
struction normalement 
if(!inst−>isCTI()) 
appelDeFonctionInst(inst, bb, position, nbInstructionsAjoutees, numeroInst 
); 
// Si l’instruction courante est un branchement, alors, on l’instrumente ain 
si que son DelaySlot 
else 
{ 
INST *instDelaySlot = bbSuivant−>getAsm(0); 
// On met le flag à 1 pour indiquer que l’instrumentation de l’instruction 
se trouvant dans le DelaySlot 
// du branchement que l’on est en train de traiter a déjà été instrumentée 
flagDelaySlot = 1; 
/*fprintf(STDERR,"Inst <%s>tttDelay <%s>n",inst−>unparse(),instDelaySl 
ot−>unparse());*/ 
// On traite l’instruction de branchement 
appelDeFonctionInst(inst, bb, position, nbInstructionsAjoutees, numeroInst 
); 
Imprimé par Benjamin Vidal 
// On traite l’instruction qui se trouve dans le DelaySlot 
appelDeFonctionInst(instDelaySlot, bb, position, nbInstructionsAjoutees, n 
umeroInst+1); 
} 
else flagDelaySlot = 0; 
} 
void Salto_hook() 
{ 
CFG *proc; 
BB *bb, *bbSuivant; 
INST *inst; 
int numeroInst = 0; 
unsigned int nbInstructionsAjoutees; 
// Parcours du programme permettant d’insérer le code d’instrumentation des in 
structions 
for (int i=0; i < numberOfCFG(); i++) 
{ 
proc = getCFG(i); 
for (int j=0; j < proc−>numberOfBB(); j++) 
{ 
bb = proc−>getBB(j); 
bbSuivant = proc−>getBB(j+1); 
int nbAsm = bb−>numberOfAsm(); 
nbInstructionsAjoutees = 0; 
for (int k=0; k < nbAsm; k++) 
{ 
numeroInst++; 
inst = bb−>getAsm(k+nbInstructionsAjoutees); 
instrumenter(inst, bb, bbSuivant, k, &nbInstructionsAjoutees, numeroInst 
); 
} 
} 
} 
// Parcours du programme permettant d’insérer le code pour l’initialisation de 
s variables globales 
for (int i=0; i < numberOfCFG(); i++) 
{ 
proc = getCFG(i); 
if(!strcmp(proc−>getName(),"main")) 
{ 
// Insertion d’un appel à la procédure initialisant les variables globales 
(proc−>getBB(0))−>insertAsm(0, newAsm("call initVariablesGlobales")); 
(proc−>getBB(0))−>insertAsm(1, newAsm(NOP)); 
} 
} 
// Envoi du code instrumenté vers la sortie standard 
produceCode(fichierSInstrumente); 
} 
19 mai 03 17:16 instrumentation2.cc Page 8/9 
mercredi 18 juin 2003 instrumentation2.cc 30/37
void Salto_init_hook(int argc, char *argv[]) 
{ 
int i,j,k; 
char nomFichierSortie[100]; 
// Récupération dans la ligne de commande entrée par l’utilisateur du nom du f 
ichier original 
for(i=1; i<argc && !estPresent("−i",argv[i]); i++); 
if(i == argc−1) 
{ 
fprintf(STDERR,"Erreur, votre ligne de commande ne comporte pas l’option "−i"n"); 
exit(6); 
} 
// On ouvre le fichier .s original à traiter pour pouvoir s’en servir dans sal 
to_hook() 
fichierSOriginal = fopen(argv[i+1],"r"); 
if(fichierSOriginal == NULL) 
{ 
fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",argv[i+1]); 
fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation2.cc)n"); 
exit(11); 
} 
nomFichierSortie[0]=’0’; 
strcat(nomFichierSortie,REPERTOIRE_FICHIER_INSTRUMENTES); 
strcat(nomFichierSortie,argv[i+1]); 
// On ouvre le fichier .s instrumenté en écriture à traiter pour pouvoir s’en 
servir dans salto_hook() 
fichierSInstrumente = fopen(nomFichierSortie,"w"); 
if(fichierSInstrumente == NULL) 
{ 
fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",nomFichierSortie); 
fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation2.cc)n"); 
exit(11); 
} 
// Recherche de l’expression "−−" dans la ligne de commande 
for(; i<argc && !estPresent("−−",argv[i]); i++); 
// Si cette expression n’est pas présente, on ne numérote pas les fichiers 
if(i == argc−1) numeroFichier = 0; 
else numeroFichier = atoi(argv[i+1]); 
} 
void Salto_end_hook() 
{ 
exit(0); 
} 
19 mai 03 17:16 instrumentation2.cc Page 9/9 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrumentation2.cc 31/37
19 mai 03 15:50 instrument2.c Page 1/3 
#include <stdio.h> 
#include <stdlib.h> 
#include "instrument.h" 
/* Constantes utiles pour inhiber l’action des instructions dynamiques inutiles 
: 
Si l’on doit annuler l’instruction, on renvoit la constante 68 permettant de 
sauter l’instruction inutile 
ainsi que son instrumentation. Si l’on ne doit pas annuler l’instruction, on 
renvoit la constante 16 permettant 
de faire sauter le PC à la suite du programme (instruction à ne pas annuler e 
t son instrumentation) */ 
#define ANNUL 52 
#define NON_ANNUL 0 
typedef unsigned char flag; 
typedef struct instru { 
unsigned int numeroDynamique; 
struct instru *precedent; 
} instruction; 
instruction *instInitiale; 
instruction *instCourante = NULL; 
unsigned int tailleListeInstInutile; 
unsigned int *listeInstInutile; 
void initVariablesGlobales(void) 
{ 
int i; 
char c; 
FILE *traceInstructionsInutiles; 
/* Création d’une instruction initiale fictive sur laquelle vont pointer celle 
s qui 
utilisent des registres déjà initialisés lors du lancement du programme */ 
instInitiale = malloc(sizeof(instruction)); 
if(instInitiale == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument2.c)n"); 
exit(10); 
} 
instInitiale−>numeroDynamique = 0; 
instInitiale−>precedent = NULL; 
instCourante = instInitiale; 
tailleListeInstInutile = 0; 
traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"r"); 
if(traceInstructionsInutiles == NULL) 
{ 
fprintf(stderr,"Problème lors de l’ouverture du fichier ""TRACE_INSTRUCTIONS_INUTILES"" " 
19 mai 03 15:50 instrument2.c Page 2/3 
); 
fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument2.c)n"); 
exit(11); 
} 
/* On compte le nombre de lignes dans le fichier */ 
do if(fgetc(traceInstructionsInutiles)==’n’) tailleListeInstInutile++; 
while(!feof(traceInstructionsInutiles)); 
fclose(traceInstructionsInutiles); 
listeInstInutile = malloc(tailleListeInstInutile*sizeof(unsigned int)); 
traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"r"); 
if(traceInstructionsInutiles == NULL) 
{ 
fprintf(stderr,"Problème lors de l’ouverture du fichier ""TRACE_INSTRUCTIONS_INUTILES"" " 
); 
fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument2.c)n"); 
exit(11); 
} 
for(i=tailleListeInstInutile−1; i>=0; i−−) fscanf(traceInstructionsInutiles, " 
%u", &listeInstInutile[i]); 
} 
/* Création de la représentation des instructions dynamiques en mémoire */ 
int instrumentationInstruction(int typeInstruction, int numeroStatique, int nume 
roFichier) 
{ 
int i; 
static int indiceListeInstructions = 0; 
instruction *tmp = malloc(sizeof(instruction)); 
if(tmp == NULL) 
{ 
fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); 
fprintf(stderr,"(fonction instrumentationInstruction dans le fichier instrument2.c)n"); 
exit(10); 
} 
if(instCourante−>numeroDynamique >= MAX_NB_INST_DYNAMIQUE) 
{ 
fprintf(stderr,"Nombre maximum d’instruction dépassé, veulliez augmenter la valeur de la constante ") 
; 
fprintf(stderr,"MAX_NB_INST_DYNAMIQUE dans le fichier instrument.hn"); 
exit(1); 
} 
tmp−>numeroDynamique = instCourante−>numeroDynamique+1; 
tmp−>precedent = instCourante; 
instCourante = tmp; 
fprintf(stderr,"I.Dynamic %dt−− I.Static %dt−− File %d",instCourante−>numeroDynamique,n 
umeroStatique,numeroFichier); 
if(typeInstruction == T_BRANCHEMENT) 
{ 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument2.c 32/37
fprintf(stderr,"t−− Branchn"); 
return NON_ANNUL; 
} 
if(typeInstruction == T_BRANCHEMENT_ANNUL_BIT) 
{ 
fprintf(stderr,"t−− Branch annul bitn"); 
return NON_ANNUL; 
} 
/* Si le numéro de l’instruction dynamique en cours de traitement est le même 
que le dernier numéro 
testé comme étant une instruction inutile, alors on retourne la valeur ANNU 
L */ 
if(instCourante−>numeroDynamique == listeInstInutile[indiceListeInstructions] 
&& 
indiceListeInstructions < tailleListeInstInutile) 
{ 
indiceListeInstructions++; 
fprintf(stderr,"t−− Non exécutéen"); 
return ANNUL; 
} 
fprintf(stderr,"n"); 
return NON_ANNUL; 
} 
19 mai 03 15:50 instrument2.c Page 3/3 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 instrument2.c 33/37
21 avr 03 16:53 redefinition.c Page 1/7 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <signal.h> 
#include <ctype.h> 
#include <errno.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include "instrument.h" 
void *my_memset(void *p, int i, size_t taille) 
{ 
echangerInstDelay(); 
instrumentationSortieMemoire((int)p, taille); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return memset(p, i, taille); 
} 
void *my_memcpy(void *p1, const void *p2, size_t taille) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)p2, taille); 
instrumentationSortieMemoire((int)p1, taille); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return memcpy(p1, p2, taille); 
} 
int my_memcmp(const void *p1, const void *p2, size_t taille) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)p1, taille); 
instrumentationEntreeMemoire((int)p2, taille); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return memcmp(p1, p2, taille); 
} 
int my_strcmp(const char *c1, const char *c2) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c1, strlen(c1)+1); 
instrumentationEntreeMemoire((int)c2, strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strcmp(c1, c2); 
} 
int my_strncmp(const char *c1, const char *c2, size_t taille) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c1, taille); 
instrumentationEntreeMemoire((int)c2, taille); 
instrumentationInstructionFin(); 
21 avr 03 16:53 redefinition.c Page 2/7 
echangerInstDelay(); 
return strncmp(c1, c2, taille); 
} 
size_t my_strlen(const char *c) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strlen(c); 
} 
char *my_strncpy(char *c1, const char *c2, size_t taille) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c2, taille); 
instrumentationSortieMemoire((int)c1, taille); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strncpy(c1, c2, taille); 
} 
char *my_strcpy(char *c1, const char *c2) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c2, strlen(c2)+1); 
instrumentationSortieMemoire((int)c1, strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strcpy(c1, c2); 
} 
char *my_strcat(char *c1, const char *c2) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c1, strlen(c1)+1); 
instrumentationEntreeMemoire((int)c2, strlen(c2)+1); 
instrumentationSortieMemoire(((int)c1)+strlen(c1), strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strcat(c1, c2); 
} 
char *my_strrchr(const char *c, int i) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strrchr(c, i); 
} 
size_t my_strcspn(const char *c1, const char *c2) 
{ 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 redefinition.c 34/37
21 avr 03 16:53 redefinition.c Page 3/7 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c1, strlen(c1)+1); 
instrumentationEntreeMemoire((int)c2, strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strcspn(c1, c2); 
} 
size_t my_strspn(const char *c1, const char *c2) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c1, strlen(c1)+1); 
instrumentationEntreeMemoire((int)c2, strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return strspn(c1, c2); 
} 
void *my_malloc(size_t taille) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return malloc(taille); 
} 
void *my_calloc(size_t taille1, size_t taille2) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return calloc(taille1, taille2); 
} 
void my_free(void *p) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
free(p); 
} 
char *my_getenv(const char *c) 
{ 
char *c2 = getenv(c); 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
if(c2!=NULL) instrumentationSortieMemoire(((int)c2), strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return c2; 
} 
int my_atoi(const char *c) 
{ 
21 avr 03 16:53 redefinition.c Page 4/7 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return atoi(c); 
} 
int my_fileno(FILE *f) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return fileno(f); 
} 
/* Cette fonction ne comportant aucun pointeur, elle ne fait aucun 
accès à la mémoire utilisable par l’appelant */ 
int my_isatty(int i) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return isatty(i); 
} 
int my_fstat(int i, struct stat *s) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)s, sizeof(s)); 
instrumentationSortieMemoire((int)s, sizeof(s)); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return fstat(i, s); 
} 
/* Cette fonction ne comportant aucun pointeur, elle ne fait aucun 
accès à la mémoire utilisable par l’appelant */ 
int my_close(int i) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return close(i); 
} 
void my_perror(const char *c) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
perror(c); 
} 
int my_unlink(const char *c) 
{ 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 redefinition.c 35/37
21 avr 03 16:53 redefinition.c Page 5/7 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return unlink(c); 
} 
int my_lstat(const char *c, struct stat *s) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationEntreeMemoire((int)s, sizeof(s)); 
instrumentationSortieMemoire((int)s, sizeof(s)); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return lstat(c, s); 
} 
int my_stat(const char *c, struct stat *s) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationEntreeMemoire((int)s, sizeof(s)); 
instrumentationSortieMemoire((int)s, sizeof(s)); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return stat(c, s); 
} 
/* Cette fonction ne comportant aucun pointeur, elle ne fait aucun 
accès à la mémoire utilisable par l’appelant */ 
off_t my_lseek(int i, off_t off, int j) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return lseek(i, off, j); 
} 
ssize_t my_read(int i, void *p, size_t taille) 
{ 
int taille_reelle = read(i, p, taille); 
echangerInstDelay(); 
instrumentationSortieMemoire((int)p, taille_reelle); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return taille_reelle; 
} 
ssize_t my_write(int i, const void *p, size_t taille) 
{ 
int taille_reelle = write(i, p, taille); 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)p, taille_reelle); 
instrumentationInstructionFin(); 
21 avr 03 16:53 redefinition.c Page 6/7 
echangerInstDelay(); 
return taille_reelle; 
} 
char *my_ctime(const time_t *time) 
{ 
char *c = ctime(time); 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)time, sizeof(time)); 
instrumentationSortieMemoire((int)c, 26); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return c; 
} 
int my_fflush(FILE *f) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return fflush(f); 
} 
char *my_fgets(char *c, int i, FILE *f) 
{ 
char *c2 = fgets(c, i, f); 
echangerInstDelay(); 
instrumentationSortieMemoire((int)c2, strlen(c2)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return c2; 
} 
int my_chmod(const char *c, mode_t mode) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return chmod(c, mode); 
} 
int my_utime(const char *c, const void *buf) 
{ 
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
/*instrumentationEntreeMemoire((int)buf, sizeof(struct utimbuf));*/ 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return utime(c, buf); 
} 
int my_chown(const char *c, uid_t uid, gid_t gid) 
{ 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 redefinition.c 36/37
echangerInstDelay(); 
instrumentationEntreeMemoire((int)c, strlen(c)+1); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
return chown(c, uid, gid); 
} 
/* Cette fonction ne comportant aucun pointeur, elle ne fait aucun 
accès à la mémoire utilisable par l’appelant */ 
void my_exit(int i) 
{ 
echangerInstDelay(); 
instrumentationInstructionFin(); 
echangerInstDelay(); 
exit(i); 
} 
21 avr 03 16:53 redefinition.c Page 7/7 
Imprimé par Benjamin Vidal 
mercredi 18 juin 2003 redefinition.c 37/37

Evaluation de la quantité de travail (in)utile dans l’exécution des programmes

  • 1.
    Rapport de stagede DEA ´Evaluation de la quantit´e de travail utile dans l’ex´ecution des programmes Benjamin Vidal Responsable de stage : Pierre Michaud Projet CAPS
  • 2.
    Sujet de stage La recherche en architecture de processeur est confront´ee actuellement `a des contraintes qui rendent de plus en plus difficile l’augmentation des performances des processeurs. Ces contraintes sont multiples : consommation ´electrique, latence de propagation des signaux sur les connexions, temps et coˆut de developpement, etc. . . Pour esp´erer trouver d’´eventuelles solutions permettant d’augmenter les performances de mani`ere significative sur une large gamme d’applications, il faut trouver de nouveaux paradigmes d’architecture. Pour cela, il faut d’abord avoir une bonne compr´e-hension du comportement des programmes. Le sujet propos´e a pour but d’´evaluer la quantit´e de travail r´eellement utile dans l’ex´ecution des programmes. L’id´ee sous-jacente est que si une fraction importante de l’ex´ecution d’un programme consiste en du travail inutile, il peut ˆetre int´eressant de chercher un paradigme architectural per-mettant d’exploiter cette propri´et´e. Le probl`eme consiste `a donner une d´efinition de l’utilit´e d’un travail. Par exemple, dans la r´ef´erence [1], un r´esultat interm´ediaire est consid´er´e inutile s’il est ´ecrit dans un registre et est ´ecras´e sans avoir ´et´e utilis´e. Dans la r´ef´erence [5], un store `a une adresse m´emoire est consid´er´e inutile s’il ´ecrit une valeur ´egale `a la valeur d´ej`a stock´ee `a cette adresse. Nous proposons d’´etudier une autre d´efinition, selon laquelle une instruction dynamique est consid´er´ee utile si – Elle produit un r´esultat ´emis en sortie du programme (ex. printf) – Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile – C’est un branchement dominant une instruction utile La partie recherche du stage consiste `a concevoir un algorithme efficace en temps et en m´emoire permettant d’´evaluer la quantit´e d’instructions dynamiques utiles. La partie mise-en-oeuvre consiste `a ´ecrire le programme correspondant, et `a l’utiliser pour obtenir des statistiques sur la fraction de travail utile, et d’autres statistiques, `a d´efinir, permettant de mieux appr´ehender le comportement des programmes. La mise en oeuvre se fera `a l’aide des outils d´evelopp´es au sein du projet CAPS. On travaillera sur des traces d’ex´ecution des programmes de la suite SPEC CPU2000. 2
  • 3.
    Remerciements Au coursde ce stage au sein de l’´equipe CAPS de l’IRISA, il m’a ´et´e possible de rencontrer un grand nombre de personnes qui m’ont aid´e `a comprendre le fonctionnement d’un laboratoire de recherche en informatique et surtout `a acqu´erir le recul n´ecessaire pour mieux appr´ehender le monde de l’architecture des microprocesseurs. Je voudrais donc remercier Ronan Amicel, Laurent Bertaux, Fran¸cois Bodin, Henri-Pierre Charles, Assia Djabelkhir, Romain Dolbeau, Antony Fraboulet, Karine Heydemann, Thierry Lafage, Antoine Monsifrot, Laurent Morin, Gilles Pokam, Olivier Rochecouste, Andr´e Seznec et ´Eric Toullec. Je tiens aussi `a remercier Yannos Sazeides (Enseignant `a l’universit´e de Chypre) avec qui j’ai eu l’occasion d’´echanger des id´ees sur la fa¸con d’´elaborer automatiquement un graphe de d´ependance de donn´ee `a partir de l’ex´ecution d’un programme. Et enfin je tiens `a remercier tr`es chaleureusement mon maˆıtre de stage, Pierre Michaud, qui m’a donn´e la libert´e de travail que j’aurais aim´e trouver tout au long de mon exp´erience universitaire et professionnelle et m’a permis ainsi de suivre les pistes que je souhaitais. Je tiens ´egalement `a le remercier pour tous les conseils qu’il a pu me donner concernant le monde de la recherche (publique ou priv´ee) et de m’avoir fait partager sa vision des choses sur de nombreux sujets. 3
  • 4.
    Table des mati`eres 1 Bibliographie 9 1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.2 Compilation et travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.2.1 Vous avez dit « instructions inutiles » ? . . . . . . . . . . . . . . . . . . . . . 10 1.2.2 Instructions statiques inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.3 Premi`ere approche : Instructions inutiles d´etect´ees dynamiquement . . . . . . . . . . . . . . . . . . . . . . 11 1.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.3.2 Description du principe de d´etection et d’´elimination des instructions inutiles 11 1.3.3 Id´ees d’impl´ementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.3.4 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.4 Deuxi`eme approche : ´Ecritures silencieuses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.2 Le ph´enom`ene d’´ecriture silencieuse . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.3 Les cons´equences de l’´elimination des ´ecritures silencieuses . . . . . . . . . . . 14 1.4.4 Id´ees d’impl´ementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.4.5 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4
  • 5.
    1.5 Troisi`eme approche: Travail inutile global lors de l’ex´ecution d’un programme . . . . . . . . . . . . . . . . 16 1.5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5.2 Evaluer l’utilit´e d’une instruction ? . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5.3 Mise en oeuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 1.5.4 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2 Compte rendu du stage 20 2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.1.1 Le travail inutile, qu’est ce que c’est ? . . . . . . . . . . . . . . . . . . . . . . 20 2.1.2 Notre protocole de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.2 La m´ethode utilis´ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.2.1 L’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.2.2 L’optimisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2.3 Le r´esultat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.3 L’environnement de travail : Les choix de mise en oeuvre . . . . . . . . . . . . . . . . 29 2.3.1 Les Outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.3.2 L’instrumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.3.3 Le choix de la Plateforme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.3.4 SPARC : Le Meilleur des Mondes ? . . . . . . . . . . . . . . . . . . . . . . . . 32 2.3.5 S’affranchir de la num´erotation des registres faite par Salto . . . . . . . . . . 34 2.3.6 Les expressions r´eguli`eres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 2.3.7 La gestion des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5
  • 6.
    2.4 R´esultats &Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.4.1 Les chiffres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.4.2 Le doute. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.4.3 La r´epartition du travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3 Annexes 46 3.1 Petit historique du stage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.2 A propos de la description machine Salto du Sparc . . . . . . . . . . . . . . . . . . . 47 3.2.1 Gestion des instructions Save et Restore . . . . . . . . . . . . . . . . . . . . . 47 3.2.2 L’instruction call & link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2.3 L’instruction addx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2.4 Un d´etail : les instructions nop, ba et bn . . . . . . . . . . . . . . . . . . . . . 47 3.3 R´esultat de l’´evaluation du travail inutile sur un exemple simple . . . . . . . . . . . 49 3.3.1 Code source en C de l’exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.3.2 Code source en assembleur Sparc de l’exemple . . . . . . . . . . . . . . . . . 49 3.3.3 Identifiant d’instruction statique . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.3.4 Trace d’ex´ecution dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.3.5 Graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.3.6 Trace d’ex´ecution dynamique 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.4 Exemple de donn´ees stock´ees en cours d’ex´ecution . . . . . . . . . . . . . . . . . . . 59 3.5 Code source du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 6
  • 7.
    Table des figures 1.1 Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es que par des instructions inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.1 La structure de donn´ee d’un noeud du graphe . . . . . . . . . . . . . . . . . . . . . . 24 2.2 Exemple de graphe g´en´er´e par l’algorithme 1 & 3 . . . . . . . . . . . . . . . . . . . . 25 2.3 Du code source en langage de haut niveau au graphe de d´ependance de donn´ee dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4 Instrumentation de code source en assembleur . . . . . . . . . . . . . . . . . . . . . . 30 2.5 Principe de l’instrumentation faite par le programme d’´evaluation de la quantit´e de travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.6 Le principe de la fenˆetre de registres du Sparc . . . . . . . . . . . . . . . . . . . . . . 33 2.7 Quantit´e d’instructions assembleurs inutiles lors de l’ex´ecution de gzip dans diff´erentes conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.8 Mise en ´evidence d’un probl`eme d’impl´ementation par divergence du flot de contrˆole 38 2.9 ´Evolution de la quantit´e de travail inutile en fonction du temps . . . . . . . . . . . . 40 3.1 Graphe d’exemple g´en´er´e par l’utilitaire « dot » . . . . . . . . . . . . . . . . . . . . . 56 3.2 Les structures de donn´ees utilis´ees par le programme pour construire le graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7
  • 8.
    Liste des Algorithmes 1 Construction du graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . 23 2 Parcours du graphe (Noeud) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3 D´etection des instructions inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 8
  • 9.
    Chapitre 1 Bibliographie 1.1 Introduction Aujourd’hui, pour am´eliorer les performances d’un programme, il ne suffit plus seulement d’ajou-ter du mat´eriel dans un syst`eme donn´e. Il faut avant tout ´etudier le comportement de ce programme afin d’adapter au mieux les ajouts qui doivent ˆetre faits au syst`eme. De ce constat, les architectes des microprocesseurs ont tir´e des id´ees aujourd’hui fondamentales (tels les diff´erents niveaux de m´emoires caches qui exploitent la propri´et´e de localit´e temporelle et spatiale d’acc`es aux donn´ees dans les programmes). En ce sens, certains travaux de recherche s’int´eressent aujourd’hui au probl`eme du travail ef-fectu ´e inutilement par un microprocesseur. Ils mettent en ´evidence une quantit´e non n´egligeable de travail inutile. Dans cette bibliographie, trois approches principales de travail inutile ont ´et´e retenues. 1. Une instruction produisant un r´esultat jamais utilis´e par une autre instruction est consid´er´ee comme inutile (approche de « l’instruction morte » retenue par l’article [1]). 2. Une instruction d’´ecriture est consid´er´ee comme inutile si cette derni`ere ne modifie pas l’´etat de la m´emoire (i.e. la mˆeme valeur est ´ecrite `a la mˆeme adresse m´emoire) (approche de « l’´ecriture silencieuse » retenue dans de nombreux articles [5, 3, 7]). 3. Une instruction est consid´er´ee comme utile si elle produit un r´esultat en sortie (affichage d’un r´esultat par exemple) ou qu’elle est elle mˆeme utile `a une instruction utile (approche retenue pour le stage). Apr`es un bref tour d’horizon des travaux d´ej`a effectu´es dans le domaine au niveau des compila-teurs, chacun des trois aspects d´ecrits ci-dessus du travail inutile sera d´evelopp´e dans un paragraphe de cette bibliographie. S’en suivra un paragraphe de discussion sur la possibilit´e de mˆeler ces deux approches pour essayer d’obtenir une coop´eration compilation/ex´ecution dans l’´elimination des instructions inutiles. 9
  • 10.
    1.2 Compilation ettravail inutile 1.2.1 Vous avez dit « instructions inutiles » ? Il peut paraˆıtre surprenant au premier abord d’entendre parler de travail inutile dans un pro-gramme. En effet, `a partir du moment ou le programmeur demande d’effectuer un travail `a la machine, (encore que celui-ci ne soit pas infaillible. . .) ce travail doit avoir une utilit´e (au sens informatique du terme bien entendu. . .). Cependant, au del`a du programmeur, il existe toute une chaˆıne de m´ecanismes permettant de passer du langage de haut niveau (i.e. langage de program-mation classique) au code machine ex´ecutable. Ainsi ce programme va passer par toute sortes de transformations qui vont introduire du travail inutile. De plus, il est possible de trouver, dans la fa¸con dont sont con¸cus les programmes, du travail inutile (redondance de calculs par exemple). 1.2.2 Instructions statiques inutiles Pour commencer, il est important de rappeler ce que sont les instructions statiques et les ins-tructions dynamiques. Une instruction statique est une instruction telle qu’on peut la trouver dans le code source d’un programme. Une instruction dynamique est une instance d’instruction statique. A chaque instruction statique peut correspondre plusieurs instructions dynamiques (autant que de fois o`u l’on ex´ecute cette instruction statique). Exemple simple : pour i de 1 à n faire t[i] := 0; fpour Instruction statique : t[i] := 0 Instructions dynamiques associées : t[1] := 0, t[2] := 0, …, t[n] := 0 Dans l’exemple suivant, il est important de noter que si n n’est pas fix´e lors de la compilation, la seule connaissance du compilateur est l’instruction statique. Il ne pourra donc pas, `a priori se servir de la valeur de n `a des fins d’optimisation. Supposons maintenant qu’un programme ne soit compos´e que des instructions de l’exemple et n’affiche aucun r´esultat. Le compilateur peut en d´eduire que l’ensemble du travail `a effectuer pour ex´ecuter cette boucle est inutile. Cependant, il suffit d’ajouter une instruction qui utilise t[m] en lecture (m ´etant un param`etre d’entr´ee du programme inconnu `a la compilation) pour que, potentiellement, l’ensemble du travail de la boucle devienne utile. En effet, le compilateur ne sachant pas quelle case du tableau t va ˆetre acc´ed´e, il est oblig´e de consid´erer que l’ensemble de la boucle fournit du travail utile. Il existe de nombreuses autres mani`eres d’´eliminer du travail inutile lors de la compilation [2, 4] que nous n’aborderons pas ici car seul l’aspect d´ecrit ci-dessus se rapproche des travaux vis´es dans cette ´etude. Dans la suite de cette bibliographie, nous ne nous int´eresserons qu’aux instructions inutiles dynamiques (i.e. qui ne peuvent pas ˆetre d´etect´ees par le compilateur puisqu’elles d´ependent de valeurs d’entr´ees du programme non connues au moment de la compilation). 10
  • 11.
    1.3 Premi`ere approche: Instructions inutiles d´etect´ees dynamiquement 1.3.1 Introduction Les auteurs de l’article [1] se sont aper¸cus que le r´earrangement des instructions fait par les com-pilateurs lors des phases d’optimisations cr´e´e des instructions inutiles. En effet, comme le montre leurs r´esultats, une compilation faite sans optimisations montre un niveau faible d’instructions in-utiles alors qu’une compilation avec un fort niveau d’optimisation montre un taux d’instructions inutiles relativement ´elev´e (parfois sup´erieur `a 10 %). Cependant, malgr´e ce travail effectu´e inuti-lement, il est bon de rappeler que globalement, le temps d’ex´ecution de ces programmes diminue (i.e. on a bien l’effet d´esir´e). La question qui vient alors est : « Comment conserver ces optimisations tout en r´eduisant le travail inutile qui leur est associ´e ? » 1.3.2 Description du principe de d´etection et d’´elimination des instructions inutiles Dans un premier temps, l’important est d’analyser les instructions ex´ecut´ees inutilement afin de savoir comment les d´etecter. Les auteurs de l’article [1] se sont ainsi aper¸cus que les instructions dynamiques inutiles ´etaient tr`es souvent des instances d’un nombre r´eduit d’instructions statiques. Ces instructions statiques sont appel´ees des instructions partiellement inutiles. En marquant ces instructions particuli`eres comme ´etant propices `a g´en´erer des instructions dynamiques inutiles, il est possible de ne faire un traitement particulier que sur ces derni`eres afin de savoir si une instance pr´ecise sera r´eellement inutile. Lors de l’ex´ecution, pour chaque instance d’une instruction partiellement inutile, une estimation de l’utilit´e de cette instruction dynamique sera faite. De cette estimation d´ecoulera son ex´ecution ou non. Dans le cas d’une mauvaise pr´ediction, un m´ecanisme de r´ecup´eration permet de lancer l’ex´ecution de cette instruction au moment ou l’on apprend que la pr´ediction est ´erron´ee. 1.3.3 Id´ees d’impl´ementation Les auteurs de l’article [1] ont donn´e quelques id´ees d’impl´ementation qui pourraient ˆetre mises en oeuvre pour la d´etection de ce type d’instructions. La plus simple consiste `a m´emoriser dans un cache totalement associatif les instructions statiques ayant d´ej`a g´en´er´e des r´esultats inutiles par le pass´e. Du fait qu’un faible nombre de ces instructions g´en`erent un grand nombre des instructions dynamiques inutiles, ce cache permettra de « suspecter » la prochaine instance d’une instruction statique ayant d´ej`a g´en´er´e des r´esultats inutiles. Par la suite, lors de la d´etection d’une instruction dynamique « suspect´ee » d’ˆetre inutile, son ex´ecution sera suspendue jusqu’au « verdict » final permettant de savoir si il ´etait juste de la sus-pecter. Si tel est le cas, cette instruction ne sera pas ex´ecut´ee, dans le cas contraire, cette instruction sera ex´ecut´ee ajoutant ainsi un surcoˆut dˆu au retard d’ex´ecution pris par cette instruction. Il est 11
  • 12.
    donc tr`es importantd’avoir une estimation la plus fine possible afin d’´eviter ce genre de cas et afin d’augmenter le nombre d’instruction inutiles suspect´ees `a juste titre. Pour cela, des optimisations sont propos´ees : utilisation de l’information de flot de contrˆole et ajout d’un compteur deux bits `a saturation principalement. Il est important de noter que ces impl´ementations ne tiennent pas compte des r´esultats calcul´es qui ne servent qu’`a des instructions inutiles. Autrement dit, cette impl´ementation ne prend pas en compte le caract`ere transitif que peuvent avoir certaines instructions inutiles. Instruction produisant un résultat R Instruction utilisant R et produisant R’ Instruction utilisant R’ et produisant R’’ Si R’’ est un résultat inutile R’ et R auront été produits inutilement Fig. 1.1 – Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es que par des instructions inutiles 1.3.4 Conclusion sur cette approche En conclusion, nous pouvons dire que les auteurs de l’article [1] ont mis en ´evidence une quantit´e non n´egligeable de travail inutile mˆeme si elle reste, aujourd’hui, difficile `a exploiter. En effet, dans un environnement o`u les ressources sont peu limit´ees, l’efficacit´e de l’impl´ementation d´ecrite ci-dessus offre des gains en performance n´egligeables. En revanche, dans des conditions de ressources plus limit´ees, les gains peuvent atteindre 10 % d’am´elioration des performances. De plus le fait d’ex´ecuter moins d’instructions permet une diminution de la charge des Unit´es Arithm´etiques et Logiques (UAL) et de la consommation ´electrique relativement importante. D’apr`es les auteurs, un m´ecanisme mat´eriel diminuant l’impact des instructions inutiles sur la performance et la consom-mation ´electrique permettrait d’appliquer des optimisations de code plus pouss´ees `a la compilation. 12
  • 13.
    1.4 Deuxi`eme approche: ´Ecritures silencieuses 1.4.1 Introduction D’apr`es les auteurs de l’article [5], il existe principalement deux types d’´ecritures silencieuses. D’une part les mises `a jours de valeurs silencieuses (qui ne changent pas l’´etat de la m´emoire dans laquelle elles ´ecrivent) et d’autre part les ´ecritures silencieuses stochastiques qui mettent `a jour la m´emoire de mani`ere pr´evisible. Dans la suite de ce chapitre, nous nous concentrerons sur les mises `a jour de valeurs silencieuses et parlerons, par abus de langage, d’´ecritures silencieuses pour les d´esigner. 1.4.2 Le ph´enom`ene d’´ecriture silencieuse Au vu de la d´efinition de ce qu’est une ´ecriture silencieuse, il parait difficile de croire que ces instructions puissent avoir un impact n´egatif important sur les performances d’un programme. Pourtant, les articles sur le sujet montrent que souvent plus de 30 % des ´ecritures sont silencieuses dans les applications test´ees. En effet, il existe de nombreux cas o`u, lors du parcours des ´el´ements d’un tableau, les modifications apport´ees par ce parcours ne concernent qu’un petit nombre des ´el´ements de ce tableau. Exemples simples : pour i de 1 à n faire b := (b & t[i]); fpour pour i de 1 à n faire t[i] := (t[i] & b); fpour (a) (b) (c) t étant un tableau de booléens b étant un booléen l'opération & étant un "ET" logique pour i de 1 à n faire t[i] := t[i] + e(i); fpour t étant un tableau d'entiers e étant une fonction Dans l’exemple (a), nous pouvons voir que pour chaque case du tableau t dont le bool´een est `a vrai, l’ex´ecution du corps de la boucle ne produit aucun travail utile (la mˆeme valeur sera r´e-´ecrite dans b). Dans l’exemple (b), le simple fait que b soit ´egal `a vrai entraˆıne une inutilit´e de l’ensemble de la boucle. Dans l’exemple (c), lorsque "(i) renvoi z´ero, le corps de la boucle peut-ˆetre consid´er´e comme inutile. Un autre cas assez fr´equent de travail inutile est celui o`u un tableau est initialis´e apr`es avoir ´et´e utilis´e une premi`ere fois. Si lors de la premi`ere utilisation de ce tableau, toutes ses valeurs n’ont pas ´et´e modifi´ees, il est inutile de r´e-initialiser l’ensemble des cases de ce tableau. Il existe d’autres situations dans lesquelles un grand nombre d’´ecritures silencieuses peuvent ˆetre observ´ees : lors de l’appel d’un sous-programme, si les registres sauvegard´es n’ont pas ´et´e utilis´es dans ce sous-programme, leur restauration sera inutile. Ce mˆeme ph´enom`ene peut-ˆetre observ´e lors de la sauvegarde/restauration de contexte d’un processus par un syst`eme d’exploitation. 13
  • 14.
    1.4.3 Les cons´equencesde l’´elimination des ´ecritures silencieuses Au del`a du gain ´evident que provoquerait un m´ecanisme fiable de suppression des ´ecritures silencieuses, un tel syst`eme permettrait ´egalement de supprimer une certaine quantit´e de travail assez importante li´ee `a ces instructions. En premier lieu, les informations de contrˆole li´ees `a ces instructions ne sont plus n´ecessaires (Ex : si une s´erie d’´ecritures silencieuses se trouve dans une boucle, il est inutile d’ex´ecuter la boucle). De plus, lors de l’ex´ecution d’une instruction de range-ment en m´emoire, tout un m´ecanisme lourd de rapatriement de la ligne de cache concern´ee vers la m´emoire est mis en place (´ecriture de la donn´ee dans le cache, marquage de la ligne de cache comme ´etant modifi´ee puis, lors du chargement de nouvelles donn´ees dans cette ligne de cache, ´ecriture de l’ancienne ligne de cache consid´er´ee comme modifi´ee en m´emoire). De fait, la suppression d’une ´ecriture ´evite d’avoir `a passer par toute ces op´erations d’acc`es `a la m´emoire tr`es coˆuteuses. Comme expliqu´e dans l’article [3], cette remarque prend encore plus d’importance dans un syst`eme multi-processeur puisque `a chaque ´ecriture m´emoire est associ´e un message d’invalidation `a destination des autres processeurs provoquant un d´efaut de cache lors du prochain acc`es `a ces donn´ees. . . Il est ´egalement important de noter que si certaines ´ecritures ne sont pas effectu´ees, de fait, certaines d´ependances de donn´ees n’existent plus. De cette fa¸con, le processeur n’est plus oblig´e d’attendre que ces valeurs soient ´ecrites pour pouvoir les utiliser. Le rendement du pipeline du processeur est alors am´elior´e. 1.4.4 Id´ees d’impl´ementation Les auteurs de l’article [5] ont propos´e une impl´ementation basique permettant de supprimer les ´ecritures silencieuses. Cette impl´ementation consiste `a remplacer toute les op´erations de ran-gement en m´emoire par trois op´erations : Chargement de l’ancienne valeur pr´esente en m´emoire, comparaison avec la valeur qui doit y ˆetre ´ecrite et enfin, dans le cas o`u ces deux valeurs ne seraient pas ´egales, ´ecriture de la nouvelle valeur en m´emoire. Cette m´ethode est sˆure et permet de d´etecter l’ensemble des ´ecritures silencieuses. De plus, les lectures pouvant ˆetre servies en parall`eles, il peut ˆetre int´eressant de remplacer les ´ecritures par des lectures suivies de comparaisons. Cependant, dans la mesure ou le nombre d’´ecritures silencieuses ne repr´esente pas la majorit´e des ´ecritures m´emoire, les auteurs ont ajout´e une « impl´ementation parfaite » dans laquelle un m´ecanisme per-met de savoir si une ´ecriture va ˆetre utile et, dans ce cas, n’effectuera que l’´ecriture en m´emoire sans avoir `a comparer la nouvelle valeur `a la valeur pr´ec´edente. D’autres id´ees d’impl´ementations apparaissent ´egalement dans l’article [5] comme par exemple la possibilit´e que la ligne de cache ne soit pas marqu´ee comme modifi´ee lorsqu’elle re¸coit une ´ecriture silencieuse ´evitant ainsi d’avoir `a propager l’´ecriture en m´emoire centrale (avantage principal de l’´elimination des ´ecriture silencieuses). L’impl´ementation retenue pour les simulations faites par les auteurs de [5] est la premi`ere propos´ee avec pour caract´eristique suppl´ementaire que seules les ´ecritures mises en attente vont subir une v´erification de leur utilit´e. Ce qui veut dire qu’une ´ecriture survenant `a un moment o`u au moins un port d’´ecriture de la m´emoire est disponible sera servie avant que la v´erification de son utilit´e n’ai pu ˆetre faite. De cette fa¸con, les performances des ´ecritures ne sont jamais d´egrad´ees puisque le m´ecanisme n’agit que sur la file d’attente des ´ecritures afin de la r´eduire. 14
  • 15.
    1.4.5 Conclusion surcette approche Les auteurs de l’article [5] ont mis en ´evidence une grande quantit´e de travail inutile `a travers les ´ecritures silencieuses. En effet, les proportions d’´ecritures silencieuses obtenues lors des tests sont parfois tr`es importantes et laissent penser qu’elles pourraient avoir une influence tr`es importante sur les performances, notamment dans les syst`emes dont la purge des lignes de cache en m´emoire est un goulet d’´etranglement. Les auteurs mettent ´egalement en avant la r´eduction du trafic sur le bus d’un syst`eme multiprocesseur `a m´emoire partag´ee qui est souvent un point critique dans ce type de syst`emes (ce trafic limite le nombre de processeurs sur un mˆeme bus). D’autres travaux ´elargissant le th`eme de l’´ecriture silencieuse ont ´egalement ´et´e pr´esent´es comme celui sur les ´ecritures silencieuses temporaires [6] consid´erant que si une valeur en m´emoire est modifi´ee puis remise `a son ancienne valeur et qu’aucune lecture ne soit intervenue sur la valeur transitoire, elle peut-ˆetre consid´er´ee comme silencieuse. Ce mod`ele semble bien s’adapter aux cas d´ecrits ci-dessus de sauvegarde et de restauration de contexte fr´equents (appel de sous-programmes, passage d’un processus `a un autre. . .). 15
  • 16.
    1.5 Troisi`eme approche: Travail inutile global lors de l’ex´ecution d’un programme 1.5.1 Introduction Dans cette approche, la probl´ematique est un peu diff´erente de celle vue dans les deux premiers paragraphes. En effet, le but de ces deux approches ´etait de d´etecter (soit par pr´evision, soit de mani`ere dynamique) une cat´egorie d’instructions inutiles afin d’´eviter leur ex´ecution « au vol ». Dans l’approche retenue pour le stage, il s’agit d’abord de regarder quelle est la quantit´e de travail inutile de fa¸con globale (essayer d’´evaluer l’ensemble du travail fait inutilement par un programme) afin d’avoir ensuite une id´ee du type de comportement ou d’application ex´ecut´e par un processeur qui produit le plus de travail inutile. De cette fa¸con, si certains r´esultats montrent une quantit´e non n´egligeable de travail inutile dans certains types d’applications, il sera ensuite possible d’´etudier pourquoi ce travail inutile est si important et si il peut ˆetre ´evit´e d’une mani`ere ou d’une autre. 1.5.2 Evaluer l’utilit´e d’une instruction ? La m´ethode retenue ici pour ´evaluer l’utilit´e d’une instruction est assez simple : Une valeur qui est affich´ee en sortie d’un programme est consid´er´ee comme un r´esultat utile. Toute instruction ayant servi `a calculer ce r´esultat est une instruction utile. Ainsi, les instructions utiles `a un r´esultat peuvent ˆetre repr´esent´ees par un arbre de d´ependance entre ces derni`eres dont la racine est le r´esultat lui-mˆeme et chaque noeud repr´esente les instructions utiles au calcul de ce r´esultat (aussi bien les instructions de rangement/r´ecup´eration en m´emoire, de calcul et de branchement). Les feuilles seront alors les valeurs d’entr´ees (param`etres fix´es lors de la compilation ou de l’ex´ecution) du programme. En regroupant l’ensemble des arbres ainsi obtenus pour chaque r´esultat en un graphe orient´e dont les sources sont les r´esultats et les puits sont les valeurs d’entr´ee du programme, il est possible d’identifier quelles sont les instructions r´eellement utiles au programme. En effet, les instructions et les valeurs d’entr´ees inutiles au programme n’appartiendront pas `a ce graphe et seront ainsi mises en ´evidence. 16
  • 17.
    Exemple simple : a := lire(); b := VRAI; c := 0; si a=0 faire b := FAUX; c := 5; fsi si b alors écrire(c) sinon écrire(a) Exemple de graphe d’exécution si a vaut 0 : écrire(a) nécessite b b := FAUX branchement correspondant Test a=0 nécessite a nécessite a a := lire() Valeur d’entrée du programme a := lire() Valeur d’entrée du programme c := 5 c := 0 b := VRAI Instructions exécutées inutilement Exemple de graphe d’exécution si a vaut 1 : nécessite b nécessite c Test a=0 nécessite a a := lire() Valeur d’entrée du programme écrire(c) c := 0 Valeur d’entrée du programme Aucune instruction n’est exécutée inutilement b := VRAI Valeur d’entrée du programme Cet exemple permet de mettre en ´evidence le fait que selon les valeurs d’entr´ees du programme, il peut y avoir du travail inutile ou pas. De plus, il met en lumi`ere (dans le cas o`u a vaut 1) le fait que le test a=0 correspondant au branchement du « si » doit ˆetre pris en compte comme ´etant du travail utile puisque de ce branchement vont d´ependre les instructions qui vont suivre (Nous pouvons dire que ces instructions « exigent » l’ex´ecution de ce branchement et donc du test qui permet de savoir si ce branchement doit ˆetre pris). Cette m´ethode d’identification des instructions inutiles semble parfaite (mˆeme si elle ne prend pas en compte certaines ´ecritures silencieuses). Cependant, elle n´ecessite le d´eroulement complet du programme afin de savoir si oui ou non une instruction dynamique du programme sera utile pour un r´esultat final. De fait, cette m´ethode ne peut pas ˆetre utilis´ee directement pour ´eliminer « au vol » les instructions inutiles. En revanche, elle permet d’exhiber de nombreux cas d’instructions inutiles que les autres m´ethodes ne d´etectent pas. Par exemple, le cas d’une instruction inutile par transitivit´e mis en ´evidence figure 1.1 sera d´etect´e par cette m´ethode. 1.5.3 Mise en oeuvre Comme d´ecrit dans le sujet de stage, la mise en oeuvre de cette approche du travail inutile consis-tera `a ´elaborer un programme permettant de d´etecter les instructions dynamiques inutiles, d’apr`es la d´efinition donn´ee ci-dessus, afin de faire des statistiques sur la quantit´e de travail inutile dans un ensemble de programmes `a tester. Diff´erentes cat´egories de travail inutile pourront ´egalement ˆetre mises en ´evidence (Ex : chargements inutiles, rangements en m´emoire inutiles, calculs inutiles. . .). Une fois les tests effectu´es, un travail de regroupement des applications test´ees selon les r´esultats pourra ˆetre fait afin de d´egager, ´eventuellement, des « motifs » de comportements permettant en-suite de savoir quelles applications sont le plus concern´ees par quel type de travail inutile. Nous pouvons imaginer, `a partir de l`a, que des ´ebauches de solutions mat´erielles et/ou logicielles ne soient 17
  • 18.
    trouv´ees pour r´eduirecette quantit´e de travail inutile. Cependant, l’objet du stage reste celui-ci : « Concevoir et ´ecrire un programme permettant de calculer la quantit´e de travail inutile dans un programme particulier apr`es son ex´ecution » . Dans ce sens, le travail `a effectuer en stage sera, dans un premier temps, de r´efl´echir `a la mani`ere de d´etecter quelles sont les instructions qui ont ´et´e ex´ecut´ees inutilement lorsque l’ex´ecution d’un programme sera termin´ee (algorithme de construction puis d’exploration du graphe de d´ependance des instructions d´ecrit dans cette section). Ces r´esultats devront ensuite ˆetre mis en forme afin de d´egager des statistiques sur la quantit´e de travail inutile (pourcentage d’instructions inutiles) et sur la nature de ces instructions (de quel type d’instructions s’agit-il ?). Une fois cet algorithme impl´ement´e, il sera int´eressant de le tester sur diff´erent type de programme afin de savoir quelle est la quantit´e de travail r´eellement inutile (d’apr`es la d´efinition donn´ee en introduction de cette section) dans ces programmes. 1.5.4 Conclusion sur cette approche En conclusion nous pouvons dire que l’approche retenue pour le stage est une d´emarche scien-tifique exp´erimentale permettant de savoir quelle est la proportion globale de travail inutile dans un programme. Si les r´esultats r´ev`elent une grande quantit´e de travail inutile, de nombreuses ouvertures paraissent possibles : D´etection de ces instructions grˆace `a des compilateurs « intel-ligents », d´etection de ces instructions « au vol » (approche d´ej`a retenue par [1]), coop´eration compilateur/mat´eriel ou encore ajout de nouvelles instructions afin de faciliter leur d´etection. 18
  • 19.
    1.6 Conclusion Enconclusion, nous pouvons dire que plusieurs mani`eres d’´eliminer le travail inutile ont d´ej`a ´et´e abord´ees (tant dans le domaine de la compilation qu’en architecture). En effet, lors de la compila-tion, une certaine quantit´e de travail inutile peut d´ej`a ˆetre supprim´ee (en fonction des informations que le compilateur peut exploiter). Cependant, nous avons ´egalement vu que certaines optimisa-tions de ces mˆemes compilateurs g´en`erent des instructions inutiles. De fait, diff´erentes m´ethodes ont ´et´e propos´ees pour ´eliminer ce travail inutile lors de l’ex´ecution (instructions dynamiques inutiles). Une autre approche int´eressante consistait `a ´eliminer les ´ecritures silencieuses, une autre forme de travail inutile. Apr`es ce tour d’horizon global, il est assez difficile de savoir de mani`ere pr´ecise quelle est la quantit´e de travail inutile effectu´ee par un microprocesseur lors de l’ex´ecution d’un programme. C’est `a cette question que va tenter de r´epondre le travail `a venir en stage. . . 19
  • 20.
    Chapitre 2 Compterendu du stage 2.1 Introduction 2.1.1 Le travail inutile, qu’est ce que c’est ? Le travail inutile est une notion difficile `a cerner. Il existe diff´erentes approches pour traiter le probl`eme du travail effectu´e inutilement par un programme. Tout d’abord, le travail inutile peut- ˆetre de nature statique (d´etectable et supprimable lors de la compilation) ou de nature dynamique (visible uniquement lors de l’ex´ecution). L’´elimination du travail inutile statique est d´ej`a bien connue et fait partie int´egrante de toute chaˆıne de compilation optimis´ee digne de ce nom. Ici nous nous int´eresserons seulement au travail inutile de nature dynamique puisque ce dernier n’est pas exploit´e par les processeurs ou les langages de programmation actuels. Une fois le cadre du travail inutile dynamique pos´e, il est n´ecessaire de se donner une d´efinition pr´ecise du travail inutile afin de pouvoir en ´evaluer la quantit´e lors de l’ex´ecution d’un programme sur un jeu de donn´ees particulier de fa¸con automatique. Cette d´efinition, dans un premier temps tr`es large, a ´et´e restreinte pour des raisons d’impl´ementation. La d´efinition prise comme base de d´epart `a cette ´evaluation ´etait la suivante : Tout travail qui ne sert, ni directement, ni indirectement, `a produire un r´esultat est jug´e inutile. De fa¸con plus pr´ecise : Une instruction dynamique est consid´er´ee comme utile si - Elle produit un r´esultat ´emis en sortie du programme (ex : affichage `a l’´ecran). - Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile. - C’est un branchement dominant une instruction utile. 20
  • 21.
    Exemple simple : instruction 1; si booléen faire instruction 2; instruction 3; fpour instruction 4; Dans cet exemple, le branchement conditionnel « domine » les instructions 2 et 3. Si le bool´een est vrai et que les instructions 2 et 3 sont inutiles, alors on peut consid´erer le branchement comme inutile. Note Par abus de langage, dans la suite de ce document, nous d´esignerons toutes les instructions de transfert de contrˆole (branchement conditionnels et inconditionnels, sauts, appels de fonctions. . .) par l’expression « instruction de branchement ». En regardant cette d´efinition de plus pr`es, un probl`eme se pose dans le cas g´en´eral : Supposons que l’instruction 2 soit un « store » qui range une valeur `a une adresse m´emoire, que l’instruction 4 soit un « load » et que le bool´een soit `a faux. Dans ce cas, si l’instruction 4 est consid´er´ee comme utile et si les deux acc`es pointent sur la mˆeme adresse m´emoire, alors le branchement devra ˆetre consid´er´e comme utile. Cependant, l’adresse de l’acc`es `a la m´emoire qui aurait pu ˆetre fait par l’instruction 2 n’est pas connue puisque cette instruction n’a pas ´et´e ex´ecut´ee. A cause de ce type d’acc`es `a la m´emoire (dont l’adresse acc´ed´ee n’est connue qu’`a l’ex´ecution) nous avons du faire l’hypoth`ese conservatrice suivante : Toutes les instructions de branchements sont consid´er´ees comme utiles. Ce qui nous donne la d´efinition suivante : Une instruction dynamique est consid´er´ee comme utile si - Elle produit un r´esultat ´emis en sortie du programme (ex : affichage `a l’´ecran). - Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile. - C’est un branchement. Note Par abus de langage, dans la suite de ce document, nous utiliserons le mot « ressource » pour d´esigner soit un registre soit un emplacement m´emoire. Partant de cette nouvelle d´efinition, l’objectif ´etait de construire un graphe de d´ependance de donn´ee en reliant les instructions lisant une ressource `a la derni`ere instruction ayant ´ecrit dans cette mˆeme ressource. De cette fa¸con, lorsqu’une ressource apparaˆıt comme utile (lorsque sa valeur est ´ecrite en sortie ou qu’elle est utilis´ee par une instruction de branchement), il devient possible, en parcourant les arcs de ce graphe, de trouver toutes les instructions qui ont ´et´e utiles pour produire 21
  • 22.
    ce r´esultat (ler´esultat est un graphe ressemblant `a la figure 2.2 page 25). 2.1.2 Notre protocole de test A partir de cette d´efinition, nous avons essay´e de mesurer la quantit´e de travail inutile dans des petits programmes d’exemple, puis, une fois ces exemples valid´es, nous avons test´e notre protocole pour mesurer le travail inutile sur un programme plus cons´equent et surtout n’ayant pas ´et´e con¸cu dans le but d’en mesurer la quantit´e de travail inutile. Pour faire nos tests, nous avons choisi l’utilitaire de compression/d´ecompression de donn´ees gzip. 22
  • 23.
    2.2 La m´ethodeutilis´ee 2.2.1 L’algorithme Pour construire notre graphe de d´ependance de donn´ee, nous avons choisi d’instrumenter chaque instruction assembleur afin de contrˆoler de fa¸con pr´ecise les entr´ees et les sorties de chacune d’elles. Les entr´ees ´etant les op´erandes d’une instruction (les ressources ´etant lues par l’instruction) et les sorties ´etant les r´esultats d’une instruction (les ressources ´etant ´ecrites par l’instruction). Dans le cas g´en´eral, notre impl´ementation de la d´etection de d´ependance de donn´ee peut se r´esumer par l’algorithme 1. Algorithme 1: Construction du graphe de d´ependance de donn´ee Entr´ee : Programme dont on veut ´evaluer la quantit´e de travail inutile. Donn´ees `a fournir en entr´ee `a ce programme. Sortie : Graphe de d´ependance de donn´ee dynamique. 1 pour chaque instruction ex´ecut´ee faire 2 Cr´eer une repr´esentation interne de cette instruction; 3 L’ajouter `a la liste des instructions dynamiques ex´ecut´ees; {Cette repr´esentation interne contient des informations concernant l’instruction : son num´ero dynamique, le fichier source auquel elle appartient, son num´ero statique dans ce fichier, son type. . . } 4 pour chaque op´erande de l’instruction {ressource lue} faire 5 Lire dans la table des ressources quelle est la derni`ere instruction qui a ´ecrit dans cette ressource; 6 Cr´eer un lien de d´ependance {arc dans le graphe} entre l’instruction courante et la derni`ere instruction ayant ´ecrit dans la ressource en question; {Associe `a l’op´erande le num´ero de l’instruction dynamique qui l’a produit} fin 7 pour chaque r´esultat de l’instruction {ressource ´ecrite} faire 8 Ecrire dans la table des ressources que l’instruction courante a modifi´ee l’´etat de cette ressource; fin fin Une fois cet algorithme ex´ecut´e sur un programme particulier, il est possible de connaˆıtre les d´ependances directes entre les instructions grˆace aux arcs construits mais aussi les d´ependances indirectes grˆace aux chemins form´es par des suites d’arcs dans le graphe (le graphe de la figure 2.2 page 25 en est un exemple). En ajoutant `a l’algorithme pr´ec´edent une condition dans la boucle principale permettant de parcourir le graphe construit lorsqu’on rencontre une instruction de sortie (affichage), il devient 23
  • 24.
    possible de connaˆıtreles instructions utiles lors de l’ex´ecution d’un programme (d’apr`es la premi`ere d´efinition donn´ee ci-dessus) (cf. algorithme 3). Proc´edure Parcours du graphe (Noeud) 1 si le noeud est marqu´e inutile alors 2 Marquer ce noeud {repr´esentant une instruction} comme ´etant utile; pour chaque noeud op´erande de ce noeud faire 3 Appeler la proc´edure Parcours du graphe (Noeud op´erande); fin fin Algorithme 3: D´etection des instructions inutiles Entr´ee : Graphe de d´ependance de donn´ee dynamique. Sortie : Quantit´e d’instructions dynamiques inutiles. Localisation de ces instructions dans le code source. pour chaque noeud du graphe faire si le noeud repr´esente une instruction de sortie ou de branchement alors Appeler la proc´edure Parcours du graphe (Noeud); fin fin Numéro d'instruction dynamique Identificateur d'instruction statique Type de l'instruction Nombre d'opérandes Liste des instructions ayant écrit en dernier dans les opérandes Fig. 2.1 – La structure de donn´ee d’un noeud du graphe Un point int´eressant de cet algorithme, dont nous nous sommes rendu compte une fois l’impl´e-mentation op´erationnelle, est qu’il permet de d´etecter les acc`es fait en lecture `a une zone m´emoire non initialis´ee pr´ealablement. Par exemple, si un tableau de taille n est d´eclar´e et initialis´e, le fait de tenter d’acc´eder `a l’adresse de la zone m´emoire n+1, qui n’a donc pas ´etait initialis´ee, provoque une incoh´erence dans l’algorithme puisqu’il est impossible de trouver la derni`ere instruction ayant ´ecrit dans cette zone m´emoire (ligne 5 dans l’algorithme 1 page pr´ec´edente). De cette fa¸con, il est possible de trouver, lors de l’ex´ecution, une erreur d’acc`es `a la m´emoire. 24
  • 25.
    39 40 38 36 37 35 34 3 14 33 0 13 25 24 32 31 30 29 28 26 27 23 17 16 22 21 20 19 18 15 9 12 11 8 10 7 2 6 5 4 1 Dans ce graphe, les noeuds sont ´etiquet´es par les num´eros dynamiques des instructions (ordre d’ex´ecution). Les noeuds en gris sont des instructions inutiles alors que les noeuds en noir sont des instructions utiles. Les instructions dynamiques 4, 12 et 20 sont issues d’une seule et mˆeme instruction statique de rangement en m´emoire dont seulement une instance est utile : l’instruction dynamique num´ero 12. Fig. 2.2 – Exemple de graphe g´en´er´e par l’algorithme 1 & 3 25
  • 26.
    2.2.2 L’optimisation Legros probl`eme de cette approche est que le graphe devient tr`es rapidement ´enorme, mˆeme avec des programmes de petite taille. En effet, ´etant donn´e qu’il faut conserver des informations concernant chaque instruction dynamique jusqu’`a la fin de l’ex´ecution, seule une ex´ecution ayant un nombre r´eduit d’instructions dynamiques peut ˆetre envisag´ee (aux alentours de 300 000 dans notre impl´ementation). Nous avons donc tent´e de r´eduire le graphe au maximum en ´eliminant au cours de l’ex´ecution les informations qui ne nous ´etaient plus n´ecessaires. Ce qui est fait. . . Dans un premier temps, pour r´eduire ce graphe et donc augmenter la taille des programmes testables, nous avons d´ecid´e de supprimer `a la vol´ee les informations concernant les instructions « certifi´ees » utiles. Ces informations ´etant le noeud repr´esentant cette instruction et les arcs sor-tant de celle-ci. Ces informations ne sont plus d’aucune utilit´e une fois que le parcours de l’arbre dont cette instruction est la racine est effectu´e. Il est alors possible de les supprimer sans perdre d’information utile `a notre calcul de quantit´e de travail inutile. Cette m´ethode permet, `a mesure que le programme se d´eroule et envoi des informations en sortie, (affichage. . .) de r´eduire le graphe. Il est alors d’autant plus r´eduit que la quantit´e de travail utile est importante (sur nos tests, le gain r´eel en occupation m´emoire est d’un facteur trois `a quatre ce qui permet de tester des programmes d´epassant le million d’instructions dynamiques sans avoir un temps d’ex´ecution r´edhibitoire). La courbe de l’occupation m´emoire de l’algorithme au cours du temps devient alors identique (`a quelques d´etails d’impl´ementation pr`es) `a la courbe repr´esentant la quantit´e de travail inutile cumul´e (figure 2.9 page 40). Quelques petites optimisations ont aussi ´etaient apport´ees au programme concernant le temps d’ex´ecution. Bien que ce facteur ne soit pas le point crucial de notre algorithme, il semblait int´eressant de s’y pencher pour ´eviter d’avoir des dur´ees de tests trop importantes. Nous avons par exemple, `a la ligne 1 de la proc´edure Parcours du graphe page 24, supprim´e le parcours d’une branche lorsque cette derni`ere poss`ede comme racine une instruction utile. En effet, si tel est le cas, cela signifie que cette branche a d´ej`a ´et´e enti`erement explor´ee et qu’il est inutile de la parcourir `a nouveau. Ce qu’il reste `a faire. . . Dans un second temps, il est int´eressant de voir que si une instruction ´ecrit dans une ou plu-sieurs ressources, puis que ces ressources sont de nouveau ´ecrites sans ˆetre lues entre temps, les informations concernant cette instruction inutile ne nous serviront jamais puisque cette instruction ne sera jamais rendue utile (notion de « valeur morte »). De cette fa¸con, il est possible, ici encore, 26
  • 27.
    de r´eduire notregraphe en supprimant les noeuds repr´esentant ce type d’instructions ainsi que leurs arcs sortants. Une mani`ere simple d’impl´ementer un tel m´ecanisme serait de consid´erer que chaque instruction inutile est un objet et que les ressources (registres et zones m´emoire) sont des moyens d’acc´eder `a ces objets. Si une instruction est accessible depuis au moins une ressource, alors il n’est pas possible de supprimer les informations concernant cette instruction. En revanche, si aucune ressource ne « r´ef´erence » l’objet, alors cet objet est inaccessible depuis les ressources et le restera jusqu’`a la fin de l’ex´ecution du programme. Cet objet peut donc ˆetre supprim´e (noeud ainsi que ses arcs sortants). Cette m´ethode s’apparente `a un syst`eme de ramasse-miettes comme il est souvent mis en place dans un environnement d’ex´ecution pour lib´erer des zones m´emoires n’´etant plus r´ef´erenc´ees par aucun pointeur. 2.2.3 Le r´esultat La figure 2.3 page suivante est un exemple simple permettant de comprendre comment est construit le graphe de d´ependance de donn´ee `a partir du code assembleur du programme dont on veut ´evaluer la quantit´e de travail inutile. Dans la figure 2.3 page suivante, les noeuds sources sont les instruction utiles par hypoth`ese (en caract`ere gras). Ces instructions sont soit des instructions de sortie (print %valeur dans l’exemple) soit des instructions de branchement (bne boucle dans l’exemple). Une fois ces instructions jug´ees comme ´etant utile au programme, nous pouvons appliquer la d´efinition r´ecursive permettant de trouver toutes les instructions ayant servi `a produire les valeurs utiles `a ces instructions. Ainsi, dans notre exemple, l’instruction dynamique num´ero 18 (print %valeur) poss`ede comme entr´ee le registre %valeur. Il est donc n´ecessaire de trouver la derni`ere instruction ayant ´ecrit dans ce registre. Cette instruction est l’instruction dynamique num´ero 17 (load [@tab+2],%valeur). Ainsi de suite r´ecursivement, l’instruction dynamique num´ero 17 poss`ede comme entr´ee la seconde case du tableau rang´e `a l’adresse m´emoire @tab dont la derni`ere ´ecriture a ´et´e faite par l’instruction dynamique num´ero 8 et ainsi de suite jusqu’`a n’arriver qu’`a des instructions n’ayant aucune entr´ee (copie d’une constante dans un registre (mov 1,%indice dans l’exemple), instruction d’entr´ee au clavier par l’utilisateur. . .). De cette mani`ere, en parcourant le graphe, il est possible d’identifier le travail utile. Les ins-tructions n’´etant accessible depuis aucune des sources (instructions de sorties ou de branchement) sont identifi´ees comme ´etant du travail inutile (instructions dynamiques 2, 3, 12 et 13 dans notre exemple). Grˆace `a cet exemple, nous avons mis en ´evidence un cas simple comportant peu de travail inutile. En revanche, il est facile de prendre conscience de l’importance que peut atteindre ce travail inutile d`es lors que le traitement `a l’int´erieur d’une boucle du type de celle pr´esent´ee dans l’exemple devient important. En effet, dans notre exemple, seule la multiplication par 10 et le rangement en m´emoire sont inutiles mais si la valeur `a ranger dans le tableau avait ´et´e un calcul effectu´e par une fonction comportant 10 000 instructions, les chiffres auraient ´et´e diff´erents. De mˆeme, si la taille du tableau avait ´et´e de 10 000 cases dont seulement une aurait ´et´e utilis´ee, la quantit´e de travail inutile aurait ´et´e beaucoup plus importante. En revanche si, dans notre exemple, les trois cases du tableau avaient 27
  • 28.
    1: mov 1,%indice boucle: 2: mul %indice,10,%valeur 3: store %valeur,[%indice+@tab-1] 4: add %indice,1,%indice 5: cmp 4,%indice 6: bne boucle 7: load [@tab+2],%valeur 8: print %valeur Code en assembleur RISC (code statique) Dépendances de données permettant d’identifier le travail utile (arcs du graphe parcourus par l’algorithme). Dépendance de donnée n’étant pas parcourues par l'algorithme. Trace d’exécution des instructions (dynamique) 1ère itération de la boucle 2ème itération de la boucle 3ème itération de la boucle Numéro Dynamique Numéro Statique 123456789 10 11 12 13 14 15 16 17 18 123456234562345678 n Numéro d’instruction statique utile par essence (instructions de sorties ou de branchement). n Numéro d’instruction statique utile après parcours du graphe de dépendance (définition récursive). n Numéro d’instruction dynamique (indique l’ordre d’éxécution des instructions dans le temps) n Numéro d’instruction statique jugés comme étant inutile d’après la définition (instruction n’appartenant pas au graphe de dépendance de données). Compilation Pour i de 1 à 3 faire t[i] := i*10; Finpour Ecrire (t[2]); Code source original Fig. 2.3 – Du code source en langage de haut niveau au graphe de d´ependance de donn´ee dynamique ´et´e affich´ees (instruction print), la quantit´e de travail inutile aurait ´et´e nulle. Un exemple plus complet montrant dans le d´etail comment l’algorithme a ´et´e impl´ement´e est en annexe (figure 3.2 page 59). 28
  • 29.
    2.3 L’environnement detravail : Les choix de mise en oeuvre 2.3.1 Les Outils Pour la mise au point de notre programme d’´evaluation de la quantit´e de travail inutile, plusieurs outils ont ´et´e mis `a contribution : - Salto : Salto est une biblioth`eque de fonctions permettant d’analyser du code source en assembleur pour en extraire les informations s´emantiques sous une forme exploitable en C++. Ces informations peuvent ˆetre de diff´erentes natures : Il est possible de connaˆıtre le d´ecoupage en blocs de base du code source, les ressources utilis´ees par une instruction pr´ecise (dans notre cas, ce qui nous int´eresse sont les acc`es `a la m´emoire et aux diff´erents registres). De plus, une des fonctionnalit´e indispensable `a notre r´ealisation disponible dans Salto est la possibilit´e d’instrumenter le code source (figure 2.4 page suivante). - Le compilateur GCC pour processeur Sparc : Compilateur C/C++ gratuit sous licence GNU. - Le compilateur CC pour processeur Sparc : Compilateur C propri´etaire de Sun disponible seulement pour la plateforme Sparc. - Les expressions r´eguli`eres en C. 2.3.2 L’instrumentation L’instrumentation c’est quoi ? L’instrumentation d’instruction est un m´ecanisme qui consiste `a ins´erer des instructions suppl´e-mentaires entre les instructions du code source d’un programme d´ej`a ´etabli afin « d’ausculter » ce dernier. Les informations qu’il est possible de r´ecup´erer par ce m´ecanisme sont de nature dynamique puisque les instructions rajout´ees par instrumentation sont ex´ecut´ees autant de fois que le sont les instructions appartenant au code source d’origine. Prenons comme exemple le cas d’une boucle dont le corps est ex´ecut´e n fois, alors le code rajout´e par instrumentation du code source d’origine dans le corps de cette boucle sera lui aussi ex´ecut´e n fois. Ceci permet de savoir de fa¸con pr´ecise quelles sont les instructions statiques qui ont ´et´e ex´ecut´ees par le processeur s´equentiellement. De plus, l’instrumentation permet de r´ecup´erer d’autres informations dynamiques comme les adresses des acc`es `a la m´emoire. Dans l’exemple de la figure 2.4 page suivante, le code source original est instrument´e afin de r´ecup´erer la valeur du r´esultat produit par l’instruction `a instrumenter et pour le traiter dans la fonction fct (tmp ´etant par exemple un registre de d´ebuggage dont le code source original ne fait jamais usage mais qui peut ˆetre utilis´e par la fonction fct pour effectuer son traitement). 29
  • 30.
    mov r1,r2 addr2,3,r2 store r2,[a0] Code source mov r1,r2 mov r2,tmp call fct add r2,3,r2 mov r2,tmp call fct store r2,[a0] load [a0],tmp call fct Code source instrumenté instrumentation Fig. 2.4 – Instrumentation de code source en assembleur Note Dans l’exemple de la figure 2.4, les instructions sont instrument´ees apr`es leur ex´ecution, ce qui n’est pas le cas dans notre impl´ementation : l’instrumentation se trouve avant l’ex´ecution de l’instruction pour des raisons de suivi des branchements (pour pouvoir instrumenter correctement les branchements, il est n´ecessaire de placer le code d’instrumentation avant ceux-ci). L’instrumentation dans notre programme L’instrumentation, dans le cas g´en´eral, permet d’ins´erer des instructions dans un programme. A partir de ce concept simple, nous avons d´ecid´e d’utiliser l’instrumentation pour ins´erer des appels de fonctions (´ecrites en C et compil´ees par ailleurs). De fait, les appels de fonctions en assembleur ne faisant pas de sauvegarde des registres globaux (accessible de n’importe o`u dans le programme). Il nous a fallu ajouter `a cette instrumentation une sauvegarde de contexte avant l’appel `a cette fonction puis une restauration apr`es (figure 2.5 page suivante). Mais ce n’est pas tout : Chaque instruction assembleur ayant un nombre variable d’op´erandes et de r´esultats, il nous a fallu ajouter une instruction d’appel `a une fonction pour chaque op´erande et pour chaque r´esultat. De plus, chaque acc`es `a la m´emoire n´ecessitant la r´ecup´eration de l’adresse de cet acc`es, il nous a fallu r´ecup´erer des informations sur la valeur des registres utilis´es par l’instruction `a instrumenter afin de savoir quelle ´etait l’adresse de cet acc`es m´emoire. L’instrumentation d’une instruction dans notre programme d’´evaluation de la quantit´e de travail inutile peut-ˆetre r´esum´ee en sept phases : – Sauvegarde : Une phase de sauvegarde du contexte (registres globaux, d´ecalage de la fenˆetre de registres. . .). – D´ebut : Une phase de cr´eation de la structure de donn´ee repr´esentant une instruction (fi-gure 2.1 page 24). – Op´erandes : Une phase permettant de cr´eer les arcs vers les instructions ayant ´ecrit en dernier dans les ressources op´erandes de l’instruction. 30
  • 31.
    inst1 inst2 inst3 Code source sauvegarde call fct restauration inst1 sauvegarde call fct restauration inst2 sauvegarde call fct restauration inst3 Code source instrumenté instrumentation Fig. 2.5 – Principe de l’instrumentation faite par le programme d’´evaluation de la quantit´e de travail inutile – Milieu : Une phase, utile seulement pour les instruction « save » et « restore », permettant de mettre `a jour le niveau de la fenˆetre de registres. – R´esultats : Une phase permettant de mettre `a jour l’´etat des ressources en fonction des r´esultats produits par l’instruction. – Fin : Une phase permettant d’´evaluer l’utilit´e de l’instruction courante. Si cette derni`ere est utile, on parcour son arbre de d´ependance de donn´ee. – Restauration : Une phase de restauration du contexte. 2.3.3 Le choix de la Plateforme Pour choisir notre plateforme de travail, nous avons pris en compte plusieurs param`etres. Nous avions le choix entre le jeu d’instruction x86 (CISC) et le jeu d’instruction Sparc (RISC). En premier lieu, l’outil mis `a notre disposition (Salto) semblait plus adapt´e `a un jeu d’instruction r´eduit. En effet, Salto ´etant bas´e sur la reconnaissance des instructions assembleur par des expressions r´eguli`eres, il est tr`es difficile de supporter un jeu d’instruction aussi vaste que le x86 d’Intel (CISC). De fait, le support d’un tel jeu d’instruction par Salto est apparu comme ´etant insuffisant. De plus, le travail `a effectuer ´etant, entre autre, d’identifier les acc`es `a la m´emoire, un jeu d’instruction r´eduit avec seulement une instruction pour le chargement et une pour le rangement en m´emoire nous est apparu plus simple `a manipuler. Cependant, le Sparc poss`ede quelques inconv´enients qui, nous le verrons plus loin, ne nous ont pas facilit´e la tˆache. Nous avons donc d´ecid´e de travailler avec un jeu d’instruction RISC pleinement support´e par Salto : le Sparc de Sun. 31
  • 32.
    2.3.4 SPARC :Le Meilleur des Mondes ? Le Sparc est une architecture RISC assez classique ce qui signifie que le nombre d’instructions est assez r´eduit, qu’elles ont toutes la mˆeme taille (quatre octets pour le Sparc) et que, chose relativement importante pour notre impl´ementation, les instructions d’acc`es `a la m´emoire sont au nombre de deux (load pour charger une valeur de la m´emoire vers un registre et store pour ranger une valeur d’un registre vers la m´emoire). Tout aurait ´et´e parfait si la description du Sparc s’´etait arrˆet´ee l`a. . . - Le delay slot est une des particularit´es de l’architecture Sparc. Il s’agit d’ex´ecuter l’instruc-tion qui suit imm´ediatement une instruction de branchement avant que cette instruction de branchement ne modifie le flot de contrˆole de l’ex´ecution du programme. Pour r´esumer, l’ins-truction qui se trouve dans le delay slot d’un branchement (elle se trouve juste apr`es dans le code source) se comporte comme si elle se trouvait avant le branchement pour ce qui est du contrˆole mais comme si elle ´etait apr`es pour ce qui est des donn´ees. - L’annul bit est une autre particularit´e « amusante » du Sparc qui est en relation directe avec le delay slot. Lorsqu’une instruction se trouve dans le delay slot d’un branchement dont l’annul bit est activ´e, (be,a : la virgule et le a indique que l’annul bit est activ´e) cette instruction ne s’ex´ecute que lorsque le branchement est pris. Dans le cas contraire, l’instruction se trouvant dans le delay slot n’est pas ex´ecut´ee (on dit qu’elle est annul´ee). - La fenˆetre de registres tournante est une fa¸con de pallier `a la lenteur des acc`es `a la pile lors du passage de param`etres `a une fonction. En effet, le Sparc se propose de r´esoudre le probl`eme du passage de param`etres `a une fonction de mani`ere originale au moyen de cette fenˆetre de registres tournante. Il s’agit de ranger les valeurs que l’on souhaite passer en param`etres `a la fonction qui va ˆetre appel´ee dans des registres sp´eciaux nomm´es registres de sortie (%o0 `a %o5) qui, une fois la fonction appel´ee, seront renomm´es en registres d’entr´ee (%i0 `a %i5) (cf. figure 2.6 page suivante). De cette fa¸con, la fonction appel´ee peut se servir de ces valeurs sans qu’elles n’aient ´et´e recopi´ees dans une quelconque pile (`a l’exception du cas o`u la taille de la fenˆetre de registres tournante n’est pas suffisante). Le delay slot Dans notre impl´ementation, nous avons du prendre en compte cette particularit´e de l’archi-tecture Sparc. En effet, afin d’instrumenter les instructions se trouvant dans le delay slot d’un branchement, nous avons du utiliser diff´erentes techniques consistant `a « remonter » notre instru-mentation de ce type d’instructions avant le branchement. Ceci `a conduit `a pas mal de probl`emes de coh´erence entre la repr´esentation des instructions cr´e´ees par l’instrumentation et les instructions r´eellement ex´ecut´ees. Mais le cas o`u le delay slot nous a pos´e le plus de probl`eme est celui o`u il nous a fallu ajouter des sauts afin d’´eviter l’ex´ecution de certaines instructions. En effet, dans ce cas, si l’instruction que l’on veut « sauter » se trouve dans le delay slot d’un branchement, il faut dupliquer cette instruction des branchement : mettre dans le code source une version normale avec l’instruction qui se trouvait dans son delay slot dans le code source d’origine et une seconde version du mˆeme branchement contenant un nop dans son delay slot. De cette fa¸con, selon que l’instruction se trouvant dans le 32
  • 33.
    delay slot dece branchement doit ˆetre ex´ecut´ee ou non, le flot de contrˆole est aiguill´ee vers l’une ou l’autre des deux versions de ce branchement. Il faut ensuite faire converger les deux versions en un mˆeme point repr´esentant la suite du programme. La fenˆetre de registres du Sparc La fenˆetre de registre tournante est une des particularit´e du processeur Sparc. Nous allons en expliquer rapidement le principe afin d’exposer la mani`ere dont nous avons trait´e cette particularit´e dans notre programme. Registres de sortie %o0 à %o7 Registres locaux %l0 à %l7 Registres d’entrée %i0 à %i7 Registres accessibles au niveau n+1 niveau n Registres de sortie %o0 à %o7 Registres locaux %l0 à %l7 Registres d’entrée %i0 à %i7 Désigne les mêmes registres physiques Registres de sortie %o0 à %o7 Registres locaux %l0 à %l7 Registres d’entrée %i0 à %i7 niveau n+2 Désigne les mêmes registres physiques L’instruction « save » permet de passer d’un niveau n `a un niveau n+1 (utilis´e g´en´eralement comme une sauvegarde de contexte avec en plus la possibilit´e de passer des param`etres d’un contexte `a l’autre au niveau de la zone de recouvrement de la fenˆetre n et n+1). L’instruction « restore » permet de passer d’un niveau n `a un niveau n-1 (utilis´e g´en´eralement comme une restauration de contexte avec en plus la possibilit´e de passer des r´esultats d’un contexte `a l’autre au niveau de la zone de recouvrement de la fenˆetre n et n-1). Fig. 2.6 – Le principe de la fenˆetre de registres du Sparc Lorsque le nombre de registres utilis´es d´epasse le nombre de registres physiques r´eellement pr´esents dans le processeur, un m´ecanisme invisible pour l’utilisateur utilise une pile en m´emoire pour sauvegarder la fenˆetre de registres la plus ancienne et r´eutiliser ainsi cette derni`ere comme une nouvelle fenˆetre vierge. De cette fa¸con, le nombre de registres virtuellement utilisables par l’utilisateur n’est limit´e que par la taille de la pile et non par la taille du fichier de registres dans le processeur. Cette fenˆetre est souvent repr´esent´ee, dans les diff´erentes documentations sur le Sparc, de fa¸con circulaire pour montrer que la plus ancienne fenˆetre et la plus r´ecente peuvent se recouvrir si le nombre de fenˆetres disponibles dans le fichier de registres est insuffisant pour l’ex´ecution d’un programme donn´e. 33
  • 34.
    2.3.5 S’affranchir dela num´erotation des registres faite par Salto Les registres du Sparc s’organisent en deux parties. D’une part, des registres dit globaux qui se comportent de mani`ere classique, et d’autre part une fenˆetre de registres coulissante comme d´ecrit sur la figure 2.6 page pr´ec´edente. Pour les registres globaux, nous avons utilis´e la num´erotation propos´ee par Salto qui convenait tout `a fait ´etant donn´e qu’un nom de registre d´esigne toujours le mˆeme registre physique. En revanche, pour la fenˆetre de registres coulissante, nous avons du mettre en place notre propre syst`eme d’identification de registres : En effet, Salto ´etant un outil qui travaille sur du code assembleur, il lui est impossible d’avoir acc`es `a des informations concernant l’ex´ecution du programme (les seules informations disponible au niveau de Salto sont les informations statiques sur le programme). De fait, les informations que nous donne Salto concernant les acc`es aux registres sont les noms de ces registres. Il lui est impossible de connaˆıtre le niveau de la fenˆetre de registre en un point donn´e du code et donc de d´esigner un registre physique de mani`ere unique. Afin d’identifier de mani`ere unique chacun des registres physiques, il a donc fallu s’affranchir du syst`eme de num´erotation des registres propos´e par Salto pour le remplacer par un calcul fait de mani`ere dynamique (au cours de l’ex´ecution du programme) permettant de savoir `a quel registre physique correspondait chaque acc`es `a un registre appartenant `a la fenˆetre de registre courante. Pour ce faire, nous avons instrument´e les instructions save qui d´ecalent la fenˆetre de registre vers le haut et les instructions restore qui d´ecalent la fenˆetre de registre vers le bas (cf. figure 2.6 page pr´ec´edente). De cette fa¸con, il est possible de tenir `a jour une variable globale indiquant le niveau actuel de la fenˆetre de registre. En utilisant ce niveau comme d´ecalage par rapport `a un point de r´ef´erence, (la premi`ere fenˆetre disponible lors du lancement du programme) il est possible de savoir dans quelle fenˆetre de registre seront fait les acc`es aux registres de la fenˆetre courante indiqu´es par Salto. Grˆace `a cette m´ethode, il est possible d’identifier de mani`ere unique chacun des registres d’une fenˆetre pr´ecise mˆeme si celle-ci peut avoir ´et´e sauvegard´ee dans la pile par manque de place dans le fichier de registres. Les acc`es `a ces registres restent ce qu’ils sont puisque cette op´eration est transparente pour l’utilisateur du processeur (en l’occurrence notre programme). 2.3.6 Les expressions r´eguli`eres L’utilisation des expressions r´eguli`eres est particuli`erement utile pour analyser une chaˆıne de caract`eres ayant un motif fixe et une partie variable. C’est justement le cas d’une instruction assembleur dont la partie fixe est le mn´emonique de cette instruction et dont les parties variables sont les arguments. De cette fa¸con, nous avons utilis´e les expressions r´eguli`eres pour « d´ecouper » les instructions load et store en plusieurs parties : le mn´emonique d’une part (permettant de connaˆıtre la taille de l’acc`es `a la m´emoire : octet, mot, double mot ou quadruple mot) et les arguments d’autre part. Une 34
  • 35.
    fois chaque argumentr´ecup´er´e, il est possible d’acc´eder aux valeurs contenues dans les registres et aux ´eventuelles constantes. Il est donc possible de connaˆıtre de fa¸con exacte `a quelle adresse m´emoire va acc´eder l’instruction et `a combien d’octets elle va acc´eder. De plus, les expressions r´eguli`eres nous ont ´et´e utiles pour r´ecup´erer les ´etiquettes des appels de fonctions afin de savoir si ces fonctions ´etaient d´efinies en local (dans le code source du pro-gramme ´etudi´e) ou si elles appartenaient `a une biblioth`eque externe au programme (stdio en C par exemple). 2.3.7 La gestion des fonctions Une fois notre d´efinition impl´ement´ee et test´ee, il reste encore de nombreux probl`emes `a r´esoudre pour pouvoir confronter cet algorithme au « monde r´eel » (`a des programmes classiques tel que gzip). En effet, ce mod`ele th´eorique serait parfait si l’ensemble du code source assembleur d’une application ´etait visible par le programme d’´evaluation. Or les programmes classiques utilisent des fonctions d´efinies dans des biblioth`eques dont on n’a pas le code source (Un programme en C travaillant sur des fichiers utilisera par exemple stdio pour lire et ´ecrire dans un fichier). Afin de r´egler cette difficult´e, nous avons du utiliser l’options de compilation -SO de cc permettant de savoir quels sont les registres du Sparc et les adresses m´emoires utilis´ees dans la pile comme param`etres lors de l’appel `a une fonction. Ainsi, il nous a ´et´e possible de rajouter « artificiellement » des arcs de d´ependance entre l’appel `a une fonction d´efinie dans une biblioth`eque externe (call printf par exemple) et les param`etres pass´es `a cette fonction. Pour cette raison, cc pour Sparc est apparu comme ´etant id´eal dans notre protocole de test. Afin de r´esoudre ce probl`eme, les fonctions appel´ees par le programme ´etudi´e ont ´et´e class´ees en trois cat´egories, chacune correspondant `a un traitement particulier `a faire pour les prendre en compte correctement : Les fonctions « internes » Les fonctions internes sont les fonctions dont le code source est disponible (peut-ˆetre utilis´e par notre programme d’´evaluation). Les appels `a ce type de fonctions peuvent donc ˆetre trait´es comme de simple branchements inconditionnels puisque les arcs du graphe de d´ependance peuvent tr`es bien relier une instruction appartenant `a cette fonction `a une instruction appartenant au programme appelant. Le graphe de d´ependance de donn´ee n’est donc pas interrompu par un tel appel de fonction. Les fonctions « externes » utilisant des pointeurs Les fonctions externes sont les fonctions dont le code source n’est pas disponible (code source d’une biblioth`eque d’entr´ee/sortie par exemple). Dans ces cas l`a, il est n´ecessaire de consid´erer que les param`etres pass´es `a la fonction sont lus par l’instruction d’appel de la fonction et que le r´esultat 35
  • 36.
    est ´ecrit parcette instruction. Cependant, dans le cas g´en´eral, il est n´ecessaire de d´etourner l’appel `a une telle fonction pour faire ajouter « artificiellement » des arcs de d´ependance de donn´ee pour traiter les donn´ees qui vont ˆetre lues et ´ecrites `a l’int´erieur de cette fonction. En effet, notre programme est incapable de connaˆıtre le type des param`etres d’une fonction (si il s’agit d’entiers, de pointeurs. . .) et donc, de savoir si une fonction d´efinie dans une biblioth`eque dont on n’a pas le code source acc`ede ou non `a une zone m´emoire dont l’adresse est un de ses param`etres. De plus, mˆeme en sachant qu’un param`etre est un pointeur, rien ne dit si la fonction dont on n’a pas le code source va y acc´eder en lecture, en ´ecriture ou pire encore, `a combien d’´el´ements elle va acc´eder ! (Il ne faut pas oublier qu’en C les tableaux sont implicites et que, par cons´equent, on ne connaˆıt pas leur taille simplement en connaissant l’adresse de leur premier ´el´ement). Afin de r´egler cette difficult´e, nous avons choisi de d´etourner les appels `a ces fonctions (au nombre de trente cinq dans gzip) afin de leur faire ex´ecuter du code permettant de simuler leur comportement en mati`ere d’acc`es `a la m´emoire. Ces fonctions sont, le plus souvent des fonctions d’entr´ees/sorties (ex : read, write, fflush. . .), des fonctions de lecture/´ecriture dans des chaˆınes de caract`eres (ex : strcat, strcpy, strcmp. . .) ou des fonctions de lecture/´ecriture de zones m´emoire en g´en´eral (ex : memset, memcpy, memcmp. . .). Exemple simple : char *my_strcpy(char *c1, const char *c2) { /* On simule un acc`es en lecture `a une zone m´emoire d´ebutant `a l’adresse contenue dans le pointeur c2 et de longueur strlen(c2)+1 (taille de la cha^ıne de caract`ere `a lire avec son terminateur) */ instrumentationEntreeMemoire((int)c2, strlen(c2)+1); /* On simule un acc`es en ´ecriture `a une zone m´emoire d´ebutant `a l’adresse contenue dans le pointeur c1 et de longueur strlen(c2)+1 (taille de la cha^ıne de caract`ere `a copier avec son terminateur) */ instrumentationSortieMemoire((int)c1, strlen(c2)+1); /* On retourne la valeur retourn´ee par la vraie fonction strcpy */ return strcpy(c1, c2); } Les fonctions « externes » n’utilisant pas de pointeurs Les fonctions externes n’utilisant pas de pointeurs sont des fonctions dites externes d’apr`es la d´efinition ci-dessus `a la diff´erence qu’elles ne font pas d’acc`es `a la m´emoire `a partir de pointeur. Elles peuvent donc ˆetre trait´ees simplement en consid´erant que les param`etres pass´es `a la fonction sont lus et que le r´esultat est ´ecrit. 36
  • 37.
    2.4 R´esultats &Analyse 2.4.1 Les chiffres. . . Une fois l’´evaluation de la quantit´e de travail inutile effectu´ee de cette mani`ere, nous obtenons les chiffres suivants (cf. figure 2.7). gzipa gzipb gzipc O0d 11.03 % 9.53 % 11.03 % O1 12.05 % 10.73 % 16.44 % O2 11.40 % 10.28 % 16.17 % O3 12.77 % 12.10 % 21.83 % O4 12.66 % 12.55 % 23.35 % O5 12.66 % 12.55 % 23.35 % gunzipe gunzipf gunzipg O0 1.09 % 1.14 % 0.21 % O1 2.94 % 3.10 % 0.29 % O2 3.51 % 4.28 % 0.41 % O3 10.01 % 10.51 % 0.47 % O4 9.83 % 10.26 % 0.52 % O5 9.83 % 10.26 % 0.52 % aCompression d’un fichier texte (RTF) de 2127 octets avec gzip (fort taux de compression (facteur : 1.9)) bCompression d’un fichier image (GIF) de 1704 octets avec gzip (faible taux de compression (facteur : 1.4)) cRe-compression d’un fichier compress´e par gzip de 1506 octets avec gzip (taux de compression nul (facteur : 0.98)) dSeuls ces r´esultats ont ´et´e valid´es avec le programme de v´erification pour des raisons d’impl´ementation eD´ecompression du fichier texte de 2127 octets fD´ecompression du fichier image de 1704 octets gD´ecompression du fichier compress´e deux fois par gzip de 1506 octets Conditions de test : gzip version 1.2.4 recompil´e avec cc de Sun pour le processeur Sparc version v8plus. Ces chiffres s’entendent en ne comptant pas les ´eventuelles instructions nop qui se trouvent dans le delay slot de certaines instructions de branchement. Fig. 2.7 – Quantit´e d’instructions assembleurs inutiles lors de l’ex´ecution de gzip dans diff´erentes conditions 2.4.2 Le doute. . . Introduction Ces chiffres n’´etant qu’une ´evaluation, rien ne permet de dire avec certitude que ce travail est effectivement inutile. D’autant plus que l’impl´ementation laisse souvent apparaˆıtre des failles que l’on n’imagine pas lorsqu’on raisonne de fa¸con abstraite sur les d´ependances de donn´ees (la gestion des fonctions dont on n’a pas le code source en est un exemple). Afin de valider notre programme et d’avoir la certitude que le travail inutile ´evalu´e en ´etait bien, nous avons mis au point un second programme r´e-ex´ecutant exactement le mˆeme programme de test (gzip dans notre cas) sur le mˆeme jeu de donn´ees (avec le mˆeme fichier en entr´ee) en n’ex´ecutant pas les instructions qui avaient ´et´e jug´ees comme ´etant inutiles lors de la premi`ere ex´ecution. Ainsi, si la seconde ex´ecution donne rigoureusement le mˆeme r´esultat que la premi`ere (le mˆeme fichier compress´e dans le cas de gzip), nous pouvons dire que les deux ex´ecutions sont ´equivalentes et que, par cons´equent, les instructions qui n’ont pas ´et´e ex´ecut´ees lors de la seconde ex´ecution n’´etaient effectivement pas utiles. 37
  • 38.
    somewhere : subr3,r4,r5 ... mov r1,r2 add r2,3,r2 cmp 0,r2 be somewhere store r2,(a0) ... Code source mov r1,r2 add r2,3,r2 cmp 0,r2 be somewhere sub r3,r4,r5 ... Trace dynamique de la première exécution (détection du travail inutile) mov r1,r2 add r2,3,r2 cmp 0,r2 be somewhere store r2,(a0) ... Trace dynamique de la deuxième exécution (non exécution du travail inutile) L'instruction add est jugée inutile : elle ne sera donc pas exécutée lors de la seconde exécution Fig. 2.8 – Mise en ´evidence d’un probl`eme d’impl´ementation par divergence du flot de contrˆole Note Reproduire une ex´ecution `a l’identique ne fonctionne que sur un programme d´eterministe : deux ex´ecutions successives sur un mˆeme jeu de donn´ee doivent s’ex´ecuter rigoureusement de la mˆeme mani`ere. De fait, il serait complexe de traiter des programmes utilisant des fonctions de tirages al´eatoires ou conservant des informations d’une ex´ecution sur l’autre (cache dans un fichier par exemple). Ce n’est pas le cas de gzip ce qui nous a permis de mener nos tests de fa¸con correcte sur ce programme. L’int´erˆet Un aspect tr`es int´eressant de l’utilisation de ce programme de v´erification est qu’il a permis d’affiner le programme d’´evaluation de la quantit´e de travail inutile. En effet, en observant les divergences entre la premi`ere et la seconde ex´ecution (figure 2.8), il a ´et´e possible de trouver les points faibles du programme d’´evaluation de la quantit´e de travail inutile et de les consolider afin de rendre les deux ex´ecutions ´equivalentes. Par exemple, lorsque la seconde ex´ecution du programme de test divergeait de la premi`ere (un branchement pris alors qu’il n’aurait pas du par exemple), cela signifiait qu’une instruction utile au flot de contrˆole avait ´etait jug´ee comme ´etant inutile `a tort. De cette mani`ere, il a ´et´e possible de trouver les incorrections du programme d’´evaluation du travail inutile et surtout de mettre en lumi`ere les lacunes d’une impl´ementation trop « na¨ıve » par rapport aux appels de fonctions d´efinies dans des biblioth`eques dont le code source n’est pas disponible. Dans l’exemple de la figure 2.8, le branchement be (branch if equal) est pris lors de la premi`ere ex´ecution alors qu’il n’est pas pris lors de la seconde, ce qui entraˆıne une incoh´erence entre les deux 38
  • 39.
    ex´ecutions qui nesont alors plus ´equivalentes. Ceci se produit en raison d’un mauvais jugement port´e sur l’instruction add. En effet, celle-ci est utile au bon d´eroulement du programme alors qu’elle a ´et´e jug´ee comme ne l’´etant pas par le programme de d´etection du travail inutile. Grˆace `a ce syst`eme, il est facile de corriger les incorrections et impr´ecisions que comporte le programme de d´etection du travail inutile. Par processus incr´emental, il est alors possible de corriger ces erreurs une `a une jusqu’`a l’obtention d’un programme qui ne juge inutile que du travail r´eellement inutile (Ce qui ne prouve pas pour autant qu’il d´etecte tout le travail inutile que peut comporter un programme). La M´ethode Pour pouvoir mettre au point ce deuxi`eme programme, il faut tout d’abord que le programme d’´evaluation de la quantit´e de travail inutile laisse une trace des instructions inutiles dans un fichier qui sera utilis´e par le programme de v´erification. Pour la mise au point de ce programme de v´erification, Salto a, l`a encore, ´et´e sollicit´e afin d’instrumenter chaque instruction. Lorsque cette instruction est jug´ee inutile (d’apr`es la trace de la premi`ere ex´ecution) alors cette instruction est saut´ee au moyen d’un jump d’une valeur constante puisque, dans les processeurs Sparc, toutes les instructions ont une taille de quatre octets (« merci » les jeux d’instructions RISC). De cette fa¸con, il est assez facile de « sauter » une instruction lorsqu’elle apparaˆıt dans la trace des instructions inutiles. Conclusion Ce programme `a permis, sur des exemples simples, de v´erifier que la quantit´e de travail inutile ´evalu´ee ´etait bien du travail inutile quelque soit le niveau d’optimisation utilis´e et les cas de figure rencontr´es. Cependant, par manque de temps, nous n’avons r´eussi `a le faire fonctionner que sur gzip compil´e avec un niveau d’optimisation de 0. N´eanmoins, cette v´erification nous `a permis d’accroˆıtre la confiance en nos r´esultats (figures 2.7 page 37 et 2.9 page suivante). 2.4.3 La r´epartition du travail inutile Une fois les informations sur le travail inutile lors de l’ex´ecution d’un programme r´ecup´er´ees, il est n´ecessaire de les organiser afin de pouvoir analyser d’ou provient ce travail inutile. Dans un premier temps, nous avons essay´e de voir `a quelles instructions statiques correspondaient nos instructions dynamiques inutiles. Nous avons trouv´e, sans surprise ´etant donn´e les r´esultats de l’article [1], que seul un petit nombre d’instructions statiques ´etaient concern´ees. Ce qui signifie que la plupart des instructions dynamiques inutiles sont concentr´ees sur un nombre d’instructions statiques r´eduit (de l’ordre de 12.4 % des instructions statiques totales g´en`erent au moins une instance dynamique inutile). Une fois ces instructions statiques en assembleur identifi´ees, nous avons cherch´e `a « remonter », lorsque c’´etait possible, au code source en C correspondant afin de mieux comprendre la raison pour laquelle ce travail est jug´e comme ´etant inutile par notre d´efinition. 39
  • 40.
    108876.0 90730.0 72584.0 54438.0 36292.0 18146.0 0.0 Compression d’un fichier RTF de 2127 octets Algorithme GZIP compile avec cc et un niveau d’optimisation de 0 0.0 268685.0 537370.0 806055.0 1074740.0 (a) Sans optimisations de compilation 53388.0 44490.0 35592.0 26694.0 17796.0 8898.0 0.0 Compression d’un fichier RTF de 2127 octets Algorithme GZIP compile avec cc et un niveau d’optimisation de 5 0.0 105695.0 211390.0 317085.0 422780.0 (b) Avec optimisations de compilation Abscisse : Num´ero d’instruction dynamiques : repr´esente le temps ´ecoul´e en nombre d’instructions Ordonn´ee : Quantit´e d’instructions inutiles (cumul´ees) Fig. 2.9 – ´Evolution de la quantit´e de travail inutile en fonction du temps Travail inutile algorithmique Introduction En observant les courbes de la figure 2.9 on s’aper¸coit que l’algorithme de gzip, dans nos conditions de test, se d´ecompose en plusieurs phases g´en´erant chacune des quantit´es de travail inutile diff´erentes. En premier lieu, il est int´eressant de noter que la phase d’initialisation comporte une grande proportion de travail inutile (presque 50 % avec un niveau d’optimisation de 0 et plus de 50 % avec un niveau de 5). Ensuite, vient une courte phase durant laquelle aucun travail inutile n’est pr´esent (quelque soit le niveau d’optimisation). Vient ensuite un ensemble de phases que nous appellerons le coeur de l’algorithme durant lequel on observe une quantit´e de travail inutile moyen non n´egligeable avec un niveau d’optimisation de 0 (de l’ordre de 6,9 %) et plus important encore avec un niveau d’optimisation de 5 (de l’ordre de 9,9 %). La phase d’initialisation Dans certains cas, le travail inutile semble ˆetre d’origine algorith-mique. En effet, en observant le code source en C de gzip, il apparaˆıt parfois du travail inutile qu’il serait simple d’´eviter en modifiant une petite partie du code. De fait, nous pouvons dire que ce travail inutile est inh´erent `a la fa¸con dont l’algorithme de gzip est impl´ement´e. De plus, ´etant donn´e une tr`es forte proportion de travail inutile durant la phase d’initialisation des structures de donn´ees utiles `a l’algorithme de gzip (aux alentours de 50%), il semble raisonnable de penser qu’une tr`es grande partie de ces structures de donn´ees sont initialis´ees puis jamais utilis´ees ou r´eutilis´ees pour ˆetre ´ecrites (ce qui g´en`ere des valeurs mortes). Ce type de travail inutile semble 40
  • 41.
    ˆetre r´eellement inh´erent`a l’algorithme et non du `a une mauvaise impl´ementation de celui-ci. Exemple simple : Dans la fonction local void gen codes (tree, max code), on observe que le code source suivant est inutile la plupart du temps (lors de l’ex´ecution sur un fichier de test) : for (bits = 1; bits <= MAX_BITS; bits++) { next_code[bits] = code = (code + bl_count[bits-1]) << 1; } L’affectation dans le tableau next code est inutile 52 fois sur 60 dans l’exemple test´e (le nombre d’it´erations de la boucle est MAX BITS et est ´egal `a 60). Le fait que cette affectation soit inutile un certain nombre de fois engendre qu’une partie des calculs fait dans la boucle devient inutile. Ce qui nous donne, pour l’ensemble de la boucle, un nombre de 824 instructions inutiles pour 1440 au total (soit une proportion de 57 %). Etant donn´e ces r´esultats, il est int´eressant de se pencher sur le cas de l’initialisation des structures de donn´ees en g´en´eral. En effet, le premier r´eflexe d’un programmeur, lorsqu’il d´eclare une structure de donn´ee (tableau, liste. . .) est de l’initialiser pour ´eviter, par la suite, d’y faire un acc`es en lecture sans y avoir pr´ealablement rang´e une valeur. Or ce r´eflexe de programmation est probablement ce que nous observons ici ´etant donn´e que les structures de donn´ees de gzip n’´echappent apparemment pas `a cette r`egle. Le coeur de l’algorithme Au coeur de l’algorithme, nous observons diff´erents cas d’instructions dynamiques inutiles. Parfois, nous observons que le travail inutile est du `a l’initialisation de va-riables locales dont le contenu est, la plupart du temps, r´e-´ecrit avant d’ˆetre lu. Parfois, il s’agit de param`etres pass´es `a une fonction et qui ne servent que dans certaines conditions. Et enfin, un cas assez fr´equent ´egalement est celui des variables globales qui sont maintenues `a jour de fa¸con inutile. En effet, si une telle variable refl`ete une valeur lors du dernier passage dans une certaine fonction, il est possible que cette fonction soit appel´ee plusieurs fois sans que cette valeur n’ai ´et´e lue entre temps. Exemples : L’affectation prev match = match start ; peut-ˆetre inutile car la seule utilisation de la variable prev match en lecture est le cas suivant : if (prev_length >= MIN_MATCH && match_length <= prev_length) { check_match(strstart-1, prev_match, prev_length); flush = ct_tally(strstart-1-prev_match, prev_length - MIN_MATCH); ... } 41
  • 42.
    Ce qui signifieque lorsque la condition ci-dessus sera fausse, l’affectation de la variable prev match sera inutile (c’est le cas 78 fois 82 dans notre test). En supposant que le calcul de la valeur de la variable match start puisse ˆetre coˆuteux, et que cette variable ne soit pas r´e-utilis´ee en lecture entre temps, on prend conscience de la port´ee que peut avoir le travail inutile. Note Dans l’exemple pr´ec´edent, il est int´eressant de noter que le d´eplacement de l’instruction d’affec-tation prev match = match start ; dans le corps de la conditionnelle aurait suffit `a ´eliminer ce travail inutile (dans la mesure o`u on ne fait pas d’´ecriture dans match start entre temps). En effet, la variable prev match n’´etant utilis´ee que dans ce bloc, il est inutile de faire cette affectation si la condition n’est pas vraie. Une macro un peu particuli`ere a ´egalement retenu notre attention. Elle se trouve au coeur de l’algorithme de compression, dans la fonction deflate(). Il s’agit de la macro INSERT STRING qui ins`ere une chaˆıne de caract`ere dans la liste des chaˆınes de caract`eres qu’utilise gzip pour trouver les chaˆınes les plus fr´equemment pr´esentes dans le fichier `a compresser. Voici le code de cette macro apr`es passage du pr´e-processeur : ((ins_h = (((ins_h)<<((15+3-1)/3)) ^ ( window[(strstart) + 3-1])) & ((unsigned)(1<<15)-1)), prev[(strstart) & (0x8000-1)] = hash_head = (prev+0x8000)[ins_h], (prev+0x8000)[ins_h] = (strstart)); Pour des raisons de lisibilit´e, nous avons r´e-´ecrit ce code : ins_h = ( ins_h<<5 ^ window[strstart+2] & (unsigned)(1<<15)-1 ); hash_head = (prev+0x8000)[ins_h]; prev[strstart & (0x8000-1)] = hash_head; (prev+0x8000)[ins_h] = strstart; Dans cette macro, qui se trouve au coeur de l’algorithme de compression, les deux derni`eres instructions (remplissage du tableau prev) se trouvent ˆetre tr`es souvent inutile (77 fois sur 82 dans notre exemple). Ceci tend `a montrer que l’algorithme de compression utilis´e par gzip contient, par nature, du travail inutile. Travail inutile introduit par le compilateur. . . . . . lors des phases d’optimisation de compilation On observe ´egalement que la version compil´ee avec un niveau d’optimisation de 0 pr´esente une quantit´e globale de travail inutile moins important (proportionnellement) `a la version compil´ee avec un niveau d’optimisation de 5. De plus, 42
  • 43.
    l’´ecart entre lesdeux versions s’accentue dans le coeur de l’algorithme. En effet, durant la phase d’initialisation, les deux versions se comportent `a peu pr`es de la mˆeme fa¸con (aux alentours de 50 % de travail inutile) alors que dans le coeur de l’ex´ecution, la version non optimis´ee comporte en moyenne 6.9 % de travail inutile `a comparer aux 9.9 % observ´e dans le cas de la version optimis´ee. Ce ph´enom`ene avait d´ej`a ´et´e constat´e dans l’article [1] mais uniquement au sujet des valeurs mortes. . . . du au jeu d’instruction du processeur Cette ´etude n’est absolument pas exhaustive sur les diverses causes que peut avoir le travail inutile. Cependant, mˆeme si cet aspect n’a pu ˆetre explor´e pour des raisons de temps, il parait raisonnable de penser qu’une partie du travail inutile pourrait avoir ´et´e introduit en raison des contraintes impos´ees par le jeu d’instructions utilis´e. En effet, dans un jeu d’instruction RISC (comme le Sparc) une instruction de haut niveau (en langage C par exemple) peut ˆetre convertie par le compilateur en une suite tr`es importante d’instructions comme en une seule. Ceci d´epend de l’´eloignement de cette instruction en langage C par rapport aux instructions disponibles dans le jeu d’instruction assembleur utilis´e. A contrario, un jeu d’instruction CISC (comme le x86) aura des instructions assembleur plus proche des instructions en langage de haut niveau. De cette fa¸con, les proportions d’instructions assembleur inutiles peuvent ne pas ˆetre identiques aux proportions d’instructions inutiles de haut niveau (en langage C par exemple). De plus, certaines optimisations de compilation effectuant un r´e-ordonnancement des instructions assembleur, il est parfois difficile de savoir quel ensemble d’instructions assembleur repr´esente quelle instruction de haut niveau. Conclusion En conclusion, nous pouvons dire que les proportions de travail inutile trouv´ees se rattachent majoritairement au travail inutile pr´esent dans l’algorithme en langage de haut niveau. De fait, une piste qui pourrait ˆetre int´eressante pour r´eduire ce travail inutile serait de signaler au programmeur, lors des premi`eres ex´ecutions d’un prototype de programme, que certaines parties de l’algorithme g´en`erent une grande quantit´e de travail inutile et que, par cons´equent, une r´e-´ecriture en prenant en compte cet ´etat de fait pourrait ´eviter ce travail. Il est mˆeme possible d’imaginer un outil proposant au programmeur une ´ebauche de solution pour l’aider `a restructurer une partie de son code afin d’´eviter ce travail inutile. Cependant, ce type d’outils ne peut rien pour aider `a ´eliminer le travail inutile intrins`eque `a l’algorithme. 43
  • 44.
    2.5 Conclusion Cette´etude est, en premier lieu, une ´etude permettant de comprendre un ph´enom`ene, `a priori, contre intuitif : Le travail inutile. Pour ce faire, nous nous sommes bas´e sur des r´esultats existants qui ont d´ej`a ´et´e publi´es et qui montre que le travail inutile existe bel et bien dans des programmes classiques. Le but de ce stage ´etait d’´elargir les d´efinitions donn´ees dans ces articles afin d’avoir une id´ee du travail inutile global qui peut se trouver dans un programme. Cette ´etude, contrairement `a celles cit´ees ci-contre, n’avait pas pour but de trouver un moyen d’exploiter ce travail inutile pour en r´eduire l’impact sur le temps d’ex´ecution ou la consommation ´electrique mais seulement de comprendre ce ph´enom`ene et de savoir pourquoi ce travail inutile est pr´esent (est-ce du au compilateur ?, au programmeur ?. . .). En conclusion, nous pouvons dire que cette ´etude `a permis de confirmer l’existence du travail inutile et de comprendre, en partie, d’o`u il provient. 44
  • 45.
    Bibliographie [1] G.Sohi A. Butt. Dynamic dead-instruction detection and elimination. ASPLOS X, October 2002. [2] Jeffrey D. Ullman Alfred V. Aho, Ravi Sethi. Compilers : Principles, Techniques and Tools. Addison-Wesley, 1986. [3] Gordon B. Bell. Characterization of silent stores. Submitted in partial fulfillment of the M.S. Degree in Electrical and Computer Engineering, May 2001. [4] F. Bodin. Cours d’optimisation : Transformer pour la performance. Septembre 2002. [5] M. Lipasti K. Lepak, G. Bell. Silent stores and store value locality. IEEE Transactions on Computers, 50(11), November 2001. [6] Mikko H. Lipasti Kevin M. Lepak. Temporally silent stores. ASPLOS X, October 2002. [7] Kevin M. Lepak. Silent stores for free : Reducing the cost of store verification. Submitted in partial fulfillment of the M.S. Degree in Electrical and Computer Engineering, December 2000. [8] Charles N. Fischer Milo M. Martin, Amir Roth. Exploiting dead value information. Proceedings of Micro-30, December 1997. 45
  • 46.
    Chapitre 3 Annexes 3.1 Petit historique du stage. . . 11/2002 : Elaboration de la bibliographie : Recherche et lecture d’articles sur le travail inutile. 12/2002 : Elaboration de la bibliographie : R´edaction du rapport bibliographique et r´eflexion autour de la partie personnelle `a ajouter (Troisi`eme approche du chapitre Bibliographie). 02/2003 : D´ebut du stage : Prise en main de l’environnement de travail (C++, gcc, cc, concept g´en´eraux de compilation. . .) et de l’outils Salto mis `a disposition par l’´equipe. 03/2003 : Elaboration du squelette de l’application d’´evaluation du travail inutile et mise au point d’un programme simple permettant de construire le graphe de d´ependance de donn´ee en utilisant l’outil Salto. 04/2003 : Impl´ementation et test sur gzip du programme d’´evaluation de la quantit´e de travail inutile et mise au point du programme de v´erification que ce travail est bien inutile. 05/2003 : Test du protocole, optimisation, collecte des r´esultats, et ´ecriture du rapport de stage. 06/2003 : Fin du rapport et pr´esentation orale du travail effectu´e en stage 46
  • 47.
    3.2 A proposde la description machine Salto du Sparc 3.2.1 Gestion des instructions Save et Restore Les instructions save et restore du Sparc poss`edent trois arguments (g´en´eralement save %sp,constante,%sp et restore %g0,%g0,%g0). Ces deux instructions font glisser la fenˆetre de registre dans le sens positif pour save (cr´eation d’un nouveau contexte) et dans le sens n´egatif pour restore (restauration de l’ancien contexte). De plus, ces instructions se comportent comme l’instruction add `a un d´etail pr`es : les registres lus (deux premiers arguments) sont lus dans l’an-cienne fenˆetre de registres alors que le registre ´ecrit (troisi`eme argument) est ´ecrit dans la nouvelle fenˆetre (apr`es la cr´eation ou la restauration du contexte). Or la description machine Salto du Sparc consid`ere que les trois registres pass´es en argument des instructions save et restore sont lus alors que le troisi`eme est ´ecrit (erreur invisible dans la mesure ou save et restore ´ecrivent aussi tous les registres de la fenˆetre. . . sauf lorsqu’on utilise comme troisi`eme argument un registre n’appartenant pas `a la fenˆetre (registre global par exemple)). 3.2.2 L’instruction call & link L’instruction call sert `a faire un appel de fonction. Cette instruction effectue deux actions : elle branche `a l’adresse pass´ee en param`etre (constante sur 30 bits) et elle sauvegarde la valeur du PC au moment de son ex´ecution dans le registre %o7 (registre 15 dans le manuel du Sparc), permettant ainsi `a la fonction appel´ee de revenir `a la suite dans le code source une fois la fonction ex´ecut´ee au moyen de l’instruction ret. Or cette deuxi`eme fonction n’est pas vue par la description machine Salto du Sparc. Il faut donc rajouter une ´ecriture dans le registre %o7 dans la d´efinition de l’instruction call pour la rendre correcte. 3.2.3 L’instruction addx L’instruction addx `a le mˆeme effet que l’instruction add `a ceci prˆet qu’elle utilise le registre des codes conditions pour ajouter, si elle est positionn´ee, la retenue de celui-ci (bit carry) au r´esultat de l’addition. Or dans la description machine Salto du Sparc, l’instruction addx est vue comme ´etant ´equivalente `a l’instruction addcc qui, elle, positionne le bit carry en fonction des arguments pass´es `a l’instruction addcc. Donc, l’instruction addx lit le registre des codes condition alors que l’instruction addcc le modifie. L’instruction addx n’est utilis´ee par les compilateurs que lorsqu’on active les optimisations de compilation. 3.2.4 Un d´etail : les instructions nop, ba et bn D’apr`es la description machine Salto du Sparc, l’instruction nop (No op´eration) consommerait un registre en entr´ee (dont le num´ero d’identification dans la description machine est 39). Or, d’apr`es la documentation sur le Sparc, l’instruction nop ne fait aucune action donc elle ne devrait 47
  • 48.
    pas utiliser deressources en entr´ee. De plus, les instructions ba (Branch always) et bn (Branch never) utilisent, d’apr`es la description machine du Sparc, le registre des codes condition en entr´ee. Or, si les instructions de branchement conditionnel utilisent en effet le registre des codes condition, les instructions ba et bn peuvent ˆetre consid´er´ees comme des instructions de branchement incon-ditionnel puisque le fait que le branchement ai lieu ne d´epend pas des bits activ´es dans le registre des codes condition. 48
  • 49.
    3.3 R´esultat del’´evaluation du travail inutile sur un exemple simple 3.3.1 Code source en C de l’exemple 1 #include <stdio.h> 2 3 4 int main(void) 5 { 6 int tab[3],i; 7 8 9 for(i=0; i<3; i++) tab[i]=i*10; 10 11 printf("%d",tab[1]); 12 13 exit(0); 14 } 3.3.2 Code source en assembleur Sparc de l’exemple Ce code est g´en´er´e par le compilateur cc avec les options ”-SO” et ”-XO0” (niveau d’optimisation ´egal `a 0). ! FILE exemple.c ! 1 !#include <stdio.h> ! 4 !int main(void) ! 5 !{ ! ! SUBROUTINE main ! ! OFFSET SOURCE LINE LABEL INSTRUCTION .global main main: /* 000000 5 */ save %sp,-120,%sp ! 6 ! int tab[3],i; ! 9 ! for(i=0; i<3; i++) tab[i]=i*10; 49
  • 50.
    .L90: /* 0x00049 */ or %g0,0,%g2 /* 0x0008 */ st %g2,[%fp-20] /* 0x000c */ ld [%fp-20],%g3 /* 0x0010 */ cmp %g3,3 /* 0x0014 */ bl .L95 /* 0x0018 */ nop /* 0x001c */ ba .L94 /* 0x0020 */ nop .L95: .L92: /* 0x0024 9 */ ld [%fp-20],%g2 /* 0x0028 */ sll %g2,2,%g3 /* 0x002c */ add %g3,%g2,%g4 /* 0x0030 */ sll %g4,1,%o2 /* 0x0034 */ ld [%fp-20],%o3 /* 0x0038 */ sll %o3,2,%o4 /* 0x003c */ add %fp,-16,%o3 /* 0x0040 */ st %o2,[%o4+%o3] ! volatile /* 0x0044 */ ld [%fp-20],%o4 /* 0x0048 */ add %o4,1,%o5 /* 0x004c */ st %o5,[%fp-20] /* 0x0050 */ ld [%fp-20],%o7 /* 0x0054 */ cmp %o7,3 /* 0x0058 */ bl .L92 /* 0x005c */ nop /* 0x0060 */ ba .L96 /* 0x0064 */ nop .L96: ! 10 ! printf("%dn",tab[1]); .L94: /* 0x0068 10 */ sethi %hi(.L97),%g2 /* 0x006c */ add %g2,%lo(.L97),%g3 /* 0x0070 */ or %g0,%g3,%g4 /* 0x0074 */ or %g0,%g4,%o0 /* 0x0078 */ ld [%fp-12],%o2 /* 0x007c */ or %g0,%o2,%o1 /* 0x0080 */ call printf ! params = %o0 %o1 ! Result = /* 0x0084 */ nop ! 11 ! ! 12 ! exit(0); /* 0x0088 12 */ or %g0,0,%o3 /* 0x008c */ or %g0,%o3,%o0 /* 0x0090 */ call exit ! params = %o0 ! Result = 50
  • 51.
    /* 0x0094 */nop /* 0x0098 */ ba .L89 /* 0x009c */ nop .L89: /* 0x00a0 */ ret ! Result = /* 0x00a4 */ restore %g0,%g0,%g0 /* 0x00a8 0 */ .type main,2 /* 0x00a8 0 */ .size main,(.-main) /* 0x00a8 0 */ .global __fsr_init_value /* 0x00a8 */ __fsr_init_value=0 3.3.3 Identifiant d’instruction statique Ce fichier texte est obtenu apr`es passage de Salto sur le code source en assembleur de notre exemple. Le num´ero statique permettant d’identifier une instruction se trouve entre crochets. Salto rev. 1.4.2beta1 (built with g++ on sun4u) Copyright (C) 1997 Inria, France Machine description file ok. CFG (0) : BB (0) : INST (0) [1] : save %o6,-120,%o6 in : 55 out : 55 BB (1) : INST (0) [2] : or %g0,0,%g2 in : 41 out : 43 INST (1) [3] : st %g2,[%i6-20] in : 71 in : 43 out : 113 INST (2) [4] : ld [%i6-20],%g3 in : 71 in : 113 out : 44 INST (3) [5] : subcc %g3,3,%g0 in : 44 out : 74 out : 41 INST (4) [6] : bl .L95 in : 74 BB (2) : INST (0) [7] : nop in : 39 INST (1) [8] : ba .L94 in : 74 BB (3) : INST (0) [9] : nop in : 39 BB (4) : INST (0) [10] : ld [%i6-20],%g2 in : 71 in : 113 out : 43 INST (1) [11] : sll %g2,2,%g3 in : 43 out : 44 INST (2) [12] : add %g3,%g2,%g4 in : 44 in : 43 out : 45 INST (3) [13] : sll %g4,1,%o2 in : 45 out : 51 INST (4) [14] : ld [%i6-20],%o3 in : 71 in : 113 out : 52 INST (5) [15] : sll %o3,2,%o4 in : 52 out : 53 INST (6) [16] : add %i6,-16,%o3 in : 71 out : 52 INST (7) [17] : st %o2,[%o4+%o3] in : 53 in : 52 in : 51 out : 113 INST (8) [18] : ld [%i6-20],%o4 in : 71 in : 113 out : 53 INST (9) [19] : add %o4,1,%o5 in : 53 out : 54 51
  • 52.
    INST (10) [20]: st %o5,[%i6-20] in : 71 in : 54 out : 113 INST (11) [21] : ld [%i6-20],%o7 in : 71 in : 113 out : 56 INST (12) [22] : subcc %o7,3,%g0 in : 56 out : 74 out : 41 INST (13) [23] : bl .L92 in : 74 BB (5) : INST (0) [24] : nop in : 39 INST (1) [25] : ba .L96 in : 74 BB (6) : INST (0) [26] : nop in : 39 BB (7) : INST (0) [27] : sethi %hi(.L97),%g2 out : 43 INST (1) [28] : add %g2,%lo(.L97),%g3 in : 43 out : 44 INST (2) [29] : or %g0,%g3,%g4 in : 41 in : 44 out : 45 INST (3) [30] : or %g0,%g4,%o0 in : 41 in : 45 out : 49 INST (4) [31] : ld [%i6-12],%o2 in : 71 in : 113 out : 51 INST (5) [32] : or %g0,%o2,%o1 in : 41 in : 51 out : 50 BB (8) : INST (0) [33] : call printf out : 56 BB (9) : INST (0) [34] : nop in : 39 INST (1) [35] : or %g0,0,%o3 in : 41 out : 52 INST (2) [36] : or %g0,%o3,%o0 in : 41 in : 52 out : 49 BB (10) : INST (0) [37] : call exit out : 56 BB (11) : INST (0) [38] : nop in : 39 INST (1) [39] : ba .L89 in : 74 BB (12) : INST (0) [40] : nop in : 39 BB (13) : INST (0) [41] : jmpl %i7+8,%g0 in : 72 out : 41 BB (14) : INST (0) [42] : restore %g0,%g0,%g0 in : 41 in : 41 out : 41 3.3.4 Trace d’ex´ecution dynamique La trace d’ex´ecution dynamique de ce programme de test est donn´ee par le programme d’´evaluation de la quantit´e de travail inutile. I.Dynamic 66 -- I.Static 38 -- File 1 -- nop -- D´epend de 0 I.Dynamic 65 -- I.Static 37 -- File 1 -- utile -- D´epend de 64 I.Dynamic 64 -- I.Static 36 -- File 1 -- utile -- D´epend de 63 I.Dynamic 63 -- I.Static 35 -- File 1 -- utile I.Dynamic 62 -- I.Static 34 -- File 1 -- nop -- D´epend de 0 I.Dynamic 61 -- I.Static 33 -- File 1 -- utile -- D´epend de 58,60 52
  • 53.
    I.Dynamic 60 --I.Static 32 -- File 1 -- utile -- D´epend de 59 I.Dynamic 59 -- I.Static 31 -- File 1 -- utile -- D´epend de 0,30,30,30,30 I.Dynamic 58 -- I.Static 30 -- File 1 -- utile -- D´epend de 57 I.Dynamic 57 -- I.Static 29 -- File 1 -- utile -- D´epend de 56 I.Dynamic 56 -- I.Static 28 -- File 1 -- utile -- D´epend de 55 I.Dynamic 55 -- I.Static 27 -- File 1 -- utile I.Dynamic 54 -- I.Static 26 -- File 1 -- nop -- D´epend de 0 I.Dynamic 53 -- I.Static 25 -- File 1 -- utile -- D´epend de 50 I.Dynamic 52 -- I.Static 24 -- File 1 -- nop -- D´epend de 0 I.Dynamic 51 -- I.Static 23 -- File 1 -- utile -- D´epend de 50 I.Dynamic 50 -- I.Static 22 -- File 1 -- utile -- D´epend de 49 I.Dynamic 49 -- I.Static 21 -- File 1 -- utile -- D´epend de 0,48,48,48,48 I.Dynamic 48 -- I.Static 20 -- File 1 -- utile -- D´epend de 0,47 I.Dynamic 47 -- I.Static 19 -- File 1 -- utile -- D´epend de 46 I.Dynamic 46 -- I.Static 18 -- File 1 -- utile -- D´epend de 0,33,33,33,33 I.Dynamic 45 -- I.Static 17 -- File 1 -- INUTILE -- D´epend de 43,44,41 I.Dynamic 44 -- I.Static 16 -- File 1 -- INUTILE -- D´epend de 0 I.Dynamic 43 -- I.Static 15 -- File 1 -- INUTILE -- D´epend de 42 I.Dynamic 42 -- I.Static 14 -- File 1 -- INUTILE -- D´epend de 0,33,33,33,33 I.Dynamic 41 -- I.Static 13 -- File 1 -- INUTILE -- D´epend de 40 I.Dynamic 40 -- I.Static 12 -- File 1 -- INUTILE -- D´epend de 39,38 I.Dynamic 39 -- I.Static 11 -- File 1 -- INUTILE -- D´epend de 38 I.Dynamic 38 -- I.Static 10 -- File 1 -- INUTILE -- D´epend de 0,33,33,33,33 I.Dynamic 37 -- I.Static 24 -- File 1 -- nop -- D´epend de 0 I.Dynamic 36 -- I.Static 23 -- File 1 -- utile -- D´epend de 35 I.Dynamic 35 -- I.Static 22 -- File 1 -- utile -- D´epend de 34 I.Dynamic 34 -- I.Static 21 -- File 1 -- utile -- D´epend de 0,33,33,33,33 I.Dynamic 33 -- I.Static 20 -- File 1 -- utile -- D´epend de 0,32 I.Dynamic 32 -- I.Static 19 -- File 1 -- utile -- D´epend de 31 I.Dynamic 31 -- I.Static 18 -- File 1 -- utile -- D´epend de 0,18,18,18,18 I.Dynamic 30 -- I.Static 17 -- File 1 -- utile -- D´epend de 28,29,26 I.Dynamic 29 -- I.Static 16 -- File 1 -- utile -- D´epend de 0 I.Dynamic 28 -- I.Static 15 -- File 1 -- utile -- D´epend de 27 I.Dynamic 27 -- I.Static 14 -- File 1 -- utile -- D´epend de 0,18,18,18,18 I.Dynamic 26 -- I.Static 13 -- File 1 -- utile -- D´epend de 25 I.Dynamic 25 -- I.Static 12 -- File 1 -- utile -- D´epend de 24,23 I.Dynamic 24 -- I.Static 11 -- File 1 -- utile -- D´epend de 23 I.Dynamic 23 -- I.Static 10 -- File 1 -- utile -- D´epend de 0,18,18,18,18 I.Dynamic 22 -- I.Static 24 -- File 1 -- nop -- D´epend de 0 I.Dynamic 21 -- I.Static 23 -- File 1 -- utile -- D´epend de 20 I.Dynamic 20 -- I.Static 22 -- File 1 -- utile -- D´epend de 19 I.Dynamic 19 -- I.Static 21 -- File 1 -- utile -- D´epend de 0,18,18,18,18 I.Dynamic 18 -- I.Static 20 -- File 1 -- utile -- D´epend de 0,17 I.Dynamic 17 -- I.Static 19 -- File 1 -- utile -- D´epend de 16 I.Dynamic 16 -- I.Static 18 -- File 1 -- utile -- D´epend de 0,3,3,3,3 I.Dynamic 15 -- I.Static 17 -- File 1 -- INUTILE -- D´epend de 13,14,11 I.Dynamic 14 -- I.Static 16 -- File 1 -- INUTILE -- D´epend de 0 I.Dynamic 13 -- I.Static 15 -- File 1 -- INUTILE -- D´epend de 12 53
  • 54.
    I.Dynamic 12 --I.Static 14 -- File 1 -- INUTILE -- D´epend de 0,3,3,3,3 I.Dynamic 11 -- I.Static 13 -- File 1 -- INUTILE -- D´epend de 10 I.Dynamic 10 -- I.Static 12 -- File 1 -- INUTILE -- D´epend de 9,8 I.Dynamic 9 -- I.Static 11 -- File 1 -- INUTILE -- D´epend de 8 I.Dynamic 8 -- I.Static 10 -- File 1 -- INUTILE -- D´epend de 0,3,3,3,3 I.Dynamic 7 -- I.Static 7 -- File 1 -- nop -- D´epend de 0 I.Dynamic 6 -- I.Static 6 -- File 1 -- utile -- D´epend de 5 I.Dynamic 5 -- I.Static 5 -- File 1 -- utile -- D´epend de 4 I.Dynamic 4 -- I.Static 4 -- File 1 -- utile -- D´epend de 0,3,3,3,3 I.Dynamic 3 -- I.Static 3 -- File 1 -- utile -- D´epend de 0,2 I.Dynamic 2 -- I.Static 2 -- File 1 -- utile I.Dynamic 1 -- I.Static 1 -- File 1 -- utile -- D´epend de 0 I.Dynamic 0 -- I.Static 0 -- File 0 -- utile Table des registres fixes : Registre 39 : Modifi´e par l’instruction 0 et lu depuis par 7 instruction(s) Registre 43 : Modifi´e par l’instruction 55 et lu depuis par 1 instruction(s) Registre 44 : Modifi´e par l’instruction 56 et lu depuis par 1 instruction(s) Registre 45 : Modifi´e par l’instruction 57 et lu depuis par 1 instruction(s) Registre 74 : Modifi´e par l’instruction 50 et lu depuis par 2 instruction(s) Table des registres tournants (niveau actuel : 1) : Registre 17 : Modifi´e par l’instruction 0 et lu depuis par 22 instruction(s) Registre 32 : Modifi´e par l’instruction 65 et lu depuis par 0 instruction(s) Registre 33 : Modifi´e par l’instruction 1 et lu depuis par 0 instruction(s) Registre 34 : Modifi´e par l’instruction 47 et lu depuis par 1 instruction(s) Registre 35 : Modifi´e par l’instruction 46 et lu depuis par 1 instruction(s) Registre 36 : Modifi´e par l’instruction 63 et lu depuis par 1 instruction(s) Registre 37 : Modifi´e par l’instruction 59 et lu depuis par 1 instruction(s) Registre 38 : Modifi´e par l’instruction 60 et lu depuis par 1 instruction(s) Registre 39 : Modifi´e par l’instruction 64 et lu depuis par 1 instruction(s) Liste des adresses m´emoires : Adresse -4261820 : Modifi´e par l’instruction 48 et lu depuis par 13 instruction(s) Adresse -4261819 : Modifi´e par l’instruction 48 et lu depuis par 1 instruction(s) Adresse -4261818 : Modifi´e par l’instruction 48 et lu depuis par 1 instruction(s) Adresse -4261817 : Modifi´e par l’instruction 48 et lu depuis par 1 instruction(s) Adresse -4261816 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) Adresse -4261815 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) Adresse -4261814 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) Adresse -4261813 : Modifi´e par l’instruction 15 et lu depuis par 0 instruction(s) Adresse -4261812 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) Adresse -4261811 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) Adresse -4261810 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) Adresse -4261809 : Modifi´e par l’instruction 30 et lu depuis par 1 instruction(s) Adresse -4261808 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) Adresse -4261807 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) Adresse -4261806 : Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) 54
  • 55.
    Adresse -4261805 :Modifi´e par l’instruction 45 et lu depuis par 0 instruction(s) Calcul prenant en compte les "nop" : Nombre d’instructions inutiles : 16/66 soit 24.242424 % Nombre d’instructions utiles : 44/66 soit 66.666664 % Nombre de nop : 6/66 soit 9.090909 % Calcul ne prenant pas en compte les "nop" : Nombre d’instructions inutiles : 16/60 soit 26.666666 % Nombre d’instructions utiles : 44/60 soit 73.333336 % Nombre d’ocurences d’instructions inutiles pour une instruction statique : 1 : 0/1 2 : 0/1 3 : 0/1 4 : 0/1 5 : 0/1 6 : 0/1 7 : 0/1 8 : 9 : 10 : 2/3 11 : 2/3 12 : 2/3 13 : 2/3 14 : 2/3 15 : 2/3 16 : 2/3 17 : 2/3 18 : 0/3 19 : 0/3 20 : 0/3 21 : 0/3 22 : 0/3 23 : 0/3 24 : 0/3 25 : 0/1 26 : 0/1 27 : 0/1 28 : 0/1 29 : 0/1 30 : 0/1 31 : 0/1 32 : 0/1 33 : 0/1 34 : 0/1 55
  • 56.
    35 : 0/1 36 : 0/1 37 : 0/1 38 : 0/1 3.3.5 Graphe de d´ependance de donn´ee Les noeuds de ce graphe sont ´etiquet´es avec les num´eros dynamiques des instructions. Les noeuds en gris repr´esente les instructions inutiles alors que les noeuds en noir repr´esente les instructions utiles. 65 64 1 63 0 61 58 60 57 59 30 28 29 26 53 56 55 50 49 51 48 47 46 45 43 33 32 44 41 42 40 39 38 36 35 34 31 18 17 27 25 24 23 21 20 19 16 3 2 15 13 14 11 12 10 9 8 6 5 4 Fig. 3.1 – Graphe d’exemple g´en´er´e par l’utilitaire « dot » 56
  • 57.
    3.3.6 Trace d’ex´ecutiondynamique 2 La trace d’ex´ecution dynamique de ce programme est donn´ee par le programme de v´erification que ce travail est bien inutile (Seconde ex´ecution avec le mˆeme jeu de donn´ees). I.Dynamic 1 -- I.Static 1 -- File 1 I.Dynamic 2 -- I.Static 2 -- File 1 I.Dynamic 3 -- I.Static 3 -- File 1 I.Dynamic 4 -- I.Static 4 -- File 1 I.Dynamic 5 -- I.Static 5 -- File 1 I.Dynamic 6 -- I.Static 6 -- File 1 -- Branch I.Dynamic 7 -- I.Static 7 -- File 1 I.Dynamic 8 -- I.Static 10 -- File 1 -- Non ex´ecut´ee I.Dynamic 9 -- I.Static 11 -- File 1 -- Non ex´ecut´ee I.Dynamic 10 -- I.Static 12 -- File 1 -- Non ex´ecut´ee I.Dynamic 11 -- I.Static 13 -- File 1 -- Non ex´ecut´ee I.Dynamic 12 -- I.Static 14 -- File 1 -- Non ex´ecut´ee I.Dynamic 13 -- I.Static 15 -- File 1 -- Non ex´ecut´ee I.Dynamic 14 -- I.Static 16 -- File 1 -- Non ex´ecut´ee I.Dynamic 15 -- I.Static 17 -- File 1 -- Non ex´ecut´ee I.Dynamic 16 -- I.Static 18 -- File 1 I.Dynamic 17 -- I.Static 19 -- File 1 I.Dynamic 18 -- I.Static 20 -- File 1 I.Dynamic 19 -- I.Static 21 -- File 1 I.Dynamic 20 -- I.Static 22 -- File 1 I.Dynamic 21 -- I.Static 23 -- File 1 -- Branch I.Dynamic 22 -- I.Static 24 -- File 1 I.Dynamic 23 -- I.Static 10 -- File 1 I.Dynamic 24 -- I.Static 11 -- File 1 I.Dynamic 25 -- I.Static 12 -- File 1 I.Dynamic 26 -- I.Static 13 -- File 1 I.Dynamic 27 -- I.Static 14 -- File 1 I.Dynamic 28 -- I.Static 15 -- File 1 I.Dynamic 29 -- I.Static 16 -- File 1 I.Dynamic 30 -- I.Static 17 -- File 1 I.Dynamic 31 -- I.Static 18 -- File 1 I.Dynamic 32 -- I.Static 19 -- File 1 I.Dynamic 33 -- I.Static 20 -- File 1 I.Dynamic 34 -- I.Static 21 -- File 1 I.Dynamic 35 -- I.Static 22 -- File 1 I.Dynamic 36 -- I.Static 23 -- File 1 -- Branch I.Dynamic 37 -- I.Static 24 -- File 1 I.Dynamic 38 -- I.Static 10 -- File 1 -- Non ex´ecut´ee I.Dynamic 39 -- I.Static 11 -- File 1 -- Non ex´ecut´ee I.Dynamic 40 -- I.Static 12 -- File 1 -- Non ex´ecut´ee I.Dynamic 41 -- I.Static 13 -- File 1 -- Non ex´ecut´ee 57
  • 58.
    I.Dynamic 42 --I.Static 14 -- File 1 -- Non ex´ecut´ee I.Dynamic 43 -- I.Static 15 -- File 1 -- Non ex´ecut´ee I.Dynamic 44 -- I.Static 16 -- File 1 -- Non ex´ecut´ee I.Dynamic 45 -- I.Static 17 -- File 1 -- Non ex´ecut´ee I.Dynamic 46 -- I.Static 18 -- File 1 I.Dynamic 47 -- I.Static 19 -- File 1 I.Dynamic 48 -- I.Static 20 -- File 1 I.Dynamic 49 -- I.Static 21 -- File 1 I.Dynamic 50 -- I.Static 22 -- File 1 I.Dynamic 51 -- I.Static 23 -- File 1 -- Branch I.Dynamic 52 -- I.Static 24 -- File 1 I.Dynamic 53 -- I.Static 25 -- File 1 -- Branch I.Dynamic 54 -- I.Static 26 -- File 1 I.Dynamic 55 -- I.Static 27 -- File 1 I.Dynamic 56 -- I.Static 28 -- File 1 I.Dynamic 57 -- I.Static 29 -- File 1 I.Dynamic 58 -- I.Static 30 -- File 1 I.Dynamic 59 -- I.Static 31 -- File 1 I.Dynamic 60 -- I.Static 32 -- File 1 I.Dynamic 61 -- I.Static 33 -- File 1 -- Branch I.Dynamic 62 -- I.Static 34 -- File 1 I.Dynamic 63 -- I.Static 35 -- File 1 I.Dynamic 64 -- I.Static 36 -- File 1 I.Dynamic 65 -- I.Static 37 -- File 1 -- Branch I.Dynamic 66 -- I.Static 38 -- File 1 58
  • 59.
    3.4 Exemple dedonn´ees stock´ees en cours d’ex´ecution Instruction dynamique : Instruction statique : Numéro de fichier : Type de l'instruction : n mf add Instruction dynamique : Instruction statique : Numéro de fichier : Type de l'instruction : n+1 m+1 f store Instruction dynamique : Instruction statique : Numéro de fichier : Type de l'instruction : n+2 m’ f bra Instruction dynamique : Instruction statique : Numéro de fichier : Type de l'instruction : n+3 m’+1 f load Instruction dynamique : Instruction statique : Numéro de fichier : Type de l'instruction : n+4 m’+2 f mul Liste chainée des instructions dynamiques (représente l'ordre d'exécution) %g0 %g1 %g2 %g3 %g4 %g5 %g6 %g7 Table des registres globaux %o0 %o1 %o2 %o3 %o4 %o5 %o6 %o7 %l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7 %o0 %o1 %o2 %o3 %o4 %o5 %o6 %o7 %i0 %i1 %i2 %i3 %i4 %i5 %i6 %i7 %l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7 Table de la fenêtre de registres %i0 %i1 %i2 %i3 %i4 %i5 %i6 %i7 0x0000 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 … Table des adresses mémoires Pointeur sur l’instruction dynamique précédente (structure de la liste chainée) Pointeur sur les instructions ayant produits les opérandes de l’instruction Pointeur sur la dernière instruction ayant écrit dans la ressource Fig. 3.2 – Les structures de donn´ees utilis´ees par le programme pour construire le graphe de d´ependance de donn´ee 59
  • 60.
    3.5 Code sourcedu programme 60
  • 61.
    12 mai 0313:40 instrumentation.h Page 1/2 /* Défini le nom du fichier contenant les fonction définies localement par rappo rt au programme (pas dans une bibliothèques) */ #define NOM_FICHIER_FONCTIONS_INTERNES "fonctions.txt" /* Défini le nom du fichier contenant les fonction définies hors du programme (b ibliothèques) mais non redéfinies dans le fichier redefinition.c */ #define NOM_FICHIER_FONCTIONS_EXTERNES_SPECIALES "fonctions_externes_speciales.txt" void appelDeFonctionInstDebut(BB *bb, int position, unsigned int *nbInstructions Ajoutees, int numeroInst, int typeInst, char *chaineAnnulBit); void appelDeFonctionInstMilieu(BB *bb, int position, unsigned int *nbInstruction sAjoutees, char *chaineAnnulBit); void appelDeFonctionInstFin(BB *bb, int position, unsigned int *nbInstructionsAj outees, char *chaineAnnulBit); void appelDeFonctionReg(BB *bb, int position, unsigned int *nbInstructionsAjoute es, int es, int indentificateurRessource, char *chaineAnnulBit); int tailleAccesMemoire(INST *inst); void appelDeFonctionMem(BB *bb, INST *inst, int position, unsigned int *nbInstru ctionsAjoutees, int es, char *acces1, char *acces2, char *chaineAnnulBit, int ta illeAccesMemoire); void appelDeFonctionCopierInstDelay(BB *bb, int position, unsigned int *nbInstru ctionsAjoutees); void appelDeFonctionEchangerInstDelay(BB *bb, int position, unsigned int *nbInst ructionsAjoutees); int rechercheChaine(char *chaine, FILE *fichier); int estPresent(char *motif, char *chaine); int typeInstruction(INST *inst); void operandeMemoire(char *instruction, char *acces1, char *acces2); // Fonction permettant de sauvagarder le contexte du programme (registres généra ux et codes conditions) afin de ne pas // intervenir sur les valeurs des registres et codes conditions utilisés par le programme void sauvegardeContexte(BB *bb, int position, unsigned int *nbInstructionsAjoute es); // Fonction permettant de restaurer le contexte du programme (registres généraux et codes conditions) afin de ne pas // intervenir sur les valeurs des registres et codes conditions utilisés par le programme void restaurationContexte(BB *bb, int position, unsigned int *nbInstructionsAjou tees); int registreSalto(char *acces); void blancSuivant(char **ptr); // Insère le code permettant d’instrumenter en fonction des entrées produites pa r inst void appelDeFonctionsEntrees(INST *inst, BB *bb, int position, unsigned int *nbI nstructionsAjoutees, char *chaineAnnulBit); // Ajout de l’instrumentation représentant les entrées fictives des call externe s void appelDeFonctionsEntreesAppelExterne(INST *inst, BB *bb, int position, unsig ned int *nbInstructionsAjoutees); // Insère le code permettant d’instrumenter en fonction des sorties produites pa r inst void appelDeFonctionsSorties(INST *inst, BB *bb, int position, unsigned int *nbI nstructionsAjoutees, char *chaineAnnulBit); // Ajout de l’instrumentation représentant les sorties fictives des call externe s void appelDeFonctionsSortiesAppelExterne(INST *inst, BB *bb, int position, unsig ned int *nbInstructionsAjoutees); void instrumenter(INST *inst, BB *bb, BB *bbSuivant, int position, unsigned int *nbInstructionsAjoutees, int numeroInst); void ajouterCommentaireAppelExterne(INST *inst); void Salto_hook(); 12 mai 03 13:40 instrumentation.h Page 2/2 void Salto_init_hook(int argc, char *argv[]); void Salto_end_hook(); Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.h 1/37
  • 62.
    03 jun 0316:11 instrumentation.cc Page 1/23 #include <stdio.h> #include <fcntl.h> #include <errno.h> #include <fstream> #include <iostream> #include <sys/types.h> #include <regex.h> #include <stdlib.h> #include <string.h> #include "salto.h" #include "instrumentation.h" #include "instrument.h" #define FICHIER_SOURCE_ASSEMBLEUR ".s$" #define REPERTOIRE_FICHIER_INSTRUMENTES "instrumente/" #define IN 0 #define OUT 1 #define EXP_REGISTRE "^%[golisf][0−7p]$" #define EXP_MEMOIRE "[[−+%a−zA−Z0−9_.()]+]" #define EXP_FONCTION "[ ]+call[ ]+" #define EXP_SAVE "[ ]+save[ ]+" #define EXP_RESTORE "[ ]+restore[ ]+" #define EXP_ANNUL_BIT ",a" #define EXP_LOAD "ld[usd]?[bh]?" #define EXP_STORE "st[bhd]?" #define ETIQUETTE_FONCTION_NOP "f_nop" #define PREFIXE_FONCTIONS_EXTERNES "my_" #define SAVE "save %sp,−136,%sp" #define RESTORE "restore %g0,%g0,%g0" #define NOP "nop" // Pointeur sur le fichier dans lequel sera écrit le code instrumenté FILE *fichierSInstrumente; // Pointeur sur le fichier contenant le code original (non instrumenté) FILE *fichierSOriginal; // Permet d’identifier de manière unique le fichier en cours de traitement unsigned char numeroFichier; void appelDeFonctionInstDebut(BB *bb, int position, unsigned int *nbInstructions Ajoutees, int numeroInst, int typeInst, char *chaineAnnulBit) { char chaine[20],tmp[100]; // On empile un paramètre de type entier à passer à la fonction (équivaut à m ov typeInst,%o0) sprintf(chaine,"or %%g0,%d,%%o0",typeInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Si la constante numeroInst à ranger dans %o1 peut être codée sur 13 bits ( Imprimé par Benjamin Vidal 03 jun 03 16:11 instrumentation.cc Page 2/23 i.e. est entre −4096 et 4095) if(numeroInst <= 4095) { // On empile un paramètre de type entier à passer à la fonction (équivaut à mov numeroInst,%o1) sprintf(chaine,"or %%g0,%d,%%o1",numeroInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; } else { // On empile ce même paramètre mais en deux fois (les 22 premiers bits du r egistre d’abbord) sprintf(chaine,"sethi %%hi(%d),%%o1",numeroInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Puis les 10 derniers bits ensuite sprintf(chaine,"or %%o1,%%lo(%d),%%o1",numeroInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; } // On empile un paramètre de type entier à passer à la fonction (équivaut à m ov numeroFichier,%o1) sprintf(chaine,"or %%g0,%d,%%o2",numeroFichier); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Si l’instruction que l’on instrumente est un branchement avec un annulBit (ex : bl,a ...) on insère une instruction qui // va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de l’instruction se trouvant dans le DelaySlot // sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être fait statiquement) if (chaineAnnulBit != NULL) { INST *inst_br_nop; inst_br_nop = newAsm(NOP); inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi t)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); (*nbInstructionsAjoutees)++; strcpy(tmp,"b "); } else strcpy(tmp,"call "); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_DEB UT_INST))); (*nbInstructionsAjoutees)++; // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } void appelDeFonctionInstMilieu(BB *bb, int position, unsigned int *nbInstruction sAjoutees, char *chaineAnnulBit) { mercredi 18 juin 2003 instrumentation.cc 2/37
  • 63.
    03 jun 0316:11 instrumentation.cc Page 3/23 char tmp[100]; // Si l’instruction que l’on instrumente est un branchement avec un annulBit (ex : bl,a ...) on insère une instruction qui // va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de l’instruction se trouvant dans le DelaySlot // sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être fait statiquement) if (chaineAnnulBit != NULL) { INST *inst_br_nop; inst_br_nop = newAsm(NOP); inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi t)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); (*nbInstructionsAjoutees)++; strcpy(tmp,"b "); } else strcpy(tmp,"call "); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_MIL IEU_INST))); (*nbInstructionsAjoutees)++; // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } void appelDeFonctionInstFin(BB *bb, int position, unsigned int *nbInstructionsAj outees, char *chaineAnnulBit) { char tmp[100]; // Si l’instruction que l’on instrumente est un branchement avec un annulBit (ex : bl,a ...) on insère une instruction qui // va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de l’instruction se trouvant dans le DelaySlot // sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être fait statiquement) if (chaineAnnulBit != NULL) { INST *inst_br_nop; inst_br_nop = newAsm(NOP); inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi t)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); (*nbInstructionsAjoutees)++; strcpy(tmp,"b "); } else strcpy(tmp,"call "); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_FIN _INST))); (*nbInstructionsAjoutees)++; Imprimé par Benjamin Vidal // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } void appelDeFonctionReg(BB *bb, int position, unsigned int *nbInstructionsAjoute es, int es, int indentificateurRessource, char *chaineAnnulBit) { char chaine[20],tmp[100]; // On empile le paramètre de type entier à passer à la fonction (équivaut à m ov %d,%o0) sprintf(chaine,"or %%g0,%d,%%o0",indentificateurRessource); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Si l’instruction que l’on instrumente est un branchement avec un annulBit (ex : bl,a ...) on insère une instruction qui // va inhiber l’effet du call suivant lorsque l’annulation de l’exécution de l’instruction se trouvant dans le DelaySlot // sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être fait statiquement) if (chaineAnnulBit != NULL) { INST *inst_br_nop; inst_br_nop = newAsm(NOP); inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi t)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); (*nbInstructionsAjoutees)++; strcpy(tmp,"b "); } else strcpy(tmp,"call "); // Appel de la fonction définie "ailleurs" (fichier instrument.c) // Si le paramètre de l’instruction à instrumentée est un paramètre d’entrée if (es==IN) { // On appelle la fonction de traitement spécifique aux paramètres d’entrée bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_I N_REG))); (*nbInstructionsAjoutees)++; } // Si le paramètre de l’instruction a instrumenter est un paramètre de sortie else { // On appelle la fonction de traitement spécifique aux paramètres de sortie bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_O UT_REG))); (*nbInstructionsAjoutees)++; } // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } 03 jun 03 16:11 instrumentation.cc Page 4/23 mercredi 18 juin 2003 instrumentation.cc 3/37
  • 64.
    03 jun 0316:11 instrumentation.cc Page 5/23 int tailleAccesMemoire(INST *inst) { regex_t *preg = new regex_t(); size_t nmatch = 1; regmatch_t pmatch[nmatch]; // Compilation de l’expression régulière permettant de détecter si une instruc tion est un load if (regcomp(preg, EXP_LOAD, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_LOAD""n"); exit(4); } // Si l’instruction est un load if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) != REG_NOMATCH) { regfree(preg); // Si le dernier caractère de l’expression est un b, alors le load est un lo ad byte if(inst−>unparse()[pmatch[0].rm_eo−1] == ’b’) return 1; // Si le dernier caractère de l’expression est un h, alors le load est un lo ad half−word if(inst−>unparse()[pmatch[0].rm_eo−1] == ’h’) return 2; // Si le dernier caractère de l’expression est un d et que ce caractère est en deuxième position, alors le load est un load word if(inst−>unparse()[pmatch[0].rm_eo−1] == ’d’ && pmatch[0].rm_eo == 3) return 4; // Si le dernier caractère de l’expression est un d et que ce caractère est en troisième position, alors le load est un load double−word if(inst−>unparse()[pmatch[0].rm_eo−1] == ’d’ && pmatch[0].rm_eo == 4) return 8; fprintf(STDERR,"Impossible de reconnaitre la taille des données lues par l’instruction "%s"n",inst−> unparse()); fprintf(STDERR,"Fonction tailleAccesMemoire du fichier instrumentation.ccn"); exit(7); } // Compilation de l’expression régulière permettant de détecter si une instruc tion est un store if (regcomp(preg, EXP_STORE, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_STORE""n"); exit(4); } // Si l’instruction est un store if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) != REG_NOMATCH) { regfree(preg); // Si le dernier caractère de l’expression est un b, alors le store est un s tore byte if(inst−>unparse()[pmatch[0].rm_eo−1] == ’b’) return 1; // Si le dernier caractère de l’expression est un h, alors le store est un s tore half−word if(inst−>unparse()[pmatch[0].rm_eo−1] == ’h’) return 2; // Si le dernier caractère de l’expression est un t, alors le store est un s tore word if(inst−>unparse()[pmatch[0].rm_eo−1] == ’t’) return 4; // Si le dernier caractère de l’expression est un d, alors le store est un s tore double−word if(inst−>unparse()[pmatch[0].rm_eo−1] == ’d’) return 8; 03 jun 03 16:11 instrumentation.cc Page 6/23 fprintf(STDERR,"Impossible de reconnaitre la taille des données lues par l’instruction "%s"n",inst−> unparse()); fprintf(STDERR,"Fonction tailleAccesMemoire du fichier instrumentation.ccn"); exit(7); } } void appelDeFonctionMem(BB *bb, INST *inst, int position, unsigned int *nbInstru ctionsAjoutees, int es, char *acces1, char *acces2, char *chaineAnnulBit, int ta illeAccesMemoire) { char chaine[200],tmp[100]; // * Sauvegarde de la valeur stockée dans %i0 dans la pile afin de pouvoir la récupérer par la suite bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("st %i0,[%sp+124]")); (*nbInstructionsAjoutees)++; // Cette instruction permet de récupérer l’état des registres tels qu’ils éta it au moment // ou l’appel de l’instruction instrumentée était iminent (on descend d’un cr an le contexte) restaurationContexte(bb, position, nbInstructionsAjoutees); // Insertion de l’instruction permettant de faire passer le résultat du calcu l au contexte supérieure // (fenêtre de registre placée au dessus) sprintf(chaine,"add %s,%s,%%o0",acces1,acces2); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Puis on repasse dans le contexte supérieur sauvegardeContexte(bb, position, nbInstructionsAjoutees); // En fin on récupère le résultat calculé par l’addition pour le passer en pa ramètre à la fonction // qui va être appellée (mov %i0,%o0) bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("or %g0,%i0,%o0")); (*nbInstructionsAjoutees)++; // * Restauration de la valeur stockée dans %o0 (valeur qui avait été sauvega rdée par "st %i0,[%sp+124]") bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("ld [%sp+124],%i0")); (*nbInstructionsAjoutees)++; // On range dans %o1 la taille de l’acces mémoire effectué (nombre d’octets l us ou écrits par l’instruction sprintf(chaine,"or %%g0,%d,%%o1",tailleAccesMemoire); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Si l’instruction que l’on instrumente est un branchement avec un annulBit (ex : bl,a ...) on insère une instruction qui // va inhiber l’effet du "call suivant" lorsque l’annulation de l’exécution d e l’instruction se trouvant dans le DelaySlot // sera effective (cela ne peut être vu qu’à l’exécution et donc ne peut être fait statiquement) if (chaineAnnulBit != NULL) { Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.cc 4/37
  • 65.
    03 jun 0316:11 instrumentation.cc Page 7/23 INST *inst_br_nop; inst_br_nop = newAsm(NOP); inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chaineAnnulBi t)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); (*nbInstructionsAjoutees)++; strcpy(tmp,"b "); } else strcpy(tmp,"call "); // Appel de la fonction définie "ailleurs" (fichier instrument.c) // Si le paramètre de l’instruction à instrumentée est un paramètre d’entrée if (es==IN) { // On appelle la fonction de traitement spécifique aux paramètres d’entrée bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_I N_MEM))); (*nbInstructionsAjoutees)++; } // Si le paramètre de l’instruction à instrumentée est un paramètre de sortie else { // On appelle la fonction de traitement spécifique aux paramètres de sortie bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_O UT_MEM))); (*nbInstructionsAjoutees)++; } // On comble le delay slot de l’appel de fonction précédent par un nop bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } void appelDeFonctionCopierInstDelay(BB *bb, int position, unsigned int *nbInstru ctionsAjoutees) { bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("call copierInstDelay")); (*nbInstructionsAjoutees)++; // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } void appelDeFonctionEchangerInstDelay(BB *bb, int position, unsigned int *nbInst ructionsAjoutees) { bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("call echangerInstDelay")); (*nbInstructionsAjoutees)++; // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } int rechercheChaine(char *chaine, FILE *fichier) { 03 jun 03 16:11 instrumentation.cc Page 8/23 char chaine2[100]; while (!feof(fichier)) { fscanf(fichier,"%s",chaine2); if(!strcmp(chaine,chaine2)) return 1; } return 0; } int estPresent(char *motif, char *chaine) { int i; regex_t *preg = new regex_t(); size_t nmatch = 10; regmatch_t pmatch[nmatch]; // Compilation de l’expression régulière if (regcomp(preg, motif, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "%s"n",motif); exit(4); } // Exécution de l’expression régulière et renvoi du résultat en fonction if (regexec(preg, chaine, nmatch, pmatch, 0) == REG_NOMATCH) { regfree(preg); return 0; } for(i=0; i<nmatch && pmatch[i].rm_so!=−1; i++); regfree(preg); return i; } int typeInstruction(INST *inst) { int i,j; char tmp[100]; regex_t *preg = new regex_t(); size_t nmatch = 1; regmatch_t pmatch[nmatch]; FILE *fichier; // Si l’instruction est une instruction "save", on retourne la valeur T_SAVE if (estPresent(EXP_SAVE,inst−>unparse())) return T_SAVE; // Si l’instruction est une instruction "restore", on retourne la valeur T_RES TORE if (estPresent(EXP_RESTORE,inst−>unparse())) return T_RESTORE; // Compilation de l’expression régulière permettant de détecter si une instruc tion est une instruction de sortie // (printf entre autre) Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.cc 5/37
  • 66.
    03 jun 0316:11 instrumentation.cc Page 9/23 if (regcomp(preg, EXP_FONCTION, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_FONCTION""n"); exit(4); } // Si l’instruction est un call if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) != REG_NOMATCH) { // On récupère dans la chaine tmp l’étiquette à laquelle on va brancher en e xécutant le call for(i=0,j=pmatch[0].rm_eo; j<strlen(inst−>unparse()); j++) if((inst−>unparse())[j]!=’ ’ && (inst−>unparse())[j]!=’t’) tmp[i++]=(inst− >unparse())[j]; tmp[i] = ’0’; regfree(preg); fichier = fopen(NOM_FICHIER_FONCTIONS_INTERNES, "r"); if(fichier == NULL) { fprintf(STDERR,"Problème lors de l’ouverture du fichier ""NOM_FICHIER_FONCTIONS_INTERNE S"" "); fprintf(STDERR,"(fonction typeInstruction dans le fichier instrumentation.cc)n"); exit(11); } // On recherche dans notre base de nom de fonctions définies localement si l a fonction désignée par l’étiquette // fait partie du code qui à était instrumenté ou non if(rechercheChaine(tmp,fichier)) { fclose(fichier); return T_APPEL_INTERNE; } fclose(fichier); fichier = fopen(NOM_FICHIER_FONCTIONS_EXTERNES_SPECIALES,"r"); if(fichier == NULL) { fprintf(STDERR,"Problème lors de l’ouverture du fichier ""NOM_FICHIER_FONCTIONS_EXTERNE S_SPECIALES"" "); fprintf(STDERR,"(fonction typeInstruction dans le fichier instrumentation.cc)n"); exit(11); } if(rechercheChaine(tmp,fichier)) { fclose(fichier); return T_APPEL_EXTERNE_SPECIAL; } fclose(fichier); return T_APPEL_EXTERNE; } // Si l’instruction est un "nop", on retourne la valeur T_NOP if (inst−>isNop()) return T_NOP; // Si l’instruction est un branchement, on retourne la valeur T_BRANCHEMENT if (inst−>isCTI()) if(estPresent(EXP_ANNUL_BIT,inst−>unparse())) Imprimé par Benjamin Vidal 03 jun 03 16:11 instrumentation.cc Page 10/23 return T_BRANCHEMENT_ANNUL_BIT; else return T_BRANCHEMENT; // Si l’instruction est une instruction "ld", on retourne la valeur T_LOAD if (estPresent(EXP_LOAD,inst−>unparse())) return T_LOAD; // Si l’instruction est une instruction "st", on retourne la valeur T_STORE if (estPresent(EXP_STORE,inst−>unparse())) return T_STORE; return T_AUTRE; } void operandeMemoire(char *instruction, char *acces1, char *acces2) { int i,j; char tmp[10]; regex_t *preg_mem = new regex_t(); regex_t *preg_signe = new regex_t(); size_t nmatch = 1; regmatch_t pmatch_mem[nmatch]; regmatch_t pmatch_signe[nmatch]; // Compilation de l’expression régulière permettant de détecter un acces mémoi re d’une instruction if (regcomp(preg_mem, EXP_MEMOIRE, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_MEMOIRE""n"); exit(4); } if (regcomp(preg_signe, "[−+]", REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "[−+]"n"); exit(4); } if (regexec(preg_mem, instruction, nmatch, pmatch_mem, 0) == REG_NOMATCH) { fprintf(STDERR,"Erreur lors de l’exécution de l’expression régulière ""EXP_MEMOIRE"" : impossible d e "); fprintf(STDERR,"trouver un motif correspondant à cette expression dans l’instruction "%s"n",instru ction); exit(4); } if (regexec(preg_signe, instruction, nmatch, pmatch_signe, 0) == REG_NOMATCH) { // On récupère la chaine de caractère correspondant au nom du registre auque l on accède for(i=0,j=pmatch_mem[0].rm_so+1; j<pmatch_mem[0].rm_eo−1; j++) acces1[i++]=i nstruction[j]; acces1[i] = ’0’; strcpy(acces2,"0"); } else { // On récupère la chaine de caractère correspondant au nom du premier regist re auquel on accède for(i=0,j=pmatch_mem[0].rm_so+1; j<pmatch_signe[0].rm_so; j++) acces1[i++]=i mercredi 18 juin 2003 instrumentation.cc 6/37
  • 67.
    03 jun 0316:11 instrumentation.cc Page 11/23 nstruction[j]; acces1[i] = ’0’; i=0; // Si le signe reconnu par l’expression régulière [−+] est un moins, on le r ejoute en début de chaine // possible seulement si le deuxième acces est une constante (ex : ld [%i6−6 0],%o0) if (instruction[pmatch_signe[0].rm_so] == ’−’) acces2[i++] = ’−’; // On récupère la chaine de caractère correspondant au nom du deuxième regis tre auquel on accède ou à la constante for(j=pmatch_signe[0].rm_eo; j<pmatch_mem[0].rm_eo−1; j++) acces2[i++] = instruction[j]; acces2[i] = ’0’; } regfree(preg_mem); regfree(preg_signe); } // Fonction permettant de sauvagarder le contexte du programme (registres généra ux et codes conditions) afin de ne pas // intervenir sur les valeurs des registres et codes conditions utilisés par le programme void sauvegardeContexte(BB *bb, int position, unsigned int *nbInstructionsAjoute es) { INST *inst_ccr; char *lectureCodesConditions = "trd %ccr,%l0n"; // "Empilement" du contexte du programme (création d’un contexte intermédiaire artificiel entre l’exécution du // programme et l’exécution des fonctions d’instrumentation du code. Ce contex te permet de travailler avec // les registres %o[0−5] afin de faire passer les paramètres aux fonctions d’i nstrumentations. bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(SAVE)); (*nbInstructionsAjoutees)++; inst_ccr = newAsm(NOP); inst_ccr−>addAttribute(UNPARSE_ATT, lectureCodesConditions, strlen(lectureCode sConditions)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_ccr); (*nbInstructionsAjoutees)++; // Sauvegarde en mémoire (dans la pile) des registres globaux for(int i=1; i<=4; i++) { char tmp[20]; // Sauvegarde du registre (%gi) (ex : "st %g1,[%sp+92]") sprintf(tmp,"st %%g%d,[%%sp+%d]",i,88+(4*i)); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); (*nbInstructionsAjoutees)++; } } // Fonction permettant de restaurer le contexte du programme (registres généraux et codes conditions) afin de ne pas // intervenir sur les valeurs des registres et codes conditions utilisés par le 03 jun 03 16:11 instrumentation.cc Page 12/23 programme void restaurationContexte(BB *bb, int position, unsigned int *nbInstructionsAjou tees) { INST *inst_ccr; char *ecritureCodesConditions = "twr %l0,%ccrn"; // Récupération des registres globaux depuis la mémoire (depuis la pile) for(int i=1; i<=4; i++) { char tmp[20]; // Restauration du registre (%gi) (ex : "ld [%sp+92],%g1") sprintf(tmp,"ld [%%sp+%d],%%g%d",88+(4*i),i); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); (*nbInstructionsAjoutees)++; } inst_ccr = newAsm(NOP); inst_ccr−>addAttribute(UNPARSE_ATT, ecritureCodesConditions, strlen(ecritureCo desConditions)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_ccr); (*nbInstructionsAjoutees)++; // "Dépilement" du contexte du programme bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(RESTORE)); (*nbInstructionsAjoutees)++; } int registreSalto(char *acces) { regex_t *preg = new regex_t(); size_t nmatch = 1; regmatch_t pmatch[nmatch]; // Compilation de l’expression régulière permettant de détecter un registre if (regcomp(preg, EXP_REGISTRE, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_REGISTRE""n"); exit(4); } if (regexec(preg, acces, nmatch, pmatch, 0) == REG_NOMATCH) { regfree(preg); return 0; } regfree(preg); // Traitement des cas particuliers : %sp et %fp (correspondant à %o6 et %i6) if (!strcmp(acces,"%sp")) return ID_REG_SALTO_O+6; if (!strcmp(acces,"%fp")) return ID_REG_SALTO_I+6; // Traitement du cas général switch (acces[1]) { case ’g’ : return ID_REG_SALTO_G+atoi(&(acces[2])); case ’o’ : return ID_REG_SALTO_O+atoi(&(acces[2])); case ’l’ : return ID_REG_SALTO_L+atoi(&(acces[2])); Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.cc 7/37
  • 68.
    03 jun 0316:11 instrumentation.cc Page 13/23 case ’i’ : return ID_REG_SALTO_I+atoi(&(acces[2])); default : fprintf(STDERR,"Passage impossible : fonction registreSalto dans instrumentation.ccn") ; } } void blancSuivant(char **ptr) { while ((*ptr)[0]!=’ ’ && (*ptr)[0]!=’t’ && (*ptr)[0]!=’n’ && (*ptr)[0]!=’0’) ( *ptr)++; while ((*ptr)[0]==’ ’ || (*ptr)[0]==’t’ || (*ptr)[0]==’n’) (*ptr)++; } // Insère le code permettant d’instrumenter en fonction des entrées produites pa r inst void appelDeFonctionsEntrees(INST *inst, BB *bb, int position, unsigned int *nbI nstructionsAjoutees, char *chaineAnnulBit) { ResourceDataBase &rdb = xxx_server −> GetResT(); res_ref* in; ResId_T identificateurRessource; // Ajout de l’instrumentation représentant les entrées de l’instruction fourni e par salto (lectures) for (int i=0; i < inst−>numberOfInput(); i++) { in = inst−>getInput(i); identificateurRessource = in−>get_res_id(); // Traitement fait pour chaque opérandes d’entrée switch ((rdb.get_res(identificateurRessource))−>getType()) { case MEMORY_RTYPE : char acces1[100],acces2[100]; operandeMemoire(inst−>unparse(), acces1, acces2); appelDeFonctionMem(bb, inst, position, nbInstructionsAjoutees, IN, acces 1, acces2, chaineAnnulBit, tailleAccesMemoire(inst)); break; case REGISTER_RTYPE : appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, identificat eurRessource, chaineAnnulBit); break; default : fprintf(STDERR,"Passage impossible : fonction appelDeFonctionsEntrees dans instrumentation.ccn" ); } } } // Ajout de l’instrumentation représentant les entrées fictives des call externe s void appelDeFonctionsEntreesAppelExterne(INST *inst, BB *bb, int position, unsig ned int *nbInstructionsAjoutees) { char acces[20]; char *ptr = (char *)(inst−>attributeValue(IN,COMMENT_ATT)); int identificateurRessource; 03 jun 03 16:11 instrumentation.cc Page 14/23 sscanf(ptr,"%s",acces); // Tant qu’on n’a pas atteint la fin de la chaine de caractère contenant les e ntrées effectuées par le call traité while (ptr[0] != ’0’) { blancSuivant(&ptr); identificateurRessource = registreSalto(acces); if(identificateurRessource != 0) appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, identificateu rRessource, NULL); else { char tmp[20],acces1[100],acces2[100]; strcpy(tmp,"["); strcat(tmp,acces); strcat(tmp,"]"); operandeMemoire(tmp, acces1, acces2); appelDeFonctionMem(bb, inst, position, nbInstructionsAjoutees, IN, acces1, acces2, NULL, 4); } sscanf(ptr,"%s",acces); } // Les fonctions définies hors du fichier local font toujours un accès en lect ure à %o6 (pointeur de pile) // et à %o7 (adresse de retour) /*appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, ID_REG_SALTO_O+ 6, NULL); appelDeFonctionReg(bb, position, nbInstructionsAjoutees, IN, ID_REG_SALTO_O+7, NULL);*/ } // Insère le code permettant d’instrumenter en fonction des sorties produites pa r inst void appelDeFonctionsSorties(INST *inst, BB *bb, int position, unsigned int *nbI nstructionsAjoutees, char *chaineAnnulBit) { ResourceDataBase &rdb = xxx_server −> GetResT(); res_ref* out; int identificateurRessource; // Ajout de l’instrumentation représentant les sorties de l’instruction fourni e par salto (écritures) for (int i=0; i < inst−>numberOfOutput(); i++) { out = inst−>getOutput(i); identificateurRessource = out−>get_res_id(); // Traitement fait pour chaque opérandes de sortie switch ((rdb.get_res(identificateurRessource))−>getType()) { case MEMORY_RTYPE : char acces1[100],acces2[100]; Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.cc 8/37
  • 69.
    03 jun 0316:11 instrumentation.cc Page 15/23 operandeMemoire(inst−>unparse(), acces1, acces2); appelDeFonctionMem(bb, inst, position, nbInstructionsAjoutees, OUT, acce s1, acces2, chaineAnnulBit, tailleAccesMemoire(inst)); break; case REGISTER_RTYPE : appelDeFonctionReg(bb, position, nbInstructionsAjoutees, OUT, identifica teurRessource, chaineAnnulBit); break; default : fprintf(STDERR,"Passage impossible : fonction appelDeFonctionsSorties dans instrumentation.ccn" ); } } } // Ajout de l’instrumentation représentant les sorties fictives des call externe s void appelDeFonctionsSortiesAppelExterne(INST *inst, BB *bb, int position, unsig ned int *nbInstructionsAjoutees) { char *acces = (char *)(inst−>attributeValue(OUT,COMMENT_ATT)); ResId_T identificateurRessource = registreSalto(acces); if(identificateurRessource == 0) { fprintf(STDERR,"Erreur : la chaine "%s" n’est pas reconnue comme étant un registre par la fonction ",a cces); fprintf(STDERR,"registreSalto(char*) (fonction appelDeFonctionsSortiesAppelExterne dans instrumentatio n.cc)n"); exit(9); } appelDeFonctionReg(bb, position, nbInstructionsAjoutees, OUT, identificateurRe ssource, NULL); } void instrumenter(INST *inst, BB *bb, BB *bbSuivant, int position, unsigned int *nbInstructionsAjoutees, int numeroInst) { static unsigned char flagDelaySlot = 0; // Si l’instruction est une instruction d’appel externe, on remplace "call tar tampion" par "call my_tartempion" // pour détourner l’appel "normal" à la fonction de la librairie en un appel à une fonction redéfinie permettant de // rendre compte des accès mémoires fait par ces fonctions if(typeInstruction(inst) == T_APPEL_EXTERNE) { char chaine[100],chaine2[100], *tmp; sscanf(inst−>unparse(),"%s %s",chaine,chaine2); strcpy(chaine, "tcall "PREFIXE_FONCTIONS_EXTERNES); // Allocation d’une nouvelle chaine de caractère contenant la nouvelle repré sentation de l’instruction tmp = (char *)malloc((strlen(chaine)+strlen(chaine2)+2)*sizeof(char)); strcpy(tmp,chaine); Imprimé par Benjamin Vidal 03 jun 03 16:11 instrumentation.cc Page 16/23 strcat(tmp,chaine2); strcat(tmp,"n"); inst−>addAttribute(UNPARSE_ATT, tmp, strlen(tmp)+1); } // Si on n’a pas à faire à une instruction se trouvant dans un DelaySlot if(flagDelaySlot == 0) // Si l’instruction n’est pas un branchement, alors, on instrumente cette in struction normalement if(!inst−>isCTI()) { sauvegardeContexte(bb, position, nbInstructionsAjoutees); appelDeFonctionInstDebut(bb, position, nbInstructionsAjoutees, numeroInst, typeInstruction(inst), NULL); appelDeFonctionsEntrees(inst, bb, position, nbInstructionsAjoutees, NULL); if(typeInstruction(inst)==T_SAVE || typeInstruction(inst)==T_RESTORE) appelDeFonctionInstMilieu(bb, position, nbInstructionsAjoutees, NULL); appelDeFonctionsSorties(inst, bb, position, nbInstructionsAjoutees, NULL); appelDeFonctionInstFin(bb, position, nbInstructionsAjoutees, NULL); restaurationContexte(bb, position, nbInstructionsAjoutees); } // Si l’instruction courante est un branchement, alors, on l’instrumente ain si que son DelaySlot else { char *chaineAnnulBit = (char *)malloc(100*sizeof(char)); INST *instDelaySlot = bbSuivant−>getAsm(0); /*fprintf(STDERR,"Inst <%s>tttDelay <%s>n",inst−>unparse(),instDelaySl ot−>unparse());*/ // On met le flag à 1 pour indiquer que l’instrumentation de l’instruction se trouvant dans le DelaySlot // du branchement que l’on est en train de traiter a déjà été instrumentée flagDelaySlot = 1; sauvegardeContexte(bb, position, nbInstructionsAjoutees); // On traite le début de l’instruction de branchement appelDeFonctionInstDebut(bb, position, nbInstructionsAjoutees, numeroIns t, typeInstruction(inst), NULL); // On traite les lectures de l’instruction de branchement appelDeFonctionsEntrees(inst, bb, position, nbInstructionsAjoutees, NULL ); // On traite les écritures de l’instruction de branchement appelDeFonctionsSorties(inst, bb, position, nbInstructionsAjoutees, NULL ); appelDeFonctionCopierInstDelay(bb, position, nbInstructionsAjoutees); // Si le branchement est un brachement avec annul_bit (ex : bl,a ... ) alors on rempli la chaine de caractère // chaineAnnulBit en conséquence (on y met les instructions pour tra iter ce cas correctement) if(typeInstruction(inst) == T_BRANCHEMENT_ANNUL_BIT) mercredi 18 juin 2003 instrumentation.cc 9/37
  • 70.
    03 jun 0316:11 instrumentation.cc Page 17/23 { int i; char tmp[20]; regex_t *preg = new regex_t(); size_t nmatch = 1; regmatch_t pmatch[nmatch]; if (regcomp(preg, EXP_ANNUL_BIT, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_ANNUL _BIT""n"); exit(4); } if (regexec(preg, inst−>unparse(), nmatch, pmatch, 0) == REG_NOMAT CH) { fprintf(STDERR,"Erreur lors de l’exécution de l’expression régulière ""EXP_ANNUL_B IT"" :n"); fprintf(STDERR,"Impossible de trouver l’expression régulière dans l’instruction "%s"n ",inst−>unparse()); exit(4); } regfree(preg); for(i=0; i<pmatch[0].rm_eo; i++) tmp[i] = (inst−>unparse())[i]; tmp[i] = ’0’; strcpy(chaineAnnulBit,"trd %pc,%o7n"); strcat(chaineAnnulBit,"tadd %o7,16,%o7n"); strcat(chaineAnnulBit,"twr %l0,%ccrn"); strcat(chaineAnnulBit,tmp); strcat(chaineAnnulBit," "ETIQUETTE_FONCTION_NOP"n"); } else chaineAnnulBit = NULL; // On traite le début de l’instruction qui se trouve dans le DelaySl ot appelDeFonctionInstDebut(bb, position, nbInstructionsAjoutees, numer oInst+1, typeInstruction(instDelaySlot), chaineAnnulBit); // On traite les lectures de l’instruction se trouvant dans le Delay Slot du branchement appelDeFonctionsEntrees(instDelaySlot, bb, position, nbInstructionsA joutees, chaineAnnulBit); appelDeFonctionInstMilieu(bb, position, nbInstructionsAjoutees, chai neAnnulBit); // On traite les écritures de l’instruction se trouvant dans le Dela ySlot du branchement appelDeFonctionsSorties(instDelaySlot, bb, position, nbInstructionsA joutees, chaineAnnulBit); // On traite la fin de l’instruction qui se trouve dans le DelaySlot appelDeFonctionInstFin(bb, position, nbInstructionsAjoutees, chaineA nnulBit); appelDeFonctionEchangerInstDelay(bb, position, nbInstructionsAjoutees); // Si l’instruction est un call externe, on lui ajoute comme entrées et sorties les paramètres consommés // et produits par cette fonction if(typeInstruction(inst) == T_APPEL_EXTERNE || typeInstruction(inst) == T_APPEL_EXTERNE_SPECIAL) Imprimé par Benjamin Vidal 03 jun 03 16:11 instrumentation.cc Page 18/23 { switch (inst−>numberOfAttributes(COMMENT_ATT)) { case 1 : appelDeFonctionsEntreesAppelExterne(inst, bb, position, nbInstruct ionsAjoutees); break; case 2 : appelDeFonctionsEntreesAppelExterne(inst, bb, position, nbInstruct ionsAjoutees); appelDeFonctionsSortiesAppelExterne(inst, bb, position, nbInstruct ionsAjoutees); break; default : fprintf(STDERR,"Passage impossible : fonction instrumenter dans instrumentation.ccn"); } } // On traite la fin de l’instruction de branchement seulment si on n’a p as à faire à un appel externe interceptées par les // fonctions redéfinies dans redefinition.c if(typeInstruction(inst)!=T_APPEL_EXTERNE) appelDeFonctionInstFin(bb, po sition, nbInstructionsAjoutees, NULL); appelDeFonctionEchangerInstDelay(bb, position, nbInstructionsAjoutees); restaurationContexte(bb, position, nbInstructionsAjoutees); } // Si l’instruction se trouve dans un DelaySlot, on remet le flag correspondan t à 0 else flagDelaySlot = 0; } void ajouterCommentaireAppelExterne(INST *inst) { int i,j; char etiquette[100], ligneCourante[200]; regex_t *preg_fct = new regex_t(); regex_t *preg_etiquette = new regex_t(); regex_t *preg_params = new regex_t(); regex_t *preg_result = new regex_t(); size_t nmatch = 1; regmatch_t pmatch[nmatch]; regmatch_t pmatch_params[nmatch]; regmatch_t pmatch_result[nmatch]; // Compilation de l’expression régulière permettant de détecter si une instruc tion est une fonction if (regcomp(preg_fct, EXP_FONCTION, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_FONCTION""n"); exit(4); } regexec(preg_fct, inst−>unparse(), nmatch, pmatch, 0); // On récupère dans la chaine etiquette l’étiquette associée au call traité for(i=0,j=pmatch[0].rm_eo; j<strlen(inst−>unparse()); j++) mercredi 18 juin 2003 instrumentation.cc 10/37
  • 71.
    03 jun 0316:11 instrumentation.cc Page 19/23 if((inst−>unparse())[j]!=’ ’ && (inst−>unparse())[j]!=’t’) etiquette[i++]=(i nst−>unparse())[j]; etiquette[i] = ’0’; if (regcomp(preg_etiquette, etiquette, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "%s"n",etiquette); exit(4); } if (regcomp(preg_params, "[ ]+! params =[ ]+", REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "[ t]+! params =[ t]+"n"); exit(4); } if (regcomp(preg_result, "[ ]+! Result =[ ]+", REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "[ t]+! Result =[ t]+"n"); exit(4); } // On cherche, dans le fichier assembleur original, la ligne contenant l’appel à la fonction traitée do { char c; i=0; do { c = fgetc(fichierSOriginal); ligneCourante[i++] = c; } while(c != ’n’); ligneCourante[i] = ’0’; } while (regexec(preg_fct, ligneCourante, nmatch, pmatch, 0) == REG_NOMATCH || regexec(preg_etiquette, ligneCourante, nmatch, pmatch, 0) == REG_NOMATC H || regexec(preg_params, ligneCourante, nmatch, pmatch_params, 0) == REG_NO MATCH || regexec(preg_result, ligneCourante, nmatch, pmatch_result, 0) == REG_NO MATCH || feof(fichierSOriginal)); regfree(preg_fct); regfree(preg_etiquette); regfree(preg_params); regfree(preg_result); if(feof(fichierSOriginal)) { fprintf(STDERR,"Erreur : fin du fichier .s original atteinte "); fprintf(STDERR,"(fonction ajouterCommentaireAppelExterne dans instrumentation.cc)n"); exit(7); } // Ici, la variable ligneCourante contient la ligne du fichier assembleur corr espondant à l’appel // de la fonction traitée (qui contient donc aussi en commentaire les paramètr es de cette fonction) char liste[100],*tmp; 03 jun 03 16:11 instrumentation.cc Page 20/23 // On récupère la liste des paramètres d’entrée for(i=0,j=pmatch_params[0].rm_eo; j<pmatch_result[0].rm_so; j++) liste[i++]=li gneCourante[j]; liste[i] = ’0’; // Puis on la range dans les attributs à mettre en commentaire dans le fichier généré // NOTE : ces commentaires servent aussi par la suite à identifier quels sont les accès fait par la fonction externe tmp = (char *)malloc((strlen(liste)+1)*sizeof(char)); strcpy(tmp,liste); inst−>addAttribute(COMMENT_ATT, tmp, strlen(liste)+1); // On récupère la liste contenant le résultat (paramètre de sortie) for(i=0,j=pmatch_result[0].rm_eo; ligneCourante[j]!=’!’ && ligneCourante[j]!=’ ’ && ligneCourante[j]!=’t’ && ligneCourante[j]!=’n’; j++) liste[i++]=lign eCourante[j]; liste[i] = ’0’; // Puis, si la chaine n’est pas vide, on la range dans les attributs à mettre en commentaire dans le fichier généré // NOTE : ces commentaires servent aussi par la suite à identifier quels sont les accès fait par la fonction externe if(liste[0] != ’0’) { tmp = (char *)malloc((strlen(liste)+1)*sizeof(char)); strcpy(tmp,liste); inst−>addAttribute(COMMENT_ATT, tmp, strlen(tmp)+1); } } void Salto_hook() { CFG *proc; BB *bb,*bbSuivant; INST *inst; int numeroInst; unsigned int nbInstructionsAjoutees; // Parcours du programme permettant de récupérer les paramètres des fonctions définies à // l’extérieur des fichiers .s locaux for (int i=0; i < numberOfCFG(); i++) { proc = getCFG(i); for (int j=0; j < proc−>numberOfBB(); j++) { bb = proc−>getBB(j); for (int k=0; k < bb−>numberOfAsm(); k++) { inst = bb−>getAsm(k); if(typeInstruction(inst) == T_APPEL_EXTERNE || Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.cc 11/37
  • 72.
    03 jun 0316:11 instrumentation.cc Page 21/23 typeInstruction(inst) == T_APPEL_EXTERNE_SPECIAL) ajouterCommentaireA ppelExterne(inst); } } } // Parcours du programme permettant d’insérer le code d’instrumentation des in structions numeroInst = 0; for (int i=0; i < numberOfCFG(); i++) { proc = getCFG(i); for (int j=0; j < proc−>numberOfBB(); j++) { bb = proc−>getBB(j); bbSuivant = proc−>getBB(j+1); int nbAsm = bb−>numberOfAsm(); nbInstructionsAjoutees = 0; for (int k=0; k < nbAsm; k++) { numeroInst++; inst = bb−>getAsm(k+nbInstructionsAjoutees); instrumenter(inst, bb, bbSuivant, k, &nbInstructionsAjoutees, numeroInst ); } } } // Parcours du programme permettant d’insérer le code pour l’initialisation de s variables globales // et d’affichage de ces variables en fin d’exécution du programme. for (int i=0; i < numberOfCFG(); i++) { proc = getCFG(i); if(!strcmp(proc−>getName(),"main")) { // Insertion d’un appel à la procédure initialisant les variables globales (proc−>getBB(0))−>insertAsm(0, newAsm("call initVariablesGlobales")); (proc−>getBB(0))−>insertAsm(1, newAsm(NOP)); for (int j=0; j < proc−>numberOfBB(); j++) { bb = proc−>getBB(j); for (int k=0; k < bb−>numberOfAsm(); k++) { inst = bb−>getAsm(k); // Insertion d’un appel à la procédure d’affichage des structures de d onnées juste avant l’instruction "call exit" if(estPresent("call",inst−>unparse()) && estPresent("exit",inst−>unparse( ))) { bb−>insertAsm(k, newAsm("call afficherSdd")); bb−>insertAsm(k+1, newAsm(NOP)); break; } } } break; } } // Envoi du code instrumenté vers la sortie standard produceCode(fichierSInstrumente); } void Salto_init_hook(int argc, char *argv[]) { int i,j,k; char nomFichierSortie[100]; Imprimé par Benjamin Vidal // Récupération dans la ligne de commande entrée par l’utilisateur du nom du f ichier original for(i=1; i<argc && !estPresent("−i",argv[i]); i++); if(i == argc−1) { fprintf(STDERR,"Erreur, votre ligne de commande ne comporte pas l’option "−i"n"); exit(6); } // On ouvre le fichier .s original à traiter pour pouvoir s’en servir dans sal to_hook() fichierSOriginal = fopen(argv[i+1],"r"); if(fichierSOriginal == NULL) { fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",argv[i+1]); fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation.cc)n"); exit(11); } nomFichierSortie[0]=’0’; strcat(nomFichierSortie,REPERTOIRE_FICHIER_INSTRUMENTES); strcat(nomFichierSortie,argv[i+1]); // On ouvre le fichier .s instrumenté en écriture à traiter pour pouvoir s’en servir dans salto_hook() fichierSInstrumente = fopen(nomFichierSortie,"w"); if(fichierSInstrumente == NULL) { fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",nomFichierSortie); fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation2.cc)n"); exit(11); } // Recherche de l’expression "−−" dans la ligne de commande for(; i<argc && !estPresent("−−",argv[i]); i++); // Si cette expression n’est pas présente, on ne numérote pas les fichiers if(i == argc−1) numeroFichier = 0; else numeroFichier=atoi(argv[i+1]); } 03 jun 03 16:11 instrumentation.cc Page 22/23 mercredi 18 juin 2003 instrumentation.cc 12/37
  • 73.
    void Salto_end_hook() { exit(0); } 03 jun 03 16:11 instrumentation.cc Page 23/23 Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation.cc 13/37
  • 74.
    16 jun 0317:02 instrument.h Page 1/1 #define STDERR stdout #define TRACE_INSTRUCTIONS_INUTILES "../trace_inutile.txt" #define TRACE_INSTRUCTIONS_STATIQUES_INUTILES "../trace_statiques_inutile.txt" #define TRACE_EVOLUTION_INUTILES "../evolution_inutile.txt" #define TRACE_VALEURS_MORTES "../evolution_valeurs_mortes.txt" /* Défini le nom de la fonction à appeler à chaque début de bloc de base */ #define NOM_FCT_IN_MEM "instrumentationEntreeMemoire" #define NOM_FCT_OUT_MEM "instrumentationSortieMemoire" #define NOM_FCT_IN_REG "instrumentationEntreeRegistre" #define NOM_FCT_OUT_REG "instrumentationSortieRegistre" #define NOM_FCT_DEBUT_INST "instrumentationInstructionDebut" #define NOM_FCT_MILIEU_INST "instrumentationInstructionMilieu" #define NOM_FCT_FIN_INST "instrumentationInstructionFin" #define NOM_FCT_INST "instrumentationInstruction" /* Défini les différentes valeur que peut prendre la variable typeInstruction dans la structure instruction */ #define T_APPEL_EXTERNE 0 #define T_APPEL_EXTERNE_SPECIAL 1 #define T_APPEL_INTERNE 2 #define T_BRANCHEMENT 3 #define T_BRANCHEMENT_ANNUL_BIT 4 #define T_SAVE 5 #define T_RESTORE 6 #define T_LOAD 7 #define T_STORE 8 #define T_NOP 9 #define T_AUTRE 10 /* Identifiant Salto du registre %g0 */ #define ID_REG_SALTO_G 41 /* Identifiant Salto du registre %o0 */ #define ID_REG_SALTO_O 49 /* Identifiant Salto du registre %l0 */ #define ID_REG_SALTO_L 57 /* Identifiant Salto du registre %i0 */ #define ID_REG_SALTO_I 65 /* Décalage, en nombre de registres, entre une fenêtre et la suivante */ #define OFFSET_FENETRE 16 /* A virer dans la version optimisée */ #define MAX_NB_INST_DYNAMIQUE 10000000 void instrumentationInstructionDebut(int typeInstruction, int numeroInst, int nu meroFichier); void instrumentationInstructionMilieu(void); void instrumentationInstructionFin(void); void copierInstDelay(void); void echangerInstDelay(void); void instrumentationEntreeRegistre(int identificateurRessource); void instrumentationSortieRegistre(int identificateurRessource); void instrumentationEntreeMemoire(int adresseMemoire, int nbOctetsLus); void instrumentationSortieMemoire(int adresseMemoire, int nbOctetsEcrits); Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument.h 14/37
  • 75.
    18 jun 0317:55 instrument.c Page 1/13 #include <stdio.h> #include <stdlib.h> #include "instrument.h" #define NB_REGISTRES 115 #define NB_REGISTRES_TOURNANTS 100000 /* MAX_NB_OPERANDES doit etre égal au maximum d’opérandes que peuvent avoir les instruction +1 ! */ #define MAX_NB_OPERANDES 150 /* Défini les différentes valeur que peuvent prendre les variables utiliteInst[i ], i allant de 0 à MAX_NB_INST_DYNAMIQUE.*/ #define INUTILE 0 #define UTILE 1 #define NOP 2 #define GENERER_FICHIER_DOT 1 #define FICHIER_TRACE_DOT "graphe_dependances.dot" #define MAX_NB_INST_STATIQUES 11000 #define MAX_INST_STATIQUES_TOTAL 250000 #define NB_FICHIERS 15 typedef unsigned char flag; typedef struct instru { unsigned char numeroFichier; unsigned int numeroStatique; unsigned int numeroDynamique; flag typeInstruction; struct instru *origineOperandes[MAX_NB_OPERANDES]; struct instru *precedent; } instruction; typedef struct mem { int adresse; unsigned int nbLecture; instruction *derniereEcriture; struct mem *suivant; } elementMemoire; /* Définition des registres généraux */ instruction *tableRegistres[NB_REGISTRES]; unsigned int tableLectureRegistres[NB_REGISTRES]; /* Définition des registres tournants */ instruction *tableRegistresTournants[NB_REGISTRES_TOURNANTS]; unsigned int tableLectureRegistresTournants[NB_REGISTRES_TOURNANTS]; unsigned int niveauFenetreRegistre = 0; /* Définition des zones mémoires (point d’entrée dans la liste chainée) */ elementMemoire *adresseInitiale = NULL; /* Définition des instructions (point d’entrée dans la liste chainée et tableau 18 jun 03 17:55 instrument.c Page 2/13 de flag) */ flag utiliteInst[MAX_NB_INST_DYNAMIQUE]; flag valeursMortes[MAX_NB_INST_DYNAMIQUE]; instruction *instInitiale; instruction *instCourante = NULL; instruction *instCouranteDelay = NULL; unsigned int occurencesInutileInstStatiques[MAX_NB_INST_STATIQUES][NB_FICHIERS]; unsigned int occurencesInstStatiques[MAX_NB_INST_STATIQUES][NB_FICHIERS]; unsigned int allocation=0; /* Utile uniquement pour la version donnant la proportion d’instruction statique s inutiles */ int cptInstStatiquesTraitees; void initVariablesGlobales(void) { int i,j; /* Création d’une instruction initiale fictive sur laquelle vont pointer celle s qui utilisent des registres déjà initialisés lors du lancement du programme */ instInitiale = malloc(sizeof(instruction)); allocation++; if(instInitiale == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument.c)n"); exit(10); } instInitiale−>numeroStatique = 0; instInitiale−>numeroDynamique = 0; instInitiale−>numeroFichier = 0; for(i=0; i<MAX_NB_OPERANDES; i++) instInitiale−>origineOperandes[i] = NULL; instInitiale−>typeInstruction = T_AUTRE; instInitiale−>precedent = NULL; instCourante = instInitiale; for(i=0; i<NB_REGISTRES; i++) { tableRegistres[i] = instInitiale; tableLectureRegistres[i] = 0; } for(i=0; i<NB_REGISTRES_TOURNANTS; i++) { tableRegistresTournants[i] = instInitiale; tableLectureRegistresTournants[i] = 0; } for(i=0; i<MAX_NB_INST_DYNAMIQUE; i++) { utiliteInst[i] = INUTILE; valeursMortes[i] = 0; } for(i=0; i<MAX_NB_INST_STATIQUES; i++) Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument.c 15/37
  • 76.
    18 jun 0317:55 instrument.c Page 3/13 for(j=0; j<NB_FICHIERS; j++) { occurencesInstStatiques[i][j]=0; occurencesInutileInstStatiques[i][j]=0; } } void remonterArbre(instruction *inst) { int i; /* Si on n’a pas encore parcouru l’arbre de dépendance de l’instuction inst, on le parcours pour pour positionner les flag d’utilité utiliteInst[inst−>n umeroDynamique] */ if(utiliteInst[inst−>numeroDynamique]!=UTILE) { utiliteInst[inst−>numeroDynamique] = UTILE; for(i=0; inst−>origineOperandes[i]!=NULL; i++) remonterArbre(inst−>origineOp erandes[i]); } } insert(float *tableauInstStatiqueInutiles, float valeur) { int i,j,k; for(i=0; tableauInstStatiqueInutiles[i]>valeur; i++); for(k=cptInstStatiquesTraitees; k!=i; k−−) tableauInstStatiqueInutiles[k]=tabl eauInstStatiqueInutiles[k−1]; tableauInstStatiqueInutiles[i] = valeur; cptInstStatiquesTraitees++; } /* Fonction de debug permettant d’afficher la liste chainée des instructions, la table des registres et enfin le pourcentage d’instructions utile et inutil es */ void afficherSdd(void) { instruction *i; elementMemoire *a; int j,k,cptUtile=0,cptInutile=0,cptNop=0,cptInstStatique=0,cptInstStatiqueOccu rencesInutile=0, cptLoadInutile=0,cptLoad=0,cptStoreInutile=0,cptStore=0,cptMortes=0,nbTotalIns tructions; unsigned int instInutileArtificiel; float tableauInstStatiqueInutiles[MAX_INST_STATIQUES_TOTAL]; /* Définition du fichier contenant la trace (numéro) des instructions inutiles ) */ FILE *traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"w"); FILE *traceInstructionsStatiquesInutiles = fopen(TRACE_INSTRUCTIONS_STATIQUES_ INUTILES,"w"); FILE *fichierEvolutionQtValeursMortes = fopen(TRACE_VALEURS_MORTES,"w"); FILE *fichierDot; if(GENERER_FICHIER_DOT) { 18 jun 03 17:55 instrument.c Page 4/13 fichierDot = fopen(FICHIER_TRACE_DOT,"w"); if(fichierDot==NULL) { fprintf(stderr,"Problème lors de l’ouverture du fichier ""FICHIER_TRACE_DOT"" "); fprintf(stderr,"(fonction afficherSdd dans le fichier instrument.c)n"); exit(11); } fprintf(fichierDot,"digraph G {n"); } if(traceInstructionsInutiles==NULL) { fprintf(stderr,"Problème lors de l’ouverture du fichier ""TRACE_INSTRUCTIONS_INUTILES"" " ); fprintf(stderr,"(fonction afficherSdd dans le fichier instrument.c)n"); exit(11); } /* Affichage des dépendances entre les instructions dynamiques (stockées dans la liste chainée d’instructions dynamiques) */ for(i=instCourante; i!=NULL; i=i−>precedent) { if(GENERER_FICHIER_DOT && utiliteInst[i−>numeroDynamique]!=NOP) { if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(fichierDot,"t%d [color=".0 .0 .8",fontcolor=".0 .0 .8"];n", i−>numeroDynamiq ue); } fprintf(stdout,"I.Dynamic %dt−− I.Static %dt−− File %d",i−>numeroDynamique,i−>numeroS tatique,i−>numeroFichier); if(utiliteInst[i−>numeroDynamique]==UTILE) fprintf(stdout,"t−− utile"); else if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(stdout,"t−− INUTILE "); else if(utiliteInst[i−>numeroDynamique]==NOP) fprintf(stdout,"t−− nop"); if(i−>origineOperandes[0]!=NULL) { fprintf(stdout,"t−− Dépend de %d",(i−>origineOperandes[0])−>numeroDynamique) ; if(GENERER_FICHIER_DOT && utiliteInst[i−>numeroDynamique]!=NOP) { fprintf(fichierDot,"t%d −> %d", i−>numeroDynamique, (i−>origineOperandes [0])−>numeroDynamique); if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(fichierDot," [color=". 0 .0 .8"]"); fprintf(fichierDot,";n"); } } for(j=1; j<MAX_NB_OPERANDES; j++) if(i−>origineOperandes[j]!=NULL) { fprintf(stdout,",%d",(i−>origineOperandes[j])−>numeroDynamique); if(GENERER_FICHIER_DOT && utiliteInst[i−>numeroDynamique]!=NOP) { fprintf(fichierDot,"t%d −> %d", i−>numeroDynamique, (i−>origineOperand es[j])−>numeroDynamique); if(utiliteInst[i−>numeroDynamique]==INUTILE) fprintf(fichierDot," [color =".0 .0 .8"]"); fprintf(fichierDot,";n"); Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument.c 16/37
  • 77.
    18 jun 0317:55 instrument.c Page 5/13 } } fprintf(stdout,"n"); if(utiliteInst[i−>numeroDynamique]==INUTILE) { /* Ecriture dans le fichier traceInstructionsInutiles du numéro des instru ctions dynamiques inutiles */ fprintf(traceInstructionsInutiles,"%un",i−>numeroDynamique); occurencesInutileInstStatiques[i−>numeroStatique][i−>numeroFichier]++; } occurencesInstStatiques[i−>numeroStatique][i−>numeroFichier]++; switch (i−>typeInstruction) { case T_LOAD : cptLoad++; if(utiliteInst[i−>numeroDynamique]==INUTILE) cptLoadInutile++; break; case T_STORE : cptStore++; if(utiliteInst[i−>numeroDynamique]==INUTILE) cptStoreInutile++; } } fclose(traceInstructionsInutiles); if(GENERER_FICHIER_DOT) { fprintf(fichierDot,"}"); fclose(fichierDot); } /* Affichage de la table des registres fixes avec les informations qui lui son t relatives */ fprintf(stdout,"nTable des registres fixes :n"); for(j=0; j<NB_REGISTRES; j++) { if(tableRegistres[j]−>numeroDynamique != 0 || tableLectureRegistres[j] != 0) { fprintf(stdout,"Registre numéro %dt",j); fprintf(stdout,": Modifié par l’instruction %dt",tableRegistres[j]−>numeroDynamique) ; fprintf(stdout,"et lu depuis par %d instruction(s)n",tableLectureRegistres[j]); } } /* Affichage de la table des registres tournants avec les informations qui lui sont relatives */ fprintf(stdout,"nTable des registres tournants (niveau actuel : %d) :n",niveauFenetreRegistre); for(j=0; j<NB_REGISTRES_TOURNANTS; j++) { if(tableRegistresTournants[j]−>numeroDynamique != 0 || tableLectureRegistres Tournants[j] != 0) { fprintf(stdout,"Registre tournant numéro %dt",j); fprintf(stdout,": Modifié par l’instruction %dt",tableRegistresTournants[j]−>numeroD ynamique); fprintf(stdout,"et lu depuis par %d instruction(s)n",tableLectureRegistresTournants[j Imprimé par Benjamin Vidal 18 jun 03 17:55 instrument.c Page 6/13 ]); } } /* Affichage de la liste chainée contenant les adresses mémoire */ fprintf(stdout,"nListe des adresses mémoires :n"); for(a=adresseInitiale; a!=NULL; a=a−>suivant) { fprintf(stdout,"Adresse mémoire %dt",a−>adresse); fprintf(stdout,": Modifié par l’instruction %dt",a−>derniereEcriture−>numeroDynamique) ; fprintf(stdout,"et lu depuis par %d instruction(s)n",a−>nbLecture); } /* On compte le nombre d’instructions utiles et inutiles afin de l’afficher */ for(j=0; j<instCourante−>numeroDynamique; j++) { switch (utiliteInst[j]) { case UTILE : cptUtile++; break; case INUTILE : cptInutile++; break; case NOP : cptNop++; break; default : fprintf(stderr,"Passage impossible : fonction afficherSdd dans instrument.cn" ); } if(valeursMortes[j] == 0) cptMortes++; if(valeursMortes[j] == 0 && utiliteInst[j] != INUTILE) { fprintf(stderr,"Instruction %d !!!!n",j); } if(j%20 == 0) fprintf(fichierEvolutionQtValeursMortes,"%dt%dn",j,cptMortes) ; } fclose(fichierEvolutionQtValeursMortes); nbTotalInstructions = instCourante−>numeroDynamique; fprintf(stdout,"nCalcul prenant en compte les "nop" :n"); fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi ons); fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%n", cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) ; fprintf(stdout,"Nombre de nop : %d/%d soit %f %%nn", cptNop,nbTotalInstructions,(float)(cptNop*100)/(float)nbTotalInstructions); nbTotalInstructions = instCourante−>numeroDynamique − cptNop; fprintf(stdout,"nCalcul ne prenant pas en compte les "nop" :n"); fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi ons); fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%nn", cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) ; fprintf(stdout,"Nombre d’instructions dont le résultat est mort : %d/%d soit %f %%nn", cptMortes,nbTotalInstructions,(float)(cptMortes*100)/(float)nbTotalInstruction s); fprintf(stdout,"nnNombre d’ocurences d’instructions inutiles pour une instruction statique : n"); mercredi 18 juin 2003 instrument.c 17/37
  • 78.
    18 jun 0317:55 instrument.c Page 7/13 tableauInstStatiqueInutiles[0] = 0.0; /* Lignes de la matrice */ for(k=0; k<MAX_NB_INST_STATIQUES; k++) { if(k==0) for(j=1; j<NB_FICHIERS; j++) fprintf(stdout," t%d",j); else { fprintf(stdout,"n%d :t",k); /* Colonnes de la matrice */ for(j=1; j<NB_FICHIERS; j++) { if(occurencesInstStatiques[k][j]!=0) { fprintf(stdout,"%u/%ut",occurencesInutileInstStatiques[k][j],occurence sInstStatiques[k][j]); cptInstStatique++; if(occurencesInutileInstStatiques[k][j]!=0) cptInstStatiqueOccurencesI nutile++; insert(tableauInstStatiqueInutiles,(float)occurencesInutileInstStatiqu es[k][j]/(float)occurencesInstStatiques[k][j]); } else fprintf(stdout," t"); } } } for(j=0; j<cptInstStatiquesTraitees; j++) fprintf(traceInstructionsStatiquesInutiles,"%dt%fn",cptInstStatiquesTraitee s,tableauInstStatiqueInutiles[j]); fclose(traceInstructionsStatiquesInutiles); fprintf(stdout,"nNombre d’instructions statique ayant des occurences inutiles / Nombre d’instructions stati ques utilisées : %d/%d soit %f %%n", cptInstStatiqueOccurencesInutile, cptInstStatique, (float)(cptInstStatiqueOccu rencesInutile*100)/(float)cptInstStatique); fprintf(stdout,"nNombre de load inutiles / Nombre de load dynamique total : %d/%d soit %f %%n", cptLoadInutile,cptLoad,(float)(cptLoadInutile*100)/(float)cptLoad); fprintf(stdout,"nNombre de store inutiles / Nombre de store dynamique total : %d/%d soit %f %%n", cptStoreInutile,cptStore,(float)(cptStoreInutile*100)/(float)cptStore); fprintf(stdout,"Nombre de noeuds total dans le graphe : %dn",allocation); } /* Création de la représentation des instructions dynamiques en mémoire */ /* !!!!! Attention, cette fonction est différente selon qu’on utilise le program me sur Sparc ou x86 (DelaySlot) */ void instrumentationInstructionDebut(int typeInstruction, int numeroInst, int nu meroFichier) { int i; instruction *tmp = malloc(sizeof(instruction)); Imprimé par Benjamin Vidal 18 jun 03 17:55 instrument.c Page 8/13 allocation++; if(tmp == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction instrumentationInstructionDebut dans le fichier instrument.c)n"); exit(10); } if(instCourante−>numeroDynamique >= MAX_NB_INST_DYNAMIQUE) { fprintf(stderr,"Nombre maximum d’instruction dépassé, veulliez augmenter la valeur de la constante ") ; fprintf(stderr,"MAX_NB_INST_DYNAMIQUE dans le fichier instrument.cn"); exit(1); } tmp−>numeroStatique = numeroInst; tmp−>numeroFichier = numeroFichier; tmp−>typeInstruction = typeInstruction; /* On passe pour la première fois dans cette fonction */ if(instCourante == NULL) { tmp−>numeroDynamique = 1; tmp−>precedent = instInitiale; } else { tmp−>numeroDynamique = instCourante−>numeroDynamique+1; tmp−>precedent = instCourante; } for(i=0; i<MAX_NB_OPERANDES; i++) tmp−>origineOperandes[i] = NULL; instCourante = tmp; /*fprintf(stderr,"Début statique %d dynamique %d fichier %d >",numeroInst,tmp− >numeroDynamique,tmp−>numeroFichier);*/ } /* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u n DelaySolt (Sparc) */ void instrumentationInstructionMilieu(void) { /* Si l’instruction est un save, on incrémente le niveau de la fenetre de regi stres */ if(instCourante−>typeInstruction == T_SAVE) niveauFenetreRegistre++; /* Si c’est un restore, on décrémente le niveau de la fenetre de registres */ else if(instCourante−>typeInstruction == T_RESTORE) niveauFenetreRegistre−−; } void instrumentationInstructionFin(void) { /* Si l’instruction courante est un branchement quelconque, un save ou un rest ore, on la marque comme utile et on marque comme utile toute les instructions dont elle dépend (appel réc ursif à remonterArbre) */ if(instCourante−>typeInstruction == T_BRANCHEMENT || instCourante−>typeInstruction == T_BRANCHEMENT_ANNUL_BIT || mercredi 18 juin 2003 instrument.c 18/37
  • 79.
    18 jun 0317:55 instrument.c Page 9/13 instCourante−>typeInstruction == T_SAVE || instCourante−>typeInstruction == T_RESTORE || instCourante−>typeInstruction == T_APPEL_INTERNE || instCourante−>typeInstruction == T_APPEL_EXTERNE || instCourante−>typeInstruction == T_APPEL_EXTERNE_SPECIAL) remonterArbre(ins tCourante); if(instCourante−>typeInstruction == T_NOP) utiliteInst[instCourante−>numeroDyn amique] = NOP; /*fprintf(stderr,"< Fin statique %d dynamique %d fichier %dn",instCourante−>n umeroStatique,instCourante−>numeroDynamique,instCourante−>numeroFichier);*/ } /* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u n DelaySolt (Sparc) */ void copierInstDelay(void) { instCouranteDelay = instCourante; } /* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u n DelaySolt (Sparc) */ void echangerInstDelay(void) { instruction *tmp = instCourante; instCourante = instCouranteDelay; instCouranteDelay = tmp; } /* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o u à des registres (en entrée) */ /* !!!!! Attention, cette fonction est différente selon qu’on utilise le program me sur Sparc ou x86 (registres) */ void instrumentationEntreeRegistre(int identificateurRessource) { int i; unsigned int identificateurRegistreTournant; /* identificateurRessource numéro ID_REG_SALTO_G = trou noir (%g0) */ if(identificateurRessource != ID_REG_SALTO_G) { /* Si le registre accédé fait partie de la fenêtre de registres tournante */ if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE G_SALTO_I+7) { identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet reRegistre*OFFSET_FENETRE); if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) { fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de "); fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); exit(1); } tableLectureRegistresTournants[identificateurRegistreTournant]++; Imprimé par Benjamin Vidal 18 jun 03 17:55 instrument.c Page 10/13 for(i=0; (instCourante−>origineOperandes)[i]!=NULL; i++) if (i >= MAX_NB_OPERANDES) { fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente r la valeur de "); fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); exit(8); } instCourante−>origineOperandes[i] = tableRegistresTournants[identificateur RegistreTournant]; } /* Sinon, on le considère comme un registre fixe */ else { tableLectureRegistres[identificateurRessource]++; for(i=0; (instCourante−>origineOperandes)[i]!=NULL; i++) if (i >= MAX_NB_OPERANDES) { fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente r la valeur de "); fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); exit(8); } instCourante−>origineOperandes[i] = tableRegistres[identificateurRessource ]; } } } /* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o u à des registres (en sortie) */ /* !!!!! Attention, cette fonction est différente selon qu’on utilise le program me sur Sparc ou x86 (registre %g0) */ void instrumentationSortieRegistre(int identificateurRessource) { unsigned int identificateurRegistreTournant; /* Mise à jour du compteur de résultat : le registre identificateurRessource e st considéré comme un résultat de l’instruction instCourante */ valeursMortes[instCourante−>numeroDynamique]++; /* Si le registre dans lequel on écrit est le numéro 41 (correspondant à %g0), on n’en tient pas compte car ce registre est un trou noir (quelque soit ce qui y est écrit, on lit toujours la valeur 0 dans ce registre) */ if(identificateurRessource != ID_REG_SALTO_G) { /* Si le registre accédé fait partie de la fenêtre de registres tournante */ if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE G_SALTO_I+7) { identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet reRegistre*OFFSET_FENETRE); if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) { fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de mercredi 18 juin 2003 instrument.c 19/37
  • 80.
    18 jun 0317:55 instrument.c Page 11/13 "); fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); exit(1); } if(tableLectureRegistresTournants[identificateurRegistreTournant] == 0) valeursMortes[(tableRegistresTournants[identificateurRegistreTournant])− >numeroDynamique]−−; tableRegistresTournants[identificateurRegistreTournant] = instCourante; tableLectureRegistresTournants[identificateurRegistreTournant] = 0; } /* Sinon, on le considère comme un registre fixe */ else { if(tableLectureRegistres[identificateurRessource] == 0) valeursMortes[(tableRegistres[identificateurRessource])−>numeroDynamique ]−−; tableRegistres[identificateurRessource] = instCourante; tableLectureRegistres[identificateurRessource] = 0; } } } void lectureMemoireOctet(int adresseMemoire) { int i; elementMemoire *a; for(a=adresseInitiale; a==NULL || adresseMemoire>a−>adresse; a=a−>suivant) if(a==NULL) { /*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’instr uction %d (fichier %d)n", adresseMemoire, instCourante−>numeroStatique, instCourante−>numeroFichier) ;*/ return; } if(adresseMemoire != a−>adresse) { /*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’instruc tion %d (fichier %d)n", adresseMemoire, instCourante−>numeroStatique, instCourante−>numeroFichier);* / return; } for(i=0; (instCourante−>origineOperandes)[i]!=NULL; i++) if (i >= MAX_NB_OPERANDES) { fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmenter la va leur de "); fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); exit(8); } instCourante−>origineOperandes[i] = a−>derniereEcriture; a−>nbLecture++; } 18 jun 03 17:55 instrument.c Page 12/13 void instrumentationEntreeMemoire(int adresseMemoire, int nbOctetsLus) { int offset; for(offset=0; offset<nbOctetsLus; offset++) lectureMemoireOctet(adresseMemoire +offset); } void ecritureMemoireOctet(int adresseMemoire) { elementMemoire *nouveau, *tmp = adresseInitiale; /* Mise à jour du compteur de résultat : la case mémoire adresseMemoire est co nsidéré comme un résultat de l’instruction instCourante */ valeursMortes[instCourante−>numeroDynamique]++; /* Cas ou la liste chainée est vide : premier accès en écriture à la mémoire * / if(adresseInitiale == NULL) { adresseInitiale = malloc(sizeof(elementMemoire)); if(adresseInitiale == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); exit(10); } adresseInitiale−>adresse = adresseMemoire; adresseInitiale−>nbLecture = 0; adresseInitiale−>derniereEcriture = instCourante; adresseInitiale−>suivant = NULL; } else { /* Cas ou l’insertion de données concerne le premier élément de la liste cha inée */ if(adresseMemoire <= adresseInitiale−>adresse) { if(adresseInitiale−>adresse == adresseMemoire) { if(adresseInitiale−>nbLecture == 0) valeursMortes[(adresseInitiale−>derniereEcriture)−>numeroDynamique]−−; adresseInitiale−>nbLecture = 0; adresseInitiale−>derniereEcriture = instCourante; } else { nouveau = malloc(sizeof(elementMemoire)); if(nouveau == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); exit(10); } nouveau−>adresse = adresseMemoire; Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument.c 20/37
  • 81.
    18 jun 0317:55 instrument.c Page 13/13 nouveau−>nbLecture = 0; nouveau−>derniereEcriture = instCourante; nouveau−>suivant = adresseInitiale; adresseInitiale = nouveau; } } /* Cas général d’insertion d’un élément dans la liste chainée */ else { /* On parcours la liste chainée jusqu’a trouver l’endroit ou il faut insér er la donnée */ while(tmp−>suivant != NULL && adresseMemoire >= tmp−>suivant−>adresse) t mp = tmp−>suivant; /* Si l’adresse mémoire à déjà était accédée par le passé, on modifie le p ointeur sur l’instruction qui a écrit dernièrement dans cette zone mémoire */ if(tmp−>adresse == adresseMemoire) { if(tmp−>nbLecture == 0) valeursMortes[(tmp−>derniereEcriture)−>numeroDynamique]−−; tmp−>nbLecture = 0; tmp−>derniereEcriture = instCourante; } /* Sinon, on créé une nouvelle cellule représentant cette zone mémoire et on l’insère dans la liste chainée */ else { nouveau = malloc(sizeof(elementMemoire)); if(nouveau == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); exit(10); } nouveau−>adresse = adresseMemoire; nouveau−>nbLecture = 0; nouveau−>derniereEcriture = instCourante; nouveau−>suivant = tmp−>suivant; tmp−>suivant = nouveau; } } } } void instrumentationSortieMemoire(int adresseMemoire, int nbOctetsEcrits) { int offset; for(offset=0; offset<nbOctetsEcrits; offset++) ecritureMemoireOctet(adresseMem oire+offset); } Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument.c 21/37
  • 82.
    18 jun 0314:19 instrument_optim.c Page 1/10 #include <stdio.h> #include <stdlib.h> #include "instrument.h" #define NB_REGISTRES 115 #define NB_REGISTRES_TOURNANTS 100000 /* MAX_NB_OPERANDES doit etre égal au maximum d’opérandes que peuvent avoir les instruction +1 ! */ #define MAX_NB_OPERANDES 1400 /* Défini les différentes valeur que peuvent prendre les variables utiliteInst[i ], i allant de 0 à MAX_NB_INST_DYNAMIQUE.*/ #define INUTILE 0 #define UTILE 1 #define NOP 2 typedef unsigned char flag; struct donnees_instruction { unsigned char numeroFichier; unsigned int numeroStatique; unsigned int numeroDynamique; flag typeInstruction; flag valeurMorte; struct instru *origineOperandes[MAX_NB_OPERANDES]; }; typedef struct instru { flag utiliteInst; struct donnees_instruction *donnees; struct instru *precedent; } instruction; typedef struct mem { int adresse; unsigned int nbLecture; instruction *derniereEcriture; struct mem *suivant; } elementMemoire; /* Définition des registres généraux */ instruction *tableRegistres[NB_REGISTRES]; unsigned int tableLectureRegistres[NB_REGISTRES]; /* Définition des registres tournants */ instruction *tableRegistresTournants[NB_REGISTRES_TOURNANTS]; unsigned int tableLectureRegistresTournants[NB_REGISTRES_TOURNANTS]; unsigned int niveauFenetreRegistre = 0; /* Définition des zones mémoires (point d’entrée dans la liste chainée) */ elementMemoire *adresseInitiale = NULL; /* Définition des instructions (point d’entrée dans la liste chainée et tableau de flag) */ instruction *instInitiale; instruction *instCourante = NULL; 18 jun 03 14:19 instrument_optim.c Page 2/10 instruction *instCouranteDelay = NULL; unsigned int numeroDynamiqueCourant; int cptUtile = 0, cptInutile = 0, cptNop = 0; unsigned int allocation=0; void initVariablesGlobales(void) { int i; struct donnees_instruction *donnees = malloc(sizeof(struct donnees_instruction )); allocation++; numeroDynamiqueCourant=0; /* Création d’une instruction initiale fictive sur laquelle vont pointer celle s qui utilisent des registres déjà initialisés lors du lancement du programme */ instInitiale = malloc(sizeof(instruction)); if(donnees == NULL || instInitiale == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument.c)n"); exit(10); } donnees−>numeroFichier = 0; donnees−>numeroStatique = 0; donnees−>numeroDynamique = numeroDynamiqueCourant; /* 0 ici */ donnees−>typeInstruction = T_AUTRE; donnees−>valeurMorte = 0; for(i=0; i<MAX_NB_OPERANDES; i++) donnees−>origineOperandes[i] = NULL; instInitiale−>utiliteInst = INUTILE; instInitiale−>donnees = donnees; instInitiale−>precedent = NULL; instCourante = instInitiale; for(i=0; i<NB_REGISTRES; i++) { tableRegistres[i] = instInitiale; tableLectureRegistres[i] = 0; } for(i=0; i<NB_REGISTRES_TOURNANTS; i++) { tableRegistresTournants[i] = instInitiale; tableLectureRegistresTournants[i] = 0; } } /* Fonction de debug permettant d’afficher la liste chainée des instructions, la table des registres et enfin le pourcentage d’instructions utile et inutil es */ void afficherSdd(void) { instruction *i; elementMemoire *a; Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument_optim.c 22/37
  • 83.
    18 jun 0314:19 instrument_optim.c Page 3/10 int j,nbTotalInstructions,cptInutile2=0,cptMortes=0,cptMortes2=0; /* Définition du fichier contenant la trace (numéro) des instructions inutiles ) */ FILE *traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"w"); FILE *fichierEvolutionQtTravailInutile = fopen(TRACE_EVOLUTION_INUTILES,"w"); FILE *fichierEvolutionQtValeursMortes = fopen(TRACE_VALEURS_MORTES,"w"); /* Affichage des dépendances entre les instructions dynamiques (stockées dans la liste chainée d’instructions dynamiques) */ /* Version optimisée : ne sont disponible que les données sur les instructions inutiles */ fprintf(stdout,"Liste des instructions dynamiques inutiles :n"); for(i=instCourante; i!=NULL; i=i−>precedent) { if(i−>utiliteInst==INUTILE) { struct donnees_instruction *d = i−>donnees; fprintf(stdout,"I.Dynamic %dt−− I.Static %dt−− File %dn",d−>numeroDynamique,d−>num eroStatique,d−>numeroFichier); /*if(d−>origineOperandes[0]!=NULL) fprintf(stdout,"t−− Dépend de %d",(d−> origineOperandes[0])−>donnees−>numeroDynamique); for(j=1; j<MAX_NB_OPERANDES; j++) if(d−>origineOperandes[j]!=NULL) fprintf(stdout,",%d",(d−>origineOperand es[j])−>donnees−>numeroDynamique); fprintf(stdout,"n");*/ /* Ecriture dans le fichier traceInstructionsInutiles du numéro des instru ctions dynamiques inutiles */ fprintf(traceInstructionsInutiles,"%un",d−>numeroDynamique); cptInutile++; if(d−>valeurMorte == 0) cptMortes++; } } fclose(traceInstructionsInutiles); for(i=instCourante,j=numeroDynamiqueCourant; i!=NULL; i=i−>precedent,j−−) { if(i−>utiliteInst == INUTILE) { cptInutile2++; if(i−>donnees−>valeurMorte == 0) cptMortes2++; } if(j%20 == 0) { fprintf(fichierEvolutionQtTravailInutile,"%dt%dn",j,cptInutile−cptInutile 2); fprintf(fichierEvolutionQtValeursMortes,"%dt%dn",j,cptMortes−cptMortes2); } } fclose(fichierEvolutionQtTravailInutile); fclose(fichierEvolutionQtValeursMortes); nbTotalInstructions = cptUtile + cptInutile + cptNop; fprintf(stdout,"nCalcul prenant en compte les "nop" :n"); fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi ons); fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%n", 18 jun 03 14:19 instrument_optim.c Page 4/10 cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) ; fprintf(stdout,"Nombre de nop : %d/%d soit %f %%nn", cptNop,nbTotalInstructions,(float)(cptNop*100)/(float)nbTotalInstructions); nbTotalInstructions = cptUtile + cptInutile; fprintf(stdout,"nCalcul ne prenant pas en compte les "nop" :n"); fprintf(stdout,"Nombre d’instructions inutiles : %d/%d soit %f %%n", cptInutile,nbTotalInstructions,(float)(cptInutile*100)/(float)nbTotalInstructi ons); fprintf(stdout,"Nombre d’instructions utiles : %d/%d soit %f %%nn", cptUtile,nbTotalInstructions,(float)(cptUtile*100)/(float)nbTotalInstructions) ; fprintf(stdout,"Nombre de noeuds dans le graphe : %dn",allocation); } void remonterArbre(instruction *inst) { int i; /* Si on n’a pas encore parcouru l’arbre de dépendance de l’instuction inst, on le parcours pour pour positionner les flag d’utilité utiliteInst[inst−>n umeroDynamique] */ if(inst−>utiliteInst == INUTILE) { inst−>utiliteInst = UTILE; cptUtile++; for(i=0; inst−>donnees−>origineOperandes[i]!=NULL; i++) remonterArbre(inst−> donnees−>origineOperandes[i]); /* On libère la zone mémoire allouée au champ de donnée lorsqu’on est sur qu e l’instruction est utile (ce champ de donnée ne nous sert plus à rien dans ce cas la) */ free(inst−>donnees); allocation−−; inst−>donnees = NULL; } } /* Création de la représentation des instructions dynamiques en mémoire */ /* !!!!! Attention, cette fonction est différente selon qu’on utilise le program me sur Sparc ou x86 (DelaySlot) */ void instrumentationInstructionDebut(int typeInstruction, int numeroInst, int nu meroFichier) { int i; instruction *inst = malloc(sizeof(instruction)); struct donnees_instruction *d = malloc(sizeof(struct donnees_instruction)); allocation++; if(inst==NULL || d==NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction instrumentationInstructionDebut dans le fichier instrument.c)n"); exit(10); } Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument_optim.c 23/37
  • 84.
    d−>numeroFichier = numeroFichier; d−>numeroStatique = numeroInst; d−>numeroDynamique = ++numeroDynamiqueCourant; d−>typeInstruction = typeInstruction; if(typeInstruction == T_NOP) cptNop++; for(i=0; i<MAX_NB_OPERANDES; i++) d−>origineOperandes[i] = NULL; inst−>utiliteInst = INUTILE; inst−>donnees = d; inst−>precedent = instCourante; instCourante = inst; /*fprintf(stderr,"Début inst statique %d, inst dynamique %d fichier %dn",nume roInst,tmp−>numeroDynamique,tmp−>numeroFichier);*/ } /* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u n DelaySolt (Sparc) */ void instrumentationInstructionMilieu(void) { /* Si l’instruction est un save, on incrémente le niveau de la fenetre de regi stres */ if(instCourante−>donnees−>typeInstruction == T_SAVE) niveauFenetreRegistre++; /* Si c’est un restore, on décrémente le niveau de la fenetre de registres */ else if(instCourante−>donnees−>typeInstruction == T_RESTORE) niveauFenetreRegi stre−−; } void instrumentationInstructionFin(void) { /*fprintf(stderr,"Fin inst statique numéro %d, inst dynamique numéro %dn",ins tCourante−>numeroStatique,instCourante−>numeroDynamique);*/ /* Si l’instruction courante est un branchement quelconque, un save ou un rest ore, on la marque comme utile et on marque comme utile toute les instructions dont elle dépend (appel réc ursif à remonterArbre) */ if(instCourante−>donnees−>typeInstruction == T_BRANCHEMENT || instCourante−>donnees−>typeInstruction == T_BRANCHEMENT_ANNUL_BIT || instCourante−>donnees−>typeInstruction == T_SAVE || instCourante−>donnees−>typeInstruction == T_RESTORE || instCourante−>donnees−>typeInstruction == T_APPEL_INTERNE || instCourante−>donnees−>typeInstruction == T_APPEL_EXTERNE || instCourante−>donnees−>typeInstruction == T_APPEL_EXTERNE_SPECIAL) remonter Arbre(instCourante); /* Si l’instruction courante est un nop, on la marque comme tel et on libère l ’espace mémoire qui avait était alloué pour sa représentation interne dans le programme */ else if(instCourante−>donnees−>typeInstruction == T_NOP) { instCourante−>utiliteInst = NOP; free(instCourante−>donnees); allocation−−; instCourante−>donnees = NULL; } } 18 jun 03 14:19 instrument_optim.c Page 5/10 18 jun 03 14:19 instrument_optim.c Page 6/10 /* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u n DelaySolt (Sparc) */ void copierInstDelay(void) { instCouranteDelay = instCourante; } /* !!!!! Attention, cette fonction n’est utile que pour les machines utilisant u n DelaySolt (Sparc) */ void echangerInstDelay(void) { instruction *tmp = instCourante; instCourante = instCouranteDelay; instCouranteDelay = tmp; } /* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o u à des registres (en entrée) */ /* !!!!! Attention, cette fonction est différente selon qu’on utilise le program me sur Sparc ou x86 (registres) */ void instrumentationEntreeRegistre(int identificateurRessource) { int i; unsigned int identificateurRegistreTournant; /* identificateurRessource numéro ID_REG_SALTO_G = trou noir (%g0) */ if(identificateurRessource != ID_REG_SALTO_G) { /* Si le registre accédé fait partie de la fenêtre de registres tournante */ if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE G_SALTO_I+7) { identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet reRegistre*OFFSET_FENETRE); if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) { fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de "); fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); exit(1); } tableLectureRegistresTournants[identificateurRegistreTournant]++; for(i=0; (instCourante−>donnees−>origineOperandes)[i]!=NULL; i++) if (i >= MAX_NB_OPERANDES) { fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente r la valeur de "); fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); exit(8); } (instCourante−>donnees−>origineOperandes)[i] = tableRegistresTournants[ide ntificateurRegistreTournant]; } /* Sinon, on le considère comme un registre fixe */ else Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument_optim.c 24/37
  • 85.
    18 jun 0314:19 instrument_optim.c Page 7/10 { tableLectureRegistres[identificateurRessource]++; for(i=0; (instCourante−>donnees−>origineOperandes)[i]!=NULL; i++) if (i >= MAX_NB_OPERANDES) { fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmente r la valeur de "); fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); exit(8); } (instCourante−>donnees−>origineOperandes)[i] = tableRegistres[identificate urRessource]; } } } /* Traitement fait lorsqu’on rencontre une instruction qui accède à la mémoire o u à des registres (en sortie) */ /* !!!!! Attention, cette fonction est différente selon qu’on utilise le program me sur Sparc ou x86 (registre %g0) */ void instrumentationSortieRegistre(int identificateurRessource) { unsigned int identificateurRegistreTournant; /* Mise à jour du compteur de résultat : le registre identificateurRessource e st considéré comme un résultat de l’instruction instCourante */ instCourante−>donnees−>valeurMorte++; /* Si le registre dans lequel on écrit est le numéro 41 (correspondant à %g0), on n’en tient pas compte car ce registre est un trou noir (quelque soit ce qui y est écrit, on lit toujours la valeur 0 dans ce registre) */ if(identificateurRessource != ID_REG_SALTO_G) { /* Si le registre accédé fait partie de la fenêtre de registres tournante */ if(identificateurRessource>=ID_REG_SALTO_O && identificateurRessource<=ID_RE G_SALTO_I+7) { identificateurRegistreTournant = (72−identificateurRessource)+(niveauFenet reRegistre*OFFSET_FENETRE); if(identificateurRegistreTournant>=NB_REGISTRES_TOURNANTS) { fprintf(stderr,"Nombre maximum de registres tournants dépassé, veulliez augmenter la valeur de "); fprintf(stderr,"la constante NB_REGISTRES_TOURNANTS dans le fichier instrument.cn"); exit(1); } if(tableLectureRegistresTournants[identificateurRegistreTournant] == 0 && tableRegistresTournants[identificateurRegistreTournant]−>utiliteInst == INUTILE) tableRegistresTournants[identificateurRegistreTournant]−>donnees−>val eurMorte−−; tableRegistresTournants[identificateurRegistreTournant] = instCourante; tableLectureRegistresTournants[identificateurRegistreTournant] = 0; } /* Sinon, on le considère comme un registre fixe */ else { Imprimé par Benjamin Vidal if(tableLectureRegistres[identificateurRessource] == 0 && tableRegistres[identificateurRessource]−>utiliteInst == INUTILE) tableRegistres[identificateurRessource]−>donnees−>valeurMorte−−; tableRegistres[identificateurRessource] = instCourante; tableLectureRegistres[identificateurRessource] = 0; } } } void lectureMemoireOctet(int adresseMemoire) { int i; elementMemoire *a; /*static elementMemoire *lecturePrecedente = NULL;*/ /* Si l’adresse accédée est consécutive à la précédente, alors on initialise a à lecturePrecedente−>suivant */ /* Sinon, on parcourt la liste chainée pour trouver l’élément mémoire corespon dant */ /*if((lecturePrecedente−>adresse)+1 == adresseMemoire) a=lecturePrecedente−>su ivant; else*/ for(a=adresseInitiale; a==NULL || adresseMemoire>a−>adresse; a=a−>suivant) if(a==NULL) { /*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’ins truction %d (fichier %d)n", adresseMemoire, instCourante−>donnees−>numeroStatique, instCourante−>don nees−>numeroFichier);*/ return; } if(adresseMemoire != a−>adresse) { /*fprintf(stderr,"Accès à l’adresse mémoire non initialisée %d par l’instruc tion %d (fichier %d)n", adresseMemoire, instCourante−>donnees−>numeroStatique, instCourante−>donnees −>numeroFichier);*/ return; } for(i=0; (instCourante−>donnees−>origineOperandes)[i]!=NULL; i++) if (i >= MAX_NB_OPERANDES) { fprintf(stderr,"Nombre maximum d’opérandes pour une instruction dépassé, veulliez augmenter la va leur de "); fprintf(stderr,"la constante MAX_NB_OPERANDES dans le fichier instrument.cn"); exit(8); } (instCourante−>donnees−>origineOperandes)[i] = a−>derniereEcriture; a−>nbLecture++; /*lecturePrecedente = a;*/ } 18 jun 03 14:19 instrument_optim.c Page 8/10 mercredi 18 juin 2003 instrument_optim.c 25/37
  • 86.
    18 jun 0314:19 instrument_optim.c Page 9/10 void instrumentationEntreeMemoire(int adresseMemoire, int nbOctetsLus) { int offset; for(offset=0; offset<nbOctetsLus; offset++) lectureMemoireOctet(adresseMemoire +offset); } void ecritureMemoireOctet(int adresseMemoire) { elementMemoire *nouveau, *tmp = adresseInitiale; /* Mise à jour du compteur de résultat : la case mémoire adresseMemoire est co nsidéré comme un résultat de l’instruction instCourante */ instCourante−>donnees−>valeurMorte++; /* Cas ou la liste chainée est vide : premier accès en écriture à la mémoire * / if(adresseInitiale == NULL) { adresseInitiale = malloc(sizeof(elementMemoire)); if(adresseInitiale == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); exit(10); } adresseInitiale−>adresse = adresseMemoire; adresseInitiale−>nbLecture = 0; adresseInitiale−>derniereEcriture = instCourante; adresseInitiale−>suivant = NULL; } else { /* Cas ou l’insertion de données concerne le premier élément de la liste cha inée */ if(adresseMemoire <= adresseInitiale−>adresse) { if(adresseInitiale−>adresse == adresseMemoire) { if(adresseInitiale−>nbLecture == 0 && (adresseInitiale−>derniereEcriture )−>utiliteInst == INUTILE) (adresseInitiale−>derniereEcriture)−>donnees−>valeurMorte−−; adresseInitiale−>nbLecture = 0; adresseInitiale−>derniereEcriture = instCourante; } else { nouveau = malloc(sizeof(elementMemoire)); if(nouveau == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); exit(10); } nouveau−>adresse = adresseMemoire; nouveau−>nbLecture = 0; nouveau−>derniereEcriture = instCourante; 18 jun 03 14:19 instrument_optim.c Page 10/10 nouveau−>suivant = adresseInitiale; adresseInitiale = nouveau; } } /* Cas général d’insertion d’un élément dans la liste chainée */ else { /* On parcours la liste chainée jusqu’a trouver l’endroit ou il faut insér er la donnée */ while(tmp−>suivant != NULL && adresseMemoire >= tmp−>suivant−>adresse) t mp = tmp−>suivant; /* Si l’adresse mémoire à déjà était accédée par le passé, on modifie le p ointeur sur l’instruction qui a écrit dernièrement dans cette zone mémoire */ if(tmp−>adresse == adresseMemoire) { if(tmp−>nbLecture == 0 && (tmp−>derniereEcriture)−>utiliteInst == INUTIL E) (tmp−>derniereEcriture)−>donnees−>valeurMorte−−; tmp−>nbLecture = 0; tmp−>derniereEcriture = instCourante; } /* Sinon, on créé une nouvelle cellule représentant cette zone mémoire et on l’insère dans la liste chainée */ else { nouveau = malloc(sizeof(elementMemoire)); if(nouveau == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction ecritureMemoireOctet dans le fichier instrument.c)n"); exit(10); } nouveau−>adresse = adresseMemoire; nouveau−>nbLecture = 0; nouveau−>derniereEcriture = instCourante; nouveau−>suivant = tmp−>suivant; tmp−>suivant = nouveau; } } } } void instrumentationSortieMemoire(int adresseMemoire, int nbOctetsEcrits) { int offset; for(offset=0; offset<nbOctetsEcrits; offset++) ecritureMemoireOctet(adresseMem oire+offset); } Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument_optim.c 26/37
  • 87.
    19 mai 0317:16 instrumentation2.cc Page 1/9 #include <stdio.h> #include <fcntl.h> #include <errno.h> #include <fstream> #include <iostream> #include <sys/types.h> #include <regex.h> #include <stdlib.h> #include <string.h> #include "salto.h" #include "instrument.h" #define FICHIER_SOURCE_ASSEMBLEUR ".s$" #define REPERTOIRE_FICHIER_INSTRUMENTES "instrumente2/" #define SAVE "save %sp,−136,%sp" #define RESTORE "restore %g0,%g0,%g0" #define NOP "nop" #define OFFSET_INSTRUMENTATION "16" #define OFFSET_INSTRUMENTATION_2 "28" #define OFFSET_INSTRUMENTATION_DELAY_SLOT "64" #define OFFSET_2_INSTRUMENTATION_DELAY_SLOT "44" #define OFFSET_INSTRUMENTATION_ANNUL_BIT "76" #define EXP_ANNUL_BIT ",a[ ]+" #define ETIQUETTE_FONCTION_NOP "f_nop" // Pointeur sur le fichier dans lequel sera écrit le code instrumenté FILE *fichierSInstrumente; // Pointeur sur le fichier contenant le code original (non instrumenté) FILE *fichierSOriginal; // Permet d’identifier de manière unique le fichier en cours de traitement unsigned char numeroFichier; // Fonction permettant de sauvagarder le contexte du programme (registres généra ux et codes conditions) afin de ne pas // intervenir sur les valeurs des registres et codes conditions utilisés par le programme void sauvegardeContexte(BB *bb, int position, unsigned int *nbInstructionsAjoute es) { INST *instCCR; char *lectureCodesConditions = "trd %ccr,%l0n"; // "Empilement" du contexte du programme (création d’un contexte intermédiaire artificiel entre l’exécution du // programme et l’exécution des fonctions d’instrumentation du code. Ce contex te permet de travailler avec // les registres %o[0−5] afin de faire passer les paramètres aux fonctions d’i nstrumentations. bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(SAVE)); 19 mai 03 17:16 instrumentation2.cc Page 2/9 (*nbInstructionsAjoutees)++; instCCR = newAsm(NOP); instCCR−>addAttribute(UNPARSE_ATT, lectureCodesConditions, strlen(lectureCodes Conditions)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, instCCR); (*nbInstructionsAjoutees)++; // Sauvegarde en mémoire (dans la pile) des registres globaux for(int i=1; i<=4; i++) { char tmp[20]; // Sauvegarde du registre (%gi) (ex : "st %g1,[%sp+92]") sprintf(tmp,"st %%g%d,[%%sp+%d]",i,88+(4*i)); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); (*nbInstructionsAjoutees)++; } } // Fonction permettant de restaurer le contexte du programme (registres généraux et codes conditions) afin de ne pas // intervenir sur les valeurs des registres et codes conditions utilisés par le programme void restaurationContexte(BB *bb, int position, unsigned int *nbInstructionsAjou tees) { INST *instCCR; char *ecritureCodesConditions = "twr %l0,%ccrn"; // Récupération des registres globaux depuis la mémoire (depuis la pile) for(int i=1; i<=4; i++) { char tmp[20]; // Restauration du registre (%gi) (ex : "ld [%sp+92],%g1") sprintf(tmp,"ld [%%sp+%d],%%g%d",88+(4*i),i); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(tmp)); (*nbInstructionsAjoutees)++; } instCCR = newAsm(NOP); instCCR−>addAttribute(UNPARSE_ATT, ecritureCodesConditions, strlen(ecritureCod esConditions)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, instCCR); (*nbInstructionsAjoutees)++; // "Dépilement" du contexte du programme bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(RESTORE)); (*nbInstructionsAjoutees)++; } int estPresent(char *motif, char *chaine) { int i; regex_t *preg = new regex_t(); size_t nmatch = 10; regmatch_t pmatch[nmatch]; Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation2.cc 27/37
  • 88.
    19 mai 0317:16 instrumentation2.cc Page 3/9 // Compilation de l’expression régulière if (regcomp(preg, motif, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière "%s"n",motif); exit(4); } // Exécution de l’expression régulière et renvoi du résultat en fonction if (regexec(preg, chaine, nmatch, pmatch, 0) == REG_NOMATCH) { regfree(preg); return 0; } for(i=0; i<nmatch && pmatch[i].rm_so!=−1; i++); regfree(preg); return i; } int typeInstruction(INST *inst) { if(inst−>isCTI()) if(estPresent(EXP_ANNUL_BIT,inst−>unparse())) return T_BRANCHEMENT_ANNUL_BIT ; else return T_BRANCHEMENT; return T_AUTRE; } void appelDeFonctionInst(INST *inst, BB *bb, int position, unsigned int *nbInstr uctionsAjoutees, int numeroInst) { static unsigned char flagDelaySlot=0; char chaine[20],tmp[100], *lecturePC = "trd %pc,%o1n"; static INST *dernierBranchement; if(!flagDelaySlot) sauvegardeContexte(bb, position, nbInstructionsAjoutees); // On empile un paramètre de type entier à passer à la fonction (équivaut à mo v typeInst,%o0) sprintf(chaine,"or %%g0,%d,%%o0",typeInstruction(inst)); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Si la constante numeroInst à ranger dans %o1 peut être codée sur 13 bits (i .e. est entre −4096 et 4095) if(numeroInst <= 4095) { // On empile un paramètre de type entier à passer à la fonction (équivaut à mov numeroInst,%o1) sprintf(chaine,"or %%g0,%d,%%o1",numeroInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; } else { // On empile ce même paramètre mais en deux fois (les 22 premiers bits du re Imprimé par Benjamin Vidal 19 mai 03 17:16 instrumentation2.cc Page 4/9 gistre d’abbord) sprintf(chaine,"sethi %%hi(%d),%%o1",numeroInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Puis les 10 derniers bits ensuite sprintf(chaine,"or %%o1,%%lo(%d),%%o1",numeroInst); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; } // On empile un paramètre de type entier à passer à la fonction (équivaut à mo v numeroFichier,%o2) sprintf(chaine,"or %%g0,%d,%%o2",numeroFichier); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(chaine)); (*nbInstructionsAjoutees)++; // Si le branchement est un brachement avec annul_bit (ex : bl,a ... ) alors on rempli la chaine de caractère // chaineAnnulBit en conséquence (on y met les instructions pour tra iter ce cas correctement) if(flagDelaySlot && typeInstruction(dernierBranchement)==T_BRANCHEME NT_ANNUL_BIT) { int i; char *chaineAnnulBit = (char *)malloc(100*sizeof(char)); regex_t *preg = new regex_t(); size_t nmatch = 1; regmatch_t pmatch[nmatch]; INST *inst_br_nop; if (regcomp(preg, EXP_ANNUL_BIT, REG_EXTENDED)) { fprintf(STDERR,"Erreur lors de la compilation de l’expression régulière ""EXP_ANNUL _BIT""n"); exit(4); } if (regexec(preg, dernierBranchement−>unparse(), nmatch, pmatch, 0 ) == REG_NOMATCH) { fprintf(STDERR,"Erreur lors de l’exécution de l’expression régulière ""EXP_ANNUL_B IT"" :n"); fprintf(STDERR,"Impossible de trouver l’expression régulière dans l’instruction "%s"n ",dernierBranchement−>unparse()); exit(4); } regfree(preg); for(i=0; i<pmatch[0].rm_eo; i++) tmp[i] = (dernierBranchement−>unparse())[i]; tmp[i] = ’0’; strcpy(chaineAnnulBit,"trd %pc,%o7n"); strcat(chaineAnnulBit,"tadd %o7,"OFFSET_INSTRUMENTATION_2",%o7n"); strcat(chaineAnnulBit,"twr %l0,%ccrn"); strcat(chaineAnnulBit,tmp); strcat(chaineAnnulBit," "ETIQUETTE_FONCTION_NOP"n"); mercredi 18 juin 2003 instrumentation2.cc 28/37
  • 89.
    19 mai 0317:16 instrumentation2.cc Page 5/9 inst_br_nop = newAsm(NOP); inst_br_nop−>addAttribute(UNPARSE_ATT, chaineAnnulBit, strlen(chai neAnnulBit)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, inst_br_nop); (*nbInstructionsAjoutees)++; strcpy(tmp,"b "); } else strcpy(tmp,"call "); bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(strcat(tmp,NOM_FCT_INST ))); (*nbInstructionsAjoutees)++; // On ajoute un nop afin de combler le delay slot bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; if(flagDelaySlot && typeInstruction(dernierBranchement)==T_BRANCHEME NT_ANNUL_BIT) { INST *instLecturePC; instLecturePC = newAsm(NOP); instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lectur ePC)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); (*nbInstructionsAjoutees)++; bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o1+" OFFSET_INSTRUMENTATION_ANNUL_BIT",%g0")); (*nbInstructionsAjoutees)++; bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; } if(!flagDelaySlot) { if(!inst−>isCTI()) { INST *instLecturePC; instLecturePC = newAsm(NOP); instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lecturePC)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); (*nbInstructionsAjoutees)++; // A ce stade, %o0 contient le resultat de la fonction NOM_FCT_INST et %o1 contient le PC à l’instant t−1 // (instruction précédente à celle−ci) bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("add %o1,%o0,%o0")); (*nbInstructionsAjoutees)++; Imprimé par Benjamin Vidal 19 mai 03 17:16 instrumentation2.cc Page 6/9 bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o0+"OFFSET_IN STRUMENTATION",%g0")); (*nbInstructionsAjoutees)++; bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; restaurationContexte(bb, position, nbInstructionsAjoutees); } else { // On met le flagDelaySlot à 1 pour signaler que la prochaine instruction se trouvera dans // le DelaySlot de celle−ci flagDelaySlot=1; // On conserve dans une variable statique un pointeur sur l’instruction en cours de traitement dernierBranchement = inst−>copy(); } } else { // On traite l’instruction se trouvant dans le DelaySlot du précédent CTI (b ranchement) INST *instLecturePC, *instBranchement; // On remet le flagDelaySlot à zéro pour le prochain passage flagDelaySlot=0; instLecturePC = newAsm(NOP); instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lecturePC)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); (*nbInstructionsAjoutees)++; // Dans le registre %o0, se trouve 0 si on n’a pas à annuler l’instruction e t 52 si on doit l’annuler (inutile) // subcc %o0,0,%g0 <=> cmp %o0,0 bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("subcc %o0,0,%g0")); (*nbInstructionsAjoutees)++; bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("be,a f_nop")); (*nbInstructionsAjoutees)++; // On utilise un jmpl dans le delay slot du brnz,a pour pouvoir brancher sur une adresse contenue dans un registre // (impossible si l’on utilise brnz,a directement car il faut lui fournir un e étiquette) bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o1+"OFFSET_INST RUMENTATION_DELAY_SLOT",%g0")); (*nbInstructionsAjoutees)++; // A mon avis, ce nop n’est jamais exécuté puisque l’instruction se trouvant dans un delay slot d’une instruction // se trouvant elle même dans un delay slot n’est jamais exécutée... bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; restaurationContexte(bb, position, nbInstructionsAjoutees); mercredi 18 juin 2003 instrumentation2.cc 29/37
  • 90.
    19 mai 0317:16 instrumentation2.cc Page 7/9 // Insertion d’une instruction équivalente à celle représentant le brancheme nt dont on est en train de traiter // le DelaySlot bb−>insertAsm(*nbInstructionsAjoutees+position, dernierBranchement); (*nbInstructionsAjoutees)++; // On insère un nop dans le Delay Slot puisque l’instruction se trouvant nor malement dans le delay slot de ce // branchement est jugée inutile bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; // On lit le PC instLecturePC = newAsm(NOP); instLecturePC−>addAttribute(UNPARSE_ATT, lecturePC, strlen(lecturePC)+1); bb−>insertAsm(*nbInstructionsAjoutees+position, instLecturePC); (*nbInstructionsAjoutees)++; // Et on branche après sur la suite du programme en sautant le branchement e t son delay slot dans le cas utile bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm("jmpl %o1+"OFFSET_2_IN STRUMENTATION_DELAY_SLOT",%g0")); (*nbInstructionsAjoutees)++; bb−>insertAsm(*nbInstructionsAjoutees+position, newAsm(NOP)); (*nbInstructionsAjoutees)++; restaurationContexte(bb, position, nbInstructionsAjoutees); } } void instrumenter(INST *inst, BB *bb, BB *bbSuivant, int position, unsigned int *nbInstructionsAjoutees, int numeroInst) { static unsigned char flagDelaySlot = 0; // Si on n’a pas à faire à une instruction se trouvant dans un DelaySlot if(!flagDelaySlot) // Si l’instruction n’est pas un branchement, alors, on instrumente cette in struction normalement if(!inst−>isCTI()) appelDeFonctionInst(inst, bb, position, nbInstructionsAjoutees, numeroInst ); // Si l’instruction courante est un branchement, alors, on l’instrumente ain si que son DelaySlot else { INST *instDelaySlot = bbSuivant−>getAsm(0); // On met le flag à 1 pour indiquer que l’instrumentation de l’instruction se trouvant dans le DelaySlot // du branchement que l’on est en train de traiter a déjà été instrumentée flagDelaySlot = 1; /*fprintf(STDERR,"Inst <%s>tttDelay <%s>n",inst−>unparse(),instDelaySl ot−>unparse());*/ // On traite l’instruction de branchement appelDeFonctionInst(inst, bb, position, nbInstructionsAjoutees, numeroInst ); Imprimé par Benjamin Vidal // On traite l’instruction qui se trouve dans le DelaySlot appelDeFonctionInst(instDelaySlot, bb, position, nbInstructionsAjoutees, n umeroInst+1); } else flagDelaySlot = 0; } void Salto_hook() { CFG *proc; BB *bb, *bbSuivant; INST *inst; int numeroInst = 0; unsigned int nbInstructionsAjoutees; // Parcours du programme permettant d’insérer le code d’instrumentation des in structions for (int i=0; i < numberOfCFG(); i++) { proc = getCFG(i); for (int j=0; j < proc−>numberOfBB(); j++) { bb = proc−>getBB(j); bbSuivant = proc−>getBB(j+1); int nbAsm = bb−>numberOfAsm(); nbInstructionsAjoutees = 0; for (int k=0; k < nbAsm; k++) { numeroInst++; inst = bb−>getAsm(k+nbInstructionsAjoutees); instrumenter(inst, bb, bbSuivant, k, &nbInstructionsAjoutees, numeroInst ); } } } // Parcours du programme permettant d’insérer le code pour l’initialisation de s variables globales for (int i=0; i < numberOfCFG(); i++) { proc = getCFG(i); if(!strcmp(proc−>getName(),"main")) { // Insertion d’un appel à la procédure initialisant les variables globales (proc−>getBB(0))−>insertAsm(0, newAsm("call initVariablesGlobales")); (proc−>getBB(0))−>insertAsm(1, newAsm(NOP)); } } // Envoi du code instrumenté vers la sortie standard produceCode(fichierSInstrumente); } 19 mai 03 17:16 instrumentation2.cc Page 8/9 mercredi 18 juin 2003 instrumentation2.cc 30/37
  • 91.
    void Salto_init_hook(int argc,char *argv[]) { int i,j,k; char nomFichierSortie[100]; // Récupération dans la ligne de commande entrée par l’utilisateur du nom du f ichier original for(i=1; i<argc && !estPresent("−i",argv[i]); i++); if(i == argc−1) { fprintf(STDERR,"Erreur, votre ligne de commande ne comporte pas l’option "−i"n"); exit(6); } // On ouvre le fichier .s original à traiter pour pouvoir s’en servir dans sal to_hook() fichierSOriginal = fopen(argv[i+1],"r"); if(fichierSOriginal == NULL) { fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",argv[i+1]); fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation2.cc)n"); exit(11); } nomFichierSortie[0]=’0’; strcat(nomFichierSortie,REPERTOIRE_FICHIER_INSTRUMENTES); strcat(nomFichierSortie,argv[i+1]); // On ouvre le fichier .s instrumenté en écriture à traiter pour pouvoir s’en servir dans salto_hook() fichierSInstrumente = fopen(nomFichierSortie,"w"); if(fichierSInstrumente == NULL) { fprintf(STDERR,"Problème lors de l’ouverture du fichier "%s" ",nomFichierSortie); fprintf(STDERR,"(fonction Salto_init_hook dans le fichier instrumentation2.cc)n"); exit(11); } // Recherche de l’expression "−−" dans la ligne de commande for(; i<argc && !estPresent("−−",argv[i]); i++); // Si cette expression n’est pas présente, on ne numérote pas les fichiers if(i == argc−1) numeroFichier = 0; else numeroFichier = atoi(argv[i+1]); } void Salto_end_hook() { exit(0); } 19 mai 03 17:16 instrumentation2.cc Page 9/9 Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrumentation2.cc 31/37
  • 92.
    19 mai 0315:50 instrument2.c Page 1/3 #include <stdio.h> #include <stdlib.h> #include "instrument.h" /* Constantes utiles pour inhiber l’action des instructions dynamiques inutiles : Si l’on doit annuler l’instruction, on renvoit la constante 68 permettant de sauter l’instruction inutile ainsi que son instrumentation. Si l’on ne doit pas annuler l’instruction, on renvoit la constante 16 permettant de faire sauter le PC à la suite du programme (instruction à ne pas annuler e t son instrumentation) */ #define ANNUL 52 #define NON_ANNUL 0 typedef unsigned char flag; typedef struct instru { unsigned int numeroDynamique; struct instru *precedent; } instruction; instruction *instInitiale; instruction *instCourante = NULL; unsigned int tailleListeInstInutile; unsigned int *listeInstInutile; void initVariablesGlobales(void) { int i; char c; FILE *traceInstructionsInutiles; /* Création d’une instruction initiale fictive sur laquelle vont pointer celle s qui utilisent des registres déjà initialisés lors du lancement du programme */ instInitiale = malloc(sizeof(instruction)); if(instInitiale == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument2.c)n"); exit(10); } instInitiale−>numeroDynamique = 0; instInitiale−>precedent = NULL; instCourante = instInitiale; tailleListeInstInutile = 0; traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"r"); if(traceInstructionsInutiles == NULL) { fprintf(stderr,"Problème lors de l’ouverture du fichier ""TRACE_INSTRUCTIONS_INUTILES"" " 19 mai 03 15:50 instrument2.c Page 2/3 ); fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument2.c)n"); exit(11); } /* On compte le nombre de lignes dans le fichier */ do if(fgetc(traceInstructionsInutiles)==’n’) tailleListeInstInutile++; while(!feof(traceInstructionsInutiles)); fclose(traceInstructionsInutiles); listeInstInutile = malloc(tailleListeInstInutile*sizeof(unsigned int)); traceInstructionsInutiles = fopen(TRACE_INSTRUCTIONS_INUTILES,"r"); if(traceInstructionsInutiles == NULL) { fprintf(stderr,"Problème lors de l’ouverture du fichier ""TRACE_INSTRUCTIONS_INUTILES"" " ); fprintf(stderr,"(fonction initVariablesGlobales dans le fichier instrument2.c)n"); exit(11); } for(i=tailleListeInstInutile−1; i>=0; i−−) fscanf(traceInstructionsInutiles, " %u", &listeInstInutile[i]); } /* Création de la représentation des instructions dynamiques en mémoire */ int instrumentationInstruction(int typeInstruction, int numeroStatique, int nume roFichier) { int i; static int indiceListeInstructions = 0; instruction *tmp = malloc(sizeof(instruction)); if(tmp == NULL) { fprintf(stderr,"Taille mémoire maximale allouable dépassée !! "); fprintf(stderr,"(fonction instrumentationInstruction dans le fichier instrument2.c)n"); exit(10); } if(instCourante−>numeroDynamique >= MAX_NB_INST_DYNAMIQUE) { fprintf(stderr,"Nombre maximum d’instruction dépassé, veulliez augmenter la valeur de la constante ") ; fprintf(stderr,"MAX_NB_INST_DYNAMIQUE dans le fichier instrument.hn"); exit(1); } tmp−>numeroDynamique = instCourante−>numeroDynamique+1; tmp−>precedent = instCourante; instCourante = tmp; fprintf(stderr,"I.Dynamic %dt−− I.Static %dt−− File %d",instCourante−>numeroDynamique,n umeroStatique,numeroFichier); if(typeInstruction == T_BRANCHEMENT) { Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument2.c 32/37
  • 93.
    fprintf(stderr,"t−− Branchn"); returnNON_ANNUL; } if(typeInstruction == T_BRANCHEMENT_ANNUL_BIT) { fprintf(stderr,"t−− Branch annul bitn"); return NON_ANNUL; } /* Si le numéro de l’instruction dynamique en cours de traitement est le même que le dernier numéro testé comme étant une instruction inutile, alors on retourne la valeur ANNU L */ if(instCourante−>numeroDynamique == listeInstInutile[indiceListeInstructions] && indiceListeInstructions < tailleListeInstInutile) { indiceListeInstructions++; fprintf(stderr,"t−− Non exécutéen"); return ANNUL; } fprintf(stderr,"n"); return NON_ANNUL; } 19 mai 03 15:50 instrument2.c Page 3/3 Imprimé par Benjamin Vidal mercredi 18 juin 2003 instrument2.c 33/37
  • 94.
    21 avr 0316:53 redefinition.c Page 1/7 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <ctype.h> #include <errno.h> #include <sys/stat.h> #include <sys/types.h> #include "instrument.h" void *my_memset(void *p, int i, size_t taille) { echangerInstDelay(); instrumentationSortieMemoire((int)p, taille); instrumentationInstructionFin(); echangerInstDelay(); return memset(p, i, taille); } void *my_memcpy(void *p1, const void *p2, size_t taille) { echangerInstDelay(); instrumentationEntreeMemoire((int)p2, taille); instrumentationSortieMemoire((int)p1, taille); instrumentationInstructionFin(); echangerInstDelay(); return memcpy(p1, p2, taille); } int my_memcmp(const void *p1, const void *p2, size_t taille) { echangerInstDelay(); instrumentationEntreeMemoire((int)p1, taille); instrumentationEntreeMemoire((int)p2, taille); instrumentationInstructionFin(); echangerInstDelay(); return memcmp(p1, p2, taille); } int my_strcmp(const char *c1, const char *c2) { echangerInstDelay(); instrumentationEntreeMemoire((int)c1, strlen(c1)+1); instrumentationEntreeMemoire((int)c2, strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return strcmp(c1, c2); } int my_strncmp(const char *c1, const char *c2, size_t taille) { echangerInstDelay(); instrumentationEntreeMemoire((int)c1, taille); instrumentationEntreeMemoire((int)c2, taille); instrumentationInstructionFin(); 21 avr 03 16:53 redefinition.c Page 2/7 echangerInstDelay(); return strncmp(c1, c2, taille); } size_t my_strlen(const char *c) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); return strlen(c); } char *my_strncpy(char *c1, const char *c2, size_t taille) { echangerInstDelay(); instrumentationEntreeMemoire((int)c2, taille); instrumentationSortieMemoire((int)c1, taille); instrumentationInstructionFin(); echangerInstDelay(); return strncpy(c1, c2, taille); } char *my_strcpy(char *c1, const char *c2) { echangerInstDelay(); instrumentationEntreeMemoire((int)c2, strlen(c2)+1); instrumentationSortieMemoire((int)c1, strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return strcpy(c1, c2); } char *my_strcat(char *c1, const char *c2) { echangerInstDelay(); instrumentationEntreeMemoire((int)c1, strlen(c1)+1); instrumentationEntreeMemoire((int)c2, strlen(c2)+1); instrumentationSortieMemoire(((int)c1)+strlen(c1), strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return strcat(c1, c2); } char *my_strrchr(const char *c, int i) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); return strrchr(c, i); } size_t my_strcspn(const char *c1, const char *c2) { Imprimé par Benjamin Vidal mercredi 18 juin 2003 redefinition.c 34/37
  • 95.
    21 avr 0316:53 redefinition.c Page 3/7 echangerInstDelay(); instrumentationEntreeMemoire((int)c1, strlen(c1)+1); instrumentationEntreeMemoire((int)c2, strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return strcspn(c1, c2); } size_t my_strspn(const char *c1, const char *c2) { echangerInstDelay(); instrumentationEntreeMemoire((int)c1, strlen(c1)+1); instrumentationEntreeMemoire((int)c2, strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return strspn(c1, c2); } void *my_malloc(size_t taille) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return malloc(taille); } void *my_calloc(size_t taille1, size_t taille2) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return calloc(taille1, taille2); } void my_free(void *p) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); free(p); } char *my_getenv(const char *c) { char *c2 = getenv(c); echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); if(c2!=NULL) instrumentationSortieMemoire(((int)c2), strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return c2; } int my_atoi(const char *c) { 21 avr 03 16:53 redefinition.c Page 4/7 echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); return atoi(c); } int my_fileno(FILE *f) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return fileno(f); } /* Cette fonction ne comportant aucun pointeur, elle ne fait aucun accès à la mémoire utilisable par l’appelant */ int my_isatty(int i) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return isatty(i); } int my_fstat(int i, struct stat *s) { echangerInstDelay(); instrumentationEntreeMemoire((int)s, sizeof(s)); instrumentationSortieMemoire((int)s, sizeof(s)); instrumentationInstructionFin(); echangerInstDelay(); return fstat(i, s); } /* Cette fonction ne comportant aucun pointeur, elle ne fait aucun accès à la mémoire utilisable par l’appelant */ int my_close(int i) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return close(i); } void my_perror(const char *c) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); perror(c); } int my_unlink(const char *c) { Imprimé par Benjamin Vidal mercredi 18 juin 2003 redefinition.c 35/37
  • 96.
    21 avr 0316:53 redefinition.c Page 5/7 echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); return unlink(c); } int my_lstat(const char *c, struct stat *s) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationEntreeMemoire((int)s, sizeof(s)); instrumentationSortieMemoire((int)s, sizeof(s)); instrumentationInstructionFin(); echangerInstDelay(); return lstat(c, s); } int my_stat(const char *c, struct stat *s) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationEntreeMemoire((int)s, sizeof(s)); instrumentationSortieMemoire((int)s, sizeof(s)); instrumentationInstructionFin(); echangerInstDelay(); return stat(c, s); } /* Cette fonction ne comportant aucun pointeur, elle ne fait aucun accès à la mémoire utilisable par l’appelant */ off_t my_lseek(int i, off_t off, int j) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return lseek(i, off, j); } ssize_t my_read(int i, void *p, size_t taille) { int taille_reelle = read(i, p, taille); echangerInstDelay(); instrumentationSortieMemoire((int)p, taille_reelle); instrumentationInstructionFin(); echangerInstDelay(); return taille_reelle; } ssize_t my_write(int i, const void *p, size_t taille) { int taille_reelle = write(i, p, taille); echangerInstDelay(); instrumentationEntreeMemoire((int)p, taille_reelle); instrumentationInstructionFin(); 21 avr 03 16:53 redefinition.c Page 6/7 echangerInstDelay(); return taille_reelle; } char *my_ctime(const time_t *time) { char *c = ctime(time); echangerInstDelay(); instrumentationEntreeMemoire((int)time, sizeof(time)); instrumentationSortieMemoire((int)c, 26); instrumentationInstructionFin(); echangerInstDelay(); return c; } int my_fflush(FILE *f) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); return fflush(f); } char *my_fgets(char *c, int i, FILE *f) { char *c2 = fgets(c, i, f); echangerInstDelay(); instrumentationSortieMemoire((int)c2, strlen(c2)+1); instrumentationInstructionFin(); echangerInstDelay(); return c2; } int my_chmod(const char *c, mode_t mode) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); return chmod(c, mode); } int my_utime(const char *c, const void *buf) { echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); /*instrumentationEntreeMemoire((int)buf, sizeof(struct utimbuf));*/ instrumentationInstructionFin(); echangerInstDelay(); return utime(c, buf); } int my_chown(const char *c, uid_t uid, gid_t gid) { Imprimé par Benjamin Vidal mercredi 18 juin 2003 redefinition.c 36/37
  • 97.
    echangerInstDelay(); instrumentationEntreeMemoire((int)c, strlen(c)+1); instrumentationInstructionFin(); echangerInstDelay(); return chown(c, uid, gid); } /* Cette fonction ne comportant aucun pointeur, elle ne fait aucun accès à la mémoire utilisable par l’appelant */ void my_exit(int i) { echangerInstDelay(); instrumentationInstructionFin(); echangerInstDelay(); exit(i); } 21 avr 03 16:53 redefinition.c Page 7/7 Imprimé par Benjamin Vidal mercredi 18 juin 2003 redefinition.c 37/37