• Aucun résultat trouvé

R`egles d’optimisation

compliqu´ee `a obtenir `a partir de l’arbre syntaxique abstrait puisque la dynamique du programme n’est pas repr´esent´ee directement.

Pour pouvoir exprimer des conditions temporelles sur le graphe de flot de contrˆole, il faut pouvoir associer `a n’importe quel sous-terme (i.e. un sous-terme de l’arbre syntaxique abstrait) un sommet du graphe de flot de contrˆole. Or nous avons construit les sommets du graphe de flot de contrˆole `a partir d’un sous-terme de l’arbre syntaxique abstrait et de la position de ce sous-terme. On d´efinit donc une fonction getNode: PIL × P os → V qui permet `a partir d’un sous-terme et d’une position dans l’arbre syntaxique abstrait de retrouver le premier sommet de ce sous-terme au niveau du graphe de flot de contrˆole.

Lorsque le sous-terme t de l’arbre syntaxique abstrait est tel que t ∈ symbol ∪ htermi ∪ hexpri, on d´etermine l’instruction i ∈ hinstri englobante de ce sous-terme et on renvoie getNode(i, wi). Les r`egles de transformation de programmes sont de la forme :

term1→ term2IF getNode(term3, ω) |= temporalCond

avec term1, term2et term3des sous-termes de l’arbre syntaxique abstrait, ω une position dans l’arbre syntaxique abstrait et temporalCond une condition temporelle sur getNode(term3, ω) dans le graphe de flot de contrˆole.

4.5

R`egles d’optimisation

`

A partir de cette d´efinition de r`egles de transformations, nous red´efinissons les optimisations du chapitre 3 ainsi que de nouvelles transformations pour les instructions letRef et letAssign.

4.5.1

Optimisation de l’instruction let

L’´elimination de variable morte et la propagation de constante peuvent toujours ˆetre d´efinies de mani`ere statique. Parmi les optimisations pr´ec´edentes du let, seul l’inlining et la fusion doivent ˆetre red´efinis pour tenir compte de la modification possible des variables contenues dans les termes.

Inlining

Notre pr´ec´edente d´efinition de l’inlining dans le chapitre 3 n’´etait valable que si le terme affect´e `a la variable ne changeait pas. Ce n’est plus le cas maintenant puisqu’il y a l’instruction letAssign. On doit donc d´efinir une condition sur le graphe de flot de contrˆole.

On d´efinit les pr´edicats suivants qui seront utilis´es dans les conditions temporelles :

– isModified prend un ensemble de variables et teste si l’une des variables est modifi´ee (par une instruction letAssign ) au sommet courant de la condition temporelle `a laquelle il appartient, – isUsed teste si une variable est utilis´ee au sommet courant,

– isNode teste si un sommet correspond au sous-terme et `a la position donn´ees en param`etre, – isAssigned teste si une variable est assign´ee au sommet courant.

ref@(let(var, term, instr)) → instr[var/term] IF getNode(ref, ω) |=

A(¬isModified(V ar(term)) U (isUsed(var) ∧ AX(A(¬isUsed(var) U isNode(f ree(var), ω))))) Cette condition temporelle teste au niveau du graphe de flot de contrˆole si `a partir du d´ebut du bloc let, on ne modifie pas les variables de term jusqu’`a un sommet n. On obtient le d´ebut de formule suivant : getNode(ref, ω) |= A(¬isModified(V ar(term)) U . . . Ce sommet n correspond `a une utilisation de var (d’o`u isUsed(var)) et comme on souhaite qu’il n’y ait qu’une seule utilisation de var, on doit v´erifier qu’`a partir de ce sommet il n’y a plus d’utilisation jusqu’`a la fin du bloc let. Tous les successeurs du sommet n (not´e AX) doivent v´erifier que A(¬isUsed(var) U isNode(f ree(var), ω)).

32 Chapitre 4. Extension du langage et nouvelles optimisations Fusion

Comme pr´ec´edemment, on ne pourra fusionner deux instructions let que si les variables du t ∈ htermi ne sont pas modifi´ees dans le bloc du premier let. On assure ainsi que les deux variables sont bien d´efinies avec la mˆeme valeur.

ref@(let(var1, term1, instr1)); let(var2, term2, instr2) → let(var1, term1, instr1; instr2[var2/var1]) IF getNode(ref, ω) |= term1∼ term2∧ A(¬isModified(V ar(term)) U isNode(f ree(var1), ω)) Par exemple, dans l’instruction PIL suivante, on ne peut pas fusionner car x est modifi´ee dans le premier bloc :

letRef(x, a, let(y1, x, hostcode(y1); letAssign(x, b, nop)); let(y2, x, hostcode(y2)))

4.5.2

Optimisation de l’instruction if

Les optimisations de l’instruction if doivent aussi ˆetre adapt´ees pour tenir compte des modifications possibles de variables.

Fusion

Les variables contenues dans la condition ne doivent pas ˆetre modifi´ees dans le premier bloc condi- tionnel car sinon l’´equivalence syntaxique ne suffira pas `a assurer l’´equivalence s´emantique. La condition assure donc que le premier bloc conditionnel ne modifie pas les variables de la condition.

ref@(if(c1, sucInstr1, f ailInstr1); if(c2, sucInstr2, f ailInstr2)) → if(c1, sucInstr1; sucInstr2, f ailInstr1; f ailInstr2)

IF getNode(ref, ω) |= c1∼ c2 ∧ A(¬isModified(V ar(c2)) U isNode(endIf, ω.1))

ω.1 correspond `a la position du premier fils (le plus `a gauche) du nœud `a la position ω dans l’arbre syntaxique abstrait. Comme le nœud `a la position ω correspond `a la s´equence, son premier fils correspond au premier bloc conditionnel.

Entrelacement

La premi`ere r`egle d’entrelacement n´ecessite une condition similaire `a celle de la fusion. Cette fois- ci, ce sont les variables de la deuxi`eme condition qui ne doivent pas ˆetre modifi´ees par le premier bloc conditionnel afin que l’orthogonalit´e des conditions soit toujours v´erifi´ee.

ref@(if(c1, sucInstr1, f ailInstr1); if(c2, sucInstr2, f ailInstr2)) → if(c1, sucInstr1; f ailInstr2, f ailInstr1; if(c2, sucInstr2, f ailinstr2)) IF getNode(ref, ω) |= c1⊥ c2 ∧ A(¬isModified(V ar(c2)) U isNode(endIf, ω.1))

Pour la deuxi`eme r`egle d’entrelacement, la condition est la mˆeme d’autant plus que c’est la deuxi`eme condition qui est ´evalu´ee d`es le d´ebut.

ref@(if(c1, sucInstr1, f ailInstr1); if(c2, sucInstr2, f ailInstr2)) → if(c2, f ailInstr1; sucInstr2, if(c1, sucInstr1, f ailinstr1); f ailInstr2) IF getNode(ref, ω) |= c1⊥ c2 ∧ A(¬isModified(V ar(c2)) U isNode(endIf, ω.1)) Permutation

Pour la r`egle de permutation, la condition sur les variables de c2 doit toujours ˆetre v´erifi´ee mais en plus les variables de c1 ne doivent pas ˆetre modifi´ees par le deuxi`eme bloc car cette fois-ci les instructions sont invers´ees. Elles sont donc susceptibles d’annuler l’orthogonalit´e des deux conditions.

ref1@(if(c1, sucInstr1, nop)); ref2@(if(c2, sucInstr2, nop)) → if(c2, sucInstr2, nop); if(c1, sucInstr1, nop)

IF getNode(ref1, ω.1) |= c1 ⊥ c2 ∧ A(¬isModified(V ar(c2)) U isNode(endIf, ω.1)) ∧ getNode(ref2, ω.2) |= A(¬isModified(V ar(c1)) U isNode(endIf, ω.2))

4.5. R`egles d’optimisation 33

4.5.3

Optimisation de l’instruction letRef

Au lieu d’adapter les r`egles du let pour le letRef, il est pr´ef´erable de le traiter au niveau des letAssign. Ainsi, lorsqu’il n’y a plus de letAssign associ´e `a un letRef, on peut le transformer en let. L’´elimination de variable morte est alors r´ealis´ee par la r`egle du let.

Cette solution a plusieurs avantages. Elle permet d’ˆetre plus fin dans l’optimisation en r´ealisant l’in- lining et l’´elimination de variable morte sur les letAssign et donc de traiter plus de cas. De plus, en transformant un letRef en let, elle permet de r´eutiliser les r`egles du let au lieu de les adapter. On minimise ainsi le nombre de r`egles de transformations de programmes.

En ce qui concerne la fusion de deux blocs letRef, la condition n´ecessite que la variable ne soit pas modifi´ee dans le premier bloc et ¸ca n’a donc pas d’int´erˆet. Cette fusion n’est pas int´eressante dans le cadre d’´equivalence syntaxique mais dans un cadre plus s´emantique, par exemple, avec la gestion d’environnement, elle pourrait ˆetre d´efinie.

Finalement, il ne reste plus qu’une r`egle pour le letRef : le remplacement d’un letRef par un let. Remplacement d’un letRef par un let

Si dans le bloc instr d’un letRef, il n’y a pas de modification de la variable alors on peut consid´erer ce letRef comme un let.

letRef(var, term, instr) → let(var, term, instr)

IF getNode(instr, ω) |= A(¬isAssigned(var) U isNode(f ree(var), ω))

Par exemple, l’instruction letRef(v, t, hostcode()) peut ˆetre r´e´ecrite en let(v, t, hostcode()). On peut alors appliquer la r`egle d’´elimination de variable morte et ainsi obtenir hostcode().

4.5.4

Optimisation de l’instruction letAssign

´

Elimination de variable morte

On ´elimine les instructions letAssign pour lesquelles il n’y a pas d’utilisation de cette nouvelle valeur. La condition doit assurer que la variable n’est pas utilis´ee avant une nouvelle affectation ou la fin de bloc du letRef.

ref@(letAssign(var, term, instr)) → instr

IF getNode(ref, ω) |= A(¬isUsed(var) U (isAssigned(var) ∨ isNode(f ree(var), )))

Le pr´edicat isNode(f ree(var), ) teste si le nœud courant est un nœud dont le sous-terme est f ree(var) et la position quelconque. Cette condition est suffisante car un programme PIL correct n’autorise pas de red´efinition de variable.

Propagation de constante et Inlining

La d´efinition de la propagation de constante et l’inlining est semblable `a celle pour let sauf que la variable doit ˆetre remplac´ee par sa d´efinition sur toute sa port´ee, c’est-`a-dire jusqu’`a une prochaine affectation ou jusqu’`a la fin du letRef. En outre, pour l’inlining, les variables composant le terme ne doivent pas ˆetre modifi´ees entre l’affectation et l’utilisation pour assurer l’´equivalence.

Remplacement d’un letAssign par un letRef

Si entre l’instruction letRef et letAssign, la variable n’est pas utilis´ee, on peut d´eplacer l’instruction letRefau niveau du letAssign, en rempla¸cant letAssign par letRef.

ref@(letRef(var, term1, x; ref Let@(letAssign(var, term2, instr)); y)) → x; letRef(var, term2, instr; y) IF getNode(ref, ω) |= A(¬isUsed(var) U isNode(ref Let, ωref Let))

34 Chapitre 4. Extension du langage et nouvelles optimisations Cette optimisation est int´eressante car elle permet de calculer la valeur d’une variable le plus tard possible et donc d’´eviter de r´ealiser des calculs coˆuteux et parfois inutiles.

Finalement, toutes ces r`egles forment un nouveau syst`eme d’optimisation. La mˆeme strat´egie que dans le chapitre 3 peut ˆetre appliqu´ee. On ajoute ces r`egles au premier op´erateur repeat. Elles peuvent s’ex´ecuter dans n’importe quel ordre comme les autres (op´erateur |) tant que l’entrelacement est toujours r´ealis´e `a la fin. On remarquera qu’aucune optimisation sur les boucles n’a ´et´e d´efinie car les boucles des programmes g´en´er´es ne n´ecessitent aucune am´elioration. Par exemple, elles ne contiennent pas d’invariant pouvant ˆetre extrait (optimisation tr`es classique des boucles).

Dans cette partie, nous avons surtout port´e notre attention sur la d´efinition des r`egles et sur la formalisation de la relation entre le graphe de flot de contrˆole et l’arbre syntaxique abstrait. Nous n’avons pas eu assez de temps pour prouver ces r`egles. En effet, les preuves sont plus techniques dans le sens o`u il faut d´efinir correctement le lien entre les formules de logique temporelle et le programme PIL. Cependant, ces preuves peuvent ˆetre r´ealis´ees de la mˆeme mani`ere que dans le chapitre 3.

Chapitre 5

Mise en œuvre et r´esultats

exp´erimentaux

5.1

Implantation des r`egles d’optimisation et de la strat´egie

L’implantation des r`egles est simplifi´ee par l’utilisation des primitives Tom. Elles s’implantent natu- rellement par un syst`eme de r`egles d´efini par l’instruction %match. La partie la plus int´eressante de la mise en œuvre est l’implantation de la strat´egie d’application.

5.1.1

Place de l’entrelacement

L’entrelacement doit ˆetre r´ealis´e en fin d’optimisation afin de ne pas bloquer les fusions. L’implantation de cette contrainte est simplifi´ee par une propri´et´e de Tom. En effet, comme Tom pr´eserve l’ordre du filtrage par compilation, en pla¸cant la r`egle d’entrelacement `a la fin du syst`eme on est assur´e qu’elle sera ex´ecut´ee apr`es toute fusion.

5.1.2

Condition d’application de la permutation

La r`egle de permutation n’a d’int´erˆet que si une future fusion est possible. Il faut donc pouvoir exprimer au niveau de l’implantation cette condition d’application. La premi`ere solution consiste `a utiliser un contexte. On ne d´efinit pas simplement la s´equence de deux blocs conditionnels, on se donne aussi un contexte dans lequel on filtre un bloc conditionnel susceptible de fusionner. Cependant, cette d´efinition rend l’application de la r`egle quadratique en temps puisqu’elle correspond `a du filtrage ´equationnel avec 3 listes `a parcourir.

Une solution alternative plus efficace est de consid´erer un ordre total sur les conditions is fsym portant sur le mˆeme sous-terme. On ordonne alors les blocs conditionnels suivant cet ordre, il en d´ecoule que deux conditions identiques deviennent contigu¨es. De plus, comme deux conditions qui n’ont pas le mˆeme ordre sont incompatibles, on est assur´e que leur permutation respecte la s´emantique du programme PIL. L’application de la r`egle devient alors lin´eaire en temps et on permute bien les blocs susceptibles de fusionner.

5.1.3

Utilisation de la biblioth`eque Mutraveler

La biblioth`eque Mutraveler bas´ee sur les travaux de E. Visser [VeABT98] et de J. Visser [Vis01b] permet de d´efinir une strat´egie `a partir d’op´erateurs de base qui sont :

– l’op´erateur de r´ecursion : µ,

– la composition s´equentielle : seq(s1, s2) (s2 seulement si s1 n’´echoue pas), – le choix : choice(s1, s2) (si s1´echoue, s2sinon s1),

– l’identit´e : id,

36 Chapitre 5. Mise en œuvre et r´esultats exp´erimentaux – l’´echec : f ail,

– la strat´egie All(s) (succ`es de All(s) si succ`es de s pour tous les fils du nœud courant),

– la strat´egie One(s) son dual (succ`es de One(s) si succ`es de s pour au moins un fils du nœud courant), – l’application d’un syst`eme de r`egles R

Grˆace `a ces op´erateurs, on peut par exemple d´efinir la strat´egie repeat qui consiste `a r´ep´eter l’appli- cation d’une strat´egie v jusqu’`a ce ne soit plus possible :

repeat(s) = µx(choice(seq(v, x), id))

Une premi`ere strat´egie de normalisation peut consister `a r´ep´eter l’application s de nos r`egles jusqu’`a obtenir l’identit´e et en traversant le terme en profondeur d’abord. On obtient la d´efinition suivante :

innermost(s) = µx(seq(All(x), choice(seq(s, x), id)))

Ces op´erateurs sont bas´es sur l’´echec et l’implantation a ´et´e r´ealis´ee par des exceptions Java. Ce m´ecanisme est coˆuteux en temps.

Contrairement `a l’exploration d’espace de recherche, dans le cas particulier de la normalisation on ne s’int´eresse pas `a l’´echec d’une strat´egie. Notre but ´etant de calculer des formes normales, on peut mod´eliser l’´echec d’une r`egle par l’identit´e (une r`egle qui ne peut pas s’appliquer ne modifie pas le terme courant). On d´efinit donc de nouveaux op´erateurs bas´es sur l’identit´e :

– choiceId(s1, s2) (s2 si l’application de s1 est ´equivalente `a id sinon s1)

– oneId(s) qui ´echoue si pour tous les fils du nœud courant, l’application de s est ´equivalente `a l’identit´e,

– seqId(s1, s2) (si s16= id, on applique ensuite s2)

Un exemple de strat´egie utilisant choiceId et oneId est onceBottomU pId d´efinie par onceBottomU pId(s) = µx(choiceId(oneId(M uV ar()), v)

Cette strat´egie part de la feuille la plus `a gauche du terme et remonte en essayant d’appliquer la strat´egie v. Elle s’arrˆete d`es que la strat´egie v succ`ede. La diff´erence avec onceBottonUp est qu’ici, une strat´egie qui renvoie l’identit´e ´echoue. Il n’y a pas besoin de g´erer explicitement l’´echec avec des exceptions. L’op´erateur seqId nous permet de red´efinir notre strat´egie innermost en une version plus efficace pour la normalisation :

innermostId(s) = µx(seq(All(x), seqId(s, x)))

Ce travail a contribu´e `a identifier une faiblesse de la biblioth`eque Mutraveler. Cela nous a amen´e `a mod´eliser l’´echec d’une autre fa¸con et `a proposer une extension de Mutraveler qui am´eliore consid´era- blement les performances lors d’un calcul de forme normale. Le temps d’optimisation d’une centaine de r`egles de filtrage syntaxique passe de 90 minutes `a 3 minutes.

Toutes les r`egles d´efinies dans le chapitre 3 ainsi que la strat´egie ont ´et´e int´egr´ees `a la version officielle et diffus´ee de Tom. Les r`egles du chapitre 4 sont en cours d’implantation. En plus de la correction formelle des r`egles dans la section 3.3, on peut v´erifier que l’implantation des r`egles est correcte grˆace `a la certification du code r´ealis´ee au sein de l’outil Tom. On ne v´erifie alors pas les r`egles mais le code g´en´er´e apr`es optimisation. Pour plus de d´etails sur cette m´ethode de certification du filtrage, on peut lire [KMR05].

L’implantation des r`egles du chapitre 3 nous a permis d’´evaluer le gain de performances sur des exemples caract´eristiques et ainsi tester l’efficacit´e de l’optimiseur.

Documents relatifs