From iris.base_logic.lib Require Export invariants token.
From iris.heap_lang Require Import lang proofmode notation par.

(* exercise: spawn_ex_spec (2 stars) *)

(** Here is a program that uses spawn and join: **)
Definition make_tuple : val :=
  λ: "<>", ref (#3, #5).

Definition spawn_ex : expr :=
  let: "h" := spawn make_tuple in
  let: "r" := join "h" in
  Fst !"r".

Section spawn_ex.

Context `{!heapGS Σ, !spawnG Σ}.

Lemma make_tuple_spec :
  {{{ True }}} make_tuple #() {{{ l, RET #l; l ↦ (#3, #5) }}}.
Proof.
  iIntros "%Φ _ HΦ".
  wp_lam.
  wp_alloc l as "Hl".
  by iApply "HΦ".
Qed.

Let N := nroot .@ "spawn_ex".
Definition make_tuple_post (v : val) : iProp Σ := ∃ l : loc, ⌜v = #l⌝ ∗ l ↦ (#3, #5).

(** Prove the following spec for spawn_ex. You will need to wp_apply the specs for
    spawn and join.
    Hint: When applying spawn_spec, you may need to supply make_tuple_post
    (defined above) for the Ψ argument. **)
Lemma spawn_ex_spec : {{{ True }}} spawn_ex {{{ v, RET v; ⌜v = #3⌝ }}}.
Proof.
  (* exercise *)
Admitted.

End spawn_ex.


Section par.

Context `{!heapGS Σ, !tokenG Σ}.

(* Instead of re-proving par_spec in this file, we'll state it as an axiom; it'll
   turn yellow in your IDE, but that's not a problem. *)
Axiom par_spec : forall (P1 P2 : iProp Σ) (e1 e2 : expr) (Q1 Q2 : val → iProp Σ),
  {{{ P1 }}} e1 {{{ v, RET v; Q1 v }}} -∗
  {{{ P2 }}} e2 {{{ v, RET v; Q2 v }}} -∗
  {{{ P1 ∗ P2 }}} (e1 ||| e2)%V {{{ v1 v2, RET (v1, v2); Q1 v1 ∗ Q2 v2 }}}.

(* exercise: parallel_add_spec (3 stars) *)

(**
  Let us try to use the [par] specification to prove a specification for a
  simple client. The client performs two `fetch and add' operations on
  the same location in parallel. The expression [FAA "l" #i] atomically
  fetches the value at location [l], adds [i] to it, and stores the
  result back in [l].

  Our specification will state that the resulting value is even.
*)

Definition parallel_add : expr :=
  let: "r" := ref #0 in
  (FAA "r" #2)
  |||
  (FAA "r" #6)
  ;;
  !"r".

(**
  We must again assume the presence of the [token] resource algebra as
  we will be using the [par] specification, which relies on it through
  [spawn].
*)
Let N := nroot .@ "par_add".

(**
  We will have an invariant stating that [r] points to an even integer.
*)
Definition parallel_add_inv (r : loc) : iProp Σ :=
  ∃ n : Z, r ↦ #n ∗ ⌜Zeven n⌝.

Lemma parallel_add_spec :
  {{{ True }}} parallel_add {{{ n, RET #n; ⌜Zeven n⌝ }}}.
Proof.
  iIntros "%Φ _ HΦ".
  rewrite /parallel_add.
  wp_alloc r as "Hr".
  wp_pures.
  iMod (inv_alloc N _ (parallel_add_inv r) with "[Hr]") as "#I".
  {
    iNext.
    iExists 0.
    iFrame.
  }
  (**
    We don't need information back from the threads, so we will simply
    use [λ _, True] as the postconditions. Similarly, we only need the
    invariant to prove the threads, and since this is in the persistent
    context, we let the preconditions be [True].
  *)
  wp_apply (par_spec (True%I) (True%I) _ _ (λ _, True%I) (λ _, True%I)).
  - iIntros (Φ') "!> _ HΦ'".
    iInv "I" as "(%n & Hr & >%Hn)".
    wp_faa.
    iModIntro.
    iSplitL "Hr".
    {
      iModIntro.
      iExists (n + 2)%Z.
      iFrame.
      iPureIntro.
      by apply Zeven_plus_Zeven.
    }
    by iApply "HΦ'".
  (* exercise *)
Admitted.

(* grad exercise *)
(** In fact, parallel_add should always return 8. We can prove this using ghost
    tokens, using the following invariant: **)
Definition parallel_add_inv2 (r : loc) γ1a γ1b γ2a γ2b : iProp Σ :=
  ∃ n1 n2 : Z, r ↦ #(n1 + n2) ∗
    ((⌜n1 = 0⌝%Z ∗ token γ1b) ∨ (⌜n1 = 2⌝%Z ∗ token γ1a)) ∗
    ((⌜n2 = 0⌝%Z ∗ token γ2b) ∨ (⌜n2 = 6⌝%Z ∗ token γ2a)).

(** The idea is that each of the two threads has two tokens, [a] and [b].
    Initially, each thread holds its [a] token, and the invariant holds the
    [b] tokens. When a thread performs its FAA operation, it gives its [a] token
    to the invariant and takes the [b] token out instead.

    The invariant knows that the value of r is the sum of [n1] and [n2], the
    "contributions" of the two threads. When the invariant has a thread's [b]
    token, its contribution is 0. When the invariant has a thread's [a] token,
    its contribution is the amount added by the thread (2 or 6, as appropriate).

    Then, when both threads finish and return their [b] tokens, we know that
    the invariant has both the [a] tokens and the total value of r is 8.

    Your job is to prove this formally, by proving the lemma below. Your proof
    should be structured similarly to parallel_inv_add, but you'll need to
    initialize the tokens before the invariant, and manage them throughout the
    proof.

    Hint 1: when you wp_apply par_spec, the pre- and posconditions for the two
    threads shouldn't just be True anymore.
    Hint 2: when you have a premise with a disjunction (∨), and you know one side
    is impossible (e.g., because it holds a token that you already have a copy of),
    destruct that premise and show that one side implies False.
    Hint 3: if you're having trouble with numbers, put (...)%Z around your
    arithmetic expressions, and don't forget the lia tactic for solving arithmetic
    goals. **)

Lemma parallel_add_spec2 :
  {{{ True }}} parallel_add {{{ n, RET n; ⌜n = #8⌝ }}}.
Proof.
  (* exercise *)
Admitted.

End par.
