(* modified from https://gitlab.mpi-sws.org/iris/tutorial-popl21/-/blob/master/exercises/ex_01_swap.v *)

(*
This exercise introduces the basic Iris Proof Mode tactics by proving a simple
example: a function that swaps the contents of two pointers. We will use this
function to implement two other functions.
*)
From iris.heap_lang Require Import proofmode notation.

Section proof.

(* boilerplate that says "assume we have a heap" *)
Context `{!heapGS Σ}.


(* HeapLang program *)
Definition plus1 : val := λ: "x",
  "x" <- !"x" + #1.

(*
     f(int* x){ *x = *x + 1; }

     fun (x : ref int) => x := !x + 1
*)







(* In Imp: X + 1 *)
(* In HeapLang: "X" + #5 *)

Lemma plus1_spec : forall p (n : nat),
  {{{ p ↦ #n }}} plus1 #p {{{ RET #(); p ↦ #(n + 1) }}}.
Proof. (* notation got desugared! *)
  intros.
  iIntros "Hpre Hpost". (* Iris hyps, postcondition is an extra hypothesis *)
  unfold plus1.
  (* eapply hoare_seq. apply hoare_funcall. ... *)
  wp_lam.   (* get the function started *)
  wp_load.  (* symbolic execution for load *)
  wp_store. (* symbolic execution for store *)
  (* Program is finished! Now we have to prove the postcondition. *)
  iApply "Hpost".
  iApply "Hpre".
Qed.








(* The swap function, defined as a heap-lang value. *)
Definition swap : val := λ: "x" "y",
  let: "tmp" := !"x" in
  "x" <- !"y";;
  "y" <- "tmp".

(* f(int* x, int* y){
     int tmp = *x;
     *x = *y;
     *y = tmp;
   }
*)




Lemma swap_spec x y v1 v2 :
  {{{ x ↦ v1 ∗ y ↦ v2 }}} swap #x #y
  {{{ RET #(); x ↦ v2 ∗ y ↦ v1 }}}.

Proof.
  intros Φ.
  iIntros "Pre Post".
  iDestruct "Pre" as "[Hx Hy]".

  unfold swap.
  wp_lam. wp_let.
  wp_load. wp_let.
  wp_load. wp_store.
  wp_store.

  iApply "Post".

(*  iSplitL "Hx".
  - iApply "Hx".
  - iApply "Hy". *)

(* HP
   HQ
   ---
   P /\ Q

split. - apply HP. - apply HQ.
*)

iFrame. auto.

(*  iSplitL "Hx".
  - iApply "Hx".
  - iApply "Hy".*)
Qed.




(* We can further automate the lemma by defining a simple Ltac tactic for
symbolic executing. *)
Ltac wp_exec := repeat (wp_lam || wp_pure _ || wp_load
  || wp_store).

(* This tactic repeatedly tries to symbolically execute pure redexes, loads and
stores. It makes use of the tactic [wp_pure t], which tries to find the redex
[t] in the goal, and executes that redex. The redex [t] may contain holes, and
as such, tactics like [wp_seq] are actually defined as [wp_pure (_ ;; _)%E]. By
using [wp_pure _] it will symbolically execute *any* pure redex. *)

(* Same lemma again, but now using the tactic we just defined. *)
Lemma swap_spec_2_more_automation x y v1 v2 :
  {{{ x ↦ v1 ∗ y ↦ v2 }}} swap #x #y
  {{{ RET #(); x ↦ v2 ∗ y ↦ v1 }}}.
Proof.
  iIntros (Φ). (* same as intros Φ *)
  iIntros "(Hx & Hy) Post". wp_exec.
  iApply "Post". iFrame. done.
Qed.



(* Using swap, we can define functions that rotate three pointers in left and
right direction. *)
Definition rotate_r : val := λ: "x" "y" "z",
  swap "y" "z";; swap "x" "y".

Definition rotate_l : val := λ: "x" "y" "z",
  swap "x" "y";; swap "y" "z".

Lemma rotate_r_spec x y z v1 v2 v3 :
  {{{ x ↦ v1 ∗ y ↦ v2 ∗ z ↦ v3 }}}
    rotate_r #x #y #z
  {{{ RET #(); x ↦ v3 ∗ y ↦ v1 ∗ z ↦ v2 }}}.
Proof.
  (* As in Coq, the IPM introduction pattern (p1 & p2 & .. & pn) ] is syntactic
  sugar for [ [p1 [p2 [... pn]]] ]. *)
  iIntros (Φ) "[Hx [Hy Hz]] Post".
  unfold rotate_r. wp_lam. do 2 wp_let.

  wp_bind (swap _ _).
  iApply (swap_spec with "[Hy Hz]").
  { iFrame. }

  iNext. iIntros "[Hy Hz]". wp_seq.

  (* We can also immediately frame hypothesis when using a lemma: *)
  iApply (swap_spec with "[$Hx $Hy]"). iNext. iIntros "[Hx Hy]".
  iApply "Post". iFrame.
Qed.

(* use wp_apply instead of iApplying swap_spec *)
Lemma rotate_r_spec_again x y z v1 v2 v3 :
  {{{ x ↦ v1 ∗ y ↦ v2 ∗ z ↦ v3 }}}
    rotate_r #x #y #z
  {{{ RET #(); x ↦ v3 ∗ y ↦ v1 ∗ z ↦ v2 }}}.
Proof.
  iIntros (Φ) "(Hx & Hy & Hz) Post". wp_lam. do 2 wp_let.
  wp_apply (swap_spec with "[$Hy $Hz]").
  iIntros "(Hy & Hz)".
  wp_seq.
  wp_apply (swap_spec with "[$Hx $Hy]").
  iIntros "(Hx & Hy)".
  iApply "Post". iFrame.
Qed.

(* *Exercise*: Do this proof yourself. Try a couple of variations of the tactics
we have seen before:
- Do the whole proof explicitly by proving [swap] inline,
- Use the specification of [swap] in combination with [iApply],
- Use the specification of [swap] in combination with [wp_apply]
*)
Lemma rotate_l_spec x y z v1 v2 v3 :
  {{{ x ↦ v1 ∗ y ↦ v2 ∗ z ↦ v3 }}}
    rotate_l #x #y #z
  {{{ RET #(); x ↦ v2 ∗ y ↦ v3 ∗ z ↦ v1 }}}.
Proof.
  (* swap ...     wp_apply swap_spec *)
  (* exercise *)
Admitted.

Local Open Scope Z.

(* Exercise: prove a specification for the program z_mult,
   which multiplies its first two arguments and stores the result in its third argument. *)

Definition z_mult : val := λ: "x" "y" "z",
  "z" <- !"x" * !"y".

Lemma z_mult_spec x y z (a b c : Z) :
  {{{ x ↦ #a ∗ y ↦ #b ∗ z ↦ #c }}}
  z_mult #x #y #z
  {{{ RET #(); x ↦ #a ∗ y ↦ #b ∗ z ↦ #(a * b) }}}.
Proof.
Admitted.



Definition times_2 : val := λ: "n", "n" * #2.

(*
    int times_2(int n) { return n * 2; }
    fun n => n * 2
*)

(* Here's a direct spec for times_2: for input n, it returns n * 2. *)
Lemma times_2_spec (n : Z) :
  {{{ True }}} times_2 #n {{{ RET #(n * 2); True }}}.
Proof.
  intros.
  iIntros "_ Post".
  wp_exec.
  iApply "Post".
  auto.
Qed.

(* Here's a related spec: if we give it a number < 5, it returns a number < 10. *)
Lemma times_2_spec_2 (n : Z) :
  {{{ ⌜n < 5⌝ }}} times_2 #n {{{ m, RET #m; ⌜m < 10⌝ }}}.
Proof.
  intros.
  iIntros (Hn) (* "pure" math *) "Post".
  wp_apply times_2_spec.
  { auto. }
  iIntros "_".
  iApply "Post".
  iPureIntro.
  lia.
Qed.

(* Exercise: Prove the following spec for max. To deal with the if-then-else,
   first destruct the condition, then use the tactics wp_if_true/wp_if_false
   as appropriate in each case. *)
Definition max : val := λ: "x" "y",
  if: !"x" < !"y" then !"y" else !"x".

Lemma max_spec x y (v1 v2 : Z) :
  {{{ x ↦ #v1 ∗ y ↦ #v2 }}}
  max #x #y
  {{{ v, RET #v; ⌜v1 <= v /\ v2 <= v⌝%Z ∗ x ↦ #v1 ∗ y ↦ #v2 }}}.
Proof.
Admitted.

End proof.
