TriplesStructural Reasoning Rules
Set Implicit Arguments.
The file LibSepReference.v contains definitions that are essentially
similar to those from Hprop.v and Himpl.v, yet with one main difference:
LibSepReference makes the definition of Separation Logic operators opaque.
This chapter and the following ones import LibSepReference.v instead of
Hprop.v and Himpl.v.
As a result, one cannot unfold the definition of hstar, hpure, etc. To
carry out reasoning, one must use the introduction and elimination lemmas,
such as hstar_intro and hstar_elim. These lemmas enforce abstraction:
they ensure that the proofs do not depend on the particular choice of the
definitions used for constructing Separation Logic.
First Pass
- applys is an enhanced version of eapply.
- specializes is an enhanced version of specialize.
- lets and forwards are forward-chaining tactics that enable instantiating a lemma.
Syntax
Inductive val : Type :=
| val_unit : val
| val_bool : bool → val
| val_int : int → val
| val_loc : loc → val
| val_fun : var → trm → val
| val_fix : var → var → trm → val
| val_ref : val
| val_get : val
| val_set : val
| val_free : val
| val_add : val
| val_div : val
| val_rand : val
| val_unit : val
| val_bool : bool → val
| val_int : int → val
| val_loc : loc → val
| val_fun : var → trm → val
| val_fix : var → var → trm → val
| val_ref : val
| val_get : val
| val_set : val
| val_free : val
| val_add : val
| val_div : val
| val_rand : val
The grammar for terms includes values, variables, function definitions,
recursive function definitions, function applications, sequences,
let-bindings, and conditionals.
with trm : Type :=
| trm_val : val → trm
| trm_var : var → trm
| trm_fun : var → trm → trm
| trm_fix : var → var → trm → trm
| trm_app : trm → trm → trm
| trm_seq : trm → trm → trm
| trm_let : var → trm → trm → trm
| trm_if : trm → trm → trm → trm.
Note that trm_fun and trm_fix denote functions that may feature free
variables, unlike val_fun and val_fix which denote closed values. The
intention is that the evaluation of a trm_fun in the empty context
produces a val_fun value. Likewise, a trm_fix eventually evaluates to a
val_fix.
State
Recall from chapter Hprop that our convention is to write s for a
full memory state, of type state, and write h for a piece of memory
state, of type heap.
For technical reasons related to the internal representation of finite maps,
to enable reading in a state, we need to justify that the grammar of values
is inhabited. This property is captured by the following command, whose
details are not relevant for understanding the rest of the chapter.
Substitution
Fixpoint subst (y:var) (w:val) (t:trm) : trm :=
let aux t := subst y w t in
let if_y_eq x t1 t2 := if var_eq x y then t1 else t2 in
match t with
| trm_val v ⇒ trm_val v
| trm_var x ⇒ if_y_eq x (trm_val w) t
| trm_fun x t1 ⇒ trm_fun x (if_y_eq x t1 (aux t1))
| trm_fix f x t1 ⇒ trm_fix f x (if_y_eq f t1 (if_y_eq x t1 (aux t1)))
| trm_app t1 t2 ⇒ trm_app (aux t1) (aux t2)
| trm_seq t1 t2 ⇒ trm_seq (aux t1) (aux t2)
| trm_let x t1 t2 ⇒ trm_let x (aux t1) (if_y_eq x t2 (aux t2))
| trm_if t0 t1 t2 ⇒ trm_if (aux t0) (aux t1) (aux t2)
end.
let aux t := subst y w t in
let if_y_eq x t1 t2 := if var_eq x y then t1 else t2 in
match t with
| trm_val v ⇒ trm_val v
| trm_var x ⇒ if_y_eq x (trm_val w) t
| trm_fun x t1 ⇒ trm_fun x (if_y_eq x t1 (aux t1))
| trm_fix f x t1 ⇒ trm_fix f x (if_y_eq f t1 (if_y_eq x t1 (aux t1)))
| trm_app t1 t2 ⇒ trm_app (aux t1) (aux t2)
| trm_seq t1 t2 ⇒ trm_seq (aux t1) (aux t2)
| trm_let x t1 t2 ⇒ trm_let x (aux t1) (if_y_eq x t2 (aux t2))
| trm_if t0 t1 t2 ⇒ trm_if (aux t0) (aux t1) (aux t2)
end.
Implicit Types and Coercions
Implicit Types b : bool.
Implicit Types v r : val.
Implicit Types t : trm.
Implicit Types s : state.
Implicit Types v r : val.
Implicit Types t : trm.
Implicit Types s : state.
A collection of coercions is then introduced. Coercions correspond to
implicit function calls, which aim to make statements more concise. For
example, val_loc is declared as a coercion, so that a location p of type
loc can be viewed as the value val_loc p where an expression of type
val is expected. Likewise, a boolean b may be viewed as the value
val_bool b, and an integer n may be viewed as the value val_int n.
The constructor trm_val is also declared as a coercion. Thus, instead of
writing trm_val v at a place where term is expected, we may write just v
.
The constructor trm_app is declared as a "Funclass" coercion. This piece
of magic enables us to write t1 t2 as a shorthand for trm_app t1 t2. The
idea of associating trm_app as the "Funclass" coercion for the type trm
is that if a term t1 of type trm is applied like a function to an
argument, then t1 should be interpreted as trm_app t1.
Interestingly, the "Funclass" coercion for trm_app can be iterated. The
expression t1 t2 t3 is parsed by Coq as (t1 t2) t3. The first
application t1 t2 is interpreted as trm_app t1 t2. This expression,
which itself has type trm, is applied to t3. Hence, t1 t2 t3 is
interpreted as trm_app (trm_app t1 t2) t3.
Standard Big-Step Semantics
1. big for values and function definitions.
A value evaluates to itself. A term function evaluates to a value
function. Likewise for a recursive function.
| big_val : ∀ s v,
big s (trm_val v) s v
| big_fun : ∀ s x t1,
big s (trm_fun x t1) s (val_fun x t1)
| big_fix : ∀ s f x t1,
big s (trm_fix f x t1) s (val_fix f x t1)
2. big for function applications.
The beta reduction rule asserts that (val_fun x t1) v2 evaluates to the
same result as subst x v2 t1. Likewise, (val_fix f x t1) v2 evaluates
to subst x v2 (subst f v1 t1), where v1 denotes the recursive function
itself, that is, val_fix f x t1.
| big_app_fun : ∀ s1 s2 v1 v2 x t1 v,
v1 = val_fun x t1 →
big s1 (subst x v2 t1) s2 v →
big s1 (trm_app v1 v2) s2 v
| big_app_fix : ∀ s1 s2 v1 v2 f x t1 v,
v1 = val_fix f x t1 →
big s1 (subst x v2 (subst f v1 t1)) s2 v →
big s1 (trm_app v1 v2) s2 v
3. big for structural constructs.
A sequence trm_seq t1 t2 first biguates t1, taking the state from s1
to s2, drops the result of t1, then evaluates t2, taking the state
from s2 to s3.
The let-binding trm_let x t1 t2 is similar, except that the variable x
gets substituted for the result of t1 inside t2.
| big_seq : ∀ s1 s2 s3 t1 t2 v1 v,
big s1 t1 s2 v1 →
big s2 t2 s3 v →
big s1 (trm_seq t1 t2) s3 v
| big_let : ∀ s1 s2 s3 x t1 t2 v1 r,
big s1 t1 s2 v1 →
big s2 (subst x v1 t2) s3 r →
big s1 (trm_let x t1 t2) s3 r
4. big for conditionals.
A conditional in a source program is assumed to be of the form
if t0 then t1 else t2, where t0 is either a variable or a value. If
t0 is a variable, then by the time it reaches an evaluation position,
the variable must have been substituted by a value. Thus, the evaluation
rule only considers the form if v0 then t1 else t2. The value v0 must
be a boolean value, otherwise evaluation gets stuck.
The term trm_if (val_bool true) t1 t2 behaves like t1, whereas the
term trm_if (val_bool false) t1 t2 behaves like t2. This behavior is
described by a single rule, leveraging Coq's "if" constructor to factor
out the two cases.
| big_if : ∀ s1 s2 b v t1 t2,
big s1 (if b then t1 else t2) s2 v →
big s1 (trm_if (val_bool b) t1 t2) s2 v
5. big for primitive stateless operations.
For similar reasons as explained above, the behavior of applied primitive
functions only need to be described for the case of value arguments.
An arithmetic operation expects integer arguments. The addition of
val_int n1 and val_int n2 produces val_int (n1 + n2).
The division operation, on the same arguments, produces the quotient
n1 / n2, under the assumption that the dividor n2 is non-zero. In
other words, if a program performs a division by zero, then it cannot
satisfy the big judgment. The random number generator val_rand n
produces an integer n1 in the range from 0 inclusive to n exclusive.
| big_add : ∀ s n1 n2,
big s (val_add (val_int n1) (val_int n2)) s (val_int (n1 + n2))
| big_div : ∀ s n1 n2,
n2 ≠ 0 →
big s (val_div (val_int n1) (val_int n2)) s (val_int (Z.quot n1 n2))
| big_rand : ∀ s n n1,
0 ≤ n1 < n →
big s (val_rand (val_int n)) s (val_int n1)
6. big for primitive operations on memory.
There remains to describe operations that act on the mutable store.
val_ref v allocates a fresh cell with contents v. The operation
returns the location, written p, of the new cell. This location must not
be previously in the domain of the store s.
val_get (val_loc p) reads the value in the store s at location p.
The location must be bound to a value in the store, else evaluation is
stuck.
val_set (val_loc p) v updates the store at a location p assumed to be
bound in the store s. The operation modifies the store and returns the
unit value.
val_free (val_loc p) deallocates the cell at location p.
| big_ref : ∀ s v p,
¬ Fmap.indom s p →
big s (val_ref v) (Fmap.update s p v) (val_loc p)
| big_get : ∀ s p,
Fmap.indom s p →
big s (val_get (val_loc p)) s (Fmap.read s p)
| big_set : ∀ s p v,
Fmap.indom s p →
big s (val_set (val_loc p) v) (Fmap.update s p v) val_unit
| big_free : ∀ s p,
Fmap.indom s p →
big s (val_free (val_loc p)) (Fmap.remove s p) val_unit.
Omni-Big-Step Semantics
The inductive definition below defines the omni-big-step judgment, of the
form eval s t Q, where Q is a postcondition. The generalization from
standard-big-step to omni-big-step follows a regular pattern, which should
become visible while stepping through the rules.
1. eval for values and function definitions.
The judgment eval s v Q asserts that the value v in the state s
satisfies the postcondition Q. The premise for deriving that judgment is
thus that Q v s must hold.
| eval_val : ∀ s v Q,
Q v s →
eval s (trm_val v) Q
| eval_fun : ∀ s x t1 Q,
Q (val_fun x t1) s →
eval s (trm_fun x t1) Q
| eval_fix : ∀ s f x t1 Q,
Q (val_fix f x t1) s →
eval s (trm_fix f x t1) Q
2. eval for function applications.
Consider a function v1 of the form val_fun x t1. The term
trm_app v1 v2 terminates with postcondition Q provided that the term
subst x v2 t1 terminates with postcondition Q. Hence, to prove
eval s1 (trm_app v1 v2) Q, one must establish eval s1 (subst x v2 t1) Q
.
| eval_app_fun : ∀ s1 v1 v2 x t1 Q,
v1 = val_fun x t1 →
eval s1 (subst x v2 t1) Q →
eval s1 (trm_app v1 v2) Q
| eval_app_fix : ∀ s v1 v2 f x t1 Q,
v1 = val_fix f x t1 →
eval s (subst x v2 (subst f v1 t1)) Q →
eval s (trm_app v1 v2) Q
3. eval for structural constructs.
Consider a sequence trm_seq t1 t2 in a state s. What is the
requirement for this configuration to terminate with postcondition Q?
First, we need the evaluation of t1 in s to terminate. Let Q1 be an
overapproximation of the set of possible results for the execution of
(s,t1). To prove that the sequence trm_seq t1 t2 terminates in Q, we
need to show that, for any intermediate state s2 satisfying Q1, the
evaluation of t2 terminates with postcondition Q. The case of
let-bindings is similar, only with an additional substitution.
| eval_seq : ∀ Q1 s t1 t2 Q,
eval s t1 Q1 →
(∀ v1 s2, Q1 v1 s2 → eval s2 t2 Q) →
eval s (trm_seq t1 t2) Q
| eval_let : ∀ Q1 s x t1 t2 Q,
eval s t1 Q1 →
(∀ v1 s2, Q1 v1 s2 → eval s2 (subst x v1 t2) Q) →
eval s (trm_let x t1 t2) Q
4. eval for conditionals.
The term (trm_if (val_bool b) t1 t2) admits the same behavior as the
term t1 when b is true, and as the term t2 when b is false.
| eval_if : ∀ s (b:bool) t1 t2 Q,
eval s (if b then t1 else t2) Q →
eval s (trm_if (val_bool b) t1 t2) Q
5. eval for primitive stateless operations.
The judgment eval s (val_add n1 n2) Q asserts that the pair made of the
state s and the value n1+n2 produced by the addition operation satisfy
the postcondition Q. Hence, the relevant premise is:
Q (val_int (n1 + n2)) s.
A more interesting case is that that of a nondeterministic construct. The
rule eval_rand gives the condition under which the term val_rand n,
evaluated in a state s, produces an output satisfying Q. The first
premise of the rule requires n > 0. The second premise requires that, for
any value n1 that val_rand n may evaluate to, that is, such that
0 ≤ n1 < n, the configuration made of n1 and s satisfies the
postcondition Q. Technically: ∀ n1, (0 ≤ n1 < n) → (Q n1 s).
| eval_add : ∀ s n1 n2 Q,
Q (val_int (n1 + n2)) s →
eval s (val_add (val_int n1) (val_int n2)) Q
| eval_div : ∀ s n1 n2 Q,
n2 ≠ 0 →
Q (val_int (Z.quot n1 n2)) s →
eval s (val_div (val_int n1) (val_int n2)) Q
| eval_rand : ∀ s n Q,
n > 0 →
(∀ n1, 0 ≤ n1 < n → Q n1 s) →
eval s (val_rand (val_int n)) Q
6. eval for primitive operations on memory.
The interesting case is that of allocation, which is nondeterministic. For
val_ref v, evaluated in a state s, to produce a configuration in Q, we
require that, for any location p fresh from the state s, the pair made
of p and of the state s extended with a binding from p to v together
satisfy Q.
| eval_ref : ∀ s v Q,
(∀ p, ¬ Fmap.indom s p →
Q (val_loc p) (Fmap.update s p v)) →
eval s (val_ref v) Q
| eval_get : ∀ s p Q,
Fmap.indom s p →
Q (Fmap.read s p) s →
eval s (val_get (val_loc p)) Q
| eval_set : ∀ s p v Q,
Fmap.indom s p →
Q val_unit (Fmap.update s p v) →
eval s (val_set (val_loc p) v) Q
| eval_free : ∀ s p Q,
Fmap.indom s p →
Q val_unit (Fmap.remove s p) →
eval s (val_free (val_loc p)) Q.
Note that if eval s t Q holds, then Q does not correspond to the set of
configurations reachable from (s,t), but to an overapproximation of that
set. Trying to set up an inductive definition that relates (s,t) with
exactly its set of reachable configurations would introduce unnecessary
complications.
Besides, observe that eval s t Q cannot hold if there exists one or more
executions of (s,t) that runs into an error, i.e., that reaches a
configuration that is stuck.
Loading of Definitions from LibSepReference
Implicit Types f : var.
Implicit Types b : bool.
Implicit Types p : loc.
Implicit Types n : int.
Implicit Types v w r : val.
Implicit Types t : trm.
Implicit Types h : heap.
Implicit Types s : state.
Implicit Types H : hprop.
Implicit Types Q : val→hprop.
Inductive eval : state → trm → (val→state→Prop) → Prop :=
| eval_val : ∀ s v Q,
Q v s →
eval s (trm_val v) Q
| eval_fun : ∀ s x t1 Q,
Q (val_fun x t1) s →
eval s (trm_fun x t1) Q
| eval_fix : ∀ s f x t1 Q,
Q (val_fix f x t1) s →
eval s (trm_fix f x t1) Q
| eval_app_fun : ∀ s1 v1 v2 x t1 Q,
v1 = val_fun x t1 →
eval s1 (subst x v2 t1) Q →
eval s1 (trm_app v1 v2) Q
| eval_app_fix : ∀ s v1 v2 f x t1 Q,
v1 = val_fix f x t1 →
eval s (subst x v2 (subst f v1 t1)) Q →
eval s (trm_app v1 v2) Q
| eval_seq : ∀ Q1 s t1 t2 Q,
eval s t1 Q1 →
(∀ v1 s2, Q1 v1 s2 → eval s2 t2 Q) →
eval s (trm_seq t1 t2) Q
| eval_let : ∀ Q1 s x t1 t2 Q,
eval s t1 Q1 →
(∀ v1 s2, Q1 v1 s2 → eval s2 (subst x v1 t2) Q) →
eval s (trm_let x t1 t2) Q
| eval_if : ∀ s (b:bool) t1 t2 Q,
eval s (if b then t1 else t2) Q →
eval s (trm_if (val_bool b) t1 t2) Q
| eval_add : ∀ s n1 n2 Q,
Q (val_int (n1 + n2)) s →
eval s (val_add (val_int n1) (val_int n2)) Q
| eval_div : ∀ s n1 n2 Q,
n2 ≠ 0 →
Q (val_int (Z.quot n1 n2)) s →
eval s (val_div (val_int n1) (val_int n2)) Q
| eval_rand : ∀ s n Q,
n > 0 →
(∀ n1, 0 ≤ n1 < n → Q n1 s) →
eval s (val_rand (val_int n)) Q
| eval_ref : ∀ s v Q,
(∀ p, ¬ Fmap.indom s p →
Q (val_loc p) (Fmap.update s p v)) →
eval s (val_ref v) Q
| eval_get : ∀ s p Q,
Fmap.indom s p →
Q (Fmap.read s p) s →
eval s (val_get (val_loc p)) Q
| eval_set : ∀ s p v Q,
Fmap.indom s p →
Q val_unit (Fmap.update s p v) →
eval s (val_set (val_loc p) v) Q
| eval_free : ∀ s p Q,
Fmap.indom s p →
Q val_unit (Fmap.remove s p) →
eval s (val_free (val_loc p)) Q.
Implicit Types b : bool.
Implicit Types p : loc.
Implicit Types n : int.
Implicit Types v w r : val.
Implicit Types t : trm.
Implicit Types h : heap.
Implicit Types s : state.
Implicit Types H : hprop.
Implicit Types Q : val→hprop.
Inductive eval : state → trm → (val→state→Prop) → Prop :=
| eval_val : ∀ s v Q,
Q v s →
eval s (trm_val v) Q
| eval_fun : ∀ s x t1 Q,
Q (val_fun x t1) s →
eval s (trm_fun x t1) Q
| eval_fix : ∀ s f x t1 Q,
Q (val_fix f x t1) s →
eval s (trm_fix f x t1) Q
| eval_app_fun : ∀ s1 v1 v2 x t1 Q,
v1 = val_fun x t1 →
eval s1 (subst x v2 t1) Q →
eval s1 (trm_app v1 v2) Q
| eval_app_fix : ∀ s v1 v2 f x t1 Q,
v1 = val_fix f x t1 →
eval s (subst x v2 (subst f v1 t1)) Q →
eval s (trm_app v1 v2) Q
| eval_seq : ∀ Q1 s t1 t2 Q,
eval s t1 Q1 →
(∀ v1 s2, Q1 v1 s2 → eval s2 t2 Q) →
eval s (trm_seq t1 t2) Q
| eval_let : ∀ Q1 s x t1 t2 Q,
eval s t1 Q1 →
(∀ v1 s2, Q1 v1 s2 → eval s2 (subst x v1 t2) Q) →
eval s (trm_let x t1 t2) Q
| eval_if : ∀ s (b:bool) t1 t2 Q,
eval s (if b then t1 else t2) Q →
eval s (trm_if (val_bool b) t1 t2) Q
| eval_add : ∀ s n1 n2 Q,
Q (val_int (n1 + n2)) s →
eval s (val_add (val_int n1) (val_int n2)) Q
| eval_div : ∀ s n1 n2 Q,
n2 ≠ 0 →
Q (val_int (Z.quot n1 n2)) s →
eval s (val_div (val_int n1) (val_int n2)) Q
| eval_rand : ∀ s n Q,
n > 0 →
(∀ n1, 0 ≤ n1 < n → Q n1 s) →
eval s (val_rand (val_int n)) Q
| eval_ref : ∀ s v Q,
(∀ p, ¬ Fmap.indom s p →
Q (val_loc p) (Fmap.update s p v)) →
eval s (val_ref v) Q
| eval_get : ∀ s p Q,
Fmap.indom s p →
Q (Fmap.read s p) s →
eval s (val_get (val_loc p)) Q
| eval_set : ∀ s p v Q,
Fmap.indom s p →
Q val_unit (Fmap.update s p v) →
eval s (val_set (val_loc p) v) Q
| eval_free : ∀ s p Q,
Fmap.indom s p →
Q val_unit (Fmap.remove s p) →
eval s (val_free (val_loc p)) Q.
Definition of Triples
The "frame rule" asserts that an arbitrary heap predicate may be added to
both the precondition and the postcondition of any triple.
The "consequence rule" asserts, like in Hoare Logic, that a triple is
perserved when strengthening its precondition or weakening its
postcondition.
The "extraction rule for pure facts" asserts that a judgment of the form
triple t (\[P] \* H) Q is derivable from P → triple t H Q. This
structural rule captures the extraction of the pure facts out of the
precondition of a triple, in a similar way as himpl_hstar_hpure_l for
entailments.
The "extraction rule for pure facts" asserts that judgment of the form
triple t (\∃ x, J x) Q is derivable from ∀ x, triple t (J x) Q
. Again, this rule is the counterpart of the corresponding rule on
entailements, named himpl_hexists_l.
Parameter triple_hexists : ∀ t (A:Type) (J:A→hprop) Q,
(∀ x, triple t (J x) Q) →
triple t (\∃ x, J x) Q.
(∀ x, triple t (J x) Q) →
triple t (\∃ x, J x) Q.
Derived Structural Rules
Lemma triple_conseq_frame : ∀ H2 H1 Q1 t H Q,
triple t H1 Q1 →
H ==> H1 \* H2 →
Q1 \*+ H2 ===> Q →
triple t H Q.
triple t H1 Q1 →
H ==> H1 \* H2 →
Q1 \*+ H2 ===> Q →
triple t H Q.
Exercise: 1 star, standard, especially useful (triple_conseq_frame)
Prove the combined consequence-frame rule.
Proof using. (* FILL IN HERE *) Admitted.
☐
☐
Exercise: 1 star, standard, optional (triple_hpure')
Prove that triple_hpure' is indeed a corollary of triple_hpure.
Lemma triple_hpure' : ∀ t (P:Prop) Q,
(P → triple t \[] Q) →
triple t \[P] Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
(P → triple t \[] Q) →
triple t \[P] Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Proof of the Consequence Rule
Lemma eval_conseq : ∀ s t Q1 Q2,
eval s t Q1 →
Q1 ===> Q2 →
eval s t Q2.
Proof using.
introv M W.
asserts W': (∀ v h, Q1 v h → Q2 v h). { auto. } clear W.
induction M; try solve [ constructors* ].
Qed.
eval s t Q1 →
Q1 ===> Q2 →
eval s t Q2.
Proof using.
introv M W.
asserts W': (∀ v h, Q1 v h → Q2 v h). { auto. } clear W.
induction M; try solve [ constructors* ].
Qed.
From there, it is straightforward to derive the consequence rule.
Lemma triple_conseq : ∀ t H' Q' H Q,
triple t H' Q' →
H ==> H' →
Q' ===> Q →
triple t H Q.
Proof using. unfolds triple. introv M MH MQ HF. applys* eval_conseq. Qed.
triple t H' Q' →
H ==> H' →
Q' ===> Q →
triple t H Q.
Proof using. unfolds triple. introv M MH MQ HF. applys* eval_conseq. Qed.
Proof of the Frame Rule
Lemma eval_frame : ∀ h1 h2 t Q,
eval h1 t Q →
Fmap.disjoint h1 h2 →
eval (h1 \u h2) t (Q \*+ (= h2)).
eval h1 t Q →
Fmap.disjoint h1 h2 →
eval (h1 \u h2) t (Q \*+ (= h2)).
This proof is by induction. The most interesting step is that of allocation.
In a derivation built using eval_ref, we are given as assumption a
property that holds for any location p fresh from h1, and we are
requested to prove a property that holds for any location p fresh from
h1 \u h2. We are thus restricting the set of p that can be considered
for allocation, hence the result holds.
Proof using.
introv M HD. gen h2. induction M; intros;
try solve [ hint hstar_intro; constructors* ].
{ rename M into M1, H into M2, IHM into IH1, H0 into IH2.
specializes IH1 HD. applys eval_seq IH1. introv HK.
lets (h1'&h2'&K1'&K2'&KD&KU): hstar_inv HK. subst. applys* IH2. }
{ rename M into M1, H into M2, IHM into IH1, H0 into IH2.
specializes IH1 HD. applys eval_let IH1. introv HK.
lets (h1'&h2'&K1'&K2'&KD&KU): hstar_inv HK. subst. applys* IH2. }
{ (* Here is the interesting case about allocation. *)
rename H into M. applys eval_ref. intros p Hp.
rewrite Fmap.indom_union_eq in Hp. rew_logic in Hp.
destruct Hp as [Hp1 Hp2].
rewrite* Fmap.update_union_not_r. applys hstar_intro.
{ applys* M. } { auto. } { applys* Fmap.disjoint_update_not_r. } }
{ applys eval_get. { rewrite* Fmap.indom_union_eq. }
{ rewrite* Fmap.read_union_l. applys* hstar_intro. } }
{ applys eval_set. { rewrite* Fmap.indom_union_eq. }
{ rewrite* Fmap.update_union_l. applys hstar_intro.
{ auto. } { auto. } { applys* Fmap.disjoint_update_l. } } }
{ applys eval_free. { rewrite* Fmap.indom_union_eq. }
{ rewrite* Fmap.remove_disjoint_union_l. applys hstar_intro.
{ auto. } { auto. } { applys* Fmap.disjoint_remove_l. } } }
Qed.
introv M HD. gen h2. induction M; intros;
try solve [ hint hstar_intro; constructors* ].
{ rename M into M1, H into M2, IHM into IH1, H0 into IH2.
specializes IH1 HD. applys eval_seq IH1. introv HK.
lets (h1'&h2'&K1'&K2'&KD&KU): hstar_inv HK. subst. applys* IH2. }
{ rename M into M1, H into M2, IHM into IH1, H0 into IH2.
specializes IH1 HD. applys eval_let IH1. introv HK.
lets (h1'&h2'&K1'&K2'&KD&KU): hstar_inv HK. subst. applys* IH2. }
{ (* Here is the interesting case about allocation. *)
rename H into M. applys eval_ref. intros p Hp.
rewrite Fmap.indom_union_eq in Hp. rew_logic in Hp.
destruct Hp as [Hp1 Hp2].
rewrite* Fmap.update_union_not_r. applys hstar_intro.
{ applys* M. } { auto. } { applys* Fmap.disjoint_update_not_r. } }
{ applys eval_get. { rewrite* Fmap.indom_union_eq. }
{ rewrite* Fmap.read_union_l. applys* hstar_intro. } }
{ applys eval_set. { rewrite* Fmap.indom_union_eq. }
{ rewrite* Fmap.update_union_l. applys hstar_intro.
{ auto. } { auto. } { applys* Fmap.disjoint_update_l. } } }
{ applys eval_free. { rewrite* Fmap.indom_union_eq. }
{ rewrite* Fmap.remove_disjoint_union_l. applys hstar_intro.
{ auto. } { auto. } { applys* Fmap.disjoint_remove_l. } } }
Qed.
The frame rule is derived from eval_frame and eval_conseq.
Lemma triple_frame : ∀ t H Q H',
triple t H Q →
triple t (H \* H') (Q \*+ H').
Proof.
introv M. intros h HF. lets (h1&h2&M1&M2&MD&MU): hstar_inv (rm HF).
subst. specializes M M1. applys eval_conseq.
{ applys eval_frame M MD. } { xsimpl. intros h' →. applys M2. }
Qed.
triple t H Q →
triple t (H \* H') (Q \*+ H').
Proof.
introv M. intros h HF. lets (h1&h2&M1&M2&MD&MU): hstar_inv (rm HF).
subst. specializes M M1. applys eval_conseq.
{ applys eval_frame M MD. } { xsimpl. intros h' →. applys M2. }
Qed.
Proof of the Extraction Rules
Lemma triple_hpure : ∀ t (P:Prop) H Q,
(P → triple t H Q) →
triple t (\[P] \* H) Q.
Proof using.
introv M. intros h (h1&h2&M1&M2&D&U). destruct M1 as (M1&HP).
lets E: hempty_inv HP. subst. rewrite Fmap.union_empty_l. applys¬M.
Qed.
(P → triple t H Q) →
triple t (\[P] \* H) Q.
Proof using.
introv M. intros h (h1&h2&M1&M2&D&U). destruct M1 as (M1&HP).
lets E: hempty_inv HP. subst. rewrite Fmap.union_empty_l. applys¬M.
Qed.
Lemma triple_hexists : ∀ t (A:Type) (J:A→hprop) Q,
(∀ (x:A), triple t (J x) Q) →
triple t (hexists J) Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
(∀ (x:A), triple t (J x) Q) →
triple t (hexists J) Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Rule for Naming Heaps
Exercise: 1 star, standard, optional (triple_named_heap)
Prove the reasoning rule hoare_named_heap, which allows introducing a name for the input heap.
Lemma triple_named_heap : ∀ t H Q,
(∀ h, H h → triple t (= h) Q) →
triple t H Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
(∀ h, H h → triple t (= h) Q) →
triple t H Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Traditional papers on Separation Logic do not include triple_hexists, but
instead include a rule called triple_hexists2 that features an existential
quantifier both in the precondition and the postcondition. As we show next,
in the presence of the consequence rule, the two rules are equivalent.
Exercise: 2 stars, standard, especially useful (triple_hexists2)
Using triple_hexists and triple_conseq, as well as the tactic xsimpl, prove that triple_hexists2 is derivable.
Lemma triple_hexists2 : ∀ A (Hof:A→hprop) (Qof:A→val→hprop) t,
(∀ x, triple t (Hof x) (Qof x)) →
triple t (\∃ x, Hof x) (fun v ⇒ \∃ x, Qof x v).
Proof using. (* FILL IN HERE *) Admitted.
(* *)
(∀ x, triple t (Hof x) (Qof x)) →
triple t (\∃ x, Hof x) (fun v ⇒ \∃ x, Qof x v).
Proof using. (* FILL IN HERE *) Admitted.
(* *)
Exercise: 2 stars, standard, especially useful (triple_hexists_of_triple_hexists2)
Reciprocally, using triple_hexists2 and triple_conseq, as well as the tactic xsimpl, prove that triple_hexists is derivable. Of course, you may not use triple_hexists in this proof.)
Lemma triple_hexists_of_triple_hexists2 : ∀ t (A:Type) (Hof:A→hprop) Q,
(∀ x, triple t (Hof x) Q) →
triple t (\∃ x, Hof x) Q.
Proof using. (* FILL IN HERE *) Admitted.
(* *)
(∀ x, triple t (Hof x) Q) →
triple t (\∃ x, Hof x) Q.
Proof using. (* FILL IN HERE *) Admitted.
(* *)
Compared with triple_hexists2, the formulation of triple_hexists is more
concise, and is easier to exploit in practice.
Small-Step Semantics
Inductive step : state → trm → state → trm → Prop :=
(* Context rules *)
| step_seq_ctx : ∀ s1 s2 t1 t1' t2,
step s1 t1 s2 t1' →
step s1 (trm_seq t1 t2) s2 (trm_seq t1' t2)
| step_let_ctx : ∀ s1 s2 x t1 t1' t2,
step s1 t1 s2 t1' →
step s1 (trm_let x t1 t2) s2 (trm_let x t1' t2)
(* Reductions *)
| step_fun : ∀ s x t1,
step s (trm_fun x t1) s (val_fun x t1)
| step_fix : ∀ s f x t1,
step s (trm_fix f x t1) s (val_fix f x t1)
| step_app_fun : ∀ s v1 v2 x t1,
v1 = val_fun x t1 →
step s (trm_app v1 v2) s (subst x v2 t1)
| step_app_fix : ∀ s v1 v2 f x t1,
v1 = val_fix f x t1 →
step s (trm_app v1 v2) s (subst x v2 (subst f v1 t1))
| step_if : ∀ s b t1 t2,
step s (trm_if (val_bool b) t1 t2) s (if b then t1 else t2)
| step_seq : ∀ s t2 v1,
step s (trm_seq v1 t2) s t2
| step_let : ∀ s x t2 v1,
step s (trm_let x v1 t2) s (subst x v1 t2)
(* Primitive operations *)
| step_add : ∀ s n1 n2,
step s (val_add (val_int n1) (val_int n2)) s (val_int (n1 + n2))
| step_div : ∀ s n1 n2,
n2 ≠ 0 →
step s (val_div (val_int n1) (val_int n2)) s (Z.quot n1 n2)
| step_rand : ∀ s n n1,
0 ≤ n1 < n →
step s (val_rand (val_int n)) s (val_int n1)
| step_ref : ∀ s v p,
¬ Fmap.indom s p →
step s (val_ref v) (Fmap.update s p v) (val_loc p)
| step_get : ∀ s p,
Fmap.indom s p →
step s (val_get (val_loc p)) s (Fmap.read s p)
| step_set : ∀ s p v,
Fmap.indom s p →
step s (val_set (val_loc p) v) (Fmap.update s p v) val_unit
| step_free : ∀ s p,
Fmap.indom s p →
step s (val_free (val_loc p)) (Fmap.remove s p) val_unit.
(* Context rules *)
| step_seq_ctx : ∀ s1 s2 t1 t1' t2,
step s1 t1 s2 t1' →
step s1 (trm_seq t1 t2) s2 (trm_seq t1' t2)
| step_let_ctx : ∀ s1 s2 x t1 t1' t2,
step s1 t1 s2 t1' →
step s1 (trm_let x t1 t2) s2 (trm_let x t1' t2)
(* Reductions *)
| step_fun : ∀ s x t1,
step s (trm_fun x t1) s (val_fun x t1)
| step_fix : ∀ s f x t1,
step s (trm_fix f x t1) s (val_fix f x t1)
| step_app_fun : ∀ s v1 v2 x t1,
v1 = val_fun x t1 →
step s (trm_app v1 v2) s (subst x v2 t1)
| step_app_fix : ∀ s v1 v2 f x t1,
v1 = val_fix f x t1 →
step s (trm_app v1 v2) s (subst x v2 (subst f v1 t1))
| step_if : ∀ s b t1 t2,
step s (trm_if (val_bool b) t1 t2) s (if b then t1 else t2)
| step_seq : ∀ s t2 v1,
step s (trm_seq v1 t2) s t2
| step_let : ∀ s x t2 v1,
step s (trm_let x v1 t2) s (subst x v1 t2)
(* Primitive operations *)
| step_add : ∀ s n1 n2,
step s (val_add (val_int n1) (val_int n2)) s (val_int (n1 + n2))
| step_div : ∀ s n1 n2,
n2 ≠ 0 →
step s (val_div (val_int n1) (val_int n2)) s (Z.quot n1 n2)
| step_rand : ∀ s n n1,
0 ≤ n1 < n →
step s (val_rand (val_int n)) s (val_int n1)
| step_ref : ∀ s v p,
¬ Fmap.indom s p →
step s (val_ref v) (Fmap.update s p v) (val_loc p)
| step_get : ∀ s p,
Fmap.indom s p →
step s (val_get (val_loc p)) s (Fmap.read s p)
| step_set : ∀ s p v,
Fmap.indom s p →
step s (val_set (val_loc p) v) (Fmap.update s p v) val_unit
| step_free : ∀ s p,
Fmap.indom s p →
step s (val_free (val_loc p)) (Fmap.remove s p) val_unit.
The judgment steps s t s' t' corresponds to the reflexive-transitive
closure of step. Concretely, this judgment asserts that the configuration
(s,t) can reduce in zero, one, or several steps to (s',t').
Inductive steps : state → trm → state → trm → Prop :=
| steps_refl : ∀ s t,
steps s t s t
| steps_step : ∀ s1 s2 s3 t1 t2 t3,
step s1 t1 s2 t2 →
steps s2 t2 s3 t3 →
steps s1 t1 s3 t3.
Lemma steps_of_step : ∀ s1 s2 t1 t2,
step s1 t1 s2 t2 →
steps s1 t1 s2 t2.
Proof using. introv M. applys steps_step M. applys steps_refl. Qed.
Lemma steps_trans : ∀ s1 s2 s3 t1 t2 t3,
steps s1 t1 s2 t2 →
steps s2 t2 s3 t3 →
steps s1 t1 s3 t3.
Proof using.
introv M1. induction M1; introv M2. { auto. } { constructors*. }
Qed.
| steps_refl : ∀ s t,
steps s t s t
| steps_step : ∀ s1 s2 s3 t1 t2 t3,
step s1 t1 s2 t2 →
steps s2 t2 s3 t3 →
steps s1 t1 s3 t3.
Lemma steps_of_step : ∀ s1 s2 t1 t2,
step s1 t1 s2 t2 →
steps s1 t1 s2 t2.
Proof using. introv M. applys steps_step M. applys steps_refl. Qed.
Lemma steps_trans : ∀ s1 s2 s3 t1 t2 t3,
steps s1 t1 s2 t2 →
steps s2 t2 s3 t3 →
steps s1 t1 s3 t3.
Proof using.
introv M1. induction M1; introv M2. { auto. } { constructors*. }
Qed.
The predicate trm_is_val t asserts that t is a value.
Consider a configuration (s,t), where t is not a value. If this
configuration cannot take any reduction step, it is said to be "stuck". On
the contrary, a configuration (s,t) that may take a step is said to be
"reducible".
Values are not reducible.
The predicate notstuck s t asserts that either t is a value or t is
reducible.
Equivalence Between Small-Step and Omni-Big-Step
Inductive terminates : state→trm→Prop :=
| terminates_step : ∀ s t,
(∀ s' t', step s t s' t' → terminates s' t') →
terminates s t.
| terminates_step : ∀ s t,
(∀ s' t', step s t s' t' → terminates s' t') →
terminates s t.
The judgment safe s t asserts that no execution may reach a stuck term. In
other words, for any configuration (s',t') reachable from (s,t), it is
the case that the configuration (s',t') is either a value or is reducible.
The judgment correct s t Q asserts that if the execution of (s,t)
reaches a final configuration, then this final configuration satisfies Q.
The conjunction of safe and correct corresponds to "partial
correctness". The conjunction of safe, correct and terminates
corresponds to "total correctness". The soundness theorem that we aim for
establishes that triple t H Q entails total correctness.
triple t H Q →
∀ s, H s → terminates s t ∧ safe s t ∧ correct s t Q.
In order to prove soundness, we introduce an inductively-defined predicate,
named seval, which captures total correctness in small-step style. On the
one hand, we prove that seval entails safe, correct, and terminates.
On the other hand, we prove that seval is related to the omni-big-step
judgment, namely eval.
The judgment seval s t Q asserts that any execution of (s,t) terminates
and reaches a configuration satisfying Q. In the "base" case,
seval s v Q holds if the terminal configuration (s,v) satisfies Q. In
the "step" case, seval s t Q holds if (1) the configuration (s,t) is
reducible, and (2) if for any step that (s,t) may take to (s',t'), the
predicate seval s' t' Q holds.
triple t H Q →
∀ s, H s → terminates s t ∧ safe s t ∧ correct s t Q.
Inductive seval : state→trm->(val→hprop)->Prop :=
| seval_val : ∀ s v Q,
Q v s →
seval s v Q
| seval_step : ∀ s t Q,
reducible s t → (* (exists s' t', step s t s' t') *)
(∀ s' t', step s t s' t' → seval s' t' Q) →
seval s t Q.
| seval_val : ∀ s v Q,
Q v s →
seval s v Q
| seval_step : ∀ s t Q,
reducible s t → (* (exists s' t', step s t s' t') *)
(∀ s' t', step s t s' t' → seval s' t' Q) →
seval s t Q.
Exercise: 2 stars, standard, optional (seval_val_inv)
As a warm-up to get some familiary with seval, prove the following inversion lemma, which asserts that, given a value v, the property seval s v Q implies Q s v.Exercise: 2 stars, standard, especially useful (seval_terminates)
Prove that seval captures termination.
Lemma seval_terminates : ∀ s t Q,
seval s t Q →
terminates s t.
Proof using. (* FILL IN HERE *) Admitted.
☐
seval s t Q →
terminates s t.
Proof using. (* FILL IN HERE *) Admitted.
☐
Exercise: 3 stars, standard, especially useful (seval_correct)
Prove that seval captures correctness.
Lemma seval_correct : ∀ s t Q,
seval s t Q →
correct s t Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
seval s t Q →
correct s t Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Lemma seval_sound : ∀ s t Q,
seval s t Q →
terminates s t ∧ safe s t ∧ correct s t Q.
Proof using.
introv M. splits.
{ applys* seval_terminates. }
{ applys* seval_safe. }
{ applys* seval_correct. }
Qed.
seval s t Q →
terminates s t ∧ safe s t ∧ correct s t Q.
Proof using.
introv M. splits.
{ applys* seval_terminates. }
{ applys* seval_safe. }
{ applys* seval_correct. }
Qed.
We now establish that the omni-big-step evaluation judgment entails the
small-step-based seval judgment. The proof is carried out by induction on
the omni-big-step relation. It relies on a number of auxiliary results
establishing that, for each term construct, the seval judgment admits an
evaluation rule that mimics the omni-big-step evaluation rule. We begin with
the statements of the auxiliary lemmas.
Lemma seval_fun : ∀ s x t1 Q,
Q (val_fun x t1) s →
seval s (trm_fun x t1) Q.
Proof using.
introv M. applys seval_step.
{ do 2 esplit. constructor. }
{ introv R. inverts R. { applys seval_val. applys M. } }
Qed.
Lemma seval_fix : ∀ s f x t1 Q,
Q (val_fix f x t1) s →
seval s (trm_fix f x t1) Q.
Proof using.
introv M. applys seval_step.
{ do 2 esplit. constructor. }
{ introv R. inverts R. { applys seval_val. applys M. } }
Qed.
Lemma seval_app_fun : ∀ s x v1 v2 t1 Q,
v1 = val_fun x t1 →
seval s (subst x v2 t1) Q →
seval s (trm_app v1 v2) Q.
Proof using.
introv E M. applys seval_step.
{ do 2 esplit. applys* step_app_fun. }
{ introv R. invert R; try solve [intros; false]. introv → → → → → R. inverts E. applys M. }
Qed.
Lemma seval_app_fix : ∀ s f x v1 v2 t1 Q,
v1 = val_fix f x t1 →
seval s (subst x v2 (subst f v1 t1)) Q →
seval s (trm_app v1 v2) Q.
Proof using.
introv E M. applys seval_step.
{ do 2 esplit. applys* step_app_fix. }
{ introv R. invert R; try solve [intros; false]. introv → → → → → R. inverts E. applys M. }
Qed.
Q (val_fun x t1) s →
seval s (trm_fun x t1) Q.
Proof using.
introv M. applys seval_step.
{ do 2 esplit. constructor. }
{ introv R. inverts R. { applys seval_val. applys M. } }
Qed.
Lemma seval_fix : ∀ s f x t1 Q,
Q (val_fix f x t1) s →
seval s (trm_fix f x t1) Q.
Proof using.
introv M. applys seval_step.
{ do 2 esplit. constructor. }
{ introv R. inverts R. { applys seval_val. applys M. } }
Qed.
Lemma seval_app_fun : ∀ s x v1 v2 t1 Q,
v1 = val_fun x t1 →
seval s (subst x v2 t1) Q →
seval s (trm_app v1 v2) Q.
Proof using.
introv E M. applys seval_step.
{ do 2 esplit. applys* step_app_fun. }
{ introv R. invert R; try solve [intros; false]. introv → → → → → R. inverts E. applys M. }
Qed.
Lemma seval_app_fix : ∀ s f x v1 v2 t1 Q,
v1 = val_fix f x t1 →
seval s (subst x v2 (subst f v1 t1)) Q →
seval s (trm_app v1 v2) Q.
Proof using.
introv E M. applys seval_step.
{ do 2 esplit. applys* step_app_fix. }
{ introv R. invert R; try solve [intros; false]. introv → → → → → R. inverts E. applys M. }
Qed.
Exercise: 5 stars, standard, especially useful (seval_seq)
Prove the big-step reasoning rule for sequence for seval.
Lemma seval_seq : ∀ s t1 t2 Q1 Q,
seval s t1 Q1 →
(∀ s1 v1, Q1 v1 s1 → seval s1 t2 Q) →
seval s (trm_seq t1 t2) Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
seval s t1 Q1 →
(∀ s1 v1, Q1 v1 s1 → seval s1 t2 Q) →
seval s (trm_seq t1 t2) Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Exercise: 5 stars, standard, optional (seval_let)
The proof of seval_let is the same as that of seval_seq. We also present it as an exercise to avoid giving away the solution.
Lemma seval_let : ∀ s x t1 t2 Q1 Q,
seval s t1 Q1 →
(∀ s1 v1, Q1 v1 s1 → seval s1 (subst x v1 t2) Q) →
seval s (trm_let x t1 t2) Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
seval s t1 Q1 →
(∀ s1 v1, Q1 v1 s1 → seval s1 (subst x v1 t2) Q) →
seval s (trm_let x t1 t2) Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Lemma seval_if : ∀ s b t1 t2 Q,
seval s (if b then t1 else t2) Q →
seval s (trm_if b t1 t2) Q.
Proof using.
introv M. applys seval_step.
{ do 2 esplit. constructors*. }
{ introv R. inverts R; tryfalse. { applys M. } }
Qed.
seval s (if b then t1 else t2) Q →
seval s (trm_if b t1 t2) Q.
Proof using.
introv M. applys seval_step.
{ do 2 esplit. constructors*. }
{ introv R. inverts R; tryfalse. { applys M. } }
Qed.
We are now ready to prove by induction that the omni-big-step judgment
entails the small-step-based judgment seval. Note that the reciprocal
entailment also holds, however it is not needed for establishing soundness.
Lemma seval_of_eval : ∀ s t Q,
eval s t Q →
seval s t Q.
Proof using.
introv M. induction M.
{ applys* seval_val. }
{ applys* seval_fun. }
{ applys* seval_fix. }
{ applys* seval_app_fun. }
{ applys* seval_app_fix. }
{ applys* seval_seq. }
{ applys* seval_let. }
{ applys* seval_if. }
{ applys seval_step.
{ do 2 esplit. applys* step_add. }
{ introv K. inverts K. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_div. }
{ introv K. inverts K. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_rand 0. math. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ forwards¬(p&D&N): (exists_fresh null s).
do 2 esplit. applys* step_ref. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_get. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_set. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_free. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
Qed.
eval s t Q →
seval s t Q.
Proof using.
introv M. induction M.
{ applys* seval_val. }
{ applys* seval_fun. }
{ applys* seval_fix. }
{ applys* seval_app_fun. }
{ applys* seval_app_fix. }
{ applys* seval_seq. }
{ applys* seval_let. }
{ applys* seval_if. }
{ applys seval_step.
{ do 2 esplit. applys* step_add. }
{ introv K. inverts K. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_div. }
{ introv K. inverts K. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_rand 0. math. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ forwards¬(p&D&N): (exists_fresh null s).
do 2 esplit. applys* step_ref. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_get. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_set. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
{ applys seval_step.
{ do 2 esplit. applys* step_free. }
{ introv K. inverts K; tryfalse. applys* seval_val. } }
Qed.
Putting it all together, we conclude on the soundness of Separation Logic
with respect to the small-step semantics defined by the relation step.
Lemma soundness_small_step : ∀ t H Q,
triple t H Q →
∀ s, H s → terminates s t ∧ safe s t ∧ correct s t Q.
Proof using.
unfold triple. introv M Hs. specializes M Hs.
lets R: seval_of_eval M. applys seval_sound R.
Qed.
triple t H Q →
∀ s, H s → terminates s t ∧ safe s t ∧ correct s t Q.
Proof using.
unfold triple. introv M Hs. specializes M Hs.
lets R: seval_of_eval M. applys seval_sound R.
Qed.
Small-Step Based Triples
The first step is to check that this new definition of triple is sound. This
property has been essentially been established in the previous section via
the lemma seval_sound.
Lemma striple_sound : ∀ t H Q,
striple t H Q →
∀ s, H s → terminates s t ∧ safe s t ∧ correct s t Q.
Proof using. introv M Hs. specializes M Hs. applys seval_sound M. Qed.
striple t H Q →
∀ s, H s → terminates s t ∧ safe s t ∧ correct s t Q.
Proof using. introv M Hs. specializes M Hs. applys seval_sound M. Qed.
The second step is to establish reasoning rules for striple. Let us begin
with the consequence rule and the frame rule. For those, we need to
establish the consequence and the frame property directly for seval. The
proofs differ from the corresponding proofs on eval.
The consequence rule for seval is established by induction.
Lemma seval_conseq : ∀ s t Q Q',
seval s t Q' →
Q' ===> Q →
seval s t Q.
Proof using.
introv M WQ. induction M.
{ applys seval_val. applys* WQ. }
{ rename H1 into IH.
applys seval_step.
{ auto. }
{ introv HR. applys* IH. } }
Qed.
seval s t Q' →
Q' ===> Q →
seval s t Q.
Proof using.
introv M WQ. induction M.
{ applys seval_val. applys* WQ. }
{ rename H1 into IH.
applys seval_step.
{ auto. }
{ introv HR. applys* IH. } }
Qed.
The consequence rule follows directly.
Lemma striple_conseq : ∀ t H' Q' H Q,
striple t H' Q' →
H ==> H' →
Q' ===> Q →
striple t H Q.
Proof using.
introv M MH MQ HF. applys seval_conseq M MQ. applys* MH.
Qed.
striple t H' Q' →
H ==> H' →
Q' ===> Q →
striple t H Q.
Proof using.
introv M MH MQ HF. applys seval_conseq M MQ. applys* MH.
Qed.
The frame property for seval is also established by induction, however the
proof is more involved.
Section stepsFrame.
Hint Constructors step.
Lemma seval_frame : ∀ h1 h2 t Q,
seval h1 t Q →
Fmap.disjoint h1 h2 →
seval (h1 \u h2) t (Q \*+ (= h2)).
Proof using.
introv M HD. gen h2. induction M; intros.
{ applys seval_val. applys* hstar_intro. }
{ rename H into M1, H0 into M2, H1 into IH2.
applys seval_step.
{ unfolds reducible. clear M2 IH2. destruct M1 as (s'&t'&R).
induction R; tryfalse; try solve [ do 2 esplit; constructors* ].
{ forwards* (s'&t'&R'): IHR. }
{ forwards* (s'&t'&R'): IHR. }
{ lets (p'&F&_): exists_fresh null (Fmap.union s h2). do 2 esplit.
applys step_ref v p'. eauto. }
{ do 2 esplit. applys step_get. applys* Fmap.indom_union_l. }
{ do 2 esplit. applys step_set. applys* Fmap.indom_union_l. }
{ do 2 esplit. applys step_free. applys* Fmap.indom_union_l. } }
{ introv R. cuts (s1'&E'&D'&R'):
(∃ s1', s' = s1' \u h2 ∧ Fmap.disjoint s1' h2 ∧ step s t s1' t').
{ subst. applys* IH2. }
clear M2 IH2.
gen_eq su: (s \u h2). gen s.
unfolds reducible. induction R; intros; subst; eauto.
{ destruct M1 as (s0&t0&R0).
rename R into R1. forwards* (s1'&E&D&R1'): IHR s.
{ inverts R0. { eauto. } { inverts R1. } } }
{ destruct M1 as (s0&t0&R0).
rename R into R1. forwards* (s1'&E&D&R1'): IHR s.
{ inverts R0. { eauto. } { inverts R1. } } }
{ rename H into D. rewrite Fmap.indom_union_eq in D. rew_logic in D.
destruct D as (D1&D2). esplit. splits.
{ rewrite* Fmap.update_union_not_r. }
{ applys* Fmap.disjoint_update_not_r. }
{ eauto. } }
{ destruct M1 as (se&te&Re). inverts Re; tryfalse.
rewrite* Fmap.read_union_l. }
{ destruct M1 as (se&te&Re). inverts Re; tryfalse. esplit. splits.
{ rewrite* Fmap.update_union_l. }
{ applys* Fmap.disjoint_update_l. }
{ eauto. } }
{ destruct M1 as (se&te&Re). inverts Re; tryfalse. esplit. splits.
{ rewrite* remove_disjoint_union_l. }
{ applys* Fmap.disjoint_remove_l. }
{ eauto. } } } }
Qed.
End stepsFrame.
Hint Constructors step.
Lemma seval_frame : ∀ h1 h2 t Q,
seval h1 t Q →
Fmap.disjoint h1 h2 →
seval (h1 \u h2) t (Q \*+ (= h2)).
Proof using.
introv M HD. gen h2. induction M; intros.
{ applys seval_val. applys* hstar_intro. }
{ rename H into M1, H0 into M2, H1 into IH2.
applys seval_step.
{ unfolds reducible. clear M2 IH2. destruct M1 as (s'&t'&R).
induction R; tryfalse; try solve [ do 2 esplit; constructors* ].
{ forwards* (s'&t'&R'): IHR. }
{ forwards* (s'&t'&R'): IHR. }
{ lets (p'&F&_): exists_fresh null (Fmap.union s h2). do 2 esplit.
applys step_ref v p'. eauto. }
{ do 2 esplit. applys step_get. applys* Fmap.indom_union_l. }
{ do 2 esplit. applys step_set. applys* Fmap.indom_union_l. }
{ do 2 esplit. applys step_free. applys* Fmap.indom_union_l. } }
{ introv R. cuts (s1'&E'&D'&R'):
(∃ s1', s' = s1' \u h2 ∧ Fmap.disjoint s1' h2 ∧ step s t s1' t').
{ subst. applys* IH2. }
clear M2 IH2.
gen_eq su: (s \u h2). gen s.
unfolds reducible. induction R; intros; subst; eauto.
{ destruct M1 as (s0&t0&R0).
rename R into R1. forwards* (s1'&E&D&R1'): IHR s.
{ inverts R0. { eauto. } { inverts R1. } } }
{ destruct M1 as (s0&t0&R0).
rename R into R1. forwards* (s1'&E&D&R1'): IHR s.
{ inverts R0. { eauto. } { inverts R1. } } }
{ rename H into D. rewrite Fmap.indom_union_eq in D. rew_logic in D.
destruct D as (D1&D2). esplit. splits.
{ rewrite* Fmap.update_union_not_r. }
{ applys* Fmap.disjoint_update_not_r. }
{ eauto. } }
{ destruct M1 as (se&te&Re). inverts Re; tryfalse.
rewrite* Fmap.read_union_l. }
{ destruct M1 as (se&te&Re). inverts Re; tryfalse. esplit. splits.
{ rewrite* Fmap.update_union_l. }
{ applys* Fmap.disjoint_update_l. }
{ eauto. } }
{ destruct M1 as (se&te&Re). inverts Re; tryfalse. esplit. splits.
{ rewrite* remove_disjoint_union_l. }
{ applys* Fmap.disjoint_remove_l. }
{ eauto. } } } }
Qed.
End stepsFrame.
The frame rule for striple then follows from the frame and the consequence
properties for seval.
Lemma striple_frame : ∀ t H Q H',
striple t H Q →
striple t (H \* H') (Q \*+ H').
Proof.
introv M. intros h HF. lets (h1&h2&M1&M2&MD&MU): hstar_inv (rm HF).
subst. specializes M M1. applys seval_conseq.
{ applys seval_frame M MD. } { xsimpl. intros h' →. applys M2. }
Qed.
striple t H Q →
striple t (H \* H') (Q \*+ H').
Proof.
introv M. intros h HF. lets (h1&h2&M1&M2&MD&MU): hstar_inv (rm HF).
subst. specializes M M1. applys seval_conseq.
{ applys seval_frame M MD. } { xsimpl. intros h' →. applys M2. }
Qed.
To establish other reasoning rules for striple, one need to prove
big-step-style evaluation rules for seval, such as the lemma seval_let
stated earlier.
In summary, it is possible to set up a Separation Logic directly based on
the small-step-based judgment seval, without going through a definition of
the omni-big-step judgment eval. However, most of the proof effort
remains, as one needs to establish the frame property and to derive
big-step-style evaluation rules such as seval_let.
Consider a programming language that is deterministic, with the only
exception of the allocation operation, which may pick any fresh location.
For such languages, it is possible to define Separation Logic triples in
terms of the standard big-step semantics, using a technique known as the
"baked-in frame rule". Recall the big-step judgment.
First, we define a (total correctness) Hoare triple, written hoare t H Q.
This judgment asserts that, starting from a state s satisfying the
precondition H, the term t evaluates to a value v and to a state s'
that, together, satisfy the postcondition Q. Formally:
Definition hoare (t:trm) (H:hprop) (Q:val→hprop) : Prop :=
∀ (s:state), H s →
∃ (s':state) (v:val), eval s t s' v ∧ Q v s'.
∀ (s:state), H s →
∃ (s':state) (v:val), eval s t s' v ∧ Q v s'.
Exercise: 2 stars, standard, especially useful (hoare_conseq)
To gain familiarity with the hoare judgment, prove the consequence rule for Hoare triples.
Lemma hoare_conseq : ∀ t H Q H' Q',
hoare t H' Q' →
H ==> H' →
Q' ===> Q →
hoare t H Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
hoare t H' Q' →
H ==> H' →
Q' ===> Q →
hoare t H Q.
Proof using. (* FILL IN HERE *) Admitted.
☐
Definition btriple (t:trm) (H:hprop) (Q:val→hprop) : Prop :=
∀ (H':hprop), hoare t (H \* H') (Q \*+ H').
∀ (H':hprop), hoare t (H \* H') (Q \*+ H').
This definition inherently satisfies the frame rule, as we show below. The
proof essentially exploits the associativity of the star operator.
Exercise: 3 stars, standard, especially useful (btriple_frame)
Prove that btriple t H Q is a judgment satisfying the frame rule.
Lemma btriple_frame : ∀ t H Q H',
btriple t H Q →
btriple t (H \* H') (Q \*+ H').
Proof using. (* FILL IN HERE *) Admitted.
☐
btriple t H Q →
btriple t (H \* H') (Q \*+ H').
Proof using. (* FILL IN HERE *) Admitted.
☐
Definition btriple_lowlevel (t:trm) (H:hprop) (Q:val→hprop) : Prop :=
∀ h1 h2,
Fmap.disjoint h1 h2 →
H h1 →
∃ h1' v,
Fmap.disjoint h1' h2
∧ eval (h1 \u h2) t (h1' \u h2) v
∧ Q v h1'.
∀ h1 h2,
Fmap.disjoint h1 h2 →
H h1 →
∃ h1' v,
Fmap.disjoint h1' h2
∧ eval (h1 \u h2) t (h1' \u h2) v
∧ Q v h1'.
Let us establish the equivalence between this alternative definition of
triple and the original one.
Lemma btriple_iff_btriple_lowlevel : ∀ t H Q,
btriple t H Q ↔ btriple_lowlevel t H Q.
Proof using.
unfold triple, btriple_lowlevel, hoare. iff M.
{ introv D P1.
forwards (h'&v&HR&HQ): M (=h2) (h1 \u h2). { applys* hstar_intro. }
destruct HQ as (h1'&h2'&N0&N1&N2&N3). subst.
∃ h1' v. auto. }
{ intros H' h. introv (h1&h2&N1&N2&D&U).
forwards (h1'&v&D'&HR&HQ): M h1 h2; auto. subst.
∃ (h1' \u h2) v. split. { eauto. } { applys* hstar_intro. } }
Qed.
btriple t H Q ↔ btriple_lowlevel t H Q.
Proof using.
unfold triple, btriple_lowlevel, hoare. iff M.
{ introv D P1.
forwards (h'&v&HR&HQ): M (=h2) (h1 \u h2). { applys* hstar_intro. }
destruct HQ as (h1'&h2'&N0&N1&N2&N3). subst.
∃ h1' v. auto. }
{ intros H' h. introv (h1&h2&N1&N2&D&U).
forwards (h1'&v&D'&HR&HQ): M h1 h2; auto. subst.
∃ (h1' \u h2) v. split. { eauto. } { applys* hstar_intro. } }
Qed.
The low-level definition of triple leveraging the baked-in frame rule may be
convenient for formalizing certain extensions of Separation Logic.
Historical Notes
(* 2024-01-03 14:19 *)