• Aucun résultat trouvé

Fragmentation sémantique

9.2 Critères de fragmentation et abstractions de fragmentations

9.2.4 Fragmentation sémantique

Le principe précédent de collecter des cellules lors de l’analyse se retrouve dans [CCL11], mais les cellules n’y sont plus regroupées selon leurs propriétés. Leur analyse construit une partition du tableau, un peu à la manière de [GDD+04, GRS05, HP08]. Les tableaux sont par-titionnés selon un ensemble totalement ordonné de bornes. Siiest une de ces bornes, alors on distingue les cellules d’indice inférieur ou égal à iet celles d’indice strictement supérieur. La différence avec les travaux précédents, réside dans le fait que les bornes sont totalement ordon-nées, c’est à dire qu’on ne peut partitionner selon deux expressions d’indice qui ne soient pas comparables. Par exemple, si une borne i peut aussi bien être supérieure qu’inférieure à une borne j, alors seule l’une d’entre elles peut servir à partitionner le tableau. En outre, pour être tout à fait précis, ces bornes sont en fait des classes d’équivalences d’expressions. C’est ce qui donne la nature sémantique à ce partitionnement : on peut interchanger une expression de borne avec une autre égale.

Les auteurs se fixent comme objectif de rechercher des propriétés non-relationnelles sur les différents fragments des tableaux. Par conséquent, la possible vacuité des fragments ne pose pas de problème.

Contrairement aux méthodes précédemment cités, il n’y a pas ici d’objectif clairement défini dans le choix de la fragmentation. On sait en revanche comment la partition est calculée à mesure que l’analyse progresse. On part d’une partition composée de deux bornes : la borne inférieure et supérieure des indices valides pour ce tableau, classiquement 0, et la taille du tableau. Cette partition est ensuite modifiée dans trois situations.

• Lorsque une cellule est affectée, on introduit un singleton pour cette cellule dans la partition. On ajoute les bornesieti+1, quitte à supprimer les bornes qui ne seraient pas comparables avec ces deux là.

• Lorsque une variable d’indice est modifiée, on met à jour la partition en conséquence pour la préserver.

• Lorsque l’on doit calculer une union, une intersection, un élargissement ou une comparai-son de valeurs abstraites, on unifie les partitions.

La caractéristique principale de l’algorithme d’unification est de ne conserver dans la partition unifiée que des bornes qui soient présentes dans les deux partitions à unifier. À chaque fois qu’on doit calculer l’union de deux valeurs abstraites venant de deux branches distinctes du programme, on ne conservera que les bornes présentes dans les deux branches à la fois. En particulier, une boucle ne peut faire apparaitre de nouvelles bornes et ne conservera que les bornes présentes à l’entrée de la boucle.

Si lors de l’union de deux valeurs abstraites on doit unifier deux partitions et que deux bornes ne sont pas ordonnées de la même manière dans deux partitions à unifier, c’est que les bornes deviennent incomparables après l’union. Dans ce cas, comme nous l’avons indiqué plus tôt, il ne sera pas possible de conserver les deux bornes dans la partition. Le comportement de l’algo-rithme d’unification dans cette situation est variable et dépend de la forme des deux partitions : ou bien l’algorithme conservera l’un des indices, ou bien il n’en conservera aucun.

On peut voir les diagrammes comme une généralisation de ce partitionnement, en lui ajoutant la capacité de considérer des bornes incomparables. Les partitions telles que présentées dans ces travaux correspondent à un cas particulier de diagramme où l’ensemble des bornes Best totalement ordonné. Pour reprendre le cas précédent, lorsque deux bornes deviennent incompa-rables lors d’une unification, les diagrammes construiront deux chemins permettant de conserver chacune des deux bornes.

Entre leur abstraction des fragmentations et la notre, la différence se résume à un choix de compromis. Tandis que les diagrammes sont plus précis, ils entraînent un nombre de fragments pouvant être très supérieur. (Au pire quadratique au lieu de linéaire.) Le choix n’est pas évident. Beaucoup de programmes s’analyseront correctement avec ces partitions simples. Mais si il semble qu’on ait rarement besoin de partitionner un tableau selon deux bornes incomparables, le cas arrive plus souvent qu’on ne pourrait le croire : les critères de fragmentation ont tendance à ajouter plus de bornes que nécessaire quitte à les supprimer dans des itérations postérieures. On peut se retrouver temporairement avec des bornes incomparables même si dans la fragmentation finale ce n’est plus le cas.

Comparons maintenant les critères de fragmentation. Celui-là partage avec le notre les défauts dûs à sa nature sémantique par rapport aux critères syntaxiques. Lorsque il y a plusieurs indices égaux, et que l’un d’entre eux est utilisé pour désigner une cellule affectée, les critères séman-tiques vont tous les considérer. Cette énumération peut même parfois conduire à l’échec de la recherche de partition. (Le problème est détaillé dans la description de l’une des expérimenta-tions en section 10.2.3) Tandis qu’un critère syntaxique sélectionnera le seul indice pertinent dans la plupart des cas, même s’il échouera dans d’autres.

Le fait de ne conserver que les bornes présentes dans les deux partitions à unifier est important pour la terminaison de l’analyse. C’est ce qui assure que le nombre de tranches reste borné. Mais ceci a des implications négatives sur la précision de l’analyse. Cette caractéristique peut être justifiée pour l’union en disant que, si une tranche n’est pas distinguée dans l’une des deux partitions, alors il y a peu de chances qu’on puisse en déduire quelque chose dans l’union des deux valeurs abstraites. L’argument peut être invalidé par l’exemple suivant. Supposons que l’on ait deux partitions d’un tableau A, d’une part composée des tranches [0..i[ et [i..n[ et d’autre part de la tranche [1..n[. Supposons également qu’on ait les valeurs abstraites pour ces deux partitions

et

A[0..n[∈[1,3]

L’algorithme d’unification ne conservant que les bornes présentes dans les deux partitions, on n’aura dans la partition unifiée que la trancheA[0..n[ ayant pour propriétéA[0..n[∈[0,4]. Alors que en prenant toutes les bornes, on aurait eu une valeur abstraite plus forte :

A[0..i[∈[0,3],A[i..n[∈[1,4]

C’est le résultat qu’on obtiendrait avec notre critère de fragmentation appliqué aux diagrammes de tranches à la condition que les deux tranches correspondent à deux accès différents.

L’incapacité pour les boucles de faire apparaitre de nouveaux singletons est également han-dicapante. Si une boucle d’indiceicommence à itérer pouri= 0, alors on pourra conserver la borneilors de l’unification puisque 0 est initialement présent dans la partition. En revanche si on commence à itérer pouri= 1, quand bien même la première itération de la boucle ajoutera la bornei= 1 à la partition, on ne pourra conserver ni l’une ni l’autre puisqu’elles ne sont pas initialement présentes. Dans le cadre de ces travaux, ce n’est pas particulièrement gênant, car si ne pas utiliser la première cellule d’un tableau n’introduit pas nécessairement de bug dans un programme, cela peut toutefois être considéré comme une erreur de programmation.

On aurait un problème similaire pour un programme ressemblant à ceux donnés en début de section 4.3. Il s’agissait de programmes parcourant successivement diverses sections du tableau. Conservons les deux premières boucles et inversons l’ordre d’itération de la première :

Pouri de j à0faire

A[i] ← 0

Pouri de j+1à nfaire

A[i] ← 1

Si la première boucle n’avait pas été inversée, c’est à dire si elle allait de manière croissante de 0 à j, on aurait eui=0 en entrant dans la boucle, et donciaurait été une borne présente dans la partition. En sortant de la boucle,i= jet on peut donc introduire jà la place dei. Mais avec la version inversée, jn’étant égal à aucune borne de la partition initiale, on ne peut pas l’introduire dans la partition.

Un autre exemple, qui n’est lui pas artificiel est celui de la segmentation d’un tableau, comme celle opérée dans un tri par segmentation. Ce programme ne crée que des propriétés relation-nelles. Bien que les travaux dont nous parlons s’intéressent avant tout aux propriétés non-relationnelles, le critère de fragmentation peut néanmoins être appliqué.

i ← 1

j ← 1

Tant quei<nfaire

SiA[i]< A[0]alors

A[j]↔A[i]

j ← j+1

i ← i+1

Dans cette version, le pivotA[0] n’est pas mentionné avant d’entrer dans la boucle. La borne 1 n’est donc pas dans la partition, et on ne peut pas décrire précisément l’invariant de la boucle de segmentation.

Terminons par un dernier exemple moins évident sur le problème de conservation de borne lors d’unification. Dans celui-ci, la seconde boucle est tout fait classique, mais l’accès aux cel-lules n’est réalisé que dans l’une des branches d’une structure conditionnelle.

Pouri de0à nfaire

A[i] ← 0

Pouri de0à nfaire

Sicondalors

A[i] ← A[i]+1

Supposons quecond est une condition qui n’est pas toujours vraie ou toujours fausse. L’af-fectation ajoutera les bornesieti+1 dans la partition. Maisi+1 n’est pas dans la partition. Dès que l’on sort de la structure conditionnelle, on doit donc retirer au moins la bornei+1 de la partition. Par conséquent, la valeur abstraite attribue àA[i..n[ une valeur comprise entre 0 et 1 à la première itération, et cet intervalle grandit d’une itération sur l’autre. On perd donc la propriété que toutes les valeurs des cellules du tableau sont dans l’intervalle [0,1].

Tous les programmes que nous venons de citer sont correctement analysés par notre méthode. Celle-ci autorise l’apparition de nouvelles bornes au sein d’une boucle. Notons que ce qui est pertinent pour la recherche de propriétés relationnelles n’est pas forcément pertinent pour la recherche de propriétés non-relationnelles. Dans la segmentation d’un tableau, il n’est pas utile d’avoirA[0] dans la partition pour en tirer des propriétés non-relationnelles :A[0] n’a aucune propriété de ce genre avant la boucle. MaisA[0] a avec les autres tranches la relation A[1..j[<

A[0] ≤ A[j..i[. PuisqueA[1..j[ etA[j..i[ sont vides à l’entrée de la boucle, cette propriété reste valide. La présence deA[0] dans la partition n’est donc utile que si on cherche des propriétés relationnelles. Ceci ne contredit toutefois pas notre première remarque : ce n’est pas parce que les propriétés cherchées sont non-relationnelles qu’il est inintéressant de conserver toutes les bornes lors d’une unification.

Enfin, nous avons cité des exemples de bornes manquantes dans la partition. Mais il est pos-sible, à l’inverse, que le critère apporte des bornes superflues. Si deux bornes égales cessent de l’être après une unification, l’algorithme d’unification conservera toutes ces bornes, même si elles ne délimitent rien.

9.2.5 Conclusion

Notre critère de fragmentation est la tentative de réunir deux méthodes de fragmentation. D’une part, il reprend le principe de collecte de cellule à l’exécution [GMT08, CCL11]. D’autre part, il essaie de reproduire les partitions obtenues dans [HP08] qui donnent très souvent de bons résultats lorsqu’il n’y a pas de subtilités dues à des égalités d’indices. Il en combine certains avantages mais échoue parfois à trouver les partitions adéquates à l’analyse de certains exemples, qui sont pourtant trouvées par au moins l’une de ces méthodes. Quelques uns de ces exemples seront présentés dans la section 10.2 consacré aux résultats d’expérimentation.