exercises.counter
From iris.algebra Require Export auth excl frac numbers.
From iris.base_logic.lib Require Export invariants.
From iris.heap_lang Require Import lang proofmode notation par.
(* ################################ *)
From iris.base_logic.lib Require Export invariants.
From iris.heap_lang Require Import lang proofmode notation par.
(* ################################ *)
(* ================================================================= *)
Implementation
- a constructor, mk_counter, which creates a new counter starting at 0.
- an increment function, incr, which increments the counter and returns the old value of the counter.
- a read function, read, which reads the current value of the counter.
Definition mk_counter : val :=
λ: <>, ref #0.
Definition incr : val :=
rec: "incr" "c" :=
let: "n" := !"c" in
let: "m" := "n" + #1 in
if: CAS "c" "n" "m" then "n" else "incr" "c".
Definition read : val :=
λ: "c", !"c".
(* ================================================================= *)
We are going to keep the fact that our counter is built on a pointer
as an implementation detail. Instead, we will define a predicate
describing when a value is a counter.
Note that #n uses an implicit coercion from nat to Z called
Z.of_nat. So if you have trouble applying lemmas that should work,
it is likely because there is a hidden coercion in the way.
This predicate is, however, not persistent, so our value would not be
shareable across threads. To fix this, we can put the knowledge into
an invariant.
Let N := nroot .@ "counter".
Definition is_counter2 (v : val) (n : nat) : iProp Σ :=
∃ l : loc, ⌜v = #l⌝ ∗ inv N (l ↦ #n).
However, with this definition, we have locked the value of the pointer
to always be n. To fix this, we could abstract the value and instead
only specify its lower bound.
Definition is_counter3 (v : val) (n : nat) : iProp Σ :=
∃ l : loc, ⌜v = #l⌝ ∗ inv N (∃ m : nat, l ↦ #m ∗ ⌜n ≤ m⌝).
We can now change what the pointer maps to, but we still cannot refine
the lower bound.
The final step is to use ghost state. The idea is to link n and m
to pieces of ghost state in such a way that the validity of their
composite is n ≤ m.
To achieve this, we will use the authoritative resource algebra,
auth. This resource algebra is parametrised by a CMRA, A. There
are two types of elements in the carrier of the authoritative RA:
The idea of the authoritative RA is as follows. The authoritative
element represents the whole of the resource, while the fragments act
as the pieces. To achieve this, the authoritative element acts like
the exclusive RA, while the fragment inherits all the operations of
A. Furthermore, validity of ● x ⋅ ◯ y is defined as ✓ x ∧ y ≼ x.
In our case, we will use the authoritative RA over the max_nat
resource algebra. The carrier of max_nat is the natural numbers, and
the operation is the maximum.
- ● x called an authoritative element
- ◯ y called a fragment
As such, ● (MaxNat m) will represent the true value of the counter
m, and ◯ (MaxNat n) will represent a fragment of the counter – a
lower bound n.
Definition is_counter (v : val) (γ : gname) (n : nat) : iProp Σ :=
∃ l : loc, ⌜v = #l⌝ ∗ own γ (◯ MaxNat n) ∗
inv N (∃ m : nat, l ↦ #m ∗ own γ (● MaxNat m)).
Global Instance is_counter_persistent v γ n :
Persistent (is_counter v γ n) := _.
(* ================================================================= *)
Properties of the Authoritative RA with MaxNat
Ownership of multiple fragments of state amounts to ownership of
their composite. So we can simplify the goal a little.
setoid_rewrite <- own_op.
Now the goal is on the form expected by own_alloc.
apply own_alloc.
However, we are only allowed to allocate valid states. So we must
prove that our desired state is a valid one.
The validity of auth says that the fragment must be included in
the authoritative element and the authoritative element must be
valid.
apply auth_both_valid_discrete.
split.
-
split.
-
Inclusion for max_nat turns out to be the natural ordering.
apply max_nat_included; simpl.
reflexivity.
-
reflexivity.
-
All elements of max_nat are valid.
cbv.
done.
Qed.
Lemma state_valid γ n m :
own γ (● MaxNat n) -∗
own γ (◯ MaxNat m) -∗
⌜m ≤ n⌝.
Proof.
iIntros "Hγ Hγ'".
iPoseProof (own_valid_2 with "Hγ Hγ'") as "%H".
iPureIntro.
apply auth_both_valid_discrete in H.
destruct H as [H _].
apply max_nat_included in H; cbn in H.
done.
Qed.
Lemma update_state γ n :
own γ (● MaxNat n) ==∗
own γ (● MaxNat (S n)) ∗ own γ (◯ MaxNat (S n)).
Proof.
iIntros "H".
rewrite <- own_op.
done.
Qed.
Lemma state_valid γ n m :
own γ (● MaxNat n) -∗
own γ (◯ MaxNat m) -∗
⌜m ≤ n⌝.
Proof.
iIntros "Hγ Hγ'".
iPoseProof (own_valid_2 with "Hγ Hγ'") as "%H".
iPureIntro.
apply auth_both_valid_discrete in H.
destruct H as [H _].
apply max_nat_included in H; cbn in H.
done.
Qed.
Lemma update_state γ n :
own γ (● MaxNat n) ==∗
own γ (● MaxNat (S n)) ∗ own γ (◯ MaxNat (S n)).
Proof.
iIntros "H".
rewrite <- own_op.
As we saw in the Resource Algebra chapter, to update a resource, we
must show that we can perform a frame preserving update to the
updated resource.
iApply (own_update with "H").
auth has its own special version of these called local updates,
as we know what the whole of the state is.
apply auth_update_alloc.
apply max_nat_local_update; cbn.
by apply le_S.
Qed.
(* ================================================================= *)
apply max_nat_local_update; cbn.
by apply le_S.
Qed.
(* ================================================================= *)
Proving the Counter Specification
Lemma mk_counter_spec :
{{{ True }}} mk_counter #() {{{ c γ, RET c; is_counter c γ 0}}}.
Proof.
(* exercise *)
Admitted.
Lemma read_spec c γ n :
{{{ is_counter c γ n }}} read c {{{ (u : nat), RET #u; ⌜n ≤ u⌝ }}}.
Proof.
(* exercise *)
Admitted.
Lemma incr_spec c γ n :
{{{ is_counter c γ n }}}
incr c
{{{ (u : nat), RET #u; ⌜n ≤ u⌝ ∗ is_counter c γ (S n) }}}.
Proof.
iIntros "%Φ (%l & -> & #Hγ' & #HI) HΦ".
iLöb as "IH".
wp_rec.
wp_bind (! _)%E.
iInv "HI" as "(%m & Hl & Hγ)".
wp_load.
iModIntro.
iSplitL "Hl Hγ".
{ iExists m. iFrame. }
wp_pures.
wp_bind (CmpXchg _ _ _).
iInv "HI" as "(%m' & Hl & Hγ)".
destruct (decide (#m = #m')) as [e | ne].
- wp_cmpxchg_suc.
injection e as e.
apply (inj Z.of_nat) in e.
subst m'.
(* exercise *)
Admitted.
(* ================================================================= *)
A Simple Counter Client
Context `{!spawnG Σ}.
Lemma par_incr :
{{{ True }}}
let: "c" := mk_counter #() in
(incr "c" ||| incr "c");;
read "c"
{{{ n, RET #(S n); True }}}.
Proof.
(* exercise *)
Admitted.
End spec1.
End spec1.
(* ================================================================= *)
Our first specification only allowed us to find a lower bound for the
value in par_incr. Any solution to this problem has to be
non-persistent, as we need to aggregate the knowledge to conclude the
total value.
To this end, we will use fractions. The frac resource algebra is
similar to dfrac but without the capability of discarding fractions.
As such, frac consists of non-negative rationals with addition as
composition, and a fraction is only valid if it is less than or equal
to 1. This means that all the fractions can add up to at most 1.
Combining frac with other resource algebras allows us to keep track
of how much of the resource we own, meaning we can do the exact kind
of aggregation we need. So this time, we will use the resource algebra
authR (optionUR (prodR fracR natR)). Here, natR is the natural
numbers with addition.
Context `{!heapGS Σ, !inG Σ (authR (optionUR (prodR fracR natR)))}.
Let N := nroot .@ "counter".
Definition is_counter (v : val) (γ : gname) (n : nat) (q : Qp) : iProp Σ :=
∃ l : loc, ⌜v = #l⌝ ∗ own γ (◯ Some (q, n)) ∗
inv N (∃ m : nat, l ↦ #m ∗ own γ (● Some (1%Qp, m))).
With this definition, we can now decompose access to the counter by
splitting the fraction, as well as splitting the knowledge of how much
the counter has been incremented.
Lemma is_counter_add (c : val) (γ : gname) (n m : nat) (p q : Qp) :
is_counter c γ (n + m) (p + q) ⊣⊢ is_counter c γ n p ∗ is_counter c γ m q.
Proof.
iSplit.
- iIntros "(%l & -> & [Hγ1 Hγ2] & #I)".
iSplitL "Hγ1".
+ iExists l.
iSplitR; first done.
by iFrame.
+ iExists l.
iSplitR; first done.
by iFrame.
- iIntros "[(%l & -> & Hγ1 & I) (%l' & %H & Hγ2 & _)]".
injection H as <-.
iExists l.
iSplitR; first done.
iCombine "Hγ1 Hγ2" as "Hγ".
iFrame.
Qed.
is_counter c γ (n + m) (p + q) ⊣⊢ is_counter c γ n p ∗ is_counter c γ m q.
Proof.
iSplit.
- iIntros "(%l & -> & [Hγ1 Hγ2] & #I)".
iSplitL "Hγ1".
+ iExists l.
iSplitR; first done.
by iFrame.
+ iExists l.
iSplitR; first done.
by iFrame.
- iIntros "[(%l & -> & Hγ1 & I) (%l' & %H & Hγ2 & _)]".
injection H as <-.
iExists l.
iSplitR; first done.
iCombine "Hγ1 Hγ2" as "Hγ".
iFrame.
Qed.
When allocating a new state, there will be one fragment, which
contains the entire fraction. Using the above lemma, we can then split
up the fragment and supply these fragments to participating threads.
Lemma alloc_initial_state :
⊢ |==> ∃ γ, own γ (● Some (1%Qp, 0)) ∗ own γ (◯ Some (1%Qp, 0)).
Proof.
setoid_rewrite <-own_op.
iApply own_alloc.
apply auth_both_valid_discrete.
split.
- reflexivity.
- apply Some_valid.
apply pair_valid.
split.
+ apply frac_valid.
reflexivity.
+ cbv.
done.
Qed.
⊢ |==> ∃ γ, own γ (● Some (1%Qp, 0)) ∗ own γ (◯ Some (1%Qp, 0)).
Proof.
setoid_rewrite <-own_op.
iApply own_alloc.
apply auth_both_valid_discrete.
split.
- reflexivity.
- apply Some_valid.
apply pair_valid.
split.
+ apply frac_valid.
reflexivity.
+ cbv.
done.
Qed.
The fragments of natural numbers add up to the full value, meaning
that, in particular, they must all be less than or equal to the
authoritative element.
Lemma state_valid γ (n m : nat) (q : Qp) :
own γ (● Some (1%Qp, n)) -∗
own γ (◯ Some (q, m)) -∗
⌜m ≤ n⌝.
Proof.
iIntros "Hγ Hγ'".
iPoseProof (own_valid_2 with "Hγ Hγ'") as "%H".
iPureIntro.
apply auth_both_valid_discrete in H.
destruct H as [H _].
apply Some_pair_included in H.
destruct H as [_ H].
rewrite Some_included_total in H.
apply nat_included in H.
done.
Qed.
own γ (● Some (1%Qp, n)) -∗
own γ (◯ Some (q, m)) -∗
⌜m ≤ n⌝.
Proof.
iIntros "Hγ Hγ'".
iPoseProof (own_valid_2 with "Hγ Hγ'") as "%H".
iPureIntro.
apply auth_both_valid_discrete in H.
destruct H as [H _].
apply Some_pair_included in H.
destruct H as [_ H].
rewrite Some_included_total in H.
apply nat_included in H.
done.
Qed.
However, when a fragment has the entire fraction, there can't be any
other fragments – intuitively, we have collected all contributions
from all threads. So the count stored in the fragment must be equal to
the one in the authoritative element.
Lemma state_valid_full γ (n m : nat) :
own γ (● Some (1%Qp, n)) -∗
own γ (◯ Some (1%Qp, m)) -∗
⌜m = n⌝.
Proof.
iIntros "Hγ Hγ'".
iPoseProof (own_valid_2 with "Hγ Hγ'") as "%H".
iPureIntro.
apply auth_both_valid_discrete in H.
destruct H as [H _].
apply Some_included_exclusive in H.
- destruct H as [_ H]; cbn in H.
apply leibniz_equiv in H.
done.
- apply _.
- done.
Qed.
own γ (● Some (1%Qp, n)) -∗
own γ (◯ Some (1%Qp, m)) -∗
⌜m = n⌝.
Proof.
iIntros "Hγ Hγ'".
iPoseProof (own_valid_2 with "Hγ Hγ'") as "%H".
iPureIntro.
apply auth_both_valid_discrete in H.
destruct H as [H _].
apply Some_included_exclusive in H.
- destruct H as [_ H]; cbn in H.
apply leibniz_equiv in H.
done.
- apply _.
- done.
Qed.
Finally, when we have both the authoritative element and a fragment,
we can increment both.
Lemma update_state γ n m (q : Qp) :
own γ (● Some (1%Qp, n)) ∗ own γ (◯ Some (q, m)) ==∗
own γ (● Some (1%Qp, S n)) ∗ own γ (◯ Some (q, S m)).
Proof.
iIntros "H".
rewrite -!own_op.
iApply (own_update with "H").
apply auth_update.
apply option_local_update.
apply prod_local_update_2.
apply (op_local_update_discrete _ _ 1).
done.
Qed.
own γ (● Some (1%Qp, n)) ∗ own γ (◯ Some (q, m)) ==∗
own γ (● Some (1%Qp, S n)) ∗ own γ (◯ Some (q, S m)).
Proof.
iIntros "H".
rewrite -!own_op.
iApply (own_update with "H").
apply auth_update.
apply option_local_update.
apply prod_local_update_2.
apply (op_local_update_discrete _ _ 1).
done.
Qed.
Let us prove the specifications for the counter functions again. This
time, however, we will have two specifications for read – one with
an arbitrary fraction and one with the whole fraction.
Lemma mk_counter_spec :
{{{ True }}} mk_counter #() {{{ c γ, RET c; is_counter c γ 0 1}}}.
Proof.
(* exercise *)
Admitted.
Lemma read_spec (c : val) (γ : gname) (n : nat) (q : Qp) :
{{{ is_counter c γ n q }}}
read c
{{{ (u : nat), RET #u; is_counter c γ n q ∗ ⌜n ≤ u⌝ }}}.
Proof.
(* exercise *)
Admitted.
Lemma read_spec_full (c : val) (γ : gname) (n : nat) :
{{{ is_counter c γ n 1 }}} read c {{{ RET #n; is_counter c γ n 1 }}}.
Proof.
(* exercise *)
Admitted.
Lemma incr_spec (c : val) (γ : gname) (n : nat) (q : Qp) :
{{{ is_counter c γ n q }}}
incr c
{{{ (u : nat), RET #u; ⌜n ≤ u⌝ ∗ is_counter c γ (S n) q }}}.
Proof.
iIntros "%Φ (%l & -> & Hγ' & #I) HΦ".
iLöb as "IH".
wp_rec.
wp_bind (! _)%E.
iInv "I" as "(%m & Hl & Hγ)".
wp_load.
iModIntro.
iSplitL "Hl Hγ".
{ iExists m. iFrame. }
wp_pures.
wp_bind (CmpXchg _ _ _).
iInv "I" as "(%m' & Hl & Hγ)".
destruct (decide (# m = # m')).
- injection e as e.
apply (inj Z.of_nat) in e.
subst m'.
wp_cmpxchg_suc.
(* exercise *)
Admitted.
End spec2.
End spec2.