WPgenWeakest Precondition Generator
Set Implicit Arguments.
From SLF Require Import WPsem.
Implicit Types f : var.
Implicit Types b : bool.
Implicit Types v : val.
Implicit Types h : heap.
Implicit Types P : Prop.
Implicit Types H : hprop.
Implicit Types Q : val→hprop.
From SLF Require Import WPsem.
Implicit Types f : var.
Implicit Types b : bool.
Implicit Types v : val.
Implicit Types h : heap.
Implicit Types P : Prop.
Implicit Types H : hprop.
Implicit Types Q : val→hprop.
First Pass
wp t1 (fun r ⇒ wp t2 Q) ==> wp (trm_seq t1 t2) Q
- how to define wpgen t Q as a recursive function in Coq,
- how to integrate support for the frame rule in this definition,
- how to carry out practical proofs using wpgen.
The Basic Structure of wpgen
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_var x ⇒ \[False]
| trm_app v1 v2 ⇒ \∃ H, H \* \[triple t H Q]
| trm_let x t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen (subst x v t2) Q)
...
end. Recall from the previous chapter that the rule wp_val asserts that Q v entails wp (trm_val v) Q. Well, wpgen (trm_val v) Q is defined as Q v.
Step 1: wpgen as a Recursive Function over Terms
Fixpoint wpgen (E:ctx) (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_var x ⇒
match lookup x E with
| Some v ⇒ Q v
| None ⇒ \[False]
end
| trm_app v1 v2 ⇒ \∃ H, H \* \[triple (isubst E t) H Q]
| trm_let x t1 t2 ⇒ wpgen E t1 (fun v ⇒ wpgen ((x,v)::E) t2 Q)
...
end.
Step 2: wpgen Reformulated to Eagerly Match on the Term
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
match t with
| trm_val v ⇒ fun (Q:val→hprop) ⇒ Q v
| trm_var x ⇒ fun (Q:val→hprop) ⇒
match lookup x E with
| Some v ⇒ Q v
| None ⇒ \[False]
end
| trm_app t1 t2 ⇒ fun (Q:val→hprop) ⇒
\∃ H, H \* \[triple (isubst E t) H Q]
| trm_let x t1 t2 ⇒ fun (Q:val→hprop) ⇒
wpgen E t1 (fun v ⇒ wpgen ((x,v)::E) t2 Q)
...
end.
Step 3: wpgen Reformulated with Auxiliary Definitions
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
...
end. Each of the auxiliary definitions introduced for wpgen comes with a custom notation that enables a nice display of the output of wpgen. For example, the notation Let' v := F1 in F2 stands for wpgen_let F1 (fun v ⇒ F2). Thanks to this notation, the result of computing wpgen on a source term Let x := t1 in t2, which is an Coq expression of type trm, is a Coq expression of type formula displayed as Let' x := F1 in F2.
Step 4: wpgen Augmented with Support for Structural Rules
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct (match t with
| trm_val v ⇒ ...
| ...
end). Without going into the details at this stage, the predicate mkstruct is a function of type formula→formula that captures the essence of the wp-style consequence-frame rule wp_conseq_frame, presented the previous chapter. This rule asserts that Q1 \*+ H ===> Q2 implies (wp t Q1) \* H ==> (wp t Q2).
Definition of wpgen for Each Term Construct
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ ..
| trm_seq t1 t2 ⇒ ..
| trm_let x t1 t2 ⇒ ..
| trm_var x ⇒ ..
| trm_app t1 t2 ⇒ ..
| trm_fun x t1 ⇒ ..
| trm_fix f x t1 ⇒ ..
| trm_if v0 t1 t2 ⇒ ..
end. Our first goal is to figure out how to fill in the dots for each of the term constructors. The intention that guides us is the soundness theorem for wpgen, which has the following form:
wpgen t Q ==> wp t Q This entailment asserts in particular that, if we are able to establish a statement of the form H ==> wpgen t Q, then we can derive H ==> wp t Q from it. The latter is also equivalent to triple t H Q. Thus, wpgen can be viewed as a practical tool for establishing triples.
Definition of wpgen for Values
The soundness theorem for wpgen requires the entailment
wpgen (trm_val v) Q ==> wp (trm_val v) Q to hold.
To satisfy this entailment, according to the rule wp_val, it suffices to
define wpgen (trm_val v) Q as Q v.
Concretely, we fill in the first dots as follows:
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
...
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
...
Definition of wpgen for Functions
So, likewise, we can define wpgen for functions and for recursive
functions as follows:
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_fun x t1 ⇒ Q (val_fun x t1)
| trm_fix f x t1 ⇒ Q (val_fix f x t1)
...
Observe that we do not attempt to recursively compute wpgen over the body
of the function. This does not harm expressiveness, because the user may
request the computation of wpgen on a local function when reaching the
corresponding function definition in the proof. Below, we will see how to
extend wpgen to eagerly process local function definitions.
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_fun x t1 ⇒ Q (val_fun x t1)
| trm_fix f x t1 ⇒ Q (val_fix f x t1)
...
The intention is for wpgen to admit the same semantics as wp. We thus
expect the definition of wpgen (trm_seq t1 t2) Q to have a similar shape
to wp t1 (fun v ⇒ wp t2 Q).
We therefore define wpgen (trm_seq t1 t2) Q as
wpgen t1 (fun v ⇒ wpgen t2 Q). The definition of wpgen is extended
as follows:
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_seq t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen t2 Q)
...
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_seq t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen t2 Q)
...
Definition of wpgen for Let-Bindings
We fill in the dots as follows:
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_let x t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen (subst x v t2) Q)
... Observe that in the above definition, the second recursive call is invoked on subst x v t2, which is not a strict subterm of t. As explained earlier, this recursion pattern motivates the introduction of substitution contexts, written E, to delay the computation of substitutions until the leaves of the recursion.
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_let x t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen (subst x v t2) Q)
... Observe that in the above definition, the second recursive call is invoked on subst x v t2, which is not a strict subterm of t. As explained earlier, this recursion pattern motivates the introduction of substitution contexts, written E, to delay the computation of substitutions until the leaves of the recursion.
Definition of wpgen for Variables
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_var x ⇒ \[False]
...
Lemma wp_var : ∀ x Q,
\[False] ==> wp (trm_var x) Q.
Proof using. intros. intros h Hh. destruct (hpure_inv Hh). false. Qed.
Lemma triple_var : ∀ x H Q,
False →
triple (trm_var x) H Q.
Proof using. intros. false. Qed.
Lemma triple_var' : ∀ x Q,
triple (trm_var x) \[False] Q.
Proof using. intros. rewrite <- wp_equiv. applys wp_var. Qed.
\[False] ==> wp (trm_var x) Q.
Proof using. intros. intros h Hh. destruct (hpure_inv Hh). false. Qed.
Lemma triple_var : ∀ x H Q,
False →
triple (trm_var x) H Q.
Proof using. intros. false. Qed.
Lemma triple_var' : ∀ x Q,
triple (trm_var x) \[False] Q.
Proof using. intros. rewrite <- wp_equiv. applys wp_var. Qed.
All these rules are correct, albeit totally useless.
Definition of wpgen for Function Applications
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_app t1 t2 ⇒ ∃ H, H \* \[triple t H Q]
...
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_app t1 t2 ⇒ wp t Q
... However, this definition would require converting the wp into a triple each time, because specifications are, by convention, expressed using the predicate triple.
Definition of wpgen for Conditionals
Parameter wp_if : ∀ (b:bool) t1 t2 Q,
(if b then (wp t1 Q) else (wp t2 Q)) ==> wp (trm_if (val_bool b) t1 t2) Q.
(if b then (wp t1 Q) else (wp t2 Q)) ==> wp (trm_if (val_bool b) t1 t2) Q.
Typically, a source program may feature a conditional
trm_if (trm_var x) t1 t2 that, after substitution for x, becomes
trm_if (trm_val v) t1 t2, for some abstract v of type val. Yet, in
general, because we are working here with possibly ill-typed programs,
this value is not know to be a boolean value. We therefore need to
define wpgen for all if-statements of the form trm_if (trm_val v0) t1 t2,
where v0 could be a value of unknown shape.
However, the reasoning rule wp_if stated above features a right-hand side
of the form wp (trm_if (val_bool b) t1 t2) Q. It only applies when the
first argument of trm_if is syntactically a boolean value b. To resolve
this mismatch, the definition of wpgen for a term trm_if t0 t1 t2
quantifies existentially over a boolean value b such that
t0 = trm_val (val_bool b). This way, if t0 is not a boolean value, then
wpgen (trm_if t0 t1 t2) Q is equivalent to \[False]. Otherwise, if t0
is of the form val_bool b, and we are able to perform a case analysis on
b, to switch between wpgen t1 Q and wpgen t2 Q depending on the value
of b.
The formal definition is:
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_if t0 t1 t2 ⇒
\∃ (b:bool), \[t0 = trm_val (val_bool b)]
\* (if b then (wpgen t1) Q else (wpgen t2) Q)
...
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
...
| trm_if t0 t1 t2 ⇒
\∃ (b:bool), \[t0 = trm_val (val_bool b)]
\* (if b then (wpgen t1) Q else (wpgen t2) Q)
...
Summary of the Definition So Far
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_fun x t1 ⇒ Q (val_fun x t)
| trm_fix f x t1 ⇒ Q (val_fix f x t)
| trm_seq t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen t2 Q)
| trm_let x t1 t2 ⇒ wpgen t1 (fun v ⇒ wpgen (subst x v t2) Q)
| trm_var x ⇒ \[False]
| trm_app t1 t2 ⇒ ∃ H, H \* \[triple t H Q]
| trm_if t0 t1 t2 ⇒
\∃ (b:bool), \[t0 = trm_val (val_bool b)]
\* (if b then (wpgen t1) Q else (wpgen t2) Q)
end. As pointed out earlier, this definition is not structurally recursive and is thus not accepted by Coq, due to the recursive call wpgen (subst x v t2) Q. Our next step is to fix this issue.
Computing with wpgen
Contexts
The "iterated substitution" operation, written isubst E t, describes the
substitution of all the bindings from a context E into a term t.
Its implementation is standard: the function traverses the term recursively
and, when reaching a variable, performs a lookup in the context E. The
operation needs to take care to respect variable shadowing. To that end,
when traversing a binder for a variable x, all occurrences of x that
might previously exist in E are removed.
The formal definition of isubst involves two auxiliary functions -- lookup
and removal -- on association lists. The definition of the operation lookup
x E on association lists is standard. It returns an option on a value.
Fixpoint lookup (x:var) (E:ctx) : option val :=
match E with
| nil ⇒ None
| (y,v)::E1 ⇒ if var_eq x y
then Some v
else lookup x E1
end.
match E with
| nil ⇒ None
| (y,v)::E1 ⇒ if var_eq x y
then Some v
else lookup x E1
end.
The definition of the removal operation, written rem x E, is also
standard. It returns a filtered context.
Fixpoint rem (x:var) (E:ctx) : ctx :=
match E with
| nil ⇒ nil
| (y,v)::E1 ⇒
let E1' := rem x E1 in
if var_eq x y then E1' else (y,v)::E1'
end.
match E with
| nil ⇒ nil
| (y,v)::E1 ⇒
let E1' := rem x E1 in
if var_eq x y then E1' else (y,v)::E1'
end.
The definition of the operation isubst E t can then be expressed as a
recursive function over the term t. It invokes lookup x E when it
reaches a variable x. It invokes rem x E when traversing a binding on
the name x.
Fixpoint isubst (E:ctx) (t:trm) : trm :=
match t with
| trm_val v ⇒
v
| trm_var x ⇒
match lookup x E with
| None ⇒ t
| Some v ⇒ v
end
| trm_fun x t1 ⇒
trm_fun x (isubst (rem x E) t1)
| trm_fix f x t1 ⇒
trm_fix f x (isubst (rem x (rem f E)) t1)
| trm_app t1 t2 ⇒
trm_app (isubst E t1) (isubst E t2)
| trm_seq t1 t2 ⇒
trm_seq (isubst E t1) (isubst E t2)
| trm_let x t1 t2 ⇒
trm_let x (isubst E t1) (isubst (rem x E) t2)
| trm_if t0 t1 t2 ⇒
trm_if (isubst E t0) (isubst E t1) (isubst E t2)
end.
match t with
| trm_val v ⇒
v
| trm_var x ⇒
match lookup x E with
| None ⇒ t
| Some v ⇒ v
end
| trm_fun x t1 ⇒
trm_fun x (isubst (rem x E) t1)
| trm_fix f x t1 ⇒
trm_fix f x (isubst (rem x (rem f E)) t1)
| trm_app t1 t2 ⇒
trm_app (isubst E t1) (isubst E t2)
| trm_seq t1 t2 ⇒
trm_seq (isubst E t1) (isubst E t2)
| trm_let x t1 t2 ⇒
trm_let x (isubst E t1) (isubst (rem x E) t2)
| trm_if t0 t1 t2 ⇒
trm_if (isubst E t0) (isubst E t1) (isubst E t2)
end.
Remark: it is also possible to define the substitution by iterating the
unary substitution subst over the list of bindings from E. However, this
alternative approach yields a less efficient function and leads to more
complicated proofs.
We now present the reformulated definition of wpgen E t, case by case.
Throughout these definitions, recall that wpgen E t is interpreted as the
weakest precondition of isubst E t.
The Let-Binding Case
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct (match t with
...
| trm_let x t1 t2 ⇒ fun Q ⇒
(wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
...
) end.
The Variable Case
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct (match t with
...
| trm_var x ⇒ fun Q ⇒
match lookup x E with
| Some v ⇒ Q v
| None ⇒ \[False]
end
...
) end.
The Application Case
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
..
| trm_app t1 t2 ⇒ fun Q ⇒ ∃ H, H \* \[triple t H Q]
Fixpoint wpgen (t:trm) : formula :=
mkstruct (match t with
...
| trm_app v1 v2 ⇒ fun Q ⇒ ∃ H, H \* \[triple (isubst E t) H Q]
..
The Function Definition Case
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct (match t with
...
| trm_fun x t1 ⇒ fun Q ⇒ Q (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ fun Q ⇒ Q (val_fix f x (isubst (rem x (rem f E)) t1))
...
) end.
At last, we arrive at a definition of wpgen that type-checks in Coq and
that can be used to effectively compute weakest preconditions in Separation
Logic.
Fixpoint wpgen (E:ctx) (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_fun x t1 ⇒ Q (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ Q (val_fix f x (isubst (rem x (rem f E)) t1))
| trm_seq t1 t2 ⇒ wpgen E t1 (fun v ⇒ wpgen E t2 Q)
| trm_let x t1 t2 ⇒ wpgen E t1 (fun v ⇒ wpgen ((x,v)::E) t2 Q)
| trm_var x ⇒
match lookup x E with
| Some v ⇒ Q v
| None ⇒ \[False]
end
| trm_app v1 v2 ⇒ \∃ H, H \* \[triple (isubst E t) H Q]
| trm_if t0 t1 t2 ⇒
\∃ (b:bool), \[t0 = trm_val (val_bool b)]
\* (if b then (wpgen E t1) Q else (wpgen E t2) Q)
end.
match t with
| trm_val v ⇒ Q v
| trm_fun x t1 ⇒ Q (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ Q (val_fix f x (isubst (rem x (rem f E)) t1))
| trm_seq t1 t2 ⇒ wpgen E t1 (fun v ⇒ wpgen E t2 Q)
| trm_let x t1 t2 ⇒ wpgen E t1 (fun v ⇒ wpgen ((x,v)::E) t2 Q)
| trm_var x ⇒
match lookup x E with
| Some v ⇒ Q v
| None ⇒ \[False]
end
| trm_app v1 v2 ⇒ \∃ H, H \* \[triple (isubst E t) H Q]
| trm_if t0 t1 t2 ⇒
\∃ (b:bool), \[t0 = trm_val (val_bool b)]
\* (if b then (wpgen E t1) Q else (wpgen E t2) Q)
end.
In the next chapter, we will establish the soundness of the wpgen. For
the moment, we simply state the soundness theorem, then explain how it can
be exploited for verifying concrete programs.
The entailment above asserts in particular that we can derive triple t H Q
by proving H ==> wpgen t Q. A useful corollary combines this soundness
theorem with the rule triple_app_fun, which allows establishing triples
for functions.
Recall triple_app_fun from chapter Rules.
Parameter triple_app_fun : ∀ x v1 v2 t1 H Q,
v1 = val_fun x t1 →
triple (subst x v2 t1) H Q →
triple (trm_app v1 v2) H Q.
v1 = val_fun x t1 →
triple (subst x v2 t1) H Q →
triple (trm_app v1 v2) H Q.
Reformulating the rule above into a rule for wpgen takes 3 steps.
First, we rewrite the premise triple (subst x v2 t1) H Q using wp. It
becomes H ==> wp (subst x v2 t1) Q.
Second, we observe that the term subst x v2 t1 is equal to
isubst ((x,v2)::nil) t1. (This equality is captured by the lemma
subst_eq_isubst_one proved in the next chapter.) Thus, the
heap predicate wp (subst x v2 t1) Q is equivalent to
wp (isubst ((x,v2)::nil) t1).
Third, according to wpgen_sound, wp (isubst ((x,v2)::nil) t1) is
entailed by wpgen ((x,v2)::nil) t1. Thus, we can use the latter as
premise in place of the former. We thereby obtain the following lemma, which
is at the heart of the implementation of the tactic xwp.
Parameter triple_app_fun_from_wpgen : ∀ v1 v2 x t1 H Q,
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
Let us exploit triple_app_fun_from_wpgen to demonstrate the computation of
wpgen on a practical program. Recall the function incr (defined in
Rules), and its specification below.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
intros. applys triple_app_fun_from_wpgen. { reflexivity. }
simpl. (* Read the goal here... *)
Abort.
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
intros. applys triple_app_fun_from_wpgen. { reflexivity. }
simpl. (* Read the goal here... *)
Abort.
At the end of the above proof fragment, the goal takes the form H ==> F Q,
where H denotes the precondition, Q the postcondition, and F is a
formula describing the body of the function incr. Inside this formula, the
reader can spot triples for the primitive operations involved. Observe that
the goal is nevertheless somewhat hard to relate to the original program. In
what follows, we explain how to remedy the situation by setting up wpgen
so that its output is human-readable and resembles the original source code.
Consequence and Frame Properties for wpgen
Exercise: 3 stars, standard, especially useful (wpgen_conseq)
Prove that wpgen E t satisfies the same consequence rule as wp t. The statement is directly adapted from wp_conseq, from WPsem. Hint: begin the proof with intros t. induction t..
Lemma wpgen_conseq : ∀ t E Q1 Q2,
Q1 ===> Q2 →
wpgen E t Q1 ==> wpgen E t Q2.
Proof using. (* FILL IN HERE *) Admitted.
☐
Q1 ===> Q2 →
wpgen E t Q1 ==> wpgen E t Q2.
Proof using. (* FILL IN HERE *) Admitted.
☐
Exercise: 4 stars, standard, especially useful (wpgen_frame)
Prove that wpgen E t satisfies the same frame rule as wp t. The statement is directly adapted from wp_frame, from WPsem. The sequence case is not easy.
Lemma wpgen_frame : ∀ t E H Q,
(wpgen E t Q) \* H ==> wpgen E t (Q \*+ H).
Proof using. (* FILL IN HERE *) Admitted.
☐
(wpgen E t Q) \* H ==> wpgen E t (Q \*+ H).
Proof using. (* FILL IN HERE *) Admitted.
☐
Optimizing the Readability of wpgen's Output
- first, we modify the presentation of wpgen so that the fun (Q:val→hprop) ⇒ appears insides the branches of the match t with rather than around it,
- second, we introduce one auxiliary definition for each branch of the match t with, and
- third, we introduce a Notation for each of these auxiliary definitions.
Reability Step 1: Moving the Function below the Branches.
Fixpoint wpgen (E:ctx) (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_fun x t1 ⇒ Q (val_fun x (isubst (rem x E) t1))
...
end. to the equivalent form:
Fixpoint wpgen (E:ctx) (t:trm) : (val→hprop)->hprop :=
match t with
| trm_val v ⇒ fun (Q:val→hprop) ⇒ Q v
| trm_fun x t1 ⇒ fun (Q:val→hprop) ⇒ Q (val_fun x (isubst (rem x E) t1))
...
end.
Readability Steps 2 and 3 for the Case of Sequences
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
match t with
...
| trm_seq t1 t2 ⇒ fun Q ⇒ (wpgen E t1) (fun v ⇒ wpgen E t2 Q)
...
end. to the equivalent form:
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
match t with
...
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
...
end. where wpgen_seq is defined below.
Here, F1 and F2 denote the results of the recursive calls, wpgen E t1
and wpgen E t2, respectively.
With the above definitions, wgpen E (trm_seq t1 t2) evaluates to
wp_seq (wpgen E t1) (wpgen E t2).
Finally, we introduce a piece of notation for each case. For sequence, we
set up the notation defined next to so that any formula of the form
wpgen_seq F1 F2 gets displayed as Seq F1 ; F2 .
Notation "'Seq' F1 ; F2" :=
((wpgen_seq F1 F2))
(at level 68, right associativity,
format "'[v' 'Seq' '[' F1 ']' ';' '/' '[' F2 ']' ']'").
((wpgen_seq F1 F2))
(at level 68, right associativity,
format "'[v' 'Seq' '[' F1 ']' ';' '/' '[' F2 ']' ']'").
Thanks to this notation, the wpgen of a sequence t1 ; t2 displays as
Seq F1 ; F2 where F1 and F2 denote the wpgen of t1 and t2,
respectively. The "format" clause in the notation definition is there only
to improve the pretty-printing of formulae. The semantics of '/' is to
encourage a line break. The semantics of square brackets is to delimit
blocks. The "v" character next to the leading bracket encourages a vertical
alignement of the blocks. Double spaces in the format clause indicate the
need to pretty-print a single space, as opposed to an empty spacing.
Readability Step 2: Auxiliary Definitions for other Constructs
Definition wpgen_val (v:val) : formula := fun Q ⇒
Q v.
Definition wpgen_let (F1:formula) (F2of:val→formula) : formula := fun Q ⇒
F1 (fun v ⇒ F2of v Q).
Definition wpgen_if (t:trm) (F1 F2:formula) : formula := fun Q ⇒
\∃ (b:bool), \[t = trm_val (val_bool b)] \* (if b then F1 Q else F2 Q).
Definition wpgen_fail : formula := fun Q ⇒
\[False].
Definition wpgen_var (E:ctx) (x:var) : formula :=
match lookup x E with
| None ⇒ wpgen_fail
| Some v ⇒ wpgen_val v
end.
Definition wpgen_app (t:trm) : formula := fun Q ⇒
\∃ H, H \* \[triple t H Q].
Q v.
Definition wpgen_let (F1:formula) (F2of:val→formula) : formula := fun Q ⇒
F1 (fun v ⇒ F2of v Q).
Definition wpgen_if (t:trm) (F1 F2:formula) : formula := fun Q ⇒
\∃ (b:bool), \[t = trm_val (val_bool b)] \* (if b then F1 Q else F2 Q).
Definition wpgen_fail : formula := fun Q ⇒
\[False].
Definition wpgen_var (E:ctx) (x:var) : formula :=
match lookup x E with
| None ⇒ wpgen_fail
| Some v ⇒ wpgen_val v
end.
Definition wpgen_app (t:trm) : formula := fun Q ⇒
\∃ H, H \* \[triple t H Q].
The new definition of wpgen reads as follows:
Module WpgenExec2.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_fun x t1 ⇒ wpgen_val (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ wpgen_val (val_fix f x (isubst (rem x (rem f E)) t1))
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
| trm_if t0 t1 t2 ⇒ wpgen_if (isubst E t0) (wpgen E t1) (wpgen E t2)
end.
End WpgenExec2.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_fun x t1 ⇒ wpgen_val (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ wpgen_val (val_fix f x (isubst (rem x (rem f E)) t1))
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
| trm_if t0 t1 t2 ⇒ wpgen_if (isubst E t0) (wpgen E t1) (wpgen E t2)
end.
End WpgenExec2.
Readability Step 3: Notations for Auxiliary Definitions
Declare Scope wpgen_scope.
Notation "'Val' v" :=
((wpgen_val v))
(at level 69) : wpgen_scope.
Notation "'Let'' x ':=' F1 'in' F2" :=
((wpgen_let F1 (fun x ⇒ F2)))
(at level 69, x name, right associativity,
format "'[v' '[' 'Let'' x ':=' F1 'in' ']' '/' '[' F2 ']' ']'")
: wpgen_scope.
Notation "'If'' b 'Then' F1 'Else' F2" :=
((wpgen_if b F1 F2))
(at level 69) : wpgen_scope.
Notation "'Fail'" :=
((wpgen_fail))
(at level 69) : wpgen_scope.
Notation "'Val' v" :=
((wpgen_val v))
(at level 69) : wpgen_scope.
Notation "'Let'' x ':=' F1 'in' F2" :=
((wpgen_let F1 (fun x ⇒ F2)))
(at level 69, x name, right associativity,
format "'[v' '[' 'Let'' x ':=' F1 'in' ']' '/' '[' F2 ']' ']'")
: wpgen_scope.
Notation "'If'' b 'Then' F1 'Else' F2" :=
((wpgen_if b F1 F2))
(at level 69) : wpgen_scope.
Notation "'Fail'" :=
((wpgen_fail))
(at level 69) : wpgen_scope.
In addition, we introduce a handy notation for the result of wpgen t where
t denotes an application. (We cover here only functions of arity one or
two. The file LibSepReference.v provides arity-generic definitions.)
Notation "'App' f v1 " :=
((wpgen_app (trm_app f v1)))
(at level 68, f, v1 at level 0) : wpgen_scope.
Notation "'App' f v1 v2 " :=
((wpgen_app (trm_app (trm_app f v1) v2)))
(at level 68, f, v1, v2 at level 0) : wpgen_scope.
((wpgen_app (trm_app f v1)))
(at level 68, f, v1 at level 0) : wpgen_scope.
Notation "'App' f v1 v2 " :=
((wpgen_app (trm_app (trm_app f v1) v2)))
(at level 68, f, v1, v2 at level 0) : wpgen_scope.
Here again, we temporarily axiomatize the soundness result for the purpose
of looking at how that result can be exploited in practice.
Parameter triple_app_fun_from_wpgen : ∀ v1 v2 x t1 H Q,
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
Consider again the example of incr.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
intros. applys triple_app_fun_from_wpgen. { reflexivity. }
simpl. (* Read the goal here... It is of the form H ==> F Q,
where F vaguely looks like the code of the body of incr. *)
Abort.
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
intros. applys triple_app_fun_from_wpgen. { reflexivity. }
simpl. (* Read the goal here... It is of the form H ==> F Q,
where F vaguely looks like the code of the body of incr. *)
Abort.
Up to whitespace, alpha-renaming, removal of parentheses, and removal
of quotes after Let and If), the formula F reads as:
Let n := App val_get p in
Let m := App (val_add n) 1 in
App (val_set p) m
With the introduction of intermediate definitions for wpgen and the
introduction of associated notations for each term construct, what we have
achieved is that the output of wpgen is, for any input term t, a human-
readable formula whose display closely resembles the source code of t.
Let n := App val_get p in
Let m := App (val_add n) 1 in
App (val_set p) m
Extension of wpgen to Handle Structural Rules
Introduction of mkstruct in the Definition of wpgen
Fixpoint wpgen (E:ctx) (t:trm) : (val→hprop)->hprop :=
mkstruct (
match t with
| trm_val v ⇒ wpgen_val v
...
end). With this definition, any output of wpgen E t is necessarily of the form mkstruct F, for some formula F describing the weakest precondition of t.
Because mkstruct appears between the prototype and the match in the body
of wpgen, it must have type formula→formula.
Next, mkstruct should satisfy reasoning rules that mimic the statements of
the frame rule and the consequence rule in weakest-precondition
style (wp_frame and wp_conseq).
Parameter mkstruct_frame : ∀ (F:formula) H Q,
(mkstruct F Q) \* H ==> mkstruct F (Q \*+ H).
Parameter mkstruct_conseq : ∀ (F:formula) Q1 Q2,
Q1 ===> Q2 →
mkstruct F Q1 ==> mkstruct F Q2.
(mkstruct F Q) \* H ==> mkstruct F (Q \*+ H).
Parameter mkstruct_conseq : ∀ (F:formula) Q1 Q2,
Q1 ===> Q2 →
mkstruct F Q1 ==> mkstruct F Q2.
In addition, the user manipulating a formula produced by wpgen should be
able to discard the predicate mkstruct from the head of the output when
there is no need to apply the frame rule or the rule of consequence. The
erasure property is captured by the following entailment.
Moreover, in order to complete the soundness proof for wpgen, the
predicate mkstruct should be covariant: if F1 entails F2, then
mkstruct F1 should entail mkstruct F2.
Parameter mkstruct_monotone : ∀ F1 F2 Q,
(∀ Q, F1 Q ==> F2 Q) →
mkstruct F1 Q ==> mkstruct F2 Q.
End MkstructProp.
(∀ Q, F1 Q ==> F2 Q) →
mkstruct F1 Q ==> mkstruct F2 Q.
End MkstructProp.
Realization of mkstruct
The magic wand may appear somewhat puzzling, but the above statement
is reminiscent from the statement of wp_ramified, which captures the
expressive power of all the structural reasoning rules of Separation Logic
at once. If we unfold the definition of the magic wand, we can see more
clearly that mkstruct F is a formula that describes a program that
produces a postcondition Q when executed in the current state if and only
if the formula F describes a program that produces a postcondtion Q1 in
a subset of the current state and if the postcondition Q1 augmented with
the remainder of the current state (i.e., the piece described by H)
corresponds to the postcondition Q.
Definition mkstruct' (F:formula) : formula :=
fun (Q:val→hprop) ⇒ \∃ Q1 H, F Q1 \* H \* \[Q1 \*+ H ===> Q].
fun (Q:val→hprop) ⇒ \∃ Q1 H, F Q1 \* H \* \[Q1 \*+ H ===> Q].
The four properties of mkstruct can be easily verified.
Lemma mkstruct_frame : ∀ F H Q,
(mkstruct F Q) \* H ==> mkstruct F (Q \*+ H).
Proof using. intros. unfold mkstruct. xpull. intros Q'. xsimpl*. Qed.
Lemma mkstruct_conseq : ∀ F Q1 Q2,
Q1 ===> Q2 →
mkstruct F Q1 ==> mkstruct F Q2.
Proof using. introv WQ. unfold mkstruct. xpull. intros Q'. xsimpl*. Qed.
Lemma mkstruct_erase : ∀ F Q,
F Q ==> mkstruct F Q.
Proof using. intros. unfold mkstruct. xsimpl. Qed.
Lemma mkstruct_monotone : ∀ F1 F2 Q,
(∀ Q, F1 Q ==> F2 Q) →
mkstruct F1 Q ==> mkstruct F2 Q.
Proof using. introv WF. unfolds mkstruct. xpull. intros Q'. xchanges WF. Qed.
(mkstruct F Q) \* H ==> mkstruct F (Q \*+ H).
Proof using. intros. unfold mkstruct. xpull. intros Q'. xsimpl*. Qed.
Lemma mkstruct_conseq : ∀ F Q1 Q2,
Q1 ===> Q2 →
mkstruct F Q1 ==> mkstruct F Q2.
Proof using. introv WQ. unfold mkstruct. xpull. intros Q'. xsimpl*. Qed.
Lemma mkstruct_erase : ∀ F Q,
F Q ==> mkstruct F Q.
Proof using. intros. unfold mkstruct. xsimpl. Qed.
Lemma mkstruct_monotone : ∀ F1 F2 Q,
(∀ Q, F1 Q ==> F2 Q) →
mkstruct F1 Q ==> mkstruct F2 Q.
Proof using. introv WF. unfolds mkstruct. xpull. intros Q'. xchanges WF. Qed.
Definition of wpgen with mkstruct
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct (match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_fun x t1 ⇒ wpgen_val (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ wpgen_val (val_fix f x (isubst (rem x (rem f E)) t1))
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
| trm_if t0 t1 t2 ⇒ wpgen_if (isubst E t0) (wpgen E t1) (wpgen E t2)
end).
mkstruct (match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_fun x t1 ⇒ wpgen_val (val_fun x (isubst (rem x E) t1))
| trm_fix f x t1 ⇒ wpgen_val (val_fix f x (isubst (rem x (rem f E)) t1))
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
| trm_if t0 t1 t2 ⇒ wpgen_if (isubst E t0) (wpgen E t1) (wpgen E t2)
end).
To avoid clutter in reading the output of wpgen, we introduce a
lightweight shorthand to denote mkstruct F, allowing it to display simply
as `F.
Once again, we temporarily axiomatize the soundness result for the purpose
of looking at how that result can be exploited in practice.
Parameter triple_app_fun_from_wpgen : ∀ v1 v2 x t1 H Q,
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
Lemmas for Handling wpgen Goals
Lemma xstruct_lemma : ∀ F H Q,
H ==> F Q →
H ==> mkstruct F Q.
Proof using. introv M. xchange M. applys mkstruct_erase. Qed.
H ==> F Q →
H ==> mkstruct F Q.
Proof using. introv M. xchange M. applys mkstruct_erase. Qed.
xval_lemma reformulates the definition of wpgen_val. It just unfolds the
definition.
xlet_lemma reformulates the definition of wpgen_let. It just unfolds the
definition.
Lemma xlet_lemma : ∀ H F1 F2of Q,
H ==> F1 (fun v ⇒ F2of v Q) →
H ==> wpgen_let F1 F2of Q.
Proof using. introv M. xchange M. Qed.
H ==> F1 (fun v ⇒ F2of v Q) →
H ==> wpgen_let F1 F2of Q.
Proof using. introv M. xchange M. Qed.
Likewise, xseq_lemma reformulates wpgen_seq.
Lemma xseq_lemma : ∀ H F1 F2 Q,
H ==> F1 (fun v ⇒ F2 Q) →
H ==> wpgen_seq F1 F2 Q.
Proof using. introv M. xchange M. Qed.
H ==> F1 (fun v ⇒ F2 Q) →
H ==> wpgen_seq F1 F2 Q.
Proof using. introv M. xchange M. Qed.
xapp_lemma applies to goals produced by wpgen on an application. In such
cases, the proof obligation is of the form H ==> (wpgen_app t) Q.
xapp_lemma reformulates the frame-consequence rule in a way that enables
exploiting a specification triple for a function to reason about a call to
that function.
Lemma xapp_lemma : ∀ t Q1 H1 H2 H Q,
triple t H1 Q1 →
H ==> H1 \* H2 →
Q1 \*+ H2 ===> Q →
H ==> wpgen_app t Q.
Proof using.
introv M WH WQ. unfold wpgen_app. xsimpl. applys* triple_conseq_frame M.
Qed.
triple t H1 Q1 →
H ==> H1 \* H2 →
Q1 \*+ H2 ===> Q →
H ==> wpgen_app t Q.
Proof using.
introv M WH WQ. unfold wpgen_app. xsimpl. applys* triple_conseq_frame M.
Qed.
xwp_lemma is another name for triple_app_fun_from_wpgen. It is used for
establishing a triple for a function application.
Lemma xwp_lemma : ∀ v1 v2 x t1 H Q,
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
Proof using. introv M1 M2. applys* triple_app_fun_from_wpgen. Qed.
v1 = val_fun x t1 →
H ==> wpgen ((x,v2)::nil) t1 Q →
triple (trm_app v1 v2) H Q.
Proof using. introv M1 M2. applys* triple_app_fun_from_wpgen. Qed.
Module ProofsWithXlemmas.
Import ExamplePrograms.
Open Scope wpgen_scope.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
intros.
applys xwp_lemma. { reflexivity. }
(* Here the wpgen function computes. *)
simpl.
(* Observe how each node begin with mkstruct.
Observe how program variables are all eliminated. *)
applys xstruct_lemma.
applys xlet_lemma.
applys xstruct_lemma.
applys xapp_lemma. { apply triple_get. } { xsimpl. }
xpull; intros ? →.
applys xstruct_lemma.
applys xlet_lemma.
applys xstruct_lemma.
applys xapp_lemma. { apply triple_add. } { xsimpl. }
xpull; intros ? →.
applys xstruct_lemma.
applys xapp_lemma. { apply triple_set. } { xsimpl. }
xsimpl.
Qed.
Import ExamplePrograms.
Open Scope wpgen_scope.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
intros.
applys xwp_lemma. { reflexivity. }
(* Here the wpgen function computes. *)
simpl.
(* Observe how each node begin with mkstruct.
Observe how program variables are all eliminated. *)
applys xstruct_lemma.
applys xlet_lemma.
applys xstruct_lemma.
applys xapp_lemma. { apply triple_get. } { xsimpl. }
xpull; intros ? →.
applys xstruct_lemma.
applys xlet_lemma.
applys xstruct_lemma.
applys xapp_lemma. { apply triple_add. } { xsimpl. }
xpull; intros ? →.
applys xstruct_lemma.
applys xapp_lemma. { apply triple_set. } { xsimpl. }
xsimpl.
Qed.
Exercise: 2 stars, standard, especially useful (triple_succ_using_incr_with_xlemmas)
Using x-lemmas, simplify the proof of triple_succ_using_incr, which was carried out using triples in chapter Rules.
Lemma triple_succ_using_incr_with_xlemmas : ∀ (n:int),
triple (trm_app succ_using_incr n)
\[]
(fun v ⇒ \[v = n+1]).
Proof using. (* FILL IN HERE *) Admitted.
☐
triple (trm_app succ_using_incr n)
\[]
(fun v ⇒ \[v = n+1]).
Proof using. (* FILL IN HERE *) Admitted.
☐
Making Proof Scripts More Concise
The tactics xval, xseq and xlet invoke the corresponding x-lemmas,
after calling xstruct if a leading mkstruct is in the way.
Tactic Notation "xstruct_if_needed" :=
try match goal with ⊢ ?H ==> mkstruct ?F ?Q ⇒ xstruct end.
Tactic Notation "xval" :=
xstruct_if_needed; applys xval_lemma.
Tactic Notation "xlet" :=
xstruct_if_needed; applys xlet_lemma.
Tactic Notation "xseq" :=
xstruct_if_needed; applys xseq_lemma.
try match goal with ⊢ ?H ==> mkstruct ?F ?Q ⇒ xstruct end.
Tactic Notation "xval" :=
xstruct_if_needed; applys xval_lemma.
Tactic Notation "xlet" :=
xstruct_if_needed; applys xlet_lemma.
Tactic Notation "xseq" :=
xstruct_if_needed; applys xseq_lemma.
xapp_nosubst applys xapp_lemma, after calling xseq or xlet if
applicable. Further on, we will define xapp as an enhanced version of
xapp_nosubst that is able to automatically perform substitutions.
Tactic Notation "xseq_xlet_if_needed" :=
try match goal with ⊢ ?H ==> mkstruct ?F ?Q ⇒
match F with
| wpgen_seq ?F1 ?F2 ⇒ xseq
| wpgen_let ?F1 ?F2of ⇒ xlet
end end.
Tactic Notation "xapp_nosubst" constr(E) :=
xseq_xlet_if_needed; xstruct_if_needed;
applys xapp_lemma E; [ xsimpl | xpull ].
try match goal with ⊢ ?H ==> mkstruct ?F ?Q ⇒
match F with
| wpgen_seq ?F1 ?F2 ⇒ xseq
| wpgen_let ?F1 ?F2of ⇒ xlet
end end.
Tactic Notation "xapp_nosubst" constr(E) :=
xseq_xlet_if_needed; xstruct_if_needed;
applys xapp_lemma E; [ xsimpl | xpull ].
xwp applys xwp_lemma, then requests Coq to evaluate the wpgen
function. (For technical reasons, in the definition shown below we need to
explicitly request the unfolding of wpgen_var.)
Tactic Notation "xwp" :=
intros; applys xwp_lemma;
[ reflexivity
| simpl; unfold wpgen_var; simpl ].
intros; applys xwp_lemma;
[ reflexivity
| simpl; unfold wpgen_var; simpl ].
Now let us revisit the previous proof scripts using x-tactics instead of
x-lemmas. The reader is invited to contemplate the gain in conciseness.
Module ProofsWithXtactics.
Import ExamplePrograms.
Open Scope wpgen_scope.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
xwp.
xapp_nosubst triple_get. intros ? →.
xapp_nosubst triple_add. intros ? →.
xapp_nosubst triple_set.
xsimpl.
Qed.
Import ExamplePrograms.
Open Scope wpgen_scope.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
xwp.
xapp_nosubst triple_get. intros ? →.
xapp_nosubst triple_add. intros ? →.
xapp_nosubst triple_set.
xsimpl.
Qed.
Exercise: 2 stars, standard, especially useful (triple_succ_using_incr_with_xtactics)
Using x-tactics, prove succ_using_incr.
Lemma triple_succ_using_incr_with_xtactics : ∀ (n:int),
triple (trm_app succ_using_incr n)
\[]
(fun v ⇒ \[v = n+1]).
Proof using. (* FILL IN HERE *) Admitted.
☐
triple (trm_app succ_using_incr n)
\[]
(fun v ⇒ \[v = n+1]).
Proof using. (* FILL IN HERE *) Admitted.
☐
Automating Introduction using the xapp Tactic.
Tactic Notation "xapp_try_subst" := (* for internal use only *)
try match goal with
| ⊢ ∀ (r:val), (r = _) → _ ⇒ intros ? →
| ⊢ ∀ (r:val), ∀ x, (r = _) → _ ⇒
let y := fresh x in intros ? y ->; revert y
end.
Tactic Notation "xapp" constr(E) :=
xapp_nosubst E; xapp_try_subst.
try match goal with
| ⊢ ∀ (r:val), (r = _) → _ ⇒ intros ? →
| ⊢ ∀ (r:val), ∀ x, (r = _) → _ ⇒
let y := fresh x in intros ? y ->; revert y
end.
Tactic Notation "xapp" constr(E) :=
xapp_nosubst E; xapp_try_subst.
Database of Specification Lemmas for the xapp Tactic.
#[global] Hint Resolve triple_get triple_set triple_ref
triple_free triple_add : triple.
triple_free triple_add : triple.
The argument-free variants xapp_subst and xapp are implemented by
invoking eauto with triple to retrieve the relevant specification.
One shortcoming of the version of the xapp tactic that leverages the
triple database is that it is not able to automatically apply
specifications that involve a premise that eauto cannot solve. To exploit
such specifications, we need to provide the specification explicitly using
xapp E. This tactic is defined in LibSepReference.v. Its implementation
is a bit technical, we do not describe it here.
Tactic Notation "xapp_nosubst" :=
xseq_xlet_if_needed; xstruct_if_needed;
applys xapp_lemma; [ solve [ eauto with triple ] | xsimpl | xpull ].
Tactic Notation "xapp" :=
xapp_nosubst; xapp_try_subst.
xseq_xlet_if_needed; xstruct_if_needed;
applys xapp_lemma; [ solve [ eauto with triple ] | xsimpl | xpull ].
Tactic Notation "xapp" :=
xapp_nosubst; xapp_try_subst.
The tactics defined in the previous sections may be used to complete proofs
like done in the first two chapters of the course. For example, here is the
proof of the increment function.
Lemma triple_incr : ∀ (p:loc) (n:int),
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
xwp. xapp. xapp. xapp. xsimpl.
Qed.
#[global] Hint Resolve triple_incr : triple.
triple (trm_app incr p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+1)).
Proof using.
xwp. xapp. xapp. xapp. xsimpl.
Qed.
#[global] Hint Resolve triple_incr : triple.
In summary, we have shown how to leverage wpgen, x-lemmas and x-tactics to
implement the core of a practical verification framework based on Separation
Logic.
On Proof Automation
xapp. { prove_side_1_1. } { prove_side_1_2. }
xapp. { prove_side_2_1. } { prove_side_2_2. } { prove_side_2_3. }
xapp. { prove_side_3_1. } { prove_side_3_2. } { prove_side_3_3. }
xgo. { prove_side_1_1. } { prove_side_1_2. }
{ prove_side_2_1. } { prove_side_2_2. } { prove_side_2_3. }
{ prove_side_3_1. } { prove_side_3_2. } { prove_side_3_3. }
Tactics xconseq and xframe
Proof using. (* FILL IN HERE *) Admitted.
☐
☐
Tactic Notation "xconseq" :=
applys xconseq_lemma.
Tactic Notation "xconseq" constr(Q) :=
applys xconseq_lemma Q.
applys xconseq_lemma.
Tactic Notation "xconseq" constr(Q) :=
applys xconseq_lemma Q.
The tactic xframe enables applying the frame rule on a formula produced by
wpgen. The syntax xframe H is used to specify the heap predicate to
keep, and the syntax xframe_out H is used to specify the heap predicate to
frame out---everything else is kept.
Lemma xframe_lemma : ∀ H1 H2 H Q Q1 F,
H ==> H1 \* H2 →
H1 ==> mkstruct F Q1 →
Q1 \*+ H2 ===> Q →
H ==> mkstruct F Q.
H ==> H1 \* H2 →
H1 ==> mkstruct F Q1 →
Q1 \*+ H2 ===> Q →
H ==> mkstruct F Q.
Exercise: 2 stars, standard, optional (xframe_lemma)
Prove the xframe_lemma. Exploit the properties of mkstruct; do not try to unfold the definition of mkstruct.
Proof using. (* FILL IN HERE *) Admitted.
☐
☐
Tactic Notation "xframe" constr(H) :=
eapply (@xframe_lemma H); [ xsimpl | | ].
Tactic Notation "xframe_out" constr(H) :=
eapply (@xframe_lemma _ H); [ xsimpl | | ].
eapply (@xframe_lemma H); [ xsimpl | | ].
Tactic Notation "xframe_out" constr(H) :=
eapply (@xframe_lemma _ H); [ xsimpl | | ].
Let's illustrate the use of xframe on an example.
Module ProofsWithStructuralXtactics.
Import ExamplePrograms.
Open Scope wpgen_scope.
Lemma triple_incr_frame : ∀ (p q:loc) (n m:int),
triple (trm_app incr p)
(p ~~> n \* q ~~> m)
(fun _ ⇒ (p ~~> (n+1)) \* (q ~~> m)).
Proof using.
xwp.
Import ExamplePrograms.
Open Scope wpgen_scope.
Lemma triple_incr_frame : ∀ (p q:loc) (n m:int),
triple (trm_app incr p)
(p ~~> n \* q ~~> m)
(fun _ ⇒ (p ~~> (n+1)) \* (q ~~> m)).
Proof using.
xwp.
Instead of calling xapp, we put aside q ~~> m and focus on p ~~> n.
Then we can work in a smaller state that mentions only p ~~> n.
xapp. xapp. xapp.
Finally we check the check that the current state augmented with the framed
predicate q ~~> m matches with the claimed postcondition.
So far in the chapter, in the definition of wpgen, we have treated the
constructions trm_fun and trm_fix as terms directly constructing a
value, following the same pattern as for the construction trm_val.
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_fun x t1 ⇒ Q (val_fun x t1)
| trm_fix f x t1 ⇒ Q (val_fix f x t1)
...
end. This section explains how to extend the definition of wpgen to make it recurse inside the body of the function definitions. Doing so does not increase expressiveness, because the user had the possibility of manually requesting the computation of wpgen on a function value of the form val_fun or val_fix. However, having wpgen automatically recurse into function bodies saves manually making such requests. In what follows, we show how such a version of wpgen can be set up.
The new definition of wpgen will take the shape shown below, for
well-suited definitions of wpgen_fun and wpgen_fix yet to be introduced.
In the code snippet below, vx denotes a value to which the function may be
applied, and vf denotes the value associated with the function itself.
This value is involved where the function defined by trm_fix f x t1
invokes itself recursively inside its body t1.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
| trm_val v ⇒ wpgen_val v
| trm_fun x t1 ⇒ wpgen_fun (fun vx ⇒ wpgen ((x,vx)::E) t1)
| trm_fix f x t1 ⇒ wpgen_fix (fun vf vx ⇒ wpgen ((f,vf)::(x,vx)::E) t1)
...
end.
Fixpoint wpgen (t:trm) (Q:val→hprop) : hprop :=
match t with
| trm_val v ⇒ Q v
| trm_fun x t1 ⇒ Q (val_fun x t1)
| trm_fix f x t1 ⇒ Q (val_fix f x t1)
...
end. This section explains how to extend the definition of wpgen to make it recurse inside the body of the function definitions. Doing so does not increase expressiveness, because the user had the possibility of manually requesting the computation of wpgen on a function value of the form val_fun or val_fix. However, having wpgen automatically recurse into function bodies saves manually making such requests. In what follows, we show how such a version of wpgen can be set up.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
| trm_val v ⇒ wpgen_val v
| trm_fun x t1 ⇒ wpgen_fun (fun vx ⇒ wpgen ((x,vx)::E) t1)
| trm_fix f x t1 ⇒ wpgen_fix (fun vf vx ⇒ wpgen ((f,vf)::(x,vx)::E) t1)
...
end.
1. Treatment of Non-Recursive Functions
Parameter triple_app_fun_from_wpgen : ∀ vf vx x t1 H' Q',
vf = val_fun x t1 →
H' ==> wpgen ((x,vx)::nil) t1 Q' →
triple (trm_app vf vx) H' Q'.
vf = val_fun x t1 →
H' ==> wpgen ((x,vx)::nil) t1 Q' →
triple (trm_app vf vx) H' Q'.
The lemma above enables establishing a triple for an application
trm_app vf vx with precondition H' and postcondition Q', from the
premise H' ==> wgen ((x,vx)::nil) t1 Q'.
It therefore makes sense, in our definition of the predicate
wpgen (trm_fun x t1) Q, which we said would take the form
\∀ vf, \[P vf] \−∗ Q vf, to define P vf as:
∀ vx H' Q', (H' ==> wpgen ((x,vx)::nil) t1 Q') →
triple (trm_app vf vx) H' Q' This proposition can be slightly simplified, by using wp instead of triple, allowing to eliminate H'. We thus define P vf as:
∀ vx H', wpgen ((x,vx)::nil) t1 Q' ==> wp (trm_app vf vx) Q'
Overall, the definition of wpgen E t is as follows. Note that the
occurence of nil is replaced with E to account for the case of a
nonempty context.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
...
| trm_fun x t1 ⇒ fun Q ⇒
let P vf :=
(∀ vx H', wpgen ((x,vx)::nil) t1 Q' ==> wp (trm_app vf vx) Q') in
\∀ vf, \[P vf] \−∗ Q vf
...
end.
The actual definition of wpgen exploits an intermediate definition called
wpgen_fun, as shown below:
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
...
| trm_fun x t1 ⇒ wpgen_fun (fun vx ⇒ wpgen ((x,vx)::E) t1)
...
end. where wpgen_fun is defined as follows:
∀ vx H' Q', (H' ==> wpgen ((x,vx)::nil) t1 Q') →
triple (trm_app vf vx) H' Q' This proposition can be slightly simplified, by using wp instead of triple, allowing to eliminate H'. We thus define P vf as:
∀ vx H', wpgen ((x,vx)::nil) t1 Q' ==> wp (trm_app vf vx) Q'
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
...
| trm_fun x t1 ⇒ fun Q ⇒
let P vf :=
(∀ vx H', wpgen ((x,vx)::nil) t1 Q' ==> wp (trm_app vf vx) Q') in
\∀ vf, \[P vf] \−∗ Q vf
...
end.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
...
| trm_fun x t1 ⇒ wpgen_fun (fun vx ⇒ wpgen ((x,vx)::E) t1)
...
end. where wpgen_fun is defined as follows:
Definition wpgen_fun (Fof:val→formula) : formula := fun Q ⇒
\∀ vf, \[∀ vx Q', Fof vx Q' ==> wp (trm_app vf vx) Q'] \−∗ Q vf.
\∀ vf, \[∀ vx Q', Fof vx Q' ==> wp (trm_app vf vx) Q'] \−∗ Q vf.
Like for other auxiliary functions associated with wpgen, we introduce a
custom notation for wpgen_fun. Here, we let Fun x := F stand for
wpgen_fun (fun x ⇒ F).
Notation "'Fun' x ':=' F1" :=
((wpgen_fun (fun x ⇒ F1)))
(at level 69, x name, right associativity,
format "'[v' '[' 'Fun' x ':=' F1 ']' ']'").
((wpgen_fun (fun x ⇒ F1)))
(at level 69, x name, right associativity,
format "'[v' '[' 'Fun' x ':=' F1 ']' ']'").
2. Treatment of Recursive Functions
∀ vx H', wpgen ((f,vf)::(x,vx)::nil) t1 Q' ==> wp (trm_app vf vx) Q' To wrap up, to hand recursive functions, we define:
Definition wpgen_fix (Fof:val→val→formula) : formula := fun Q ⇒
\∀ vf, \[∀ vx Q', Fof vf vx Q' ==> wp (trm_app vf vx) Q'] \−∗ Q vf.
\∀ vf, \[∀ vx Q', Fof vf vx Q' ==> wp (trm_app vf vx) Q'] \−∗ Q vf.
Then we integrate this definition is wpgen as shown below.
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
| ..
| trm_fix f x t1 ⇒ wpgen_fix (fun vf v ⇒ wpgen ((f,vf)::(x,v)::E) t1)
| ..
end Here again, we introduce a piece of notation for wpgen_fix. We let Fix f x := F stand for wpgen_fix (fun f x ⇒ F).
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
| ..
| trm_fix f x t1 ⇒ wpgen_fix (fun vf v ⇒ wpgen ((f,vf)::(x,v)::E) t1)
| ..
end Here again, we introduce a piece of notation for wpgen_fix. We let Fix f x := F stand for wpgen_fix (fun f x ⇒ F).
Notation "'Fix' f x ':=' F1" :=
((wpgen_fix (fun f x ⇒ F1)))
(at level 69, f name, x name, right associativity,
format "'[v' '[' 'Fix' f x ':=' F1 ']' ']'").
((wpgen_fix (fun f x ⇒ F1)))
(at level 69, f name, x name, right associativity,
format "'[v' '[' 'Fix' f x ':=' F1 ']' ']'").
3. Final Definition of wpgen, with Processing a Local Functions
Fixpoint wpgen (E:ctx) (t:trm) : formula :=
mkstruct match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_fun x t1 ⇒ wpgen_fun (fun v ⇒ wpgen ((x,v)::E) t1)
| trm_fix f x t1 ⇒ wpgen_fix (fun vf v ⇒ wpgen ((f,vf)::(x,v)::E) t1)
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
| trm_if t0 t1 t2 ⇒ wpgen_if (isubst E t0) (wpgen E t1) (wpgen E t2)
end.
mkstruct match t with
| trm_val v ⇒ wpgen_val v
| trm_var x ⇒ wpgen_var E x
| trm_fun x t1 ⇒ wpgen_fun (fun v ⇒ wpgen ((x,v)::E) t1)
| trm_fix f x t1 ⇒ wpgen_fix (fun vf v ⇒ wpgen ((f,vf)::(x,v)::E) t1)
| trm_app t1 t2 ⇒ wpgen_app (isubst E t)
| trm_seq t1 t2 ⇒ wpgen_seq (wpgen E t1) (wpgen E t2)
| trm_let x t1 t2 ⇒ wpgen_let (wpgen E t1) (fun v ⇒ wpgen ((x,v)::E) t2)
| trm_if t0 t1 t2 ⇒ wpgen_if (isubst E t0) (wpgen E t1) (wpgen E t2)
end.
4. Tactic for Reasoning About Functions
Lemma xfun_spec_lemma : ∀ (S:val→Prop) H Q Fof,
(∀ vf,
(∀ vx H' Q', (H' ==> Fof vx Q') → triple (trm_app vf vx) H' Q') →
S vf) →
(∀ vf, S vf → (H ==> Q vf)) →
H ==> wpgen_fun Fof Q.
Proof using.
introv M1 M2. unfold wpgen_fun. xsimpl. intros vf N.
applys M2. applys M1. introv K. rewrite <- wp_equiv. xchange K. applys N.
Qed.
Tactic Notation "xfun" constr(S) :=
xseq_xlet_if_needed; xstruct_if_needed; applys xfun_spec_lemma S.
(∀ vf,
(∀ vx H' Q', (H' ==> Fof vx Q') → triple (trm_app vf vx) H' Q') →
S vf) →
(∀ vf, S vf → (H ==> Q vf)) →
H ==> wpgen_fun Fof Q.
Proof using.
introv M1 M2. unfold wpgen_fun. xsimpl. intros vf N.
applys M2. applys M1. introv K. rewrite <- wp_equiv. xchange K. applys N.
Qed.
Tactic Notation "xfun" constr(S) :=
xseq_xlet_if_needed; xstruct_if_needed; applys xfun_spec_lemma S.
Second, we describe the tactic xfun with no argument. It applies to a goal
of the form H ==> wpgen_fun Fof Q. The tactic xfun simply provides an
hypothesis about the local function. The user may subsequently exploit this
hypothesis for reasoning about a call to that function, just like if the
code of the function was inlined at its call site. In practice, the use of
xfun without argument is most relevant for local functions that are
invoked exactly once. For example, higher-order iterators such as
List.iter or List.map are typically invoked with an ad-hoc functions
that appears exactly once.
Lemma xfun_nospec_lemma : ∀ H Q Fof,
(∀ vf,
(∀ vx H' Q', (H' ==> Fof vx Q') → triple (trm_app vf vx) H' Q') →
(H ==> Q vf)) →
H ==> wpgen_fun Fof Q.
Proof using.
introv M. unfold wpgen_fun. xsimpl. intros vf N. applys M.
introv K. rewrite <- wp_equiv. xchange K. applys N.
Qed.
Tactic Notation "xfun" :=
xseq_xlet_if_needed; xstruct_if_needed; applys xfun_nospec_lemma.
(∀ vf,
(∀ vx H' Q', (H' ==> Fof vx Q') → triple (trm_app vf vx) H' Q') →
(H ==> Q vf)) →
H ==> wpgen_fun Fof Q.
Proof using.
introv M. unfold wpgen_fun. xsimpl. intros vf N. applys M.
introv K. rewrite <- wp_equiv. xchange K. applys N.
Qed.
Tactic Notation "xfun" :=
xseq_xlet_if_needed; xstruct_if_needed; applys xfun_nospec_lemma.
A generalization of xfun that handles recursive functions may be defined
following exactly the same pattern. Details may be found in the file
LibSepReference.v
This completes our presentation of a version of wpgen that recursively
processes the local definition of non-recursive functions. An practical
example is presented next.
Consider the following example program, which involves a local function
definition, then two successive calls to that local function.
We first illustrate a call to xfun with an explicit specification. Here,
the function f is specified as incrementing the reference p. Observe
that the body function of the function f is verified only once. The
reasoning about the two calls to the function f that appears in the code
are done with respect to the specification that we provide as argument to
xfun in the proof script.
Lemma triple_myfun : ∀ (p:loc) (n:int),
triple (trm_app myfun p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+2)).
Proof using.
xwp.
xfun (fun (f:val) ⇒ ∀ (m:int),
triple (f())
(p ~~> m)
(fun _ ⇒ p ~~> (m+1))); intros f Hf.
{ intros.
applys Hf. (* Hf is the name of the hypothesis on the body of f *)
clear Hf.
xapp. (* exploits triple_incr *) xsimpl. }
xapp. (* exploits Hf. *)
xapp. (* exploits Hf. *)
replace (n+1+1) with (n+2); [|math]. xsimpl.
Qed.
triple (trm_app myfun p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+2)).
Proof using.
xwp.
xfun (fun (f:val) ⇒ ∀ (m:int),
triple (f())
(p ~~> m)
(fun _ ⇒ p ~~> (m+1))); intros f Hf.
{ intros.
applys Hf. (* Hf is the name of the hypothesis on the body of f *)
clear Hf.
xapp. (* exploits triple_incr *) xsimpl. }
xapp. (* exploits Hf. *)
xapp. (* exploits Hf. *)
replace (n+1+1) with (n+2); [|math]. xsimpl.
Qed.
We next illustrate a call to xfun without argument. The "generic
specification" that comes as hypothesis about the local function is a
proposition that describes the behavior of the function in terms of the
weakest-precondition of its body. When reasoning about a call to the
function, one can invoke this generic specification. The effect is
equivalent to that of inlining the code of the function at its call site.
The example shown below contains two calls to the function f. The script
invokes xfun without argument. Then, each of the two calls to f executes
a call to incr. Thus the proof script involves two calls to xapp that
each exploit the specification triple_incr.
Lemma triple_myfun' : ∀ (p:loc) (n:int),
triple (trm_app myfun p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+2)).
Proof using.
xwp.
xfun; intros f Hf.
xapp. (* exploits Hf *)
xapp. (* exploits triple_incr *)
xapp. (* exploits Hf *)
xapp. (* exploits triple_incr *)
replace (n+1+1) with (n+2); [|math]. xsimpl.
Qed.
End WPgenRec.
triple (trm_app myfun p)
(p ~~> n)
(fun _ ⇒ p ~~> (n+2)).
Proof using.
xwp.
xfun; intros f Hf.
xapp. (* exploits Hf *)
xapp. (* exploits triple_incr *)
xapp. (* exploits Hf *)
xapp. (* exploits triple_incr *)
replace (n+1+1) with (n+2); [|math]. xsimpl.
Qed.
End WPgenRec.
Historical Notes
(* 2024-02-26 12:11 *)