• Aucun résultat trouvé

Langages de verrouilleurs

Les verrouilleurs peuvent arbitrairement modifier le flot de contrôle et l’état du programme aussi longtemps que cet état reste dans l’ensemble des états atteignables par le programme de base. Il est difficile de concevoir un langage général qui assure cette propriété car il doit permettre aux aspects d’exprimer différents comportements sur les variables du programme de base sans permettre à celles-ci de sortir de l’ensemble des états atteignables du programme de base. Cependant, deux langages spécialisés des verrouilleurs viennent à l’esprit :

– un langage d’optimisation dont les actions permettraient d’aller directement vers des états futurs atteignables ;

– un langage de tolérance aux fautes dont les actions retourneraient vers des états sûrs précédemment atteignables.

Ici, nous proposons un langage spécialisé pour définir des aspects memo. Un aspectmemo

est un aspect d’optimisation qui sauvegarde et restaure dans un cache des résultats de calcul. Lorsqu’un calcul à sauvegarder est effectué pour la première fois, l’aspect sauvegarde les ar-guments et les résultats de celui-ci. Quand ce calcul doit à nouveau être effectué, l’aspect court-circuite son exécution et retourne directement les résultats sauvegardés précédemment. La Grammaire 12 présente la syntaxe de ce langage.

GRAMMAIRE12.

Aspm ::= memo(Im(Ap1, . . . , Ap

n)∧if(Bo))

Im ::= p|βI

Un aspect memo se résume à une primitive memo qui sauvegarde et restaure les résultats des appels de procédure. Ces appels de procédure sont filtrés par la partie statique du point de coupure qui lui est passé en paramètre si le prédicat de la partie dynamique est vrai.

Pour donner la sémantique des aspects memo, nous avons besoin des listes des variables lues et écrites par une procédure. Ces deux listes sont calculées par les fonctionsread etwrite. La fonctionread prend les déclarationsDen paramètre et retourne une fonction qui associe à chaque nom de procédure sa liste de variables lues. Pour effectuer cette tache,read parcours la séquence des déclarations (d1;d2) et retourne l’union (⊎) des fonctions retournées par chaque déclaration. Lorsqu’une déclaration est une procédure,read retourne une fonction qui associe au nom de la procédure (p) la liste (construite par la fonctionmklist) des variables lues par son corps (s). Lorsqu’une déclaration est une variable,read retourne la fonction vide (⊥).

read :Decl →Identifier →List(Var)

read[[d1; d2]] = read[[d1]]⊎read[[d2]]

read[[proc p(...) s]] = [p7→mklist(readS[[s]]{p})]

read[[var g := a]] = ⊥

L’ensemble des variables lues est calculé par la fonction readS qui prend en paramètres le corps de la procédure ainsi que l’ensemble des procédures déjà analysées (pour assurer la

terminaison de l’analyse).

readS :Statement →P(Identifier)→P(Var)

readS[[S1; S2]]ps = readS[[S1]]ps ∪ readS[[S2]]ps readS[[g := a]]ps = readA[[a]]

readS[[while(b) s]]ps = readB[[b]]∪readS[[s]]ps readS[[loop(a) s]]ps = readA[[a]]∪readS[[s]]ps

readS[[if(b) then s1 else s2]]ps = readB[[b]]∪readS[[s1]]ps ∪ readS[[s2]]ps readS[[p(a1, . . . , an)]]ps = readS[[body(p)]](ps∪ {p})

i=1...nreadA[[ai]]si p /∈ps readS[[_]]ps = ∅ sinon

Les variables lues par une séquence de deux commandes sont l’union des variables lues par la première et la seconde commande. Les variables lues par une affectation sont les variables qui apparaissent dans la partie droite a. Ces variables sont collectées par la fonction readA

(définie dans l’Appendice B.2). Les variables lues par une boucle while (resp. loop) sont des variables lues par la condition b (resp. l’expressiona) et les variables lues par les corps de ces boucles. Les variables lues par l’instruction conditionnelleifsont les variables lues par la condition et celles lues dans les deux branches. Enfin, les variables lues lorsqu’une procédure est appelée sont celles lues par le corps de la procédure. Le second argument de readS (ps) enregistre les procédures analysées afin de déplier chaque procédure une seule fois. Dans les autre cas, tels que les commandesskipetabortou un appel à une procédure déjà analysée,

readS retourne l’ensemble vide.

La fonction write prend les déclarations D en paramètre et retourne une fonction qui as-socie à chaque procédure sa liste de variables écrites. Sa définition est similaire à read (voir l’Appendice B.2).

Nous pouvons maintenant définir la sémantique d’un aspectmemocomme une transforma-tion de programme prenant en entrée l’aspect et les déclaratransforma-tions (D) du programme de base :

T[[memo(Im(a1, . . . , an) ∧ if(Bo))]]D = {var cache := empty;

{around(Im(a1, . . . , an) ∧ if(Bo))

if contain(Im, a1:. . .:an, read[[D]]Im) then write[[D]]Im :=

lookup(Im, a1:. . .:an, read[[D]]Im)

else proceed; store(Im, a1:. . .:an, read[[D]]Im, write[[D]]Im) }}

Notons que, par souci de concision, nous avons défini l’action avec le langage de base étendu avec des structures de données (c.-à-d., un cache implémentant une table de hachage, et des listes pour représenter les valeurs des variables lues et écrites) et une valeur de retour pour les procédures (par ex.,contain,lookup).

Un aspectmemodéfini initialement un cache vide qui va contenir les résultats sauvegardés. Une entrée du cache associe un triplet (contain(Im, a1 :. . .: an,read[[D]]Im)) composé du nom d’une procédure, la liste de ses arguments et de ses variables lues, à la liste des valeurs des variables écrites par celle-ciwrite[[D]]Im.

Lorsque l’instruction courante est filtrée par le point de coupure, la substitution retournée

σest appliquée à l’action ce qui instancie complètement la procédure, ses arguments, ainsi que les listes des variables lues (read[[D]]Im) et écrites (write[[D]]Im). Quand l’action est exécutée, si le cache contient le résultat du calcul (contain(Im, a1 : . . . : an,read[[D]]Im)) alors le résultat sauvegardé dans le cache est affecté aux variables écrites

(lookup(Im, a1 : . . . : an,read[[D]]Im)), sinon le calcul est effectué et le cache mis à jour (store(Im, a1 : . . . : an,read[[D]]Im,write[[D]]Im)). Un tel aspect est verrouilleur unique-ment si l’affectation (write[[D]]Im := lookup(. . .)) est atomique. Si tel n’est pas le cas, l’affectation de plusieurs variables peut produire des états temporaires non atteignables.

L’Exemple 25 défini un aspect memopour la procédurefib(Exemple 19). Il est facile de vérifier quefibne lit aucune variable et écrit la variableresult.

EXEMPLE25. L’aspect memo ci-dessous mémorise les appels à fibuniquement si son argu-ment est supérieur à 10 (pour amortir le coût de la sauvegarde dans le cache par exemple).

memo (fib(βA) ∧ if(βA > 10))

La transformationT qui lui correspond est la suivante :

var cache := empty

{around (fib(βA) ∧ if(βA > 10)) {if(contain(fib,[βA],[]))

then result:=lookup(fib,[βA],[])

else proceed; store(fib,[βA],[],[result])}}

Notre version defib(Exemple 19) calcule plusieurs fois les mêmes appels et a une complexité exponentielle. L’aspectmemoci-dessus permet de ramener cette complexité à un temps linéaire.

La propriété 26 formule le fait que tout aspectmemoest un verrouilleur. PROPRIÉTÉ26. ∀a∈Aspm.[[T[[a]]]]∈ Av

Nous n’avons pas prouvé cette propriété. Une preuve consisterait à montrer que dans la trace du programme tissé, les variables du programme de base ne sortent jamais de l’ensemble des états atteignables par l’exécution du programme de base. Ce comportement est garanti par la transformation T. Elle utilise uniquement des instructions dont la sémantique, soit restaure

le résultat d’une instruction du programme de base si elle a déjà été calculée et sauvegardée (lookup), soit exécute cette instruction (proceed) et sauvegarde (store) son résultat. Cela suppose comme mentionné ci-dessus que la restauration de plusieurs variables se fasse de façon atomique.

Dans le but d’implémenter des stratégies de mémoisation sophistiquées, un aspect memo

peut être combiné avec un observateur. Comme les observateurs et les terminateurs sont inclus dans la catégorie des verrouilleurs, la composition d’un aspect verrouilleur avec un observateur ou un terminateur est aussi un verrouilleur. Par exemple, le programme de base pourrait être tissé avec un observateur qui collecte des statistiques sur les appels de procédures (par ex.,nombre d’appels, la profondeur d’une récursion,etc.) dans ses variables. Ensuite un aspectmemodont le prédicat teste ces variables peut être tissé.