mirror of
https://github.com/achlipala/frap.git
synced 2024-12-01 00:26:18 +00:00
MessagesAndRefinement: comments
This commit is contained in:
parent
fdc5d2dee2
commit
7a864f14df
2 changed files with 193 additions and 30 deletions
|
@ -1,5 +1,5 @@
|
||||||
(** Formal Reasoning About Programs <http://adam.chlipala.net/frap/>
|
(** Formal Reasoning About Programs <http://adam.chlipala.net/frap/>
|
||||||
* Chapter 15: Pi-Calculus and Behavioral Refinement
|
* Chapter 15: Process Algebra and Behavioral Refinement
|
||||||
* Author: Adam Chlipala
|
* Author: Adam Chlipala
|
||||||
* License: https://creativecommons.org/licenses/by-nc-nd/4.0/ *)
|
* License: https://creativecommons.org/licenses/by-nc-nd/4.0/ *)
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ Require Import Frap Eqdep FunctionalExtensionality.
|
||||||
Set Implicit Arguments.
|
Set Implicit Arguments.
|
||||||
Set Asymmetric Patterns.
|
Set Asymmetric Patterns.
|
||||||
|
|
||||||
|
(** * First, an unexplained tactic that will come in handy.... *)
|
||||||
|
|
||||||
Ltac invert H := (Frap.invert H || (inversion H; clear H));
|
Ltac invert H := (Frap.invert H || (inversion H; clear H));
|
||||||
repeat match goal with
|
repeat match goal with
|
||||||
| [ x : _ |- _ ] => subst x
|
| [ x : _ |- _ ] => subst x
|
||||||
|
@ -15,21 +17,43 @@ Ltac invert H := (Frap.invert H || (inversion H; clear H));
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
(** * A process algebra: syntax and semantics *)
|
||||||
|
|
||||||
|
(* A process algebra defines a set of communicating *processes*, which might
|
||||||
|
* more commonly be called *threads*. Typically processes communicate not with
|
||||||
|
* locks and shared memory, as they have in the prior two chapters, but instead
|
||||||
|
* with *message passing*. Messages are passed over synchronous *channels*,
|
||||||
|
* which we will just represent as numbers. *)
|
||||||
Notation channel := nat (only parsing).
|
Notation channel := nat (only parsing).
|
||||||
|
|
||||||
|
(* Here are the basic syntactic constructions of processes. *)
|
||||||
Inductive proc :=
|
Inductive proc :=
|
||||||
| NewChannel (notThese : list channel) (k : channel -> proc)
|
| NewChannel (notThese : list channel) (k : channel -> proc)
|
||||||
|
(* Pick a new channel [ch] name not found in [notThese], and continue like
|
||||||
|
* [k ch]. *)
|
||||||
|
|
||||||
| BlockChannel (ch : channel) (pr : proc)
|
| BlockChannel (ch : channel) (pr : proc)
|
||||||
|
(* Act like [pr], but prevent interaction with other processes through channel
|
||||||
|
* [ch]. We effectively force [ch] to be *private*. *)
|
||||||
|
|
||||||
| Send (ch : channel) {A : Set} (v : A) (k : proc)
|
| Send (ch : channel) {A : Set} (v : A) (k : proc)
|
||||||
| Recv (ch : channel) {A : Set} (k : A -> proc)
|
| Recv (ch : channel) {A : Set} (k : A -> proc)
|
||||||
|
(* When one process runs a [Send] and the other a [Recv] on the same channel
|
||||||
|
* simultaneously, the [Send] moves on to its [k], while the [Recv] moves on to
|
||||||
|
* its [k v], for [v] the value that was sent. *)
|
||||||
|
|
||||||
| Par (pr1 pr2 : proc)
|
| Par (pr1 pr2 : proc)
|
||||||
|
(* This one, at least, is just like it was in the last chapter: parallel
|
||||||
|
* composition of threads. *)
|
||||||
|
|
||||||
| Dup (pr : proc)
|
| Dup (pr : proc)
|
||||||
| Done.
|
(* An idiosyncratic convention of process algebra: [Dup pr] acts like an
|
||||||
|
* *infinite* number of *copies* of [pr]. It replaces conventional loops. *)
|
||||||
|
|
||||||
Notation pid := nat (only parsing).
|
| Done
|
||||||
Notation procs := (fmap pid proc).
|
(* This process can't do anything *).
|
||||||
Notation channels := (set channel).
|
|
||||||
|
|
||||||
|
(* Some nicer notations: *)
|
||||||
Notation "'New' ls ( x ) ; k" := (NewChannel ls (fun x => k)) (right associativity, at level 51, ls at level 0).
|
Notation "'New' ls ( x ) ; k" := (NewChannel ls (fun x => k)) (right associativity, at level 51, ls at level 0).
|
||||||
Notation "'Block' ch ; k" := (BlockChannel ch k) (right associativity, at level 51).
|
Notation "'Block' ch ; k" := (BlockChannel ch k) (right associativity, at level 51).
|
||||||
Notation "!! ch ( v ) ; k" := (Send ch v k) (right associativity, at level 45, ch at level 0).
|
Notation "!! ch ( v ) ; k" := (Send ch v k) (right associativity, at level 45, ch at level 0).
|
||||||
|
@ -39,19 +63,31 @@ Infix "||" := Par.
|
||||||
|
|
||||||
(** * Example *)
|
(** * Example *)
|
||||||
|
|
||||||
|
(* Let's build highly exciting processes for adding constants to numbers. *)
|
||||||
|
|
||||||
|
(* This one accepts one number [n] on channel [input] and returns [n + k] as the
|
||||||
|
* result, by writing it to [output]. *)
|
||||||
Definition addN (k : nat) (input output : channel) : proc :=
|
Definition addN (k : nat) (input output : channel) : proc :=
|
||||||
??input(n : nat);
|
??input(n : nat);
|
||||||
!!output(n + k);
|
!!output(n + k);
|
||||||
Done.
|
Done.
|
||||||
|
|
||||||
|
(* We wrap that last one in a [Dup] to turn it into a kind of immortal server,
|
||||||
|
* happy to keep responding to "please add k to this" requests forever. *)
|
||||||
Definition addNs (k : nat) (input output : channel) : proc :=
|
Definition addNs (k : nat) (input output : channel) : proc :=
|
||||||
Dup (addN k input output).
|
Dup (addN k input output).
|
||||||
|
|
||||||
|
(* Chaining together two "+1" boxes is one way to build an alternative "+2"
|
||||||
|
* box! *)
|
||||||
Definition add2 (input output : channel) : proc :=
|
Definition add2 (input output : channel) : proc :=
|
||||||
Dup (New[input;output](intermediate);
|
Dup (New[input;output](intermediate);
|
||||||
addN 1 input intermediate
|
addN 1 input intermediate
|
||||||
|| addN 1 intermediate output).
|
|| addN 1 intermediate output).
|
||||||
|
|
||||||
|
(* However, we implement addition, we might compose with this tester process,
|
||||||
|
* which uses an adder as a subroutine in a larger protocol. [metaInput] and
|
||||||
|
* [metaOutput] are the input and output of the whole system, while [input] and
|
||||||
|
* [output] are used internally, say to communicate with an adder. *)
|
||||||
Definition tester (metaInput input output metaOutput : channel) : proc :=
|
Definition tester (metaInput input output metaOutput : channel) : proc :=
|
||||||
??metaInput(n : nat);
|
??metaInput(n : nat);
|
||||||
!!input(n * 2);
|
!!input(n * 2);
|
||||||
|
@ -62,6 +98,12 @@ Definition tester (metaInput input output metaOutput : channel) : proc :=
|
||||||
|
|
||||||
(** * Labeled semantics *)
|
(** * Labeled semantics *)
|
||||||
|
|
||||||
|
(* Let's explain how programs run. We'll give a flavor of operational semantics
|
||||||
|
* called a "labelled transition system," because each step will include a label
|
||||||
|
* that explains what happened. In this case, the only relevant happenings are
|
||||||
|
* sends or receives on channels. Crucially, we supress send/receive labels for
|
||||||
|
* operations blocked by [Block]s. *)
|
||||||
|
|
||||||
Record message := {
|
Record message := {
|
||||||
Channel : channel;
|
Channel : channel;
|
||||||
TypeOf : Set;
|
TypeOf : Set;
|
||||||
|
@ -76,8 +118,10 @@ Inductive label :=
|
||||||
| Silent
|
| Silent
|
||||||
| Action (a : action).
|
| Action (a : action).
|
||||||
|
|
||||||
|
(* This command lets us use [action]s implicitly as [label]s. *)
|
||||||
Coercion Action : action >-> label.
|
Coercion Action : action >-> label.
|
||||||
|
|
||||||
|
(* This predicate captures when a label doesn't use a channel. *)
|
||||||
Definition notUse (ch : channel) (l : label) :=
|
Definition notUse (ch : channel) (l : label) :=
|
||||||
match l with
|
match l with
|
||||||
| Action (Input r) => r.(Channel) <> ch
|
| Action (Input r) => r.(Channel) <> ch
|
||||||
|
@ -85,7 +129,13 @@ Definition notUse (ch : channel) (l : label) :=
|
||||||
| Silent => True
|
| Silent => True
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
(* Now, our labelled transition system: *)
|
||||||
Inductive lstep : proc -> label -> proc -> Prop :=
|
Inductive lstep : proc -> label -> proc -> Prop :=
|
||||||
|
|
||||||
|
(* Sends and receives generate the expected labels. Note that, for a [Recv],
|
||||||
|
* the value being received is "pulled out of thin air"! However, it gets
|
||||||
|
* determined concretely by comparing against a matching [Send], in a rule that
|
||||||
|
* we get to shortly. *)
|
||||||
| LStepSend : forall ch {A : Set} (v : A) k,
|
| LStepSend : forall ch {A : Set} (v : A) k,
|
||||||
lstep (Send ch v k)
|
lstep (Send ch v k)
|
||||||
(Output {| Channel := ch; Value := v |})
|
(Output {| Channel := ch; Value := v |})
|
||||||
|
@ -94,21 +144,39 @@ Inductive lstep : proc -> label -> proc -> Prop :=
|
||||||
lstep (Recv ch k)
|
lstep (Recv ch k)
|
||||||
(Input {| Channel := ch; Value := v |})
|
(Input {| Channel := ch; Value := v |})
|
||||||
(k v)
|
(k v)
|
||||||
|
|
||||||
|
(* A [Dup] always has the option of replicating itself further. *)
|
||||||
| LStepDup : forall pr,
|
| LStepDup : forall pr,
|
||||||
lstep (Dup pr) Silent (Par (Dup pr) pr)
|
lstep (Dup pr) Silent (Par (Dup pr) pr)
|
||||||
|
|
||||||
|
(* A channel allocation operation nondeterministically picks the new channel ID,
|
||||||
|
* only checking that it isn't in the provided blacklist. We install a [Block]
|
||||||
|
* node to keep this channel truly private. *)
|
||||||
| LStepNew : forall chs ch k,
|
| LStepNew : forall chs ch k,
|
||||||
~In ch chs
|
~In ch chs
|
||||||
-> lstep (NewChannel chs k) Silent (BlockChannel ch (k ch))
|
-> lstep (NewChannel chs k) Silent (BlockChannel ch (k ch))
|
||||||
|
|
||||||
|
(* [Block] nodes work as expected, disallowing labels that use the channel. *)
|
||||||
| LStepBlock : forall ch k l k',
|
| LStepBlock : forall ch k l k',
|
||||||
lstep k l k'
|
lstep k l k'
|
||||||
-> notUse ch l
|
-> notUse ch l
|
||||||
-> lstep (BlockChannel ch k) l (BlockChannel ch k')
|
-> lstep (BlockChannel ch k) l (BlockChannel ch k')
|
||||||
|
|
||||||
|
(* When either side of a parallel composition can step, we may bubble that step
|
||||||
|
* up to the top. *)
|
||||||
| LStepPar1 : forall pr1 pr2 l pr1',
|
| LStepPar1 : forall pr1 pr2 l pr1',
|
||||||
lstep pr1 l pr1'
|
lstep pr1 l pr1'
|
||||||
-> lstep (Par pr1 pr2) l (Par pr1' pr2)
|
-> lstep (Par pr1 pr2) l (Par pr1' pr2)
|
||||||
| LStepPar2 : forall pr1 pr2 l pr2',
|
| LStepPar2 : forall pr1 pr2 l pr2',
|
||||||
lstep pr2 l pr2'
|
lstep pr2 l pr2'
|
||||||
-> lstep (Par pr1 pr2) l (Par pr1 pr2')
|
-> lstep (Par pr1 pr2) l (Par pr1 pr2')
|
||||||
|
|
||||||
|
(* These two symmetrical rules are the heart of how communication happens in our
|
||||||
|
* language. Namely, in a parallel composition, when one side is prepared to
|
||||||
|
* write a value to a channel, and the other side is prepared to read the same
|
||||||
|
* value from the same channel, the two sides *rendezvous*, and the value is
|
||||||
|
* exchanged. This is the only mechanism to let two transitions happen at
|
||||||
|
* once. *)
|
||||||
| LStepRendezvousLeft : forall pr1 ch {A : Set} (v : A) pr1' pr2 pr2',
|
| LStepRendezvousLeft : forall pr1 ch {A : Set} (v : A) pr1' pr2 pr2',
|
||||||
lstep pr1 (Input {| Channel := ch; Value := v |}) pr1'
|
lstep pr1 (Input {| Channel := ch; Value := v |}) pr1'
|
||||||
-> lstep pr2 (Output {| Channel := ch; Value := v |}) pr2'
|
-> lstep pr2 (Output {| Channel := ch; Value := v |}) pr2'
|
||||||
|
@ -118,6 +186,49 @@ Inductive lstep : proc -> label -> proc -> Prop :=
|
||||||
-> lstep pr2 (Input {| Channel := ch; Value := v |}) pr2'
|
-> lstep pr2 (Input {| Channel := ch; Value := v |}) pr2'
|
||||||
-> lstep (Par pr1 pr2) Silent (Par pr1' pr2').
|
-> lstep (Par pr1 pr2) Silent (Par pr1' pr2').
|
||||||
|
|
||||||
|
(* Here's a shorthand for silent steps. *)
|
||||||
|
Definition lstepSilent (pr1 pr2 : proc) := lstep pr1 Silent pr2.
|
||||||
|
|
||||||
|
(* Our key proof task will be to prove that one process "acts like" another.
|
||||||
|
* We'll use *simulation* as the precise notion of "acts like." *)
|
||||||
|
|
||||||
|
(* We say that a relation [R] is a *simulation* when it satisfies the first two
|
||||||
|
* conditions below. The [simulates] predicate additionally asserts that a
|
||||||
|
* particular pair of processes belongs to [R]. *)
|
||||||
|
Definition simulates (R : proc -> proc -> Prop) (pr1 pr2 : proc) :=
|
||||||
|
(* First, consider a pair of processes related by [R]. When the lefthand
|
||||||
|
* process can take a silent step, the righthand process can take zero or more
|
||||||
|
* silent steps to "catch up," arriving at a new righthand process related to
|
||||||
|
* the new lefthand process. *)
|
||||||
|
(forall pr1 pr2, R pr1 pr2
|
||||||
|
-> forall pr1', lstepSilent pr1 pr1'
|
||||||
|
-> exists pr2', lstepSilent^* pr2 pr2'
|
||||||
|
/\ R pr1' pr2')
|
||||||
|
|
||||||
|
(* Now consider the same scenario where the lefthand process make a nonsilent
|
||||||
|
* step. We require that the righthand process can "catch up" in a way that
|
||||||
|
* generates the same label that the lefthand process generated. *)
|
||||||
|
/\ (forall pr1 pr2, R pr1 pr2
|
||||||
|
-> forall a pr1', lstep pr1 (Action a) pr1'
|
||||||
|
-> exists pr2' pr2'', lstepSilent^* pr2 pr2'
|
||||||
|
/\ lstep pr2' (Action a) pr2''
|
||||||
|
/\ R pr1' pr2'')
|
||||||
|
|
||||||
|
(* Finally, the provided process pair is in the relation. *)
|
||||||
|
/\ R pr1 pr2.
|
||||||
|
|
||||||
|
(* One process *refines* another when there exists some simulation. *)
|
||||||
|
Definition refines (pr1 pr2 : proc) :=
|
||||||
|
exists R, simulates R pr1 pr2.
|
||||||
|
|
||||||
|
Infix "<|" := refines (no associativity, at level 70).
|
||||||
|
|
||||||
|
(* That's a somewhat fancy notion of compatibility! We can also relate it to
|
||||||
|
* more intuitive conditions that aren't strong enough for many of the proofs we
|
||||||
|
* want to do later. *)
|
||||||
|
|
||||||
|
(* This predicate captures all finite traces of actions that a process could
|
||||||
|
* generate. *)
|
||||||
Inductive couldGenerate : proc -> list action -> Prop :=
|
Inductive couldGenerate : proc -> list action -> Prop :=
|
||||||
| CgNothing : forall pr,
|
| CgNothing : forall pr,
|
||||||
couldGenerate pr []
|
couldGenerate pr []
|
||||||
|
@ -130,27 +241,8 @@ Inductive couldGenerate : proc -> list action -> Prop :=
|
||||||
-> couldGenerate pr' tr
|
-> couldGenerate pr' tr
|
||||||
-> couldGenerate pr (a :: tr).
|
-> couldGenerate pr (a :: tr).
|
||||||
|
|
||||||
Definition lstepSilent (pr1 pr2 : proc) := lstep pr1 Silent pr2.
|
(* Skip ahead to [refines_couldGenerate] to see the top-level connection from
|
||||||
|
* [refines]. *)
|
||||||
Definition simulates (R : proc -> proc -> Prop) (pr1 pr2 : proc) :=
|
|
||||||
(forall pr1 pr2, R pr1 pr2
|
|
||||||
-> forall pr1', lstepSilent pr1 pr1'
|
|
||||||
-> exists pr2', lstepSilent^* pr2 pr2'
|
|
||||||
/\ R pr1' pr2')
|
|
||||||
/\ (forall pr1 pr2, R pr1 pr2
|
|
||||||
-> forall a pr1', lstep pr1 (Action a) pr1'
|
|
||||||
-> exists pr2' pr2'', lstepSilent^* pr2 pr2'
|
|
||||||
/\ lstep pr2' (Action a) pr2''
|
|
||||||
/\ R pr1' pr2'')
|
|
||||||
/\ R pr1 pr2.
|
|
||||||
|
|
||||||
Definition refines (pr1 pr2 : proc) :=
|
|
||||||
exists R, simulates R pr1 pr2.
|
|
||||||
|
|
||||||
Infix "<|" := refines (no associativity, at level 70).
|
|
||||||
|
|
||||||
|
|
||||||
(** * Simulation: a proof method *)
|
|
||||||
|
|
||||||
Hint Constructors couldGenerate.
|
Hint Constructors couldGenerate.
|
||||||
|
|
||||||
|
@ -189,6 +281,7 @@ Proof.
|
||||||
eauto.
|
eauto.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
(* This theorem says that refinement implies *trace inclusion*. *)
|
||||||
Theorem refines_couldGenerate : forall pr1 pr2, pr1 <| pr2
|
Theorem refines_couldGenerate : forall pr1 pr2, pr1 <| pr2
|
||||||
-> forall tr, couldGenerate pr1 tr
|
-> forall tr, couldGenerate pr1 tr
|
||||||
-> couldGenerate pr2 tr.
|
-> couldGenerate pr2 tr.
|
||||||
|
@ -199,6 +292,9 @@ Qed.
|
||||||
|
|
||||||
(** * Tactics for automating refinement proofs *)
|
(** * Tactics for automating refinement proofs *)
|
||||||
|
|
||||||
|
(* Well, you're used to unexplained automation tactics by now, so here are some
|
||||||
|
* more. ;-) *)
|
||||||
|
|
||||||
Lemma invert_Recv : forall ch (A : Set) (k : A -> proc) (x : A) pr,
|
Lemma invert_Recv : forall ch (A : Set) (k : A -> proc) (x : A) pr,
|
||||||
lstep (Recv ch k) (Input {| Channel := ch; Value := x |}) pr
|
lstep (Recv ch k) (Input {| Channel := ch; Value := x |}) pr
|
||||||
-> pr = k x.
|
-> pr = k x.
|
||||||
|
@ -228,11 +324,16 @@ Hint Extern 1 (NoDup _) => lists.
|
||||||
|
|
||||||
(** * Examples *)
|
(** * Examples *)
|
||||||
|
|
||||||
|
(* OK, let's verify a simplification of the example we started with. *)
|
||||||
Definition add2_once (input output : channel) : proc :=
|
Definition add2_once (input output : channel) : proc :=
|
||||||
New[input;output](intermediate);
|
New[input;output](intermediate);
|
||||||
(addN 1 input intermediate
|
(addN 1 input intermediate
|
||||||
|| addN 1 intermediate output).
|
|| addN 1 intermediate output).
|
||||||
|
|
||||||
|
(* Here's our first, painstakingly crafted simulation relation. It needs to
|
||||||
|
* identify all pairs of processes that should be considered compatible. Think
|
||||||
|
* of the first process as the fancy *implementation* and the second process as
|
||||||
|
* the simpler *specification*. *)
|
||||||
Inductive R_add2 : proc -> proc -> Prop :=
|
Inductive R_add2 : proc -> proc -> Prop :=
|
||||||
| Initial : forall input output,
|
| Initial : forall input output,
|
||||||
input <> output
|
input <> output
|
||||||
|
@ -273,20 +374,25 @@ Proof.
|
||||||
exists R_add2.
|
exists R_add2.
|
||||||
first_order.
|
first_order.
|
||||||
|
|
||||||
clear input output H.
|
|
||||||
invert H0; simplify; inverter; eauto.
|
invert H0; simplify; inverter; eauto.
|
||||||
replace (n + 1 + 1) with (n + 2) by linear_arithmetic.
|
replace (n + 1 + 1) with (n + 2) by linear_arithmetic.
|
||||||
eauto.
|
eauto.
|
||||||
|
|
||||||
clear input output H.
|
|
||||||
invert H0; simplify; inverter; eauto 10; simplify; equality.
|
invert H0; simplify; inverter; eauto 10; simplify; equality.
|
||||||
|
|
||||||
unfold add2_once, addN; eauto.
|
unfold add2_once, addN; eauto.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
(* Well, good! The fancy version doesn't produce any traces that the simple
|
||||||
|
* version couldn't also produce. (It may, however, fail to produce some traces
|
||||||
|
* that the spec allows.) *)
|
||||||
|
|
||||||
|
|
||||||
(** * Compositional reasoning principles *)
|
(** * Compositional reasoning principles *)
|
||||||
|
|
||||||
|
(* It turns out that refinement has all sorts of convenient algebraic
|
||||||
|
* properties. To start with, it's a preorder. *)
|
||||||
|
|
||||||
Theorem refines_refl : forall pr,
|
Theorem refines_refl : forall pr,
|
||||||
pr <| pr.
|
pr <| pr.
|
||||||
Proof.
|
Proof.
|
||||||
|
@ -339,6 +445,8 @@ Qed.
|
||||||
|
|
||||||
(** ** Dup *)
|
(** ** Dup *)
|
||||||
|
|
||||||
|
(* Refinement can be "pushed inside" a [Dup] operation. *)
|
||||||
|
|
||||||
Inductive RDup (R : proc -> proc -> Prop) : proc -> proc -> Prop :=
|
Inductive RDup (R : proc -> proc -> Prop) : proc -> proc -> Prop :=
|
||||||
| RDupLeaf : forall pr1 pr2,
|
| RDupLeaf : forall pr1 pr2,
|
||||||
R pr1 pr2
|
R pr1 pr2
|
||||||
|
@ -466,6 +574,8 @@ Qed.
|
||||||
|
|
||||||
(** ** Par *)
|
(** ** Par *)
|
||||||
|
|
||||||
|
(* Refinement can also be "pushed inside" parallel composition. *)
|
||||||
|
|
||||||
Inductive RPar (R1 R2 : proc -> proc -> Prop) : proc -> proc -> Prop :=
|
Inductive RPar (R1 R2 : proc -> proc -> Prop) : proc -> proc -> Prop :=
|
||||||
| RPar1 : forall pr1 pr2 pr1' pr2',
|
| RPar1 : forall pr1 pr2 pr1' pr2',
|
||||||
R1 pr1 pr1'
|
R1 pr1 pr1'
|
||||||
|
@ -570,6 +680,8 @@ Qed.
|
||||||
|
|
||||||
(** ** Block *)
|
(** ** Block *)
|
||||||
|
|
||||||
|
(* A few similar properties apply to [Block], too. *)
|
||||||
|
|
||||||
Inductive RBlock (R : proc -> proc -> Prop) : proc -> proc -> Prop :=
|
Inductive RBlock (R : proc -> proc -> Prop) : proc -> proc -> Prop :=
|
||||||
| RBlock1 : forall pr1 pr2 ch,
|
| RBlock1 : forall pr1 pr2 ch,
|
||||||
R pr1 pr2
|
R pr1 pr2
|
||||||
|
@ -631,6 +743,8 @@ Proof.
|
||||||
eauto 10.
|
eauto 10.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
(* This predicate is handy for side conditions, to enforce that a process never
|
||||||
|
* uses a particular channel for anything. *)
|
||||||
Inductive neverUses (ch : channel) : proc -> Prop :=
|
Inductive neverUses (ch : channel) : proc -> Prop :=
|
||||||
| NuRecv : forall ch' (A : Set) (k : A -> _),
|
| NuRecv : forall ch' (A : Set) (k : A -> _),
|
||||||
ch' <> ch
|
ch' <> ch
|
||||||
|
@ -722,7 +836,10 @@ Proof.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
|
||||||
(** * Example again *)
|
(** * The first example again *)
|
||||||
|
|
||||||
|
(* Those tools will help us lift our earlier adder proof to the immortal-server
|
||||||
|
* case, without writing any new simulation relations ourselves. *)
|
||||||
|
|
||||||
Theorem refines_add2 : forall input output,
|
Theorem refines_add2 : forall input output,
|
||||||
input <> output
|
input <> output
|
||||||
|
@ -733,6 +850,8 @@ Proof.
|
||||||
apply add2_once_refines_addN; auto.
|
apply add2_once_refines_addN; auto.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
(* We can even check refinement of our different adders when run together with
|
||||||
|
* the tester, carefully marking internal channels as private with [Block]. *)
|
||||||
Theorem refines_add2_with_tester : forall metaInput input output metaOutput,
|
Theorem refines_add2_with_tester : forall metaInput input output metaOutput,
|
||||||
input <> output
|
input <> output
|
||||||
-> Block input; Block output; add2 input output || tester metaInput input output metaOutput
|
-> Block input; Block output; add2 input output || tester metaInput input output metaOutput
|
||||||
|
@ -748,16 +867,23 @@ Qed.
|
||||||
|
|
||||||
(** * Tree membership *)
|
(** * Tree membership *)
|
||||||
|
|
||||||
|
(* Here's one more example of a parallel program, which searches a binary tree
|
||||||
|
* in parallel, checking if a value is found at one of the leaves. *)
|
||||||
|
|
||||||
Inductive tree :=
|
Inductive tree :=
|
||||||
| Leaf (n : nat)
|
| Leaf (n : nat)
|
||||||
| Node (l r : tree).
|
| Node (l r : tree).
|
||||||
|
|
||||||
|
(* This function formalizes the membership property that we check. *)
|
||||||
Fixpoint mem (n : nat) (t : tree) : bool :=
|
Fixpoint mem (n : nat) (t : tree) : bool :=
|
||||||
match t with
|
match t with
|
||||||
| Leaf m => if m ==n n then true else false
|
| Leaf m => if m ==n n then true else false
|
||||||
| Node l r => mem n l || mem n r
|
| Node l r => mem n l || mem n r
|
||||||
end%bool.
|
end%bool.
|
||||||
|
|
||||||
|
(* Here's the lame (but straightforward) sequential implementation. Note that
|
||||||
|
* we do nothing if the value is not found, and we send exactly one [tt] value
|
||||||
|
* as output if the value is found. *)
|
||||||
Definition inTree_seq (t : tree) (input output : channel) :=
|
Definition inTree_seq (t : tree) (input output : channel) :=
|
||||||
Dup (??input(n : nat);
|
Dup (??input(n : nat);
|
||||||
if mem n t then
|
if mem n t then
|
||||||
|
@ -766,6 +892,8 @@ Definition inTree_seq (t : tree) (input output : channel) :=
|
||||||
else
|
else
|
||||||
Done).
|
Done).
|
||||||
|
|
||||||
|
(* Helper function for a fancier parallel version, creating many threads that
|
||||||
|
* are all allowed to send to a channel [output], if they find the value. *)
|
||||||
Fixpoint inTree_par' (n : nat) (t : tree) (output : channel) :=
|
Fixpoint inTree_par' (n : nat) (t : tree) (output : channel) :=
|
||||||
match t with
|
match t with
|
||||||
| Leaf m =>
|
| Leaf m =>
|
||||||
|
@ -778,6 +906,7 @@ Fixpoint inTree_par' (n : nat) (t : tree) (output : channel) :=
|
||||||
inTree_par' n l output || inTree_par' n r output
|
inTree_par' n l output || inTree_par' n r output
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
(* Top-level wrapper for an immortal-server tree-searcher *)
|
||||||
Definition inTree_par (t : tree) (input output : channel) :=
|
Definition inTree_par (t : tree) (input output : channel) :=
|
||||||
Dup (??input(n : nat);
|
Dup (??input(n : nat);
|
||||||
New[input;output](output');
|
New[input;output](output');
|
||||||
|
@ -785,6 +914,13 @@ Definition inTree_par (t : tree) (input output : channel) :=
|
||||||
|| ??output'(_ : unit);
|
|| ??output'(_ : unit);
|
||||||
!!output(tt);
|
!!output(tt);
|
||||||
Done).
|
Done).
|
||||||
|
(* Note the second part of the parallel composition, which makes sure we send
|
||||||
|
* *at most one* notification to the outside world, though the internal threads
|
||||||
|
* may generate as many notifications as there are tree leaves. *)
|
||||||
|
|
||||||
|
(* OK, now we get into the complex part, to prove the simulation. We will let
|
||||||
|
* the relations and lemmas below "speak for themselves," though admittedly it's
|
||||||
|
* a pretty involved argument. *)
|
||||||
|
|
||||||
Inductive TreeThreads (output' : channel) : bool -> proc -> Prop :=
|
Inductive TreeThreads (output' : channel) : bool -> proc -> Prop :=
|
||||||
| TtDone : forall maySend,
|
| TtDone : forall maySend,
|
||||||
|
@ -796,6 +932,7 @@ Inductive TreeThreads (output' : channel) : bool -> proc -> Prop :=
|
||||||
-> TreeThreads output' maySend pr2
|
-> TreeThreads output' maySend pr2
|
||||||
-> TreeThreads output' maySend (pr1 || pr2).
|
-> TreeThreads output' maySend (pr1 || pr2).
|
||||||
|
|
||||||
|
(* This is the main simulation relation. *)
|
||||||
Inductive RTree (t : tree) (input output : channel) : proc -> proc -> Prop :=
|
Inductive RTree (t : tree) (input output : channel) : proc -> proc -> Prop :=
|
||||||
| TreeInit :
|
| TreeInit :
|
||||||
RTree t input output
|
RTree t input output
|
||||||
|
@ -912,6 +1049,7 @@ Qed.
|
||||||
|
|
||||||
Hint Resolve TreeThreads_inTree_par'.
|
Hint Resolve TreeThreads_inTree_par'.
|
||||||
|
|
||||||
|
(* Finally, the main theorem: *)
|
||||||
Theorem refines_inTree_par : forall t input output,
|
Theorem refines_inTree_par : forall t input output,
|
||||||
inTree_par t input output <| inTree_seq t input output.
|
inTree_par t input output <| inTree_seq t input output.
|
||||||
Proof.
|
Proof.
|
||||||
|
@ -969,6 +1107,9 @@ Proof.
|
||||||
invert H7.
|
invert H7.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
(* Hey, let's reason about plugging together the adder and the tree-searcher,
|
||||||
|
* because we can! The adder produces a number that is fed into the
|
||||||
|
* tree-searcher as input. *)
|
||||||
Theorem gratuitous_composition : forall t ch1 ch2 ch3,
|
Theorem gratuitous_composition : forall t ch1 ch2 ch3,
|
||||||
ch1 <> ch2
|
ch1 <> ch2
|
||||||
-> Block ch2; add2 ch1 ch2 || inTree_par t ch2 ch3
|
-> Block ch2; add2 ch1 ch2 || inTree_par t ch2 ch3
|
||||||
|
@ -980,10 +1121,17 @@ Proof.
|
||||||
apply refines_add2; auto.
|
apply refines_add2; auto.
|
||||||
apply refines_inTree_par.
|
apply refines_inTree_par.
|
||||||
Qed.
|
Qed.
|
||||||
|
(* Note how we didn't need to revisit any details of the proofs of the
|
||||||
|
* individual components. Now that's modularity in action! *)
|
||||||
|
|
||||||
|
|
||||||
(** * One more example: handoff lemma *)
|
(** * One more example: handoff lemma *)
|
||||||
|
|
||||||
|
(* Let's prove an even simpler specification related to the last example proof.
|
||||||
|
* We define some relations and lemmas in service of the key handoff lemma, but
|
||||||
|
* feel free to search for [Theorem] to skip ahead to its (much simpler)
|
||||||
|
* statement. *)
|
||||||
|
|
||||||
Inductive manyOf (this : proc) : proc -> Prop :=
|
Inductive manyOf (this : proc) : proc -> Prop :=
|
||||||
| MoOne : manyOf this this
|
| MoOne : manyOf this this
|
||||||
| MoDup : manyOf this (Dup this)
|
| MoDup : manyOf this (Dup this)
|
||||||
|
@ -1192,6 +1340,12 @@ Proof.
|
||||||
apply IHmanyOfAndOneOf in H6; first_order; subst; eauto.
|
apply IHmanyOfAndOneOf in H6; first_order; subst; eauto.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
|
(* When one thread is ready to send a message, and there is an immortal server
|
||||||
|
* ready to accept that message, the process is equivalent to one that just
|
||||||
|
* skips right to running a single server thread. It is crucial that the body
|
||||||
|
* of each server thread has nothing more to do with the channel we are using to
|
||||||
|
* send it requests! Otherwise, we would need to keep some [Dup] present
|
||||||
|
* explicitly in the spec (righthand argument of [<|]). *)
|
||||||
Theorem handoff : forall ch (A : Set) (v : A) k,
|
Theorem handoff : forall ch (A : Set) (v : A) k,
|
||||||
neverUses ch (k v)
|
neverUses ch (k v)
|
||||||
-> Block ch; (!!ch(v); Done) || Dup (Recv ch k)
|
-> Block ch; (!!ch(v); Done) || Dup (Recv ch k)
|
||||||
|
@ -1259,6 +1413,12 @@ Ltac neverUses :=
|
||||||
| _ => repeat (constructor; simplify)
|
| _ => repeat (constructor; simplify)
|
||||||
end; lists.
|
end; lists.
|
||||||
|
|
||||||
|
(* OK, let's prove a final and satisfyingly simple spec for a system that
|
||||||
|
* combines an adder and a tree-searcher. We send some seed value to an adder,
|
||||||
|
* which forwards it to the tree-searcher. When the value we expect it to send
|
||||||
|
* is indeed present in the tree, the whole contraption is equivalent to just
|
||||||
|
* signalling a "yes" answer! In our setting, that means sending a message to
|
||||||
|
* the final output channel [ch3]. *)
|
||||||
Theorem gratuitous_composition_expanded : forall n t ch1 ch2 ch3,
|
Theorem gratuitous_composition_expanded : forall n t ch1 ch2 ch3,
|
||||||
mem (n + 2) t = true
|
mem (n + 2) t = true
|
||||||
-> NoDup [ch1; ch2; ch3]
|
-> NoDup [ch1; ch2; ch3]
|
||||||
|
@ -1288,3 +1448,5 @@ Proof.
|
||||||
rewrite H.
|
rewrite H.
|
||||||
apply refines_refl.
|
apply refines_refl.
|
||||||
Qed.
|
Qed.
|
||||||
|
(* Note how, again, we used the correctness theorems for our components as black
|
||||||
|
* boxes, so that all that's left is algebraic reasoning over [<|]. *)
|
||||||
|
|
|
@ -30,3 +30,4 @@ SeparationLogic.v
|
||||||
SharedMemory.v
|
SharedMemory.v
|
||||||
ConcurrentSeparationLogic_template.v
|
ConcurrentSeparationLogic_template.v
|
||||||
ConcurrentSeparationLogic.v
|
ConcurrentSeparationLogic.v
|
||||||
|
MessagesAndRefinement.v
|
||||||
|
|
Loading…
Reference in a new issue