diff --git a/Makefile.coq.local b/Makefile.coq.local index 6673f6b65b31d869f02e6bb0247334e98ffba901..7ab058805d104843c953b103da089146595463a4 100644 --- a/Makefile.coq.local +++ b/Makefile.coq.local @@ -26,5 +26,8 @@ hocap: $(filter theories/hocap/%,$(VOFILES)) logatom: $(filter theories/logatom/%,$(VOFILES)) .PHONY: logatom +array_based_queuing_lock: $(filter theories/array_based_queuing_lock/%,$(VOFILES)) +.PHONY: array_based_queuing_lock + proph: $(filter theories/proph/%,$(VOFILES)) .PHONY: proph diff --git a/README.md b/README.md index 9d665eb3c1e424fc3e353e3031f8b3196f291b80..a153625f4f374db7ec0dba858d193414249f0b06 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,11 @@ This repository contains the following case studies: * [hocap](theories/hocap): Formalizations of the concurrent bag and concurrent runners libraries from the [HOCAP paper](https://dl.acm.org/citation.cfm?id=2450283) (by Dan Frumin). See the associated [README](theories/hocap/README.md). +* [array-based_queuing_lock](/theories/array_based_queuing_lock): Proof of + safety of an implementation of the array-based queuing lock. This example is + also covered in the chapter ["Case study: The Array-Based Queueing + Lock"](https://iris-project.org/tutorial-pdfs/iris-lecture-notes.pdf#section.10) + in the Iris lecture notes. ## For Developers: How to update the Iris dependency diff --git a/_CoqProject b/_CoqProject index 895b21feb100bd2486c6322dd5d25dd1c2cb7229..41ae9bcdb5639b8b5ca934282ef21aa7c539798d 100644 --- a/_CoqProject +++ b/_CoqProject @@ -108,3 +108,5 @@ theories/proph/lazy_coin_one_shot_typed.v theories/proph/clairvoyant_coin_spec.v theories/proph/clairvoyant_coin.v theories/proph/clairvoyant_coin_typed.v + +theories/array_based_queuing_lock/abql.v diff --git a/theories/array_based_queuing_lock/abql.v b/theories/array_based_queuing_lock/abql.v new file mode 100644 index 0000000000000000000000000000000000000000..7b75db1876560435017f14b99320add9553b478b --- /dev/null +++ b/theories/array_based_queuing_lock/abql.v @@ -0,0 +1,612 @@ +(** An implementation and safety proof of the Array-Based Queuing Lock (ABQL). + + This data-structure was included in the VerifyThis 2018 program verification + competition [1, 2]. The example is also described in the chapter "Case study: + The Array-Based Queuing Lock" in the Iris lecture notes [2]. + + 1: https://hal.inria.fr/hal-01981937/document + 2: https://ethz.ch/content/dam/ethz/special-interest/infk/chair-program-method/pm/documents/Verify%20This/Solutions%202018/abql.pdf + 3: https://iris-project.org/tutorial-pdfs/iris-lecture-notes.pdf#section.10 + *) + +From iris.program_logic Require Export weakestpre. +From stdpp Require Export list. +From iris.heap_lang Require Export notation lang. +From iris.proofmode Require Export tactics. +From iris.heap_lang Require Import proofmode. +From iris.base_logic.lib Require Export invariants. +From iris.algebra Require Import numbers excl auth gset frac. +From iris_string_ident Require Import ltac2_string_ident. + +Section abql_code. + + (* The ABQL is a variant of a ticket lock which uses an array. + + The lock consists of three things: + - An array of booleans which contains true at a single index and false at + all other indices. + - A natural number, which represents the next available ticket. Tickets are + increasing and the remainder from dividing the ticket with the length of + the array gives an index in the array to which the ticket corresponds. + - The length of the array. The lock can handle at most this many + simultaneous attempts to acquire the lock. We call this the capacity of + the lock. *) + Definition newlock : val := + λ: "cap", let: "array" := AllocN "cap" #false in + ("array" +ₗ #0%nat) <- #true ;; ("array", ref #0, "cap"). + + (* wait_loop is called by acquire with a ticket. As mentioned, the ticket + corresponds to an index in the array. If the array contains true at that + index we may acquire the lock otherwise we spin. *) + Definition wait_loop : val := + rec: "spin" "lock" "ticket" := + let: "array" := Fst (Fst "lock") in (* Get the first element of the triple *) + let: "idx" := "ticket" `rem` (Snd "lock") in (* Snd "Lock" gets the third element of the triple *) + if: ! ("array" +ₗ "idx") then #() else ("spin" "lock" "ticket"). + + (* To acquire the lock we first take the next ticket using FAA. The wait_loop + function then spins until the ticket grants access to the lock. *) + Definition acquire : val := + λ: "lock", let: "next" := Snd (Fst "lock") in + let: "ticket" := FAA "next" #1%nat in + wait_loop "lock" "ticket" ;; + "ticket". + + (* To release the lock, the index in the array corresponding to the current + ticket is set to false and the next index (possibly wrapping around the end + of the array) is set to true. *) + Definition release : val := + λ: "lock" "o", let: "array" := Fst (Fst "lock") in + let: "cap" := Snd "lock" in + "array" +ₗ ("o" `rem` "cap") <- #false ;; + "array" +ₗ (("o" + #1) `rem` "cap") <- #true. + +End abql_code. + +Module spec. + + (* Acquiring the lock consists of taking a ticket and spinning on an entry in + the array. If there are more threads waiting to acquire the lock than there + are entries in the array, several threads will spin on the same entry in + the array, and they may both enter their critical section when this entry + becomes true. Thus, to ensure safety the specification must ensure that + this can't happen. To this end the specification is that of a ticket lock, + but extended with _invitations_. + + Invitations are non-duplicable tokens which gives permission to acquire the + lock. In newlock, when a lock is created with capacity `n` the same amount + of invitations are created. These are tied to the lock through the ghost + name γ. acquire_spec consumes one invitaiton and release_spec hands back + one invitation. Together this ensures that at most `n` threads may + simultaneously attempt to acquire the lock. + + invitation_split allows the client to split and combine invitations. *) + + Structure abql_spec Σ `{!heapG Σ} := abql { + (* Operations *) + newlock : val; + acquire : val; + release : val; + (* Predicates *) + is_lock (γ ι κ : gname) (lock : val) (cap : nat) (R : iProp Σ) : iProp Σ; + locked (γ κ: gname) (o : nat) : iProp Σ; + invitation (ι : gname) (n : nat) (cap : nat) : iProp Σ; + (* General properties of the predicates *) + is_lock_persistent γ ι κ lock cap R : Persistent (is_lock γ ι κ lock cap R); + locked_timeless γ κ o : Timeless (locked γ κ o); + locked_exclusive γ κ o o' : locked γ κ o -∗ locked γ κ o' -∗ False; + invitation_split γ (n m cap : nat) : + invitation γ (n + m) cap ⊣⊢ invitation γ n cap ∗ invitation γ m cap; + (* Program specs *) + newlock_spec (R : iProp Σ) (cap : nat) : + {{{ R ∗ ⌜0 < cap⌝ }}} + newlock (#cap) + {{{ lk γ ι κ, RET lk; (is_lock γ ι κ lk cap R) ∗ invitation ι cap cap }}}; + acquire_spec γ ι κ lk cap R : + {{{ is_lock γ ι κ lk cap R ∗ invitation ι 1 cap}}} + acquire lk + {{{ t, RET #t; locked γ κ t ∗ R }}}; + release_spec γ ι κ lk cap o R : + {{{ is_lock γ ι κ lk cap R ∗ locked γ κ o ∗ R }}} + release lk #o + {{{ RET #(); invitation ι 1 cap }}}; + }. + +End spec. + +Section algebra. + + (* We create a resource algebra used to represent invitations. The RA can be + thought of as "addition with an upper bound". The carrier of the resource + algebra is pairs of natural numbers. The first number represents how many + invitations we have and the second how many invitations exists in total. + + We want (a, n) ⋅ (b, n) to be equal to (a + b, n). We never combine + elements (a, n) ⋅ (b, m) where n ≠ m. Hence what happens it that case is + arbitary, as long the laws for a RA are satisfied. We can not, for instance, + just disregard the second upper bound as that whould violate commutativity, + and using `max` would not satisfy the laws for validity. We could use + agreement, but choose to use `min` instead as it is easier to work with. *) + Record addb : Type := Addb { addb_proj : nat * min_nat }. + + Canonical Structure sumRAC := leibnizO addb. + + Instance addb_op : Op addb := λ a b, Addb (addb_proj a ⋅ addb_proj b). + + (* The definition of validity matches the intuition that if there exists n + invitations in totoal then one can at most have n invitations. *) + Instance addb_valid : Valid addb := + λ a, match a with Addb (x, MinNat n) => x ≤ n end. + + (* Invitations should not be duplicable. *) + Instance addb_pcore : PCore addb := λ _, None. + + (* We need these auxiliary lemmas in the proof below. *) + Lemma sumRA_op_second a b (n : min_nat) : Addb (a, n) ⋅ Addb (b, n) = Addb (a + b, n). + Proof. by rewrite /op /addb_op -pair_op idemp_L. Qed. + + (* If (a, n) is valid ghost state then we can conclude that a ≤ n *) + Lemma sumRA_valid a n : ✓(Addb (a, n)) ↔ a ≤ min_nat_car n. + Proof. destruct n. split; auto. Qed. + + Definition sumRA_mixin : RAMixin addb. + Proof. + split; try apply _; try done. + - intros ???. rewrite /op /addb_op /=. apply f_equal, assoc, _. + - intros ??. rewrite /op /addb_op /=. apply f_equal, comm, _. + - intros [[?[?]]] [[?[?]]]. rewrite 2!sumRA_valid nat_op_plus /=. lia. + Qed. + + Canonical Structure sumRA := discreteR addb sumRA_mixin. + + Global Instance sumRA_cmra_discrete : CmraDiscrete sumRA. + Proof. apply discrete_cmra_discrete. Qed. + + Class sumG Σ := SumG { sum_inG :> inG Σ sumRA }. + Definition sumΣ : gFunctors := #[GFunctor sumRA]. + + Instance subG_sumΣ {Σ} : subG sumΣ Σ → sumG Σ. + Proof. solve_inG. Qed. + +End algebra. + +Section array_model. + + (* A few auxillary lemmas used to handle arrays in relation to the ABQL. *) + + Context `{!heapG Σ}. + + (* is_array relates a location to a list of booleans. We use this since "_ ↦∗ + _" relates a location to a list of values, but we need the extra + information offered here. *) + Definition is_array (array : loc) (xs : list bool) : iProp Σ := + let vs := (fun b => # (LitBool b)) <$> xs + in array ↦∗ vs. + + (* `list_with_one n i` denotes a list with lenght n, a single true at index i, + and false everywhere else. *) + Definition list_with_one (length index : nat) : list bool := + <[index:=true]> (replicate length false). + + Lemma array_store E (i : nat) (v : bool) arr (xs : list bool) : + {{{ ⌜i < length xs⌝ ∗ ▷ is_array arr xs }}} + #(arr +ₗ i) <- #v + @ E {{{ RET #(); is_array arr (<[i:=v]>xs) }}}. + Proof. + iIntros (ϕ) "[% isArr] Post". + unfold is_array. + iApply (wp_store_offset with "isArr"). + { apply lookup_lt_is_Some_2. by rewrite fmap_length. } + rewrite (list_fmap_insert ((λ b : bool, #b) : bool → val) xs i v). + iAssumption. + Qed. + + Lemma insert_list_with_one (i : nat) l : + <[i:=false]> (list_with_one l i) = replicate l false. + Proof. + by rewrite /list_with_one list_insert_insert insert_replicate. + Qed. + + (* The repeat code behaves similar to the replicate function *) + Lemma array_repeat (b : bool) (n : nat) : + {{{ ⌜0 < n⌝ }}} AllocN #n #b {{{ arr, RET #arr; is_array arr (replicate n b) }}}. + Proof. + iIntros (ϕ) "% Post". + apply inj_lt in a. + iApply wp_allocN; try done. + iNext. iIntros (l) "[lPts _]". + iApply "Post". + unfold is_array. + rewrite Nat2Z.id. + rewrite -> fmap_replicate. + iAssumption. + Qed. + +End array_model. + +(** The CMRAs we need. *) +Class alockG Σ := { + tlock_G :> inG Σ (authR (prodUR (optionUR (exclR natO)) (gset_disjUR nat))); + tlock_sumG :> sumG Σ; + tlock_tokenG :> inG Σ ((prodR (optionUR (exclR unitO)) (optionUR (exclR unitO)))); +}. + +Section proof. + + Context `{!heapG Σ, !alockG Σ}. + Let N := nroot .@ "abql". + + (* The ghost state both, left, and right is used to keep track of the state of + the lock: + State of lock: open --> closed --> clopen --> open + Owned by lock: both --> left --> right --> both + Outside lock: none --> right --> left --> none *) + Definition both (κ : gname) : iProp Σ := own κ (Excl' (), Excl' ()). + Definition left (κ : gname) : iProp Σ := own κ (Excl' (), None). + Definition right (κ : gname) : iProp Σ := own κ (None, Excl' ()). + + Definition invitation (ι : gname) (x cap : nat) : iProp Σ := + own ι (Addb (x, MinNat cap))%I. + + Definition issued (γ : gname) (x : nat) : iProp Σ := own γ (◯ (ε, GSet {[ x ]}))%I. + + (* The lock invariant *) + Definition lock_inv (γ ι κ : gname) (arr : loc) (cap : nat) (nextPtr : loc) (R : iProp Σ) : iProp Σ := + (∃ (o i : nat) (xs : list bool), (* o: who may acquire the lock, i: nr of invitations in lock *) + ⌜length xs = cap⌝ ∗ + nextPtr ↦ #(o + i)%nat ∗ + is_array arr xs ∗ + invitation ι i cap ∗ (* The invitations currently owned by the lock. *) + own γ (● (Excl' o, GSet (set_seq o i))) ∗ + ((* Lock is open, R belongs to the lock *) + (own γ (◯ (Excl' o, GSet ∅)) ∗ R ∗ both κ ∗ ⌜xs = list_with_one cap (Nat.modulo o cap)⌝) ∨ + (* Lock is clopen, in the process of being released. *) + (issued γ o ∗ right κ ∗ ⌜xs = replicate cap false⌝) ∨ + (* Lock is closed, i.e. it is locked *) + (issued γ o ∗ left κ ∗ ⌜xs = list_with_one cap (Nat.modulo o cap)⌝)))%I. + + (* The lock predicate *) + (* cap is the length or the capacity of the lock *) + Definition is_lock γ ι κ (lock : val) (cap : nat) P := + (∃ (arr : loc) (nextPtr : loc), + ⌜lock = (#arr, #nextPtr, #cap)%V⌝ ∗ + ⌜0 < cap⌝ ∗ + inv N (lock_inv γ ι κ arr cap nextPtr P))%I. + + Definition locked (γ κ : gname) (o : nat) : iProp Σ := (own γ (◯ (Excl' o, GSet ∅)) ∗ right κ)%I. + + Global Instance is_lock_persistent γ ι κ lk n R : Persistent (is_lock γ ι κ lk n R). + Proof. apply _. Qed. + Global Instance locked_timeless γ κ o : Timeless (locked γ κ o). + Proof. apply _. Qed. + + Lemma locked_exclusive (γ κ : gname) o o' : locked γ κ o -∗ locked γ κ o' -∗ False. + Proof. + iIntros "[H1 _] [H2 _]". + iDestruct (own_valid_2 with "H1 H2") as %[[] _]. + Qed. + + (* Only one thread can know the exact value of `o`. *) + Lemma know_o_exclusive γ o o' : + own γ (◯ (Excl' o, GSet ∅)) -∗ own γ (◯ (Excl' o', GSet ∅)) -∗ False. + Proof. + iIntros "H1 H2". iDestruct (own_valid_2 with "H1 H2") as %[[]]. + Qed. + + (* Lemmas about both, left, and right. *) + Lemma left_left_false (κ : gname) : left κ -∗ left κ -∗ False. + Proof. + iIntros "L L'". iCombine "L L'" as "L". + iDestruct (own_valid with "L") as %[Hv _]. inversion Hv. + Qed. + + Lemma right_right_false (κ : gname) : right κ -∗ right κ -∗ False. + Proof. + iIntros "R R'". iCombine "R R'" as "R". + iDestruct (own_valid with "R") as %[_ Hv]. inversion Hv. + Qed. + + Lemma both_split (κ : gname) : both κ -∗ (left κ ∗ right κ). + Proof. iIntros "Both". iApply own_op. iFrame. Qed. + + Lemma left_right_to_both (κ : gname) : left κ -∗ right κ -∗ both κ. + Proof. iIntros "L R". iCombine "L" "R" as "?". iFrame. Qed. + + Lemma valid_ticket_range (γ : gname) (x o i : nat) : + own γ (◯ (ε, GSet {[ x ]})) -∗ own γ (● (Excl' o, GSet (set_seq o i))) + -∗ ⌜o ≤ x < o + i⌝ ∗ own γ (◯ (ε, GSet {[ x ]})) ∗ own γ (● (Excl' o, GSet (set_seq o i))). + Proof. + iIntros "P O". + iDestruct (own_valid_2 with "O P") as %[[_ xIncl%gset_disj_included]%prod_included Hv]%auth_both_valid. + iFrame. iPureIntro. + apply elem_of_subseteq_singleton, elem_of_set_seq in xIncl. + lia. + Qed. + + Lemma ticket_i_gt_zero (γ : gname) (o i : nat) : + own γ (◯ (ε, GSet {[ o ]})) -∗ own γ (● (Excl' o, GSet (set_seq o i))) + -∗ ⌜0 < i⌝ ∗ own γ (◯ (ε, GSet {[ o ]})) ∗ own γ (● (Excl' o, GSet (set_seq o i))). + Proof. + iIntros "P O". + iDestruct (own_valid_2 with "O P") as %HV%auth_both_valid. + iFrame. iPureIntro. + destruct HV as [[_ H%gset_disj_included]%prod_included _]. + apply elem_of_subseteq_singleton, set_seq_len_pos in H. + lia. + Qed. + + Lemma invitation_cap_bound γ i cap : + invitation γ i cap -∗ ⌜i ≤ cap⌝. + Proof. + iIntros "I". by iDestruct (own_valid with "I") as %H. + Qed. + + Lemma invitation_split γ (n m cap : nat) : + invitation γ (n + m) cap ⊣⊢ invitation γ n cap ∗ invitation γ m cap. + Proof. + iSplit. + - iIntros "I". iDestruct (own_valid with "I") as %Hv. + by rewrite -own_op sumRA_op_second. + - iIntros "[I1 I2]". iCombine "I1 I2" as "I". by rewrite sumRA_op_second. + Qed. + + Lemma invitation_split_one γ (i cap : nat) : + 0 < i → invitation γ i cap -∗ invitation γ (i - 1) cap ∗ invitation γ 1 cap. + Proof. + iIntros (Hl) "I". iDestruct (own_valid with "I") as %Hv. + rewrite -own_op sumRA_op_second Nat.sub_add; auto; lia. + Qed. + + Lemma newlock_spec (R : iProp Σ) (cap : nat) : + {{{ R ∗ ⌜0 < cap⌝ }}} + newlock (#cap) + {{{ lk γ ι κ, RET lk; (is_lock γ ι κ lk cap R) ∗ invitation ι cap cap }}}. + Proof. + iIntros (Φ) "[Pre %] Post". + wp_lam. + wp_apply array_repeat. done. + iIntros (arr) "isArr". + wp_pures. + wp_apply (array_store with "[$isArr]"); first by rewrite replicate_length. + iIntros "isArr". + (* We allocate the ghost states for the tickets and value of o. *) + iMod (own_alloc (● (Excl' 0, GSet ∅) ⋅ ◯ (Excl' 0, GSet ∅))) as (γ) "[Hγ Hγ']". + { by apply auth_both_valid. } + (* We allocate the ghost state for the invitations. *) + iMod (own_alloc (Addb (cap, MinNat cap) ⋅ Addb (0, MinNat cap))) as (ι) "[Hinvites HNoInvites]". + { rewrite sumRA_op_second Nat.add_0_r. apply sumRA_valid. auto. } + (* We allocate the ghost state for the lock state indicatior. *) + iMod (own_alloc ((Excl' (), Excl' ()))) as (κ) "Both". { done. } + wp_alloc p as "pts". + iMod (inv_alloc _ _ (lock_inv γ ι κ arr cap p R) with "[-Post Hinvites]"). + { iNext. rewrite /lock_inv. iExists 0, 0, (<[0:=true]> (replicate cap false)). + iFrame. iSplitR. + - by rewrite insert_length replicate_length. + - iLeft. iFrame. rewrite Nat.mod_0_l. done. lia. } + wp_pures. + iApply "Post". + rewrite /is_lock. iFrame. + iExists _, _. auto. + Qed. + + Lemma rem_mod_eq (x y : nat) : (0 < y) → (x `rem` y)%Z = x `mod` y. + Proof. + intros Hpos. rewrite Z.rem_mod_nonneg; try lia. rewrite mod_Zmod; lia. + Qed. + + Lemma minus_plus_eq a b c : (a - b)%Z = c → a = (c + b)%Z. + Proof. lia. Qed. + + Lemma mod_fact_Z (t o i cap : Z) : + (0 < cap → o <= t < o + i → i <= cap → t `mod` cap = o `mod` cap → t = o)%Z. + Proof. + intros LeqCap [sLeX xLeSCap] ILeqCap ModEq. + assert (t < o + cap)%Z as Eq1; first lia. + rewrite (Z.div_mod o cap) in sLeX Eq1 |- *; last lia. + rewrite (Z.div_mod t cap) in sLeX Eq1 |- *; last lia. + remember (t `mod` cap)%Z as a. remember (o `mod` cap)%Z as b. + remember (o `div` cap)%Z as c. remember (t `div` cap)%Z as d. + rewrite -> ModEq in * |- *. + assert (c ≤ d)%Z. { eapply Zmult_lt_0_le_reg_r. apply LeqCap. subst. lia. } + assert (d < 1 + c)%Z. { eapply Zmult_lt_reg_r. apply LeqCap. lia. } + assert (d = c) as ->. lia. + reflexivity. + Qed. + + Lemma mod_fact (t o i cap : nat) : + 0 < cap → o <= t → t < o + i → i <= cap → t `mod` cap = o `mod` cap → t = o. + Proof. + intros LeqCap SLeqX XLeqSi ILeqCap ModEq%inj_eq. + repeat rewrite mod_Zmod in ModEq; try lia. + apply Nat2Z.inj. + eapply (mod_fact_Z _ _ i cap); lia. + Qed. + + Lemma nth_list_with_one (n a b : nat) : + list_with_one n a !! b = Some true → a = b. + Proof. + rewrite /list_with_one. + by intros [[Eq] | [_ [[=] _]%lookup_replicate_1]]%list_lookup_insert_Some. + Qed. + + Lemma wait_loop_spec γ ι κ lk cap t R : + {{{ is_lock γ ι κ lk cap R ∗ issued γ t }}} + wait_loop lk #t + {{{ RET #(); locked γ κ t ∗ R }}}. + Proof. + iIntros (ϕ) "(Lock & Ticket) Post". + (* Since `wait_loop` invokes itself recursively we use Löb *) + iLöb as "IH". + wp_lam. wp_let. + iDestruct "Lock" as (arr nextPtr -> capPos) "#LockInv". + wp_pures. + wp_bind (! _)%E. + iInv N as (o i xs) "(>%lenEq & >nextPts & isArr & >Inv & Auth & Part)" "Close". + rewrite /is_array rem_mod_eq //. + pose proof (lookup_lt_is_Some_2 ((λ b : bool, #b) <$> xs) ((t `mod` cap))) as [x1 Hsome]. + { subst. rewrite fmap_length. apply Nat.mod_upper_bound. lia. } + wp_apply (wp_load_offset with "isArr"); first apply Hsome. + iIntros "isArr". + apply list_lookup_fmap_inv in Hsome as (x & -> & xsLookup). + destruct x. + - rewrite /issued. + iDestruct (valid_ticket_range with "Ticket Auth") as "([% %] & Ticket & Auth)". + iDestruct (invitation_cap_bound with "Inv") as "%". + (* We now destruct the three cases that the lock can be in. *) + iDestruct "Part" as "[(Ho & Hr & Both & %xsEq) | [(Ticket2 & Right & %xsEq) | (Ticket2 & Left & %xsEq)]]". + * (* The case where the lock is currently open. *) + rewrite xsEq in xsLookup. + apply nth_list_with_one in xsLookup. + assert (t = o) as ->. { apply (mod_fact _ _ i cap); done. } + iDestruct (both_split with "Both") as "[Left Right]". + iMod ("Close" with "[nextPts isArr Inv Auth Ticket Left]") as "_". + { iNext. iExists o, i, (list_with_one cap (o `mod` cap)). + rewrite /is_array xsEq. iFrame. + iSplit. + - by rewrite /list_with_one insert_length replicate_length. + - iRight. iRight. iFrame. done. } + iModIntro. wp_pures. iApply "Post". iFrame. + * (* The case where the lock is in the clopen state. In this state all the + indices in the array are false and hence we can not possibly have + read a true in the array. *) + rewrite xsEq in xsLookup. + apply lookup_replicate_1 in xsLookup as [[=] _]. + * (* The case where the lock is closed. *) + rewrite xsEq in xsLookup. apply nth_list_with_one in xsLookup. + assert (t = o) as ->. { apply (mod_fact _ _ i (cap)); auto. } + iDestruct (own_valid_2 with "Ticket Ticket2") as % [_ ?%gset_disj_valid_op]. + set_solver. + - iMod ("Close" with "[nextPts isArr Inv Auth Part]") as "_". + { iNext. iExists o, i, xs. iFrame. auto. } + iModIntro. wp_pures. iApply ("IH" with "[LockInv] Ticket"). + { iExists arr, nextPtr. auto. } + done. + Qed. + + Lemma acquire_spec γ ι κ lk cap R : + {{{ is_lock γ ι κ lk cap R ∗ invitation ι 1 cap}}} + acquire lk + {{{ t, RET #t; locked γ κ t ∗ R }}}. + Proof. + iIntros (ϕ) "[#IsLock Invitation] Post". + iPoseProof "IsLock" as (arr nextPtr) "(-> & % & LockInv)". + wp_lam. wp_pures. wp_bind (FAA _ _). + iInv N as (o i xs) "(>% & >nextPts & psPts & >Invs & >Auth & np)" "Close". + wp_faa. + iMod (own_update with "Auth") as "[Auth Hofull]". + { eapply auth_update_alloc, prod_local_update_2. + eapply (gset_disj_alloc_empty_local_update _ {[ (o + i) ]}). + apply (set_seq_S_end_disjoint o). } + iMod ("Close" with "[-Post Hofull]") as "_". + { iNext. rewrite /lock_inv. iExists o, (i+1), xs. iFrame. + iSplit; first done. + iSplitL "nextPts". + { repeat rewrite Nat2Z.inj_add. by rewrite Z.add_assoc. } + iSplitL "Invitation Invs". + { rewrite /invitation -sumRA_op_second own_op. iFrame. } + by rewrite -(set_seq_S_end_union_L) Nat.add_1_r. } + iModIntro. + wp_let. + wp_apply (wait_loop_spec γ ι κ (#arr, #nextPtr, #cap)%V cap (o + i) R with "[Hofull]"). + { rewrite /issued. by iFrame. } + iIntros "[locked Res]". wp_seq. iApply "Post". iFrame. + Qed. + + Lemma frame_update_lemma_discard_ticket o i : + ● (Excl' o, GSet (set_seq o i)) ⋅ (◯ (Excl' o, GSet ∅) ⋅ ◯ (ε, GSet {[o]})) ~~> + ● (Excl' (o + 1), GSet (set_seq (o + 1) (i - 1))) ⋅ (◯ (Excl' (o + 1), GSet ∅)). + Proof. + rewrite -auth_frag_op -pair_op right_id left_id. + apply auth_update. + destruct i. + - apply local_update_total_valid. + intros _ _ [_ H%gset_disj_included]%prod_included. + set_solver. + - apply prod_local_update; simpl. + * apply option_local_update, exclusive_local_update. done. + * rewrite Nat.sub_0_r Nat.add_1_r -gset_op_union -gset_disj_union. + apply gset_disj_dealloc_empty_local_update. + apply set_seq_S_start_disjoint. + Qed. + + Lemma release_spec γ ι κ lk cap o R : + {{{ is_lock γ ι κ lk cap R ∗ locked γ κ o ∗ R }}} + release lk #o + {{{ RET #(); invitation ι 1 cap }}}. + Proof. + iIntros (ϕ) "(#IsLock & [Locked Right] & R) Post". + iPoseProof "IsLock" as (arr nextPtr) "(-> & % & LockInv)". + wp_lam. wp_pures. + (* Focus the store such that we can open the invariant. *) + wp_bind (_ <- _)%E. + iInv N as (o' i xs) "(>%lenEq & >nextPts & arrPts & >Invs & >Auth & Part)" "Close". + (* We want to show that o and o' are equal. We know this from the loked γ o ghost state. *) + iDestruct (own_valid_2 with "Auth Locked") as + %[[<-%Excl_included%leibniz_equiv _]%prod_included _]%auth_both_valid. + rewrite rem_mod_eq //. + iApply (array_store with "[arrPts]"). + { iFrame. iPureIntro. rewrite lenEq. apply Nat.mod_upper_bound. lia. } + iModIntro. iIntros "psPts". + iDestruct "Part" as "[(Locked' & _ & Both & _) | [(_ & Right' & _) | (Issued & Left & %xsEq)]]". + { iDestruct (know_o_exclusive with "Locked Locked'") as %[]. } + { iDestruct (right_right_false with "Right Right'") as %[]. } + iMod ("Close" with "[nextPts Invs Auth psPts Issued Right]") as "_". + { iNext. iExists o, i, (<[(o `mod` cap) := false]> xs). + rewrite insert_length. + iFrame. iSplit; first done. + iRight. iLeft. iFrame. iPureIntro. subst. + rewrite -> xsEq at 2. + by rewrite insert_list_with_one. } + clear xsEq lenEq xs i. + iModIntro. wp_pures. + rewrite -(Nat2Z.inj_add o 1). + iInv N as (o' i xs) "(>% & >nextPts & arrPts & >Invs & >Auth & Part)" "Close". + (* We destruct the disjunction in the lock invariant. We know that we are in + the half-locked state so the first and third case results in + contradictions. *) + iDestruct "Part" as "[(>Locked' & _ & Both & _) | [(Issued & Right & >%xsEq) | (Issued & >Left' & Eq)]]". + * iDestruct (know_o_exclusive with "Locked Locked'") as "[]". + (* This is the case where the lock is clopen, that is, the actual state + of the lock. *) + * iDestruct (own_valid_2 with "Auth Locked") as + %[[<-%Excl_included%leibniz_equiv _]%prod_included _]%auth_both_valid. + rewrite rem_mod_eq //. + iApply (wp_store_offset with "arrPts"). + { apply lookup_lt_is_Some_2. rewrite fmap_length H0. apply Nat.mod_upper_bound. lia. } + iModIntro. iIntros "isArr". + (* Combine the left and right we have into a both. *) + iDestruct (left_right_to_both with "Left Right") as "Both". + iDestruct (ticket_i_gt_zero with "Issued Auth") as "(% & Issued & Auth)". + iMod (own_update with "[Issued Auth Locked]") as "Hγ". + { apply frame_update_lemma_discard_ticket. } + { rewrite own_op own_op. iFrame. } + iDestruct "Hγ" as "[Locked Auth]". + iDestruct (invitation_split_one with "Invs") as "[Invs Inv]"; first done. + iMod ("Close" with "[nextPts Invs Auth isArr Both R Locked]") as "_". + { iNext. iExists (o + 1), (i - 1), (list_with_one cap ((o + 1) `mod` cap)). + assert (o + 1 + (i - 1) = o + i) as -> by lia. + iFrame. + iSplit. { by rewrite /list_with_one insert_length replicate_length. } + iSplitL "isArr". { by rewrite /list_with_one /is_array list_fmap_insert xsEq. } + iLeft. iFrame. done. } + iModIntro. iApply "Post". done. + * iDestruct (left_left_false with "Left Left'") as "[]". + Qed. + +End proof. + +Program Definition abql `{!heapG Σ, !alockG Σ} : spec.abql_spec Σ := + {| spec.newlock := newlock; + spec.acquire := acquire; + spec.release := release; + spec.is_lock := is_lock; + spec.locked := locked; + spec.invitation := invitation; + spec.is_lock_persistent := is_lock_persistent; + spec.locked_timeless := locked_timeless; + spec.locked_exclusive := locked_exclusive; + spec.invitation_split := invitation_split; + spec.newlock_spec := newlock_spec; + spec.acquire_spec := acquire_spec; + spec.release_spec := release_spec; + |}.