Analyse statique et application. Typage. Analyse de flots de données. Exemples de transformations simples de programmes au niveau source. Prépa Agreg de Maths, option Info - ENS Cachan-Bretagne Thomas Gazagnaire thomas.gazagnaire@irisa.fr 14 février 2006
Plan 1 Introduction Définition du problème de l analyse statique Génération des (in)équations Résolution des (in)équations Preuves de correction 2 Analyse Data-Flow Introduction Techniques mises en jeu Exemple détaillé : Liveness Analysis 3 Typage Introduction Techniques mises en jeu 4 Transformations Exemple de transformation de programme guidé par l analyse statique
Plan 1 Introduction Définition du problème de l analyse statique Génération des (in)équations Résolution des (in)équations Preuves de correction 2 Analyse Data-Flow Introduction Techniques mises en jeu Exemple détaillé : Liveness Analysis 3 Typage Introduction Techniques mises en jeu 4 Transformations Exemple de transformation de programme guidé par l analyse statique
Définition du problème de l analyse statique Analyse statique, kesako? L analyse statique d un programme consiste à analyser ce programme sans l exécuter (en opposition avec l analyse dynamique). Consiste à annoter le programme avec des informations supplémentaires Vérification : vérifie que les annotations sont cohérentes entre elles et les précisent pour qu elles soient plus pertinentes Optimisation : utilise les annotations pour améliorer le programme en le transformant
Définition du problème de l analyse statique Exemples d analyses statique Annotations Vérification Optimisation variable initialisée non-déterminisme accès à un tableau segfault tests de borne signe des variables code mort constante pré-calculs données typées sûreté
Définition du problème de l analyse statique Séparation en 2 phases Interprétation abstraite qui génére un système d équations ou de contraintes Définition d un domaine abstrait D pour les variables Génération d un système d (in)égalité C en parcourant une représentantation adaptée du programme Preuves de correction Trouver une solution (la seule, la meilleure, la plus petite,...) Décidabilité de l analyse statique Dans le cas général, le problème de l analyse statique est indécidable. On se restreint à des analyses spécifiques et décidables.
Génération des (in)équations Définition du domaine abstrait D Definition D est l ensemble des propriétés que certaines parties du programmes peuvent vérifier D est une abstraction du comportement normal qui se focalise sur certaines activités du programme. En général on se focalise sur des propriétés liées aux variables. Exemples : D = {< 0, > 0, = 0, } pour les signes des variables D = {, } pour la définition des variables D est souvent un treillis (pour les preuves) Utilisation du produit cartésien pour complexifier les domaines (ex : signes des variables définies)
Génération des (in)équations Principe général Definition A chaque (bloc d ) instruction(s) du programme on va associer une (in)équation de variables prenant valeur dans D Il existe deux grandes familles de méthodes : les équations flot de données les systèmes d inférence
Génération des (in)équations Equations Data-Flow On manipule des équations sur des ensembles En chaque point du programme, on va dire comment l ensemble que l on regarde évolue en fonction des exécutions dynamiques possibles du programme Exemple : analyse de la validité des définitions p : a b + c out(p) : définitions valides après le point p in(p) : définitions valides avant le point p out(p) = [in(p) \ {p i p i : a...}] {p} Plus de détails dans la 2 e partie
Génération des (in)équations Système de règles d inférence Approche symbolique qui s inspire des règles de sémantique opérationnelle En chaque point du programme, on ajoute des nouvelles règles de raisonnement qui ne dépendent que de la structure de l instruction courante Exemple : typage simple Γ a : int Γ b : int Γ plus : int int int Γ plus(a, b) : int Plus de détails dans la 3 e partie
Résolution des (in)équations Résolution des (in)équations Il existe deux familles de méthodes : Celles basées sur la résolution symbolique : possibles quand Cd n est pas trop complexe Celles basées sur des résolutions itératives : on part d un élément extrémal et on converge vers un point fixe
Preuves de correction Preuves Il faut montrer que l abstraction est correcte et complète. C est pour cela qu on utilise un treillis pour D car on sait dire des choses intelligentes grâce aux connections de Galois Il faut montrer que l algorithme itératif converge. Or on sait que si f, à valeur dans un treillis, est monotone alors f à un plus petit point fixe. Et si f est continue : lpf(f) = i=0 f n ( ). Si le treillis est fini, on sait que ça converge Structure de D Si D à une structure de treillis fini on est content : les preuves sont presque déjà toutes faites
Preuves de correction Conclusion Séparation en 2 phases : Génération d un système (in)équations C à valeur sur D Résolution de C Preuves de corrections en kit si D à une structure de treillis fini
Plan 1 Introduction Définition du problème de l analyse statique Génération des (in)équations Résolution des (in)équations Preuves de correction 2 Analyse Data-Flow Introduction Techniques mises en jeu Exemple détaillé : Liveness Analysis 3 Typage Introduction Techniques mises en jeu 4 Transformations Exemple de transformation de programme guidé par l analyse statique
Introduction Introduction On veut calculer en chaque point d exécution du programme un certain nombre de propriétés qui portent sur les variables On veut que ce soit efficace pour de très gros programmes : en général on code la propriété ensembliste à vérifier sous forme de champ de bits Exemple : On a n variables x 1... x n dans notre programme et on veut savoir à tout moment lesquelles sont utile pour la suite. On code cela dans {0, 1} n. On prend des plus gros grains d exécution (plusieurs instructions au lieu d une seule). L analyse est moins précise, mais on peut l approfondir pour certains blocs
Introduction Exemples classiques Une explication avec les mains des exemples classiques : Analyse de vivacité (liveness analysis) P(x) = {variables qui sont utilisées après le point x} Validité des définitions (Reaching Definitions) P(x) = {instructions qui ont définis des variables utilisables au point x} Disponibilité des expressions (Available Expressions) P(x) = {expressions utilisables au point x}
Techniques mises en jeu Définition des ensembles Le programme est sous forme de Graphe de Flot de Contrôle (CFG) On manipule 4 ensembles Gen est l ensemble des propriétés que le grain courant va rendre vrai Kill est l ensemble des propriétés que le grain courant va rendre faux In est l ensemble des propriétés qui sont vraies juste avant le grain courant Out est l ensemble des propriétés qui sont vraies juste après le grain courant
Techniques mises en jeu Génération des équations 1 opérateur de jonction qui indique comment fusionner les informations. On utilise en général des vecteurs de bits, avec un opérateur de jonction adapté. All Path : ou AND Any Path : ou OR 2 analyses possibles In(b) = Out(p) Forward : p:prédecesseurs dynamiques de b Out(b) = Gen(b) (In(b) \ Kill(b)) Out(b) = In(s) Backward : s:successeurs dynamiques de b In(b) = Gen(b) (Out(b) \ Kill(b))
Exemple détaillé : Liveness Analysis Présentation Vivacité v est une variable vivante si elle est définie et qu elle est potentiellement utilisée plus tard. ie, une variable v est vivante si elle est utilisée dans l instruction courante et si il existe un chemin dans le passé vers une instruction qui définit v Utilisé pour repartir les variables dans les registres réels. Graphe d Interférence : deux variables interfèrent si elles sont vivantes en même temps Utilisé pour éliminer le code mort : une variable définie et pas utilisée est inutile
Exemple détaillé : Liveness Analysis Formalisation du domaine Kill(s) = Def(s) = { variables affectées (définies) au noeud s} Gen(s) = Use(s) = { variables utilisées au noeud s} In(s) = { variables qui sont vivantes à l entrée du noeud s} Out(s) = { variables qui sont vivantes en sortie du noeud s} Domaine abstrait de l analyse de vivacité D = P(V) ordonné par inclusion
Exemple détaillé : Liveness Analysis Formalisation de la génération d équations On veut quantifier sur l existence ou non d un chemin vers une définition : on utilise l opérateur de jonction On veut quantifier dans le passé : on utilise les équations d analyse en-arrière Out(b) = In(s) s:succ(b) In(b) = Gen(b) (Out(b) \ Kill(b)) Génération d équations Pour chaque instruction du programme on génére 2 équations ensemblistes. Les constantes sont les ensembles Gen(s) et Kill(s) pour tout s, et les variables sont In(s) et Out(s). Correction et Complétude?
Exemple détaillé : Liveness Analysis Formalisation de la résolution du système d équations On résoud par itération successives en partant de l ensemble vide pour chaque In et Out L ordre de réduction ne change pas le résultat MAIS une stratégie partant des noeuds finaux pour remonter aux noeuds initiaux est beaucoup plus efficace en pratique Résolution du système d équations Résolution par itération jusqu à atteindre un plus petit point fixe. L algorithme converge car D est un treillis fini, f est monotone.
Exemple détaillé : Liveness Analysis Exemple On veut faire l analyse du code suivant : a = 0; L 1 b = a + 1 c = c + b a = b 2 if a < N goto L 1 return c
Exemple détaillé : Liveness Analysis Exemple : CFG On construit (au tableau) le CFG du programme B 1 : a = 0; B 2 : b = a + 1 c = c + b a = b 2 if a < N B 3 : return c B 1 B 2, B 2 B 3 et B 2 B 2
Exemple détaillé : Liveness Analysis Exemple : Résolution state use def 1 out 1 in 2 out 2 in 3 out 3 in 6 c c c c 5 a c ac ac ac ac ac 4 b a ac bc ac bc ac bc 3 bc c bc bc bc bc bc bc 2 a b bc ac bc ac bc ac 1 a ac c ac c ac c
Exemple détaillé : Liveness Analysis Conclusion D est une famille d ensembles var = {In(s), Out(s)} s et cst = {Gen(s), Kill(s)}. C = f(var, cst) Résolution : itération de F n (In(s), Out(s) ) Méthodes d accélération : opérations sur les codages plutôt que les ensembles
Plan 1 Introduction Définition du problème de l analyse statique Génération des (in)équations Résolution des (in)équations Preuves de correction 2 Analyse Data-Flow Introduction Techniques mises en jeu Exemple détaillé : Liveness Analysis 3 Typage Introduction Techniques mises en jeu 4 Transformations Exemple de transformation de programme guidé par l analyse statique
Introduction Les types, kesako? Renseignements sur les données du programme Abstraction (Curry) Spécification (Church) Associé à la valeur de la variable ou à son nom Reconnaissance des erreurs de typage Statiquement Lors de l exécution / Pas toujours Fortes garanties lors de l exécution
Introduction Exemples Nombreux systèmes de type type simple int, float,... types énumérés bool type somme en Caml types composés array, list pointeurs, fonctions, objets,...
Techniques mises en jeu Type-Checking D = {int, float, char, t t} Uniquement des contantes de type qui sont tous spécifiés par l utilisateur Vérification de l équivalence de type C = {t 1 = t 2 } Résolution simple car juste test d égalité de structures d autres techniques dures à faire entrer dans le cadre Conversions explicites (cast) Conversions implicites (coercion)
Techniques mises en jeu Type-Inference Constantes de type + Variables de type L utilisateur précise le moins de choses possibles, le compilateur doit deviner Le compilateur associe une variable de type T x à chaque variable x du programme Chaque constructeur génère de nouvelles contraintes sur les T x On veut savoir si le programme est typable, ie. trouver Φ : T x > c ste qui réalise C
Techniques mises en jeu Types monomorphes On associe à chaque variable x du programme une variable de type T x dans un environnement de typage Γ. On a un ensemble d équations C de type qu on génère en parcourant le terme à typer. Exemple : type de fun x -> x + 1 dans Γ = on type x + 1 dans Γ = {x : T x } et C = :? on type 1 dans Γ = {x : T x } et C = : int on type x dans Γ = {x : T x } et C = : T x on type x+1 dans Γ = {x : T x } et C = {T x = int} : int on type fun x -> x+1 dans Γ = et C = : int int Ici on a mélangé la génération et la résolution comme dans beaucoup d algo de résolution de système mais on peut faire plus propre
Techniques mises en jeu Unification La résolution des équations de type, c est le même algorithme que pour l unification en programmation logique! A 1 (int A 2 ) = (A 3 bool) A 1 se traduit en formule logique : fleche(a 1, fleche(int, A 2 ) = fleche(fleche(a 3, bool), A 1 ) Algo d unification une constante s unifie uniquement avec elle même 2 structures s unifient si elles ont le même nom, le même nombre d arguments et si les arguments s unifient entre eux une variable s unifie avec n importe quoi. Si l autre chose est une constante, la variable est instanciée. Si l autre chose est une variables, elles deviennent liées.
Techniques mises en jeu Type polymorphes on veut pouvoir typer : let f = fun x -> x in (f 5, f "toto") (qui est typable en caml) types polymorphes : on rajoute un niveau, les scémas de type t = cst var t t et s = var [t] vars Γ associe un schéma S x à chaque variable x algorithmes qui mélangent génération d équations et résolutions quand on rencontre un let (comme dans W,J,...) mais on peut aussi distinguer les 2 phases pour retomber sur le cadre général
Techniques mises en jeu Conclusion D = {t} C = { contraintes de type } Résolution : unification et itération avec différentes stratégies d évaluation
Plan 1 Introduction Définition du problème de l analyse statique Génération des (in)équations Résolution des (in)équations Preuves de correction 2 Analyse Data-Flow Introduction Techniques mises en jeu Exemple détaillé : Liveness Analysis 3 Typage Introduction Techniques mises en jeu 4 Transformations Exemple de transformation de programme guidé par l analyse statique
Exemple de transformation de programme guidé par l analyse statique On veut analyser et optimiser le bout de code suivant : int toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + w; k = 2*z; u = y; v = k + u; v = x - v; } printf("x=%s, k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique On a un domaine (un peu résumé) : D = {v, c ste (int), c opy (v), e xpr (e)} Remarque : ce D est inventé pour l occasion, généralement on fait ça avec du data-flow mais c est pour l exemple v : variable dont on ne sait rien c ste (int) : constante, avec sa valeur c copy (v) : copie de la variable v (dont on ne sait rien) e xpr (e) : variable contenant une expression arithmétique, avec son expression symbolique (sous forme d arbre par exemple) Si on veut garder la consistance des types, il va falloir faire des calculs avant l exécution! x := y avec y : c ste (5) donnera x : c ste (5) et pas x : c opy (y) x := y + z avec y : c ste (5) et z : c ste (3) donnera x : c ste (8)
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + \alert{10}; k = 2*z; u = y; v = k + u; v = w - v; } printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + 10; k = 2*\alert{5}; u = y; v = k + u; v = x - v; } printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + 10; k = \alert{10}; u = y; v = k + u; v = x - v; } printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + 10; k = 10; u = y; v = \alert{10 + y}; v = x - v; } printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + 10; k = 10; u = y; v = \alert{x}; v = x - v; } printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; while(v > 0) { z = 5; w = 10; x = y + 10; k = 10; u = y; v = x; v = \alert{0}; } printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; \alert{\* while(v > 0) { *\} z = 5; w = 10; x = y + 10; k = 10; u = y; v = x; v = 0; \alert{\* } \*} printf("x=%s k=%s",x,k); }
Exemple de transformation de programme guidé par l analyse statique void toto(int x, int y) { int u =1, v=1, z, w, k; z = 5; w = 10; x = y + 10; k = 10; u = y; v = x; v = 0; printf("x=%s k=%i",x,10); }
Exemple de transformation de programme guidé par l analyse statique Analyse de vie void toto(int x, int y) { x = y + 10; printf("x=%s k=%i",x,10); }
Exemple de transformation de programme guidé par l analyse statique Conclusion J ai juste marqué les transformations du programme, mais chaque transformation est dictée par une contrainte de type Les contraintes de type sont construites et immédiatement résolues On raisonne par itération, donc il se peut qu il faille entrelacer l analyse de type et l analyse de vivacité pour obtenir un point fixe (principe des compilateurs à plusieurs phases) On a du mal à reconnaître le programme initial : le compilateur prend beaucoup d initiatives (et encore on a pas tout vu)
Conclusion Belle théorie : un domaine abstrait D, des contraintes C générées dans une première phase, et résolution de C dans une seconde, avec des belles preuves Typage et Analyse Data-Flow : même principe venant de communautés différentes, qui permettent de faire plus ou moins la même chose (avec des preuves pour le typage, et des méthodes de résolution efficaces pour l analyse data-flow) En pratique, on mélange génération et résolution de C dans des algorithmes itératifs qui convergent plus ou moins rapidement vers un point fixe Ça ne sert à rien d optimiser à la main vos programmes, le compilateur va de toute façon complètement réécrire votre code à sa sauce : seul compte la complexité de l algorithme
Références HDR Analyse statique de programmes : fondements et applications de Thomas Jensen Cours de programmation Daniel Hirshckoff Cours de compilation de François Bodin Programming Language Pragmatics de Michael Scott Compilateurs de Grune, Bal et Jacobs