solutions.persistently
From iris.base_logic Require Import iprop.
From iris.proofmode Require Import proofmode.
From iris.heap_lang Require Import lang proofmode notation par.
(* ################################ *)
From iris.proofmode Require Import proofmode.
From iris.heap_lang Require Import lang proofmode notation par.
(* ################################ *)
Section persistently.
Context `{!heapGS Σ}.
(* ================================================================= *)
Introduction
The introduction pattern "#_" allows us to place a persistent
hypothesis into the persistent context.
iIntros "#HP HQ".
iSplitR "HQ".
iSplitR "HQ".
Notice that after splitting, both the non-spatial and persistent
contexts are duplicated. In particular, HP is available in both
subgoals.
- iApply "HP".
- iApply "HQ".
Qed.
- iApply "HQ".
Qed.
Note that all propositions in the spatial context are treated as
non-persistent, regardless of whether they are persistent.
We introduce HP to the spatial context.
iIntros "HP HQ".
iSplitR "HQ".
iSplitR "HQ".
HP is no longer duplicated.
- iApply "HP".
- iApply "HQ".
Qed.
- iApply "HQ".
Qed.
Exercise: prove that persistent propositions are duplicable.
Lemma pers_dup (P : iProp Σ) `{!Persistent P} : P ⊢ P ∗ P.
(* SOLUTION *) Proof.
iIntros "#HP".
iSplit.
- done.
- done.
Qed.
Persistent propositions satisfy a lot of nice properties simply by
being duplicable P ⊢ P ∗ P. For example, P ∧ Q and P ∗ Q
coincide when either P or Q is persistent. Likewise, P → Q and
P -∗ Q coincide when P is persistent.
Check bi.persistent_and_sep.
Check bi.impl_wand.
The Iris Proof Mode knows these facts and allows iSplit to introduce
∗ when one of its arguments is persistent.
(* ================================================================= *)
Proving Persistency
The iModIntro tactic introduces a modality in the goal. In this
case, since the modality is a □, it throws away the spatial
context.
iModIntro.
iApply "HP".
Qed.
iApply "HP".
Qed.
Since the only difference between □ P and P is that the former
does not claim the resources are exclusive, it follows that the
persistently modality is idempotent.
Iris already knows that □ is idempotent, so it automatically
removes all persistently modalities from a proposition when adding
it to the persistent context. One may think of all propositions in
the persistent context as having an implicit □ in front.
iDestruct "HP" as "#HP".
done.
- iIntros "#HP".
done.
- iIntros "#HP".
Similarly, we do not have to introduce □ before proving it.
done.
Qed.
Qed.
Only propositions that are instances of the Persistent typeclass can
be added to the persistent context. As with the typeclasses for pure,
the Persistent typeclass can automatically identify most persistent
propositions.
The Persistent typeclass detects that □ P ∗ □ Q is persistent.
iIntros "#HPQ".
iDestruct "HPQ" as "[#HP #HQ]".
iModIntro.
iDestruct "HPQ" as "[#HP #HQ]".
iModIntro.
By default, iFrame will not frame propositions from the
persistent context. To make it do so, we must give it the argument
"#".
iFrame "#".
(* BEGIN SOLUTION *)
- iIntros "#[HP HQ]".
iFrame "#".
Qed.
(* END SOLUTION BEGIN TEMPLATE
- (* exercise *)
Admitted.
END TEMPLATE *)
(* BEGIN SOLUTION *)
- iIntros "#[HP HQ]".
iFrame "#".
Qed.
(* END SOLUTION BEGIN TEMPLATE
- (* exercise *)
Admitted.
END TEMPLATE *)
Persistency is preserved by quantifications.
Lemma pers_all `{A} (P : A → iProp Σ) : (∀x, □ P x) ⊢ ∀y, P y ∗ P y.
Proof.
iIntros "#Hp %y".
iSplitL; iApply ("Hp" $! y).
Qed.
For simple predicates, such as myPredicate below, the Persistent
typeclass can automatically infer that it is persistent. However, we
can still manually make propositions instances of Persistent.
Definition myPredicate (x : val) : iProp Σ := ⌜x = #5⌝.
Local Instance myPredicate_persistent x : Persistent (myPredicate x).
Proof.
As mentioned in the beginning of the chapter, a proposition P is
persistent when P ⊢ □ P. This is almost what Persistent requires
us to prove.
rewrite /Persistent.
Our actual goal is of the form P ⊢ <pers> P. Technically, there is
a small discrepancy between □ and <pers>, but we will ignore
that here.
As pure propositions are persistent, we quite easily prove this.
Iris is quite smart, so we do not have to spell out the proof.
For more complicated predicates, such as ones defined as a fixpoint,
Persistent cannot automatically infer its persistence. The following
predicate asserts that all values in a given list are equal to 5.
Fixpoint myPredFix (xs : list val) : iProp Σ :=
match xs with
| [] => True
| x :: xs' => ⌜x = #5⌝ ∗ myPredFix xs'
end.
This predicate only consists of pure propositions, so it should of
course be persistent, but when we try to add it to the persistent
context, Iris complains that it is not `intuitionistic' (i.e.
persistent).
Lemma first_is_5 (x : val) (xs : list val) :
myPredFix (x :: xs) -∗ ⌜x = #5⌝ ∗ myPredFix (x :: xs).
Proof.
Fail iIntros "#H".
Abort.
myPredFix (x :: xs) -∗ ⌜x = #5⌝ ∗ myPredFix (x :: xs).
Proof.
Fail iIntros "#H".
Abort.
For such predicates, we have to prove that it is persistent manually,
as we did for myPredicate above.
We prove it by induction in xs.
induction xs as [| x xs' IH ].
-
-
True is persistent
apply _.
-
-
By IH, myPredFix xs' is persistent. As ⌜x = 5⌝ is also persistent,
it follows that [myPredFix (x :: xs')] is persistent.
apply _.
Qed.
Qed.
Iris now recognises myPredFix as persistent.
Lemma first_is_5 (x : val) (xs : list val) :
myPredFix (x :: xs) -∗ ⌜x = #5⌝ ∗ myPredFix (x :: xs).
Proof.
iIntros "#H".
iPoseProof is similar to iDestruct, but it does not throw away
the hypothesis being destructed, if it is persistent.
iPoseProof "H" as "[Hx _]".
iFrame "H Hx".
Qed.
(* ================================================================= *)
iFrame "H Hx".
Qed.
(* ================================================================= *)
Examples of Persistent Propositions
(* ----------------------------------------------------------------- *)
Hoare Triples
If inc is a function which adds 1 to the contents of a given
location, we would expect the above program to return 2. We express
this by describing the specification for inc in the precondition of
the specification for counter.
Lemma counter_spec (inc : val) :
{{{
∀ (l : loc) (z : Z),
{{{ l ↦ #z }}} inc #l {{{ v, RET v; l ↦ #(z + 1) }}}
}}}
counter inc
{{{ v, RET v; ⌜v = #2⌝ }}}.
(* SOLUTION *) Proof.
iIntros (Φ) "#Hinc_spec HΦ".
rewrite /counter.
wp_alloc l as "Hl".
wp_let.
wp_apply ("Hinc_spec" with "Hl").
iIntros (v) "Hl".
wp_seq.
wp_apply ("Hinc_spec" with "Hl").
iIntros (v') "Hl".
wp_seq.
wp_load.
by iApply "HΦ".
Qed.
(* ----------------------------------------------------------------- *)
The resource of heaps is more sophisticated than what we have been
letting on. The general shape of a points-to predicate is actually
l ↦ dq v, where dq is a `discarded fraction'. We return to the
`discarded' part momentarily, but for now, we assume that dq is a
fraction in the interval (0; 1]. The predicate l ↦ v is a special
case where the fraction dq is 1. The basic idea is that points-to
predicates can be split up and recombined, allowing ownership of
points-to predicates to be shared.
Lemma pt_split l v : l ↦ v ⊣⊢ (l ↦{# 1/2 } v) ∗ (l ↦{# 1/2 } v).
Proof.
iSplit.
- iIntros "Hl".
iDestruct "Hl" as "[Hl1 Hl2]".
iFrame.
- iIntros "[Hl1 Hl2]".
iCombine "Hl1" "Hl2" as "Hl".
iFrame.
Qed.
Crucially, a store operation can only take place if the entire
fraction is owned, i.e. dq = 1. However, load operations can occur
for any fraction.
Lemma only_read (l : loc) (v : val) :
{{{ l ↦ v }}} #l <- #2 ;; !#l ;; #l <- #3 {{{ w, RET w; l ↦ #3 }}}.
Proof.
iIntros (Φ) "Hl HΦ".
wp_store.
Throw away half of the points-to predicate.
iDestruct "Hl" as "[Hl1 _]".
We can still load.
wp_load.
wp_seq.
wp_seq.
But we can no longer update the pointer.
Fail wp_store.
Abort.
Abort.
Fractional points-to predicates are especially useful in scenarios
where a location is read by multiple threads in parallel but later
only used by a single thread.
Example par_read_write (l : loc) : expr :=
let: "r" := (!#l ||| !#l) in
#l <- #5.
Lemma par_read_write_spec (l : loc) (v : val) :
{{{ l ↦ v }}}
par_read_write l
{{{ RET #(); l ↦ #5 }}}.
Proof.
iIntros (Φ) "[Hl1 Hl2] HΦ".
rewrite /par_read_write.
The idea is to give each thread half of the points-to predicate and
assert that both will return their halves afterwards.
set t_post := (λ w : val, (⌜w = v⌝ ∗ l ↦{#1 / 2} v)%I).
wp_pures.
wp_apply (wp_par t_post t_post with "[Hl1] [Hl2]").
wp_pures.
wp_apply (wp_par t_post t_post with "[Hl1] [Hl2]").
Each thread has a fraction of the points-to predicate, so both can
perform the load.
1, 2: wp_load; by iFrame.
The threads return their fractions.
iIntros (v1 v2) "[[-> Hl1] [-> Hl2]]".
iNext.
wp_let.
iNext.
wp_let.
We combine them, allowing us to perform the store.
iCombine "Hl1 Hl2" as "Hl".
wp_store.
by iApply "HΦ".
Qed.
wp_store.
by iApply "HΦ".
Qed.
Let us now turn to the `discarded' part. If one owns a fraction of a
points-to predicate, one can decide to discard the fraction. This
means that it is no longer possible to recombine points-to predicates
to get the full fraction. As such, the value in the points-to
predicate can never be changed again – the location has become
read-only. We write l ↦□ v for a points-to predicate whose fraction
has been discarded. As the □ suggests, this predicate is persistent.
Persistent points-to predicates are great if your program has a
read-only location, as it becomes trivial to give all threads access
to the associated points-to predicate, and it ensures that no thread
can sneakily update the location.
The pointsto_persist lemma asserts that we can update any points-to
predicate l ↦{dq} v into a persistent one l ↦□ v.
Check pointsto_persist.
There are some caveats as to when we can discard fractions; the
proposition P ==∗ Q is equivalent to P -∗ (|==> Q), where |==>
is the update modality, the details of which we defer to later. For
now, we remark that the iMod tactic can remove the update modality
in many cases. For instance, if the goal is a weakest precondition.
Lemma pt_persist (l : loc) (v : val) :
l ↦ v -∗ WP !#l {{ w , ⌜w = v⌝ ∗ l ↦□ v }}.
Proof.
iIntros "Hl".
Make the points-to predicate persistent.
iMod (pointsto_persist with "Hl") as "#Hl".
wp_load.
by iFrame "#".
Qed.
wp_load.
by iFrame "#".
Qed.
Exercise: finish the proof of the par_read_spec specification.
Example par_read : expr :=
let: "l" := ref #7 in
let: "r" := ( (!"l" + #14) ||| (!"l" * #3) ) in
Fst "r" + Snd "r".
Lemma par_read_spec :
{{{ True }}} par_read {{{ v, RET v; ⌜v = #42⌝ }}}.
Proof.
iIntros (Φ _) "HΦ".
rewrite /par_read.
Both threads have the same postcondition, t_post.
set t_post := (λ v, (⌜v = #21⌝)%I : iProp Σ).
(* BEGIN SOLUTION *)
wp_alloc l as "Hl".
iMod (pointsto_persist with "Hl") as "#Hl".
wp_pures.
wp_apply (wp_par t_post t_post).
1, 2: wp_load; wp_pures; done.
iIntros (v1 v2) "[-> ->]".
iNext.
wp_pures.
by iApply "HΦ".
Qed.
(* END SOLUTION BEGIN TEMPLATE
(* exercise *)
Admitted.
END TEMPLATE *)
End persistently.
(* BEGIN SOLUTION *)
wp_alloc l as "Hl".
iMod (pointsto_persist with "Hl") as "#Hl".
wp_pures.
wp_apply (wp_par t_post t_post).
1, 2: wp_load; wp_pures; done.
iIntros (v1 v2) "[-> ->]".
iNext.
wp_pures.
by iApply "HΦ".
Qed.
(* END SOLUTION BEGIN TEMPLATE
(* exercise *)
Admitted.
END TEMPLATE *)
End persistently.