• Aucun résultat trouvé

6.3.1 Les contraintes structurelles

Nous avons vu que REAL pouvait être utilisé pour traduire les contraintes dénies dans les spécications de l'application. Cependant, il ne s'agit pas du seul type de contraintes que doivent vérier les versions optimisées du modèle. En eet, le modèle initial va traduire un certain nombre

6.3. Vérier la cohérence des modèles avec REAL de contraintes implicites aux spécications  par exemple les ots de données, ou encore le typage des diverses connexions. Cette information ne peut être extraite automatiquement des spécications car celles-ci sont rédigées au moins pour partie en langage naturel  il s'agit alors typiquement du travail de l'architecte.

Cependant, ce travail devrait être limité au minimum, c'est-à-dire être eectué une seule fois, pour limiter non seulement les coûts de développement, mais surtout les risques d'incohérences inhérentes au travail humain, surtout répétitif. Or, dans une approche par optimisation, il est nécessaire d'orir un mécanisme pour assurer que les contraintes structurelles sont conservées entre les diérentes versions du modèle. Vérier cette cohérence demande typiquement deux fois le même travail : d'une part la traduction explicite des contraintes structurelles de l'application  obligatoirement à partir du modèle initial, pour les raisons sus-citées  et d'autre par un mécanisme permettant de les vérier dans les modèles optimisés.

6.3.2 Génération de théorèmes

Des théorèmes REAL dans les modèles optimisés peuvent assurer que ceux-ci sont conformes aux contraintes structurelles, une fois ces contraintes connues. D'autres théorèmes  en grande partie factorisables avec les précédents  peuvent extraire ces contraintes du modèle initial. Nous proposons donc une approche permettant de déduire automatiquement les théorèmes REAL vé- riant les contraintes structurelles issues du modèle initial sur les modèles optimisés, en générant celles-ci.

Nous dénissons donc une approche en deux étapes, applicable à toute contrainte structurelle :  Calcul de l'invariant ;

 Génération du théorème.

Pour factoriser le code REAL, nous supposons que nous possédons un théorème REAL compute_constraint_value, calculant la valeur d'une contrainte structurelle. Nous ne faisons pas d'hypothèse sur la portée d'une contrainte structurelle : celle-ci peut s'exercer aussi bien au niveau du système qu'au niveau des process ou threads  c'est pourquoi le paramètre unique de toutes les fonctions de calcul d'invariant doit être local_set.

Calcul de l'invariant

Le théorème 6.11donne la fonction nale de calcul d'invariant qui sera utilisée pour chaque composant impliqué du modèle. Ce théorème, appelé de façon externe sur le modèle initial (par exemple par un script shell, voir la section 6.1 pour savoir comment appeler un théo- rème REAL avec l'API de Ocarina), retourne la valeur de l'invariant dans ce modèle. Cette information est enregistrée dans une variable (nous considérerons par la suite qu'il s'agit de CONSTRAINT_INVARIANT).

theorem co mp ut e _in va r ia nt foreach s e t i n l o c a l _ s e t do

var i n v := compute compute_constraint_value ( s e t ) ; r e t u r n ( i n v ) ;

Listing 6.11  Calcul de l'invariant Génération de la contrainte structurelle

Générer le théorème exprimant la contrainte structurelle dans les modèles optimisés peut ensuite être fait sans calcul supplémentaire, et avec un minimum de code REAL généré. Comme le théorème compute_constraint_value permet d'obtenir la valeur de l'invariant pour le modèle courant (optimisé), et que nous connaissons la valeur attendue pour celui-ci, il sut de géné- rer le code REAL illustré par le théorème 6.12, et l'ajouter en annexe des composants AADL concernés. Nous nommons build_test_theorem l'application qui pour une contrainte dénie par compute_constraint_value et sa valeur construit un tel théorème.

theorem t e s t _ i n v a r i a n t

foreach s e t i n l o c a l _ s e t do

var a c t u a l _ i n v := compute compute_constraint_value ( s e t ) ; check ( a c t u a l _ i n v = CONSTRAINT_INVARIANT ) ;

end t e s t _ i n v a r i a n t ;

Listing 6.12  Vérication de l'invariant Ajout de la contrainte en annexe

Nous avons évoqué le fait que les théorèmes puissent s'appliquer à plusieurs niveaux du modèle. Il faut donc avoir un mécanisme qui permette d'associer les théorèmes applicables pour chaque type de composants. Nous utilisons l'algorithme 3, qui va sélectionner les contraintes structurelles en fonction de leur préxe, et les appliquer à tous les composants du modèle dont le type correspond au préxe en question. A partir de ces éléments, nous proposons l'approche suivante :

 toutes les contraintes structurelles sont ajoutées à une bibliothèque REAL ;

 chaque contrainte structurelle est préxée par le type de composant à laquelle elle s'ap- plique ;

 on applique l'algorithme 3 sur le modèle initial ;

 les phases d'optimisation ultérieures font hériter les composants modiés (threads, pro- cess...) des contraintes structurelles de leurs parents.

On notera que nous faisons l'hypothèse que toutes les contraintes structurelles sont symé- triques  c'est-à-dire qu'elles s'appliquent à tous les composants d'un même type, et que la valeur de l'invariant ne change pas. Il est possible de décrire plus nement les contraintes struc- turelles en levant cette hypothèse, mais il faut alors trouver des règles d'association pour les contraintes dans les composants transformés (par exemple quand deux threads sont fusionnés). Nous n'avons pour l'heure pas de solution uniforme pour régler cette situation.

6.3.3 Un exemple de contrainte structurelle

Les connexions entre les diérents threads dans le modèle initial représentent les ots de données de l'application. Dans le modèle optimisé, cette donnée devrait être invariante quelle que soit la suite d'opérations eectuée, sans quoi le modèle aura été altéré.

6.3. Vérier la cohérence des modèles avec REAL

Input: System S

Input: Theorem_List T L Output: System S2 S2 ← S forall t ∈ T L do

if Is_P refix(system_, t) then

CON ST RAIN T_INV ARIANT ← Call(t, S)

t2 ← build_test_theorem(t, CONST RAINT _INV ARIANT ) forall Csystem∈ Systems(S2) do

Annexes(Csystem) ← Annexes(Csystem) ∪ t2

end end

if Is_P refix(process_, t) then

CON ST RAIN T_INV ARIANT ← Call(t, S)

t2 ← build_test_theorem(t, CONST RAINT _INV ARIANT ) forall Cprocess∈ P rocesses(S2)do

Annexes(Cprocess) ← Annexes(Cthread) ∪ t2

end end

if Is_P refix(thread_, t) then

CON ST RAIN T_INV ARIANT ← Call(t, S)

t2 ← build_test_theorem(t, CONST RAINT _INV ARIANT ) forall Cthread∈ T hreads(S2)do

Annexes(Cthread) ← Annexes(Cthread) ∪ t2

end end end

L'opération merge ne devrait ainsi avoir aucun impact sur le nombre de connexions, puis- qu'une connexion sortante ou entrante est soit transformée en auto-connexion, soit conservée. L'opération Move peut avoir un impact, puisqu'elle peut éclater une connexions locale à un pro- cess en trois connexions, dont deux sont locales à des process et l'autre locale au system. Pour prendre en compte ce biais, nous proposons comme invariant l'opération suivante : nombre de connexions locales au process − nombre de connexions locales au system.

theorem compute_inter_thread_connections_number foreach s i n system_set do proc_set := {p i n p r o c e s s _ s e t | ( is_subcomponent_of ( p , s ) ) } ; t h r e a d _ s e t := { t i n t h r e a d _ s e t | ( is_subcomponent_of ( t , proc_set ) ) } ; inter_thread_cnx_set := { c i n c o n n e c t i o n _ s e t | ( i s _ a c c e s s i n g _ t o ( c , t h r e a d _ s e t ) ) } ; i n t e r _ p r o c e s s _ c n x _ s e t := { c i n c o n n e c t i o n _ s e t | ( i s _ a c c e s s i n g _ t o ( c , p r o c e s s _ s e t ) ) } ; r e t u r n (msum ( c a r d i n a l ( inter_thread_cnx_set ) − c a r d i n a l ( i n t e r _ p r o c e s s _ c n x _ s e t ) ) ) ; end compute_inter_thread_connections_number ;

Listing 6.13  Nombre de connexions inter-thread

Le théorème 6.13 calcule cette valeur. On notera qu'on dénit les connexions locales à un process comme égale aux connexions accédant à au moins un thread. Cette dénition ne prend pas en compte les accès aux objets partagés, et est donc restrictive. Les connexions à des ports en entrée ou sortie sont par contre incluses dans cette valeur.

Nous utilisons ensuite ce théorème pour évaluer le nombre de connexions inter-threads dans le modèle initial. Une fois la valeur récupérée et stoquée dans une variable CNX_NUMBER, nous pouvons générer la contrainte structurelle, qui est ajoutée en annexe du system du modèle optimisé, comme illustré par le théorème 6.14.

system t o p _ l e v e l −− ( . . . )

annex r e a l _ s p e c i f i c a t i o n {∗∗

theorem test_inter_thread_connections_number foreach s e t i n l o c a l _ s e t do

var a c t u a l _ i n v := compute compute_inter_thread_connections_number ( s e t ) ; check ( a c t u a l _ i n v = CNX_NUMBER) ;

end test_inter_thread_connections_number ; ∗ ∗} ;

end t o p _ l e v e l ;