• Aucun résultat trouvé

5.2 Vérification du typage d’horloges

6.1.4 Preuve de correction

Les sources Coq contenant les diverses représentations formelles des notions énoncées dans cette section, ainsi que les preuves des lemmes et théorèmes associés, sont disponibles en ligne [⚓1].

Sémantique non-déterministe du langage idéalisé

Comme nous l’avons illustré précédemment, l’introduction de valeurs inconnues (>) dans le langage idéalisé induit le non-déterminisme de son exécution : certains effacements peuvent mener à des traces d’exécutions différentes qui, bien que toutes valides, peuvent avoir des coûts qui varient fortement. Afin de représenter ce non-déterministe nous présentons une sémantique non-déterministe pour le langage idéalisé, capable de manipuler des valeurs inconnues. Celle-ci, dont les règles d’évaluation sont décrites dans la figure 6.2, correspond à la fois à la transposition directe des règles déterministes définies dans la figure 6.12, ainsi qu’à l’ajout de deux règles permettant de traiter le cas, pour une instruction de 2. À ceci près que les opérations d’addition et de soustraction sont étendues pour retourner la valeur> dès qu’une des opérandes est inconnue : par exemple x+> = >. Pour dénoter ce comportement, les opérateurs arithmétiques seront représentés en gras.

branchement conditionnel (Branchif) où la valeur de la condition est inconnue. Les prémisses de la règle de branchement conditionnel traitant le cas où la condition est fausse et de celle qui gère le cas où elle est vraie ne sont pas distinguables dès lors que la valeur de la condition de branchement est inconnue. L’adjonction de la notion de valeurs non-connues à la compilation, associée à l’ajout de ces deux règles apportent ainsi le caractère non-déterministe attendu à la sémantique des instructions du langage idéalisé. σ ⇝ σ0 P[pc]= Init x v ( P, pc, M)(P, pc + 1, M[x := v]) P[pc]= Assign x y M[y]= v ( P, pc, M)(P, pc + 1, M[x := v]) P[pc]= Add x y M[x]= v M[y]= w ( P, pc, M)(P, pc + 1, M[x := v +++ w]) P[pc]= Sub x y M[x]= v M[y]= w ( P, pc, M)(P, pc + 1, M[x := v −−− w]) P[pc]= Branch v ( P, pc, M)⇝ (P, v, M) P[pc]= Branchif x v M[x]= 0 ( P, pc, M)(P, pc + 1, M) P[pc]= Branchif x v M[x], 0 ( P, pc, M)⇝ (P, v, M) P[pc]= Branchif x v M[x]= > ( P, pc, M)(P, pc + 1, M) P[pc]= Branchif x v M[x]= > ( P, pc, M)⇝ (P, v, M)

Figure 6.2 – Sémantique non-déterministe du langage idéalisé

Conservation de transitions et de traces

Notre raisonnement porte sur le coût maximal des traces d’exécution d’un programme possédant une mémoire qui contient des variables effacées : les valeurs issues de l’environnement, inconnues en amont de l’exécution du programme. Pourtant, lors de l’exécution réelle du programme, la mémoire ne contient aucune valeur inconnue, et la trace d’exécution correspondante est la suite unique des

6.1. Validation formelle de la méthode en Coq 157

transitions dans la sémantique opérationnelle déterministe du langage idéalisé. Il est alors important, afin que le raisonnement appliqué aux traces « non-déterministes » du programme puisse être pertinent, de s’assurer que la trace d’exécution réelle du programme est bien contenue dans l’ensemble des traces non-déterministes considérées. En d’autres termes, raisonner sur des versions « effacées » de la mémoire ne peut être utile qu’à condition que, parmi toutes les traces effacées valides calculées, l’une de ces dernières corresponde à la trace réelle du programme (sinon, nous ne pourrions rien conclure concernant l’exécution réelle du programme).

Nous commençons d’abord par raisonner à l’échelle des transitions de la sémantique du langage. À cet effet, nous définissons d’abord un premier lemme déclarant qu’une transition dans la sémantique déterministe perdure après « plongement » dans la sémantique non-déterministe du langage idéalisé. Cette propriété est évidente, puisque la sémantique non-déterministe correspond à une extension de la sémantique déterministe du langage idéalisé :

Lemme 6.1.3. ∀ σ σ0, (σ −→ σ0)⇒ (σ ⇝ σ0)

De plus, toute transition est conservée après effacement de la mémoire du programme : s’il existe une transition d’un étatσ1 vers un étatσ2dans la sémantique non-déterministe, alors pour tout effacement σ0

1de l’état d’origine il existe une transition menant à un étatσ0

2, et ce dernier est un effacement de σ2: Lemme 6.1.4. ∀ σ1σ2σ0 1, (σ1⇝ σ2)∧ (σ1d σ0 1)⇒ (∃ σ0 2, (σ0 1⇝ σ0 2)∧ (σ2d σ0 2))

En combinant les deux lemmes précédents, nous sommes alors en mesure de déduire que toute transition dans la sémantique déterministe est conservée après plongement dans la sémantique non-déterministe, même après effacement (partiel ou complet) de la mémoire de l’état d’origine :

Lemme 6.1.5(Conservation). ∀ σ1σ2σ0 1, (σ1 −→ σ2)∧ (σ1d σ0 1)⇒ (∃ σ0 2, (σ0 1 ⇝ σ0 2)∧ (σ2d σ0 2)) Cette propriété est représentée schématiquement dans la figure 6.3.

σ1 σ2

σ0

1 σ0

2

Figure 6.3 – Conservation de transition

Enfin, comme illustré par la figure 6.4, l’extension des propriétés précédentes aux traces d’exécution est directe. σ1 σ2 . . . σn σ0 1 σ0 2 . . . σ0 n . . . σ00 1 σ00 2 σ00 n

Majoration du coût de l’exécution réelle du programme

Afin de vérifier que la fonction costmax concerne bien la plus « coûteuse » trace d’exécution du pro-gramme, nous définissons formellement une telle trace, nommée trace d’exécution maximale.

Définition 6.1.6(Trace maximale). La fonction runmaxqui calcule la trace d’exécution maximale est définie par induction de la façon suivante :

runmax(σ) = T σ −→ σ0 runmax0)= T runmax(σ) = (σ :: T ) P[pc]= Stop runmax(( P, pc, M))= [(P, pc, M)] P[pc]= Branchif x v M[x]= > runmax((

P, pc + 1, M))= T runmax((P, v, M)) = T0 cost(T ) > cost(T0) runmax((

P, pc, M))= ((P, pc, M) :: T )

P[pc]= Branchif x v M[x]= >

runmax((

P, pc + 1, M))= T runmax((P, v, M)) = T0 cost(T ) ≤ cost(T0) runmax((

P, pc, M))= ((P, pc, M) :: T0)

De la même façon que costmax, la fonction runmaxest une fonction partielle définie par un inductif dans Coq.

La trace maximale contient les mêmes états que ceux considérés par la fonction costmax: elle parcourt les chemins pour lesquels la somme totale des coûts est la plus importante. Son coût est donc bien identique au coût maximum d’exécution :

Lemme 6.1.6. ∀ σ, costmax(σ) = cost(runmax(σ))

Par définition, la trace maximale est une trace finie. De plus, le coût de celle-ci est supérieur à celui de toute autre trace finie qui commence par le même état :

Lemme 6.1.7. ∀ σ T k, f inite(σ :: T ) ⇒ costmax(σ) = k ⇒ k ≥ cost(σ :: T ))

En appliquant le lemme de conservation aux traces d’exécution, nous dérivons alors la propriété principale de la preuve de correction de notre méthode : le coût de la trace maximale d’exécution est plus grand que le coût de toute trace finie qui commence par le même état, même lorsque l’on considère un effacement de sa mémoire.

Lemme 6.1.8. ∀ σ T k, f inite(σ :: T ) ⇒ ∀ σ0, σd σ0⇒ costmax0)= k ⇒ k ≥ cost(σ :: T )

Puisque la trace déterministe est finie, la combinaison des précédents lemmes nous permet enfin de conclure avec le théorème de correction qui stipule que le coût maximal d’un programme, estimé à partir d’un effacement de la mémoire initiale, est un majorant du coût réel d’exécution du programme : Théorème 6.1.2(Correction). ∀ σ T k, run(σ) = T ⇒ ∀ σ0, σd σ0⇒ costmax0)= k ⇒ k ≥ cost(T )