From 732b28a3cfa8c6cf1b72674730cbc7d3b3256c90 Mon Sep 17 00:00:00 2001 From: Yusuke Matsushita <y.skm24t@gmail.com> Date: Wed, 20 Apr 2022 02:54:25 +0900 Subject: [PATCH] Add walkthrough --- walkthrough.md | 1336 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1336 insertions(+) create mode 100644 walkthrough.md diff --git a/walkthrough.md b/walkthrough.md new file mode 100644 index 00000000..dc3e6d80 --- /dev/null +++ b/walkthrough.md @@ -0,0 +1,1336 @@ +# Walkthrough + +Here we present a walkthrough of RustHornBelt's Coq mechanization. + +RustHornBelt's proof is built on many artifacts: Iris Proof Mode & the Iris +separation logic, RustBelt's lifetime logic, the λ core calculus and its +low-level program logic in Iris, RustHornBelt's prophecy framework, +dependent-type heterogeneous lists, and RustHornBelt's semantic type-spec system. +There are many tricky things going on behind the scene. +Still, what we really need to understand for verification in RustHornBelt +is fairly limited. + +Below we walk through some real examples of verification in our Coq mechanization +of RustHornBelt to provide readers with a good intuition of how to reuse and +extend our Coq artifacts. + +## Verifying integer addition `+` + +Let's start with a small example: integer addition `+` (see `typing/int.v`). + +### Semantic lemma for the type-spec rule + +It's treated as an *instruction* (instead of a function) in RustHornBelt's +type-spec system. +We have a type-spec rule for this instruction. Since we are in a *semantic* +framework, it can be formulated as a separate *lemma* in Coq, as follows: +``` +Lemma type_plus_instr E L p1 p2 : + typed_instr_ty E L +[p1 ◁ int; p2 ◁ int] (p1 + p2) int + (λ post '-[z; z'], post (z + z')). +``` + +The semantic Coq predicate +``` +typed_instr_ty E L +[p ◁ ty; ...] e ty' tr : Prop +``` +means that, by taking objects `p ◁ ty` (`p: path` typed `ty`) etc. +as input, you can execute the expression `e: expr` and get an object typed `ty'`, +under the *spec (predicate transformer)* `tr`. +We have two lifetime contexts, the external one `E` and the local one `L`, +but usually you can ignore them. + +For integer addition, you take two integer operands `p1 ◁ int` +and `p2 ◁ int`, perform `p1 + p2`, and output `int`. +Here, `int` is a *semantic Rust type* for integer, typed `int: type Zₛ`. +Also, `+` in `p1 + p2` is not usual Coq addition, but the notation for +addition in the λRust core calculus. + +Here, `type 𝔄: Type` is the Coq data type for a *semantic Rust type* +(`typing/type.v`). +It is parametrized over `𝔄: syn_type`, which is the *sort* of +a representation value for the Rust type. +Here, `syn_type` (`prophecy/syn_type.v`) is an inductive Coq data type for +a *syntax tree* representing a Coq type. +There is a function `of_syn_type: syn_type -> Type` for interpreting +a `syn_type`, which is registered as a coercion. +``` +Coercion of_syn_type: syn_type >-> Sortclass. +``` + +For the integer, we use `Zₛ: syn_type` (where the subscript `ₛ` indicates that +it is syntactic), which is interpreted into Coq's integer type `Z: Type`. + +For integer addition, the input type context (list of typed input objects) is: +``` ++[p1 ◁ int; p2 ◁ int] : tctx [Zₛ; Zₛ] +``` +Here, `tctx 𝔄l` (`𝔄l: list syn_type`), the type for a type context, is actually +a shorthand for `hlist tctx_elt 𝔄l`. +Each object `p ◁ ty` (`ty: type 𝔄`) has the type `tctx_elt 𝔄`. +The Coq type `hlist F [X; Y; ...]` (`util/fancy_lists.v`) is for a +*heterogeneous list* that consists of items of Coq type `F X`, `F Y`, and so on. +You can use the notation `+[x; y; ...]` for constructing a heterogeneous list. + +Finally, the spec (predicate transformer) for integer addition is: +``` +λ (post: Z → Prop) ('-[z; z']: *[Z; Z]), post (z + z') : Prop +``` +It takes the *postcondition* `post: Z → Prop` and the input objects' integer values +`z, z': Z`, and returns the *precondition* `post (z + z') : Prop`. +Intuitively, it is like in the *continuation-passing style*, with the postcondition +`post` working as a kind of *continuation*. +The Coq type `*[A; B; ...]` is for what we call a *passive heterogeneous list*, +which is of the form `-[a; b; ...]`, where `a: A`, `b: B`, and so on. +(Technically, the passive heterogeneous list type is a type *function* whereas the +heterogeneous list type `hlist` is a GADT, but let's first get used to real usage.) + +### Proving the lemma + +Proof of the lemma can go as follows, for example. +``` +Proof. + intros tid postπ (zπ & zπ' &[]). + iIntros "_ _ _ _ _ $ $ (P1 & P2 &_) Obs". + wp_apply (wp_hasty with "P1"). + iIntros (v1 d1 _) "⧖". iIntros ([z [-> [=->]]]). + wp_apply (wp_hasty with "P2"). + iIntros (v2 d2 _) "_". iIntros ([z' [-> [=->]]]). + wp_op. iExists -[const (z + z')]. iFrame "Obs". + iSplit; [|done]. rewrite tctx_hasty_val'; [|done]. + iExists d1. iFrame "⧖". by iExists (z + z'). +Qed. +``` +Let's go step by step. + +First, you perform `intros tid postπ (zπ & zπ' & [])`, because the goal is universally +quantified over the *thread id* `tid: thread_id` (you can usually ignore it), +the *clairvoyant postcondition* `postπ: proph (*[Z] → Prop)`, and the list of +*clairvoyant input values* `zπl: *[proph Z; proph Z]`, which gets decomposed +into `zπ, zπ': proph Z`. +The type `proph A: Type` is actually a shorthand for the *clairvoyant* reader +monad `proph_asn → A`. +In Coq, we indicate clairvoyant values by the suffix `π` (instead of the hat +used in the paper). + +Now you have the following goal (it is modified a bit for better readability; +the same applies hereafter). +``` +lft_ctx -∗ time_ctx -∗ proph_ctx -∗ uniq_ctx -∗ + elctx_interp E -∗ na_own tid ⊤ -∗ llctx_interp L 1 -∗ + tctx_interp tid +[p1 ◁ int; p2 ◁ int] -[zπ; zπ'] -∗ + ⟨π, postπ π -[(zπ π + zπ' π)]⟩ -∗ + WP p1 + p2 {{ v, ∃ zπl': *[proph Z], + na_own tid ⊤ ∗ llctx_interp L 1 + ∗ tctx_interp tid +[v ◁ int] zπl' + ∗ ⟨π, postπ π (zπl' -$ π)⟩ + }} +``` +This is an *Iris proposition*, of the type `iProp Σ: Type` (`Σ` specifies the +form of resources, but you can ignore it). +As expected, `∗` is the *separating conjunction*, `-∗` is the *magic wand* +(right-associative, just like implication), and `∃ x: A, P` is the existential +quantifier (notation overloaded for Iris and pure Coq). +The predicate `WP e {{ v, P }}` is Iris's weakest precondition for the expression +`e` and the postcondition `(λ v, P) : val → iProp Σ`. + +You are given some *contexts* `lft_ctx`, `time_ctx`, `proph_ctx`, `uniq_ctx`, +i.e., global Iris invariants, but they can be ignored now. +You are also given `elctx_interp E` (persistent resource for `E`), `na_own tid ⊤` +(token for the thread `tid`), and `llctx_interp L 1` (resource for `L`), +but they can be usually ignored. + +More importantly, you get the resource for the *input objects* +`tctx_interp tid +[p1 ◁ int; p2 ◁ int] -[zπ; zπ']` +and the prophecy observation for the *precondition* `⟨π, postπ π -[(zπ π + zπ' π)]⟩`. +Because the predicate `tctx_interp` is defined as `foldr` over the list of input +objects, the resource for the input objects is unfolded into the following. +``` +tctx_elt_interp tid (p1 ◁ int) zπ ∗ tctx_elt_interp tid (p2 ◁ int) zπ' ∗ True +``` + +In the postcondition, we can specify the list of *clairvoyant output values* +`zπl': *[proph Z]`, and return the resource for the *output objects* +`tctx_interp tid +[v ◁ int] zπl'` and the *postcondition prophecy observation* +`⟨π, postπ π (zπl' -$ π)⟩` (`fl -$ x` feeds the argument `x` to each element of +the list `fl`), along with the minor resources `na_own tid ⊤` and `llctx_interp L 1`. + +Let's do `iIntros "_ _ _ _ _ $ $ (P1 & P2 &_) Obs"`, a tactic of *Iris Proof Mode*. +You ignore the contexts and `elctx_interp E`. +You want to directly pass the resources `na_own tid ⊤` and `llctx_interp L 1` +to `WP`'s postcondition. +In that case, you can *frame* them out using the directive `$`. +You name the resources for the input objects `p1` and `p2` as `P1` and `P2` +respectively, and name the precondition prophecy observation as `Obs`. + +Now the goal is as follows. +``` +"P1" : tctx_elt_interp tid (p1 ◁ int) zπ +"P2" : tctx_elt_interp tid (p2 ◁ int) zπ' +"Obs" : ⟨π, postπ π -[(zπ π + zπ' π)]⟩ +--------------------------------------∗ +WP p1 + p2 {{ v, ∃ zπl': *[proph Z], + tctx_interp tid +[v ◁ int] zπl' + ∗ ⟨π, postπ π (zπl' -$ π)⟩ +}} +``` +Here, `P1`, `P2` and `Obs` are *Iris-level hypotheses*. + +Technically, `p1` and `p2` are paths, which can contain some location-offset +operations, so we should evaluate them. +By the tactic `wp_apply (wp_hasty with "P1")`, you can execute `p1` +using the lemma `wp_hasty` and the resource `P1`. +Then we have the following goal. +``` +"P2" : tctx_elt_interp tid (p2 ◁ int) zπ' +"Obs" : ⟨π, postπ π -[(zπ π + zπ' π)]⟩ +--------------------------------------∗ +∀ (v1: val) (d1: nat), + ⌜Some v1 = eval_path p1⌝ -∗ ⧖d1 -∗ + int.(ty_own) zπ d1 tid [v1] -∗ + WP v1 + p2 {{ v, ∃ zπl': *[proph Z], + tctx_interp tid +[v ◁ int] zπl' + ∗ ⟨π, postπ π (zπl' -$ π)⟩ + }} +``` +The path `p1` gets evaluated into a low-level value `v1: val`, and `p1 + p2` +has changed into `v1 + p2` (usually `Some v1 = eval_path p1` can be ignored). +The integer object has the pointer-nesting depth `d1: nat`, and so we get a +time receipt `⧖d1` for it. +Therefore we do `iIntros (v1 d1 _) "⧖"`. + +We also have the ownership predicate `int.(ty_own) zπ d1 tid [v1]`, which is +unfolded into the following lifted pure proposition (after slight modification): +``` +⌜∃ z: Z, zπ = const z ∧ [v] = [#z]⌝ +``` +It says that the low-level value `v: val` is the literal `#z` for some Coq +integer `z: Z` and the clairvoyant value `zπ` is actually constantly `z`. +By performing `iIntros ([z [-> [=->]]])`, we can destruct it to get `z: Z`, +rewrite `zπ` into `const z`, and rewrite `v` into `#z` +(the `[=...]` pattern destructs `[v] = [#z]` into `v = #z`). + +Now the goal is as follows. +``` +"P2" : tctx_elt_interp tid (p2 ◁ int) zπ' +"Obs" : ⟨π, postπ π -[(z + zπ' π)]⟩ +"⧖" : ⧖d1 +--------------------------------------∗ +WP #z + p2 {{ v, ∃ zπl': *[proph Z], + tctx_interp tid +[v ◁ int] zπl' + ∗ ⟨π, postπ π (zπl' -$ π)⟩ +}} +``` + +You perform similar tactics `wp_apply (wp_hasty with "P2")`, `iIntros (v2 d2 _) "_"` +and `iIntros ([z' [-> [=->]]])` for `p2` (now you ignore the time receipt) to get: +``` +"Obs" : ⟨π, postπ π -[(z + z')]⟩ +"⧖" : ⧖d1 +--------------------------------------∗ +WP #z + #z' {{ v, ∃ zπl': *[proph Z], + tctx_interp tid +[v ◁ int] zπl' + ∗ ⟨π, postπ π (zπl' -$ π)⟩ +}} +``` +Finally, you can perform the integer addition by `wp_op`. +As expected, the operation returns `#(z + z')`, the literal for `z + z'`. + +Now the goal is as follows, out of `WP`. +``` +"Obs" : ⟨π, postπ π -[(z + z')]⟩ +"⧖" : ⧖d1 +--------------------------------------∗ +∃ zπl': *[proph Z], + tctx_interp tid +[#(z + z') ◁ int] zπl' + ∗ ⟨π, postπ π (zπl' -$ π)⟩ +``` + +By `iExists -[const (z + z')]`, you set `zπl'` to `-[const (z + z')]`, +which updates the goal into the following (unfolding `tctx_interp`): +``` +"Obs" : ⟨π, postπ π -[(z + z')]⟩ +"⧖" : ⧖d1 +--------------------------------------∗ +(tctx_elt_interp tid (#(z + z') ◁ int) (const (z + z')) ∗ True) +∗ ⟨π, postπ π -[(z + z')]⟩ +``` +Now the postcondition prophecy observation is exactly the same as `Obs`, +the precondition prophecy observation. +So you can frame it out by `iFrame "Obs"`. +You can remove `∗ True` by `iSplit; [|done]`. + +Now the goal is as follows. +``` +"⧖" : ⧖d1 +--------------------------------------∗ +tctx_elt_interp tid (#(z + z') ◁ int) (const (z + z')) +``` +You can change the goal into the following by the tactic +`rewrite tctx_hasty_val'; [|done]`: +``` +"⧖" : ⧖d1 +--------------------------------------∗ +∃ d: nat, ⧖d + ∗ ty_own int (const (z + z')) d tid [#(z + z')] +``` +Let's set `d` to `d1` by `iExists d1`. +Then you can frame out `⧖d1` by `iFrame "⧖"`. +Now the goal is just `ty_own int ...`, which is unfolded into an existential +quantification over the Coq integer as we observed before. +So you set the integer to `z + z'` and finish the remaining goal by +`by iExists (z + z')`. + +## Modeling `Vec<T>` + +Now let's verify the `Vec<T>` API, whose methods encapsulate *unsafe* implementation! + +We first model the vector type itself (`typing/lib/vec`). +We use the following model: +``` +Program Definition vec_ty {𝔄} (ty: type 𝔄) : type (listₛ 𝔄) := {| + ty_size := 3; ty_lfts := ty.(ty_lfts); ty_E := ty.(ty_E); + ty_own alπ d tid vl := + [S(d') := d] ∃(l: loc) (len ex: nat) (aπl: vec (proph 𝔄) len), + ⌜vl = [#l; #len; #ex] ∧ alπ = lapply aπl⌝ ∗ + ▷ ([∗ list] i ↦ aπ ∈ aπl, (l +ₗ[ty] i) ↦∗: ty.(ty_own) aπ d' tid) ∗ + (l +ₗ[ty] len) ↦∗len (ex * ty.(ty_size)) ∗ + freeable_sz' ((len + ex) * ty.(ty_size)) l; + ty_shr alπ d κ tid l' := + [S(d') := d] ∃(l: loc) (len ex: nat) (aπl: vec (proph 𝔄) len), + ⌜alπ = lapply aπl⌝ ∗ + &frac{κ} (λ q, l' ↦∗{q} [#l; #len; #ex]) ∗ + ▷ [∗ list] i ↦ aπ ∈ aπl, ty.(ty_shr) aπ d' κ tid (l +ₗ[ty] i); +|}%I. +``` +The vector type `vec_ty ty` is parametrized over the element type `ty: type 𝔄`. +The representation sort is set to `listₛ 𝔄`, where `listₛ` is a data constructor +in `syn_type`, corresponding to `list: Type → Type`. + +The field `ty_size` is the number of memory cells it takes (at the topmost level). + +The fields `ty_lfts` and `ty_E` are newly introduced in RustHornBelt (not +existent in RustBelt), but you can usually ignore them. + +The most important are the ownership predicate `ty_own` and the sharing predicate `ty_shr`. + +### Ownership predicate `(vec_ty ty).(ty_own)` + +Let's read the ownership predicate of `vec_ty ty`. +``` +ty_own alπ d tid vl := + [S(d') := d] ∃(l: loc) (len ex: nat) (aπl: vec (proph 𝔄) len), + ⌜vl = [#l; #len; #ex] ∧ alπ = lapply aπl⌝ ∗ + ▷ ([∗ list] i ↦ aπ ∈ aπl, (l +ₗ[ty] i) ↦∗: ty.(ty_own) aπ d' tid) ∗ + (l +ₗ[ty] len) ↦∗len (ex * ty.(ty_size)) ∗ + freeable_sz' ((len + ex) * ty.(ty_size)) l +``` + +The exotic notation `[S(d') := d] P` is shorthand of +`match d with S d' => P | _ => False`. +The vector object's (pointer-nesting) depth `d: nat` is the successor of +the depth `d': nat` of the element objects. + +The low-level values `vl` of the vector consists of the literals for +the memory block's head location `l: loc`, the vector's length `len: nat`, +and the extra capacity `ex`. + +Letting `aπl = [# aπ0; aπ1; ...]` the list of the elements' values (whose +length is `len`), the vector object's representation value `alπ π : list 𝔄` +equals `lapply aπl π = [aπ0 π; aπ1 π; ...]`. + +The main resources consist of +1. `▷ ([∗ list] i ↦ aπ ∈ aπl, (l +ₗ[ty] i) ↦∗: ty.(ty_own) aπ d' tid)` --- + the iterated separating conjunction of the i-th element object with the + points-to token `(l +ₗ[ty] i) ↦∗: ty.(ty_own) aπ d' tid)`, + under the later modality `▷`; +2. `(l +ₗ[ty] len) ↦∗len (ex * ty.(ty_size))` --- the points-to token over + the extra region; and +3. `freeable_sz' ((len + ex) * ty.(ty_size)) l` --- the right to free the + memory block. + +The notation `l ↦∗: Φ` stands for `∃vl, l ↦∗ vl ∗ Φ vl` and the notation +`l ↦∗len n` stands for `∃vl, ⌜length vl = n⌝ ∗ l ↦∗ vl`. +The later modality `▷` added to the element objects is for *contractiveness* +of `vec_ty ty` over `ty` (a technical key for supporting recursive types with +self reference under `vec_ty`). + +Usually, the ownership predicate appears in the form +`l' ↦∗ (vec_ty ty).(ty_own) alπ d tid`, accompanied with the points-to token. +To smoothly handle this form, we have a special lemma `split_mt_vec`, +which allows us to rewrite `l' ↦∗ (vec_ty ty).(ty_own) alπ d tid` into the form: +``` +[S(d') := d] ∃(l: loc) (len ex: nat) (aπl: vec (proph 𝔄) len), + ⌜alπ = lapply aπl⌝ ∗ l' ↦∗ [#l; #len; #ex] ∗ + ...(the main resources)... +``` +This makes work a bit easier. + +### Sharing predicate `(vec_ty ty).(ty_shr)` + +Let's read the sharing predicate of `vec_ty ty`. +``` +ty_shr alπ d κ tid l' := + [S(d') := d] ∃(l: loc) (len ex: nat) (aπl: vec (proph 𝔄) len), + ⌜alπ = lapply aπl⌝ ∗ + &frac{κ} (λ q, l' ↦∗{q} [#l; #len; #ex]) ∗ + ▷ [∗ list] i ↦ aπ ∈ aπl, ty.(ty_shr) aπ d' κ tid (l +ₗ[ty] i); +``` +Basically, `(vec_ty ty).(ty_shr) alπ d κ tid l'` is quite similar to +`l' ↦∗ (vec_ty ty).(ty_own) alπ d κ tid`, but we have: +1. `&frac{κ} (λ q, l' ↦∗{q} [#l; #len; #ex])` --- a fractional borrow + over the topmost memory cells; and +2. `▷ [∗ list] i ↦ aπ ∈ aπl, ty.(ty_shr) aπ d' κ tid (l +ₗ[ty] i)` --- + the iterated separating conjunction of the sharing predicate of the i-th + element object `ty.(ty_shr) aπ d' κ tid (l +ₗ[ty] i)`, + under the later modality `▷`. + +### Pre-installed Lemmas + +We have not yet finished defining `vec_ty`. +Like in RustBelt, RustHornBelt's semantic type `type 𝔄` is equipped with several +pre-installed lemmas about `ty_own` and `ty_shr`, the ownership and sharing +predicates: `ty_size_eq`, `ty_own/shr_depth_mono`, `ty_shr_lft_mono`, +`ty_share`, and `ty_own/shr_proph`. +This is why we used `Program Definition` instead of just `Definition`. +We can start proving the next remaining lemma by: +``` +Next Obligation. +``` +Typically, the proof is mostly boilerplate code. + +## Verifying `Vec::new` + +Now let's verify a simple method of the `Vec` API, `Vec::new` +(`typing/lib/vec/vec_basic.v`). + +### Implementation in λRust + +It is implemented as follows. +``` +Definition vec_new: val := + fn: [] := + let: "r" := new [ #3] in + "r" <- new [ #0];; "r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; + return: ["r"]. +``` +You can use a *function* literal `fn: ["x"; "y"; ...] := e`, where `"x"`, +`"y"`, ... are the arguments and `e: expr` is the executed body. + +The function first allocates a new memory block of length 3 (`new [#3]`) +and names its head location `"r"`. +It allocates a memory block of length `0` (`new [#0]`) and writes its +head location at "r". +Then it writes `#0` to `"r" +ₗ #1` and `"r" +ₗ #2` (`+ₗ` is the location +offset operation), to set the length and extra capacity to 0. +Finally it returns `"r"`. + +In this way, the function is expected to return a *box pointer* to +a vector of empty elements, `box (vec_ty ty)` (`Box<Vec<T>>` in Rust). +Note that the ownership predicate of the box pointer +`(box ty').(ty_own) pπ d tid [l']` is the following: +``` +[S(d') := d] + ▷ l ↦∗: ty'.(ty_own) pπ d' tid ∗ freeable_sz ty'.(ty_size) ty'.(ty_size) l'; +``` +Here, `freeable_sz n n l'` is the same as `freeable_sz' l'` +(as stated by the lemma `freeable_sz_full` in `theories/typing/own.v`). + +### The type-spec rule + +Now let's prove the type-spec rule for the function `vec_new`. +``` +Lemma vec_new_type {𝔄} (ty: type 𝔄) : + typed_val vec_new (fn(∅) → vec_ty ty) (λ post _, post []). +``` +This type-spec rule allows you to use `vec_new` as a *function object* of +type `fn(∅) → vec_ty ty`, which takes no input (you can ignore the `∅` part) +and outputs a *box pointer* to a vector `vec_ty ty`. +Its representation value is a *predicate transformer* spec +`(λ post _, post []) : (list 𝔄 → Prop) → *[] → Prop`, +which just returns the empty list `[]: list 𝔄`. + +### Proving the type-spec rule + +Proof of this lemma can go as follows. +``` +Proof. + eapply type_fn; [apply _|]. intros _ ϝ k []. simpl_subst. + iIntros (tid [] postπ) "_ #TIME _ _ _ Na L C _ Obs". + (* new [ #3] *) + wp_apply wp_new; [done..|]. iIntros (r). + rewrite !heap_mapsto_vec_cons !shift_loc_assoc. + iIntros "[† (↦₀ & ↦₁ & ↦₂ &_)]". wp_let. + (* "r" <- new [ #0];; *) + iMod persistent_time_receipt_0 as "⧖". wp_bind (new _). + iApply (wp_persistent_time_receipt with "TIME ⧖"); [done|]. + iApply wp_new; [done..|]. iIntros "!>" (l) "[†' _] ⧖". + wp_bind (_ <- _)%E. + iApply (wp_persistent_time_receipt with "TIME ⧖"); [done|]. + wp_write. iIntros "⧖". wp_seq. + (* "r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; *) + wp_op. wp_write. wp_op. wp_write. do 2 wp_seq. + (* continuation *) + rewrite cctx_interp_singleton. + iApply ("C" $! [# #r] -[const []] with "Na L [-Obs] Obs"). + iSplit; [|done]. iExists #r, 2. do 2 (iSplit; [done|]). + rewrite/= freeable_sz_full. iFrame "†". iNext. + (* constructing the vector *) + iExists [ #l; #0; #0]. + rewrite !heap_mapsto_vec_cons !shift_loc_assoc heap_mapsto_vec_nil. + iFrame "↦₀ ↦₁ ↦₂". iExists l, 0, 0, [#]. + iSplit; [done|]. iFrame "†'". iSplit; [by iNext|]. + iExists []. by rewrite heap_mapsto_vec_nil. +Qed. +``` + +The first trick is `eapply type_fn; [apply _|]`. +This makes the goal into the following form. +``` +∀(_: ()) (ϝ: lft) (k: val) (_: *[]), ⊢ typed_body ... +``` +We have some Coq-level inputs here. +* The first input `_: ()` is the parameter of the function, which is just +the unit for this function. +* The second input `ϝ: lft` is the *lifetime of the function* (`ϝ` is digamma). +* The third input `k: val` is the *continuation*. + In λRust, a function is in the continuation-passing style, and the `return: [e]` + directive actually calls the continuation function with the return value `e`. + The continuation function is internally bound to a special variable `"return"`. + Here we get the actual function value `k: val` of the continuation. +* The fourth input `_: *[]` is the list of *clairvoyant input values*. + In this case, it is empty. + +We perform `intros _ ϝ k []` to introduce these inputs. +We perform `simpl_subst` to complete substitution of `"return"` into `k`. + +Now the goal is as follows: +``` +⊢ typed_body + (fp_E {|fp_E_ex := ∅; fp_ityl := +[]; fp_oty := vec_ty ty|} ϝ) + [ϝ ⊑ₗ []] + [k ◁cont{[ϝ ⊑ₗ []], λ v: vec val 1, +[vhd v ◁ box (vec_ty ty)]} tr_ret] + +[] + (let: "r" := new [#3] in + "r" <- new [#0];; "r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; + jump: k ["r"]) + (λ (post: list 𝔄 → Prop) (_: *[]), post []) +``` +The sequent `⊢ P : Prop` embeds an Iris proposition `P: iProp Σ` into +a pure proposition. +The *Iris* predicate `typed_body` (`typing/programs.v`) takes several arguments. +1. The first argument `fp_E ... ϝ` is the external lifetime context. You can + usually ignore it. +2. The second argument `[ϝ ⊑ₗ []]` is the local lifetime +3. The third argument + `[k ◁cont{[ϝ ⊑ₗ []], λ v: vec val 1, +[vhd v ◁ box (vec_ty ty)]} tr_ret]` + is the continuation context. + The argument `v` is the continuation's argument list, which consists of + only one value `vhd v`. + We return a box pointer to a vector `box (vec_ty ty)`. + The spec of the output is `tr_ret`, which is shorthand for + `λ post '-[a], post a`. +4. The fourth argument `+[]` is the input type context. + For this function, it is empty. +5. The fifth argument `let: "r" := ... in ...` is the executed expression. + Note that `return: ["r"]` has been updated into `jump: k ["r"]`. +6. The sixth argument `λ (post: list 𝔄 → Prop) (_: *[]), post [])` + is the predicate transformer spec. + +The `typed_body` goal unfolds into the following: +``` +⊢ ∀ tid (pπl: *[]) (postπ : proph (list 𝔄 → Prop)), + lft_ctx -∗ time_ctx -∗ proph_ctx -∗ uniq_ctx -∗ + elctx_interp (fp_E ... ϝ) -∗ na_own tid ⊤ -∗ llctx_interp [ϝ ⊑ₗ []] 1 -∗ + cctx_interp tid postπ [k ◁cont{...} tr_ret] -∗ + tctx_interp tid +[] pπl -∗ ⟨π, postπ π []⟩ -∗ + WP let: "r" := new [#3] in + "r" <- new [#0];; "r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; + jump: k ["r"] {{ _, True }} +``` +By `iIntros (tid [] postπ) "_ #TIME _ _ _ Na L C _ Obs"`, +the goal turns into the following. +``` +"TIME" : time_ctx +--------------------------------------□ +"Na" : na_own tid ⊤ +"L" : llctx_interp [ϝ ⊑ₗ []] 1 +"C" : cctx_interp tid postπ [k ◁cont{...} tr_ret] +"Obs" : ⟨π, postπ π []⟩ +--------------------------------------∗ +WP let: "r" := new [#3] in + "r" <- new [#0];; "r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; + jump: k ["r"] {{ _, True }} +``` + +#### **Execute `new [#3]`** + +We first execute `new [#3]` by `wp_apply wp_new; [done..|]`. +This updates the goal into the following. +``` +... +--------------------------------------∗ +∀ r: loc, + freeable_sz' 3 r ∗ r ↦∗ [#☠; #☠; #☠] -∗ + WP let: "r" := #r in + "r" <- new [#0];; "r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; + jump: k ["r"] {{ _, True }} +``` +Here, `r` is the return value of `new [#3]`. +You get the right to free the memory block `freeable_sz' 3 r` and the +points-to token `r ↦∗ [#☠; #☠; #☠]`. +You first do `iIntros (r)`. +By `rewrite !heap_mapsto_vec_cons !shift_loc_assoc`, +you can destruct `r ↦∗ [#☠; #☠; #☠]` into three points-to tokens to +each location of the memory block. +By `iIntros "[† (↦₀ & ↦₁ & ↦₂ &_)]"`, you can name the resources. +By `wp_let`, you can resolve `let: "r" := #r in`, replacing `"r"` with `#r`. + +#### **Execute `"r" <- new [ #0];;`** + +Now the goal is as follows. +``` +... +"Obs" : ⟨π, postπ π []⟩ +"†" : freeable_sz' 3 r +"↦₀" : r ↦ #☠ +"↦₁" : (r +ₗ 1) ↦ #☠ +"↦₂" : (r +ₗ (1 + 1)) ↦ #☠ +--------------------------------------∗ +WP #r <- new [#0];; #r +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;; + jump: k ["r"] {{ _, True }} +``` +Now you want to construct a time receipt `⧖2` for the returned object. +You can get `⧖0` for free by `iMod persistent_time_receipt_0 as "⧖0"`; +this adds the hypothesis: +``` +... +"⧖0" : ⧖0 +--------------------------------------∗ +... +``` +You can grow up a time receipt by 1 for each program step. +Let's use the two program steps of `#r <- new [#0]`. +You first do `wp_bind (new _)`, which updates the goal into: +``` +... +"⧖0" : ⧖0 +--------------------------------------∗ +WP new [#0] {{ v, + WP #r <- v;; #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; + jump: k [#r] {{ _, True }} +}} +``` +We perform `iApply (wp_persistent_time_receipt with "TIME ⧖0"); [done|]` +to grow `⧖0` into `⧖1`. +Now we have the goal: +``` +... +--------------------------------------∗ +WP new [#0] @ ⊤ ∖ ↑timeN {{ v, + ⧖1 -∗ WP #r <- v;; #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; + jump: k [#r] {{ _, True }} +}} +``` +Here, `@ ⊤ ∖ ↑timeN` is the mask (this prevents us from using the `timeN` +invariant multiple times at the program step; you can usually ignore it). +By `iApply wp_new; [done..|]`, we can execute `new [#0]`, +which turns the goal into: +``` +... +--------------------------------------∗ +▷ ∀ l: loc, freeable_sz' 0 l ∗ l ↦∗ [] -∗ ⧖1 -∗ + WP #r <- l;; #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; + jump: k [#r] {{ _, True }} +``` +By `iIntros "!>" (l) "[†' _] ⧖1"`, the goal turns into: +``` +... +"†'" : freeable_sz' 0 l +"⧖1" : ⧖1 +--------------------------------------∗ +WP #r <- l;; #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; + jump: k [#r] {{ _, True }} +``` +Similarly, you perform `wp_bind (_ <- _)%E` and +`iApply (wp_persistent_time_receipt with "TIME ⧖1"); [done|]`. +``` +... +"↦₀" : r ↦ #☠ +... +--------------------------------------∗ +WP #r <- #l @ ⊤ ∖ ↑timeN {{ v, + ⧖2 -∗ WP #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; + jump: k [#r] {{ _, True }} +}} +``` +By `wp_write`, we perform `#r <- #l`, which writes the value `#l` to the +location `r`, updating the goal into the following. +``` +... +"↦₀" : r ↦ #l +... +--------------------------------------∗ +⧖2 -∗ +WP #☠;; #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; jump: k [#r] {{ _, True }} +``` +Here, `#☠` is the return value of the operation, which is unusable +(as indicated by the mark `☠`). +By `iIntros "⧖2"` and `wp_seq`, the goal turns into: +``` +... +"⧖2" : ⧖2 +--------------------------------------∗ +WP #r +ₗ #1 <- #0;; #r +ₗ #2 <- #0;; jump: k [#r] {{ _, True }} +``` + +#### **Execute `"r" +ₗ #1 <- #0;; "r" +ₗ #2 <- #0;;`** + +Now let's perform the remaining writes. +By `wp_op`, we can perform the location offset operation `+ₗ`: +``` +... +--------------------------------------∗ +WP #(r +ₗ 1) <- #0;; #r +ₗ #2 <- #0;; jump: k [#r] {{ _, True }} +``` +Here, `+ₗ` in `r +ₗ 1` is a Coq-level operation, whereas `+ₗ` in `#r +ₗ #1` +was a connective in λRust. +By `wp_write` we perform the write to `r +ₗ 1`, updating `"↦₁"`. +Similarly, by `wp_op` and `wp_write`, we perform the write to `r +ₗ 2`, +updating `"↦₂"`. +We perform `do 2 wp_seq` to perform extra program steps included in +`jump: k [#r]`. + +#### **Call the continuation** + +Now the goal is as follows. +``` +"Na" : na_own tid ⊤ +"L" : llctx_interp [ϝ ⊑ₗ []] 1 +"C" : cctx_interp tid postπ [k ◁cont{...} tr_ret] +"Obs" : ⟨π, postπ π []⟩ +"†" : freeable_sz' 3 r +"↦₀" : r ↦ #l +"↦₁" : (r +ₗ 1) ↦ #0 +"↦₂" : (r +ₗ 2) ↦ #0 +"†'" : freeable_sz' 0 l +"⧖2" : ⧖2 +--------------------------------------∗ +WP k [#r] {{ _, True }} +``` +By performing `rewrite cctx_interp_singleton`, +you can modify `"C"` into `cctx_elt_interp tid postπ (k ◁cont{...} tr_ret)`, +which unfolds into the following: +``` +∀ (vl: vec val 1) (pπl: *[list 𝔄]), + na_own tid ⊤ -∗ llctx_interp [ϝ ⊑ₗ []] 1 -∗ + tctx_interp tid +[vhd vl ◁ box (vec_ty ty)] pπl -∗ + ⟨π, tr_ret (postπ π) (pπl -$ π)⟩ -∗ + WP k (map of_val vl) {{ _, True }} +``` +Setting `vl` to `[# #r]` and `pπl` to `-[const []]`, this simplifies into +the following: +``` +na_own tid ⊤ -∗ llctx_interp [ϝ ⊑ₗ []] 1 -∗ +tctx_interp tid +[#r ◁ box (vec_ty ty)] pπl -∗ +⟨π, postπ π []⟩ -∗ +WP k [#r] {{ _, True }} +``` +So we can apply `"C"` to the goal. +The premises `na_own tid ⊤` and `llctx_interp [ϝ ⊑ₗ []] 1` can be resolved +by `"Na"` and `"L"`. +The premise `⟨π, postπ π []⟩` can be resolved by `"Obs"`. +So the remaining goal is the output type context +`tctx_interp tid +[#r ◁ box (vec_ty ty)] pπl`. +For that, you can perform the tactic: +``` +iApply ("C" $! [# #r] -[const []] with "Na L [-Obs] Obs"). +``` +After `$!` are the pure-level arguments `vl := [# #r]` and +`pπl := -[const []]`. +For the output type context goal, we specify `[-Obs]`, +which allows us to use all the resources we have other than `"Obs"`. + +Now we have the following goal: +``` +"†" : freeable_sz' 3 r +"↦₀" : r ↦ #l +"↦₁" : (r +ₗ 1) ↦ #0 +"↦₂" : (r +ₗ 2) ↦ #0 +"†'" : freeable_sz' 0 l +"⧖2" : ⧖2 +--------------------------------------∗ +tctx_interp tid +[#r ◁ box (vec_ty ty)] -[const []] +``` +The main goal simplifies into +`tctx_elt_interp tid (#r ◁ box (vec_ty ty)) (const []) ∗ True`. +You can remove `∗ True` by `iSplit; [|done]`. +The main goal `tctx_elt_interp ...` further simplifies into: +``` +∃ (v: val) (d: nat), + ⌜eval_path #r = Some v⌝ ∗ ⧖d ∗ + ty_own (box (vec_ty ty)) (const []) d tid [v] +``` +You set `v := #r` and `d := 2` by `iExists #r, 2`. +You can resolve `⌜eval_path #r = Some v⌝` and `⧖d` by `do 2 (iSplit; [done|])`. +Now the main goal is `ty_own (box (vec_ty ty)) (const []) 2 tid [#r]`, which +simplifies into: +``` +▷ (∃ vl, r ↦∗ vl ∗ ty_own (vec_ty ty) (const []) 2 tid vl) ∗ +freeable_sz 3 3 r +``` +By `rewrite/= freeable_sz_full`, `freeable_sz 3 3 r` turns into `freeable_sz' 3 r`. +So you can frame out `†`, using `iFrame "†"`. +You can remove the later modality `▷` by `iNext`. + +#### **Construct the vector** + +You set `vl := [#l; #0; #0]` by `iExists [ #l; #0; #0]`. +You can destruct the goal's `r ↦∗ [#l; #0; #0]` by +`rewrite !heap_mapsto_vec_cons !shift_loc_assoc heap_mapsto_vec_nil`. +Then you can frame out `"↦₀"`, `"↦₁"` and `"↦₂"` by `iFrame "↦₀ ↦₁ ↦₂"`. + +Now the main goal is just `ty_own (vec_ty ty) (const []) 2 tid vl`, +which simplifies into: +``` +∃ (l0 : loc) (len ex : nat) (aπl : vec (proph 𝔄) len), + ⌜[#l; #0; #0] = [#l0; #len; #ex] ∧ const [] = lapply aπl⌝ ∗ + ▷ ([∗ list] i↦aπ ∈ aπl, (l0 +ₗ[ty] i) ↦∗: ty_own ty aπ 0 tid) ∗ + (l0 +ₗ[ty] len) ↦∗len ex * ty.(ty_size) ∗ + freeable_sz' ((len + ex) * ty_size ty) l0 +``` +You set `l0 := l`, `len := 0`, `ex := 0`, and `aπl := [#]` +(the empty list) by `iExists l, 0, 0, [#]`. +Now the main goal simplifies into: +``` +⌜[#l; #0; #0] = [#l; #0; #0] ∧ const [] = lapply []⌝ ∗ +▷ True ∗ (l +ₗ[ty] 0) ↦∗len 0 ∗ freeable_sz' 0 l +``` +The pure proposition can be resolved by `iSplit; [done|]`. +You can frame out `"†'"` by `iFrame "†'"`. +The `▷ True` part can be resolved by `iSplit; [by iNext|]`. +Finally, the main goal simplifies into: +``` +∃ vl, ⌜length vl = 0⌝ ∗ (l +ₗ 0) ↦∗ vl +``` +You can resolve it by `iExists []` and `by rewrite heap_mapsto_vec_nil`. + +## Verifying swap via mutable references `mem::swap` + +Now let's verify a simple function that handle *mutable borrows*, +`mem::swap`. +The function takes two mutable references of type `&'a mut T` +and swaps the contents of the two. +(Rather surprisingly, it cannot be implemented within safe code in Rust, +due to the limit of Rust's automated ownership/borrow check.) + +### Implementation in λRust + +Here is the implementation of the function in λRust (`typing/lib/swap.v`). +``` +Definition swap {𝔄} (ty: type 𝔄) : val := + fn: ["ba"; "bb"] := + let: "a" := !"ba" in let: "b" := !"bb" in + delete [ #1; "ba"];; delete [ #1; "bb"];; + lang.lib.swap.swap ["a"; "b"; #ty.(ty_size)];; + let: "r" := new [ #0] in return: ["r"]. +``` + +The inputs `"ba"` and `"bb"` are respectively a *box pointer* to a mutable +reference, because for simplicity a function's inputs is always *boxed* in our +type-spec system. +The mutable references `"a"` and `"b"` can be taken by dereferencing them +(`!"ba"` and `!"bb"`), +and then we can delete the memory blocks for the box pointers +(`delete [ #1; "ba"]` and `delete [ #1; "bb"]`). + +Now we swap the values of `"a"` and `"b"`'s memory regions. +The size of the memory regions is respectively `ty.(ty_size)`, +where `ty` is the target type of the mutable references. +We can perform this (low-level) swap by `lang.lib.swap.swap` (`lang/lib/swap.v`), +which satisfies the following Hoare triple law (the lemma `wp_swap`). +``` + Z.of_nat (length vl1) = n → Z.of_nat (length vl2) = n → + {{{ l1 ↦∗ vl1 ∗ l2 ↦∗ vl2 }}} + swap [ #l1; #l2; #n] @ E + {{{ RET #☠; l1 ↦∗ vl2 ∗ l2 ↦∗ vl1 }}} +``` + +Finally, we should return a box pointer to a unit (because the type-spec +system expects some boxed output, again for simplicity). +So we do `let: "r" := new [ #0] in return: ["r"]`. + +### The type-spec rule + +The lemma for the type-spec rule of `swap` is as follows. +``` +Lemma swap_type {𝔄} (ty: type 𝔄) : + typed_val (swap ty) (fn<α>(∅; &uniq{α} ty, &uniq{α} ty) → ()) + (λ post '-[(a, a'); (b, b')], a' = b → b' = a → post ()). +``` +The function inputs two mutable references typed `&uniq{α} ty` and +outputs a unit `()` (where the inputs and output are actually boxed, +as we saw earlier). +The function has the lifetime parameter `α`. + +Let's focus on the predicate transformer spec: +``` +λ post '-[(a, a'); (b, b')], a' = b → b' = a → post () +``` +The mutable references are respectively represented by a *pair* of +values, `(a, a')` / `(b, b')`. +Here, `a` / `b` is the target object's *current* representation value, +whereas `a'` / `b'` is the *prophecy* for the target object's *final* +representation value. + +Because you *drop* the input mutable references, you *resolve* +the prophecies `a'` and `b'` to the final values, which are +`b` and `a`, respectively, as the result of swap. +So we get equalities `a' = b` and `b' = a` as *postconditions*, +which are appended to `post ()` by *implication* `→`. +(Recall that the predicate transformer returns the *precondition*; +hence we use *implication* to weaken the precondition.) + +### Proving the type-spec rule + +Proof of this lemma can go as follows. +``` +Proof. + eapply type_fn; [apply _|]. + intros α ϝ k (w & w' &[]). simpl_subst. + (* destruct typed_body *) + iIntros (tid (pπ & pπ' &[]) postπ) + "/= #LFT TIME PROPH #UNIQ #E Na L C (ba & bb &_) #Obs". + rewrite/= !tctx_hasty_val. + (* destruct box pointers *) + iDestruct "ba" as ([|da']) "[_ box]"=>//. + iDestruct "bb" as ([|db']) "[_ box']"=>//. + case w as [[|bl|]|]=>//. case w' as [[|bl'|]|]=>//=. + rewrite !split_mt_uniq_bor. + iDestruct "box" as "[[#In mut] †ba]". + iDestruct "box'" as "[[_ mut'] †bb]". + iDestruct "mut" as (l d ξi) "(>[% %Eq] & >↦ba & Vo & Bor)". + iDestruct "mut'" as (l' d' ξi') "(>[% %Eq'] & >↦bb & Vo' & Bor')". + (* let: "a" := !"ba" in let: "b" := !"bb" in *) + wp_read. wp_let. wp_read. wp_let. + (* delete [ #1; "ba"];; delete [ #1; "bb"];; *) + rewrite -!heap_mapsto_vec_singleton !freeable_sz_full. + wp_apply (wp_delete with "[$↦ba $†ba]"); [done|]. iIntros "_". wp_seq. + wp_apply (wp_delete with "[$↦bb $†bb]"); [done|]. iIntros "_". + (* get access to the borrows's contents *) + iMod (lctx_lft_alive_tok α with "E L") as (q) "([α α₊] & L & ToL)"; + [solve_typing..|]. + iMod (bor_acc with "LFT Bor α") as "[big ToBor]"; [done|]. + iMod (bor_acc with "LFT Bor' α₊") as "[big' ToBor']"; [done|]. wp_seq. + iDestruct "big" as (vπ dx) "(#⧖ & Pc & %vl & ↦ & ty)". + iDestruct "big'" as (vπ' dx') "(#⧖' & Pc' & %vl' & ↦' & ty')". + iDestruct (uniq_agree with "Vo Pc") as %[<-<-]. + iDestruct (uniq_agree with "Vo' Pc'") as %[<-<-]. + (* perform swap *) + iDestruct (ty_size_eq with "ty") as %?. + iDestruct (ty_size_eq with "ty'") as %?. + wp_apply (wp_swap with "[$↦ $↦']"); [lia..|]. iIntros "[↦ ↦']". wp_seq. + (* perform update *) + iMod (uniq_update with "UNIQ Vo Pc") as "[Vo Pc]"; [done|]. + iMod (uniq_update with "UNIQ Vo' Pc'") as "[Vo' Pc']"; [done|]. + (* close borrows and L' *) + iMod ("ToBor" with "[Pc ↦ ty']") as "[Bor α]". + { iNext. iExists _, _. iFrame "⧖' Pc". iExists _. iFrame. } + iMod ("ToBor'" with "[Pc' ↦' ty]") as "[Bor' α₊]". + { iNext. iExists _, _. iFrame "⧖ Pc'". iExists _. iFrame. } + iMod ("ToL" with "[$α $α₊] L'") as "L". + (* wrap up *) + set qπ := λ π, ((pπ' π).1, (pπ π).2). + set qπ' := λ π, ((pπ π).1, (pπ' π).2). + iApply (type_type +[#l ◁ &uniq{α} ty; #l' ◁ &uniq{α} ty] -[qπ; qπ'] + with "[] LFT TIME PROPH UNIQ E Na L C [-] []"). + - iApply type_new; [done|]. intro_subst. + iApply type_jump; [solve_typing|solve_extract|solve_typing]. + - iSplitL "Vo Bor"; [|iSplitL; [|done]]. + + rewrite (tctx_hasty_val #l). iExists (S d'). + iFrame "⧖' In". iExists d', ξi. + move: Eq. rewrite (proof_irrel (prval_to_inh (fst ∘ pπ)) + (prval_to_inh (fst ∘ qπ)))=> ?. + by iFrame. + + rewrite (tctx_hasty_val #l'). iExists (S d). + iFrame "⧖ In". iExists d, ξi'. + move: Eq'. rewrite (proof_irrel (prval_to_inh (fst ∘ pπ')) + (prval_to_inh (fst ∘ qπ')))=> ?. + by iFrame. + - iApply proph_obs_impl; [|done]=>/= π. by case (pπ π), (pπ' π). +Qed. +``` + +First, you do magical `eapply type_fn; [apply _|]`, and the goal turns into: +``` +∀ (α ϝ: lft) (k: val) (wl: *[val; val]), + ⊢ typed_body ... +``` +Here, `α` is the lifetime parameter and `wl` is the list of input low-level values. +So you can introduce the Coq-level values by `intros α ϝ k (w & w' &[])`, +destructing `wl` into `-[w; w']`. +You do `simpl_subst` to substitute the low-level values `k`, `w`, `w'`. + +#### **Destruct `typed_body`** + +Now the goal is as follows: +``` +⊢ typed_body (fp_E ... ϝ) [ϝ ⊑ₗ []] + [k ◁cont{[ϝ ⊑ₗ []], λ v: vec val 1, +[vhd v ◁ box ()]} tr_ret] + +[w ◁ box (&uniq{α} ty); w' ◁ box (&uniq{α} ty)] + (let: "a" := !w in let: "b" := !w' in + delete [#1; w];; delete [#1; w'];; + lib.swap.swap ["a"; "b"; #(ty_size ty)];; + let: "r" := new [#0] in jump: k ["r"]) + (λ (post: () → Prop) '-[(a, a'); (b, b')], a' = b → b' = a → post ()) +``` +You can destruct `typed_body` by: +``` +iIntros (tid (pπ & pπ' &[]) postπ) + "/= #LFT TIME PROPH #UNIQ #E Na L C (ba & bb &_) #Obs". +``` +Here, `pπ` and `pπ'` are the clairvoyant *pair* that represents each of +the two mutable references. +The Iris resources `"ba"` and `"bb"` are items of the input type context. +By `rewrite/= !tctx_hasty_val`, you further update `"ba"` and `"bb"`. + +#### **Destruct the box pointers** + +Now the goal is as follows. +``` +"LFT" : lft_ctx +"UNIQ" : uniq_ctx +"E" : elctx_interp (fp_E ... ϝ) +"Obs" : ⟨π, let '(a, a') := pπ π in let '(b, b') := pπ' π in + a' = b → b' = a → postπ π ()⟩ +--------------------------------------□ +"TIME" : time_ctx +"PROPH" : proph_ctx +"Na" : na_own tid ⊤ +"L" : llctx_interp [ϝ ⊑ₗ []] 1 +"C" : cctx_interp tid postπ [k ◁cont{...} tr_ret] +"ba" : ∃ do, ⧖do ∗ ty_own (box (&uniq{α} ty)) pπ dd tid [w] +"bb" : ∃ do', ⧖do' ∗ ty_own (box (&uniq{α} ty)) pπ' dd' tid [w'] +--------------------------------------∗ +WP let: "a" := !w in let: "b" := !w' in + delete [#1; w];; delete [#1; w'];; + lib.swap.swap ["a"; "b"; #(ty_size ty)];; + let: "r" := new [#0] in jump: k ["r"] {{ _, True }} +``` +Let's destruct the boxes of `"ba"` and `"bb"`. + +By `iDestruct "ba" as ([|di]) "[_ box]"; [done|]`, +you can destruct the depth `do` under `"ba"`'s `∃` into `S di` and +take out `ty_own ... [w]` as `"box"`. +(The case `do := 0` is resolved by `done`.) +Similarly, you do `iDestruct "bb" as ([|di']) "[_ box]"; [done|]`. + +By `case w as [[|bl|]|]=>//=`, you destruct `w` into `#bl` +for the location `bl: loc` +(the other cases for `w` are resolved by `//`). +Similarly, you do `case w' as [[|bl'|]|]=>//=`. +Now you have "unlocked" the box pointers! +You can further simplify the Iris propositions by `rewrite !split_mt_uniq_bor`. + +Now `"box"` and `"box'"` are as follows. +``` +"box" : ▷ (α ⊑ ty_lft ty + ∗ (∃ (l: loc) (d: nat) (ξi: positive), ...)) + ∗ freeable_sz 1 1 bl +"box'" : ▷ (α ⊑ ty_lft ty + ∗ (∃ (l': loc) (d': nat) (ξi': positive), ...)) + ∗ freeable_sz 1 1 bl' +``` +You destruct `"box"` by `iDestruct "box" as "[[#In mut] †ba]"`. +Similarly, you do `iDestruct "box'" as "[[_ mut'] †bb]"`. +You destruct `"mut"` by +`iDestruct "mut" as (l d ξi) "(>[% %Eq] & >↦ba & Vo & Bor)"`. +Similarly, you do +`iDestruct "mut'" as (l' d' ξi') "(>[% %Eq'] & >↦bb & Vo' & Bor')"`. + +#### **Execute the preliminary code** + +Now the premises are as follows: +``` +... +"↦ba" : bl ↦ #l +"Vo" : ▷ .VO[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] (fst ∘ pπ) d +"Bor" : ▷ &{α} ... +"†ba" : freeable_sz 1 1 bl +"↦bb" : bl' ↦ #l' +"Vo'" : ▷ .VO[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ')) ξi'] (fst ∘ pπ') d' +"Bor'" : ▷ &{α} ... +"†bb" : freeable_sz 1 1 bl' +``` +By `wp_read` and `wp_let`, you can execute `let: "a" := !"ba" in`. +Similarly, you do `wp_read` and `wp_let` again to execute `let: "b" := !"bb" in`. +The later modality `▷` on `"Vo"`/`"Vo'"` and `"Bor"`/`"Bor'"` disappears +after a program step passes. + +Now let's execute `delete [#1; "ba"];; delete [#1; "bb"];;`. +You first do `rewrite -!heap_mapsto_vec_singleton !freeable_sz_full` +to modify `"↦ba"`/`"↦bb"` and `"†ba"`/`"†bb"`. +Now you can execute `delete [#1; "ba"]` by +`wp_apply (wp_delete with "[$↦ba $†ba]"); [done|]` and `iIntros "_"`. +This consumes `"↦ba"` and `"†ba"` (because of framing by `$`). +You execute `#☠;;` by `wp_seq` (where `☠` is the unusable return value of +`delete`). +Similarly you do `wp_apply (wp_delete with "[$↦bb $†bb]"); [done|]` and +`iIntros "_"`. + +#### **Get access to the borrows's contents** + +Now the goal is as follows. +``` +"Vo" : .VO[PrVar (𝔄 ↾ ...) ξi] (fst ∘ pπ) d +"Bor" : &{α} (∃ (vπ: proph (𝔄 ↾ ...)) (dx: nat), + ⧖(S dx) ∗ .PC[PrVar (𝔄 ↾ ...) ξi] vπ dx ∗ + l ↦∗: ty_own ty vπ dx tid) +"Vo'" : .VO[PrVar (𝔄 ↾ ...) ξi'] (fst ∘ pπ') d' +"Bor'" : &{α} (∃ (vπ': proph (𝔄 ↾ ...)) (dx': nat), + ⧖(S dx') ∗ .PC[PrVar (𝔄 ↾ ...) ξi'] vπ' dx' ∗ + l' ↦∗: ty_own ty vπ’ dx' tid) +--------------------------------------∗ +WP #☠;; lib.swap.swap [#l; #l'; #(ty_size ty)] ;; + let: "r" := new [#0] in jump: k ["r"] {{ _, True }} +``` +You want to get access to the contents of the borrows `"Bor"` and `"Bor'"`. + +You first need the *lifetime tokens* for the lifetime `α`. +You can do so by the following tactic, which uses the lemma `lctx_lft_alive_tok`: +``` +iMod (lctx_lft_alive_tok α with "E L") as (q) "([α α₊] & L & ToL)"; + [solve_typing..|]. +``` +Now you get the following: +``` +"α" : (q / 2).[α] +"α₊" : (q / 2).[α] +"L'" : llctx_interp [ϝ ⊑ₗ []] q +"ToL" : q.[α] -∗ llctx_interp [ϝ ⊑ₗ []] q ={⊤}=∗ llctx_interp [ϝ ⊑ₗ []] 1 +``` + +You can get access to the borrow `"Bor"`'s contents by the lifetime logic's law +`bor_acc` (in `lifetime/lifetime.v`; called LftL-bor-acc in the paper). +More specifically, you do the following tactic: +``` +iMod (bor_acc with "LFT Bor α") as "[big ToBor]"; [done|]`. +``` +Out of the full borrow `"Bor" : &{α} P` and the lifetime token `(q / 2).[α]`, +you can get the borrow's content `"big" : ▷ P` and +the resource for closing the borrow `"ToBor" : ▷ P ={⊤}=∗ &{α} P ∗ (q / 2).[α]`. +Similarly, you do +`"iMod (bor_acc with "LFT Bor' α₊") as "[big' ToBor']"; [done|]"`. +You can do `wp_seq` here to execute `#☠;;` and remove the later modality on +`"big"`/`"big'"`. + +Now you get the following: +``` +"big" : ∃ (vπ: proph (𝔄 ↾ ...)) (dx: nat), + ⧖(S dx) ∗ .PC[PrVar (𝔄 ↾ ...) ξi] vπ dx ∗ + l ↦∗: ty_own ty vπ dx tid +"ToBor" : ▷ (∃ (vπ: proph (𝔄 ↾ ...)) (dx: nat), + ⧖(S dx) ∗ .PC[PrVar (𝔄 ↾ ...) ξi] vπ dx ∗ + l ↦∗: ty_own ty vπ dx tid) + ={⊤}=∗ &{α} (∃ (vπ: proph (𝔄 ↾ ...)) (dx: nat), + ⧖(S dx) ∗ .PC[PrVar (𝔄 ↾ ...) ξi] vπ dx ∗ + l ↦∗: ty_own ty vπ dx tid) ∗ + (q / 2).[α] +"big'" : ∃ (vπ': proph (𝔄 ↾ ...)) (dx': nat), + ⧖(S dx') ∗ .PC[PrVar (𝔄 ↾ ...) ξi'] vπ' dx' ∗ + l' ↦∗: ty_own ty vπ’ dx' tid +"ToBor'" : ▷ (∃ (vπ': proph (𝔄 ↾ ...)) (dx': nat), + ⧖(S dx') ∗ .PC[PrVar (𝔄 ↾ ...) ξi'] vπ' dx' ∗ + l' ↦∗: ty_own ty vπ’ dx' tid) + ={⊤}=∗ &{α} (∃ (vπ': proph (𝔄 ↾ ...)) (dx': nat), + ⧖(S dx') ∗ .PC[PrVar (𝔄 ↾ ...) ξi'] vπ' dx' ∗ + l' ↦∗: ty_own ty vπ’ dx' tid) ∗ + (q / 2).[α] +``` + +Now you destruct `"big"` by +`iDestruct "big" as (vπ dx) "(#⧖ & Pc & %vl & ↦ & ty)"`. +Similarly you do +`iDestruct "big'" as (vπ' dx') "(#⧖' & Pc' & %vl' & ↦' & ty')"`. + +By agreement between `"Vo"` and `"Pc"` (by `uniq_agree`), +you can get `fst ∘ pπ = vπ` and `d = dx`, +so you do replacements `vπ -> fst ∘ pπ` and `dx -> d`. +This can be done by the tactic +`iDestruct (uniq_agree with "Vo Pc") as %[<-<-]`. +Similarly, you do +`iDestruct (uniq_agree with "Vo' Pc'") as %[<-<-]`. + +Now the goal is as follows. +``` +"Pc" : .PC[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] + (fst ∘ pπ) d +"↦" : l ↦∗ vl +"ty" : ty_own ty (fst ∘ pπ) d tid vl +"ToBor" : ... +"Pc'" : .PC[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ')) ξi'] + (fst ∘ pπ') d' +"↦'" : l' ↦∗ vl' +"ty'" : ty_own ty (fst ∘ pπ') d' tid vl' +"ToBor'" : ... +``` + +Thanks to `"ty"` and the lemma `ty.(ty_size_eq)`, you can know +`length vl = ty.(ty_size)`, through the tactic +`iDestruct (ty_size_eq with "ty") as %?`. +You similarly do `iDestruct (ty_size_eq with "ty'") as %?`. + +#### **Perform swap and update values** + +Now you can perform `lang.lib.swap.swap`, simply by +`wp_apply (wp_swap with "[$↦ $↦']"); [lia..|]`. +By `iIntros "[↦ ↦']"` you get the updated points-to tokens. +By `wp_seq` you execute `#☠;;`. + +Let's also update the values observed by +`"Vo"`/`"Vo'"` and `"Pc"`/`"Pc'"`. +For that, yoi can use the lemma `uniq_update`. +You do `iMod (uniq_update with "UNIQ Vo Pc") as "[Vo Pc]"; [done|]` +and `iMod (uniq_update with "UNIQ Vo' Pc'") as "[Vo' Pc']"; [done|]`. +Here, the updated values are left to be *evars*, existential +variables in Coq. + +#### **Wrap up** + +Now you can close `"ToBor"` using `"Pc"`, `"↦"` and `"ty'"` to retrieve +`"Bor"` and `"α"`, by the tactic +`iMod ("ToBor" with "[Pc ↦ ty']") as "[Bor α]"`. +Here, you use `"ty'"` instead of `"ty"`, because the target objects have +been swapped. +The small goal can be resolved by +`{ iNext. iExists _, _. iFrame "⧖' Pc". iExists _. iFrame. }`. +Similarly, you do `iMod ("ToBor'" with "[Pc' ↦' ty]") as "[Bor' α₊]"` +and `{ iNext. iExists _, _. iFrame "⧖ Pc'". iExists _. iFrame. }`. +Now you can close `ToL` to retrieve `L` by +`iMod ("ToL" with "[$α $α₊] L'") as "L"`. + +The goal is as follows now: +``` +"Vo" : .VO[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] (fst ∘ pπ') d' +"Vo'" : .VO[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ')) ξi'] (fst ∘ pπ) d +"Bor" : &{α} (∃ (vπ': proph (𝔄 ↾ prval_to_inh (fst ∘ pπ))) (dx': nat), + ⧖(S dx') ∗ + .PC[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] vπ' dx' ∗ + l ↦∗: ty_own ty vπ' dx' tid) +"Bor'" : &{α} (∃ (vπ: proph (𝔄 ↾ prval_to_inh (fst ∘ pπ'))) (dx: nat), + ⧖(S dx) ∗ + .PC[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ')) ξi'] vπ dx ∗ + l' ↦∗: ty_own ty vπ dx) +"L" : llctx_interp [ϝ ⊑ₗ []] 1 +--------------------------------------∗ +WP let: "r" := new [#0] in jump: k ["r"] {{ _, True }} +``` +You are reaching the end! + +Let's do `set qπ := λ π, ((pπ' π).1, (pπ π).2)` +and `set qπ' := λ π, ((pπ π).1, (pπ' π).2)`. +You know that you can wrap up by creating the type context +``` ++[#l ◁ &uniq{α} ty; #l' ◁ &uniq{α} ty] +``` +with values `-[qπ; qπ']`. +In that case, you perform the following tactic +to call the key lemma `type_type` (`typing/programs.v`). +``` +iApply (type_type +[#l ◁ &uniq{α} ty; #l' ◁ &uniq{α} ty] -[qπ; qπ'] + with "[] LFT TIME PROPH UNIQ E Na L C [-] []"). +``` +The goal divides into three small goals. + +The first goal is the `typed_body ...` thing. +Here, you can really use type-spec rules. +First, you do `iApply type_new; [done|]` and `intro_subst` +to perform `let: "r" := new [#0] in`, +getting a box pointer to a unit. +Then you return from the function, executing `jump: k ["r"]`. +The type-spec rule for the jump +*automatically drops up all the unreturned objects*, +and when a mutable reference is dropped +*its prophecy is resolved*! +So the following does the work. +``` +iApply type_jump; [solve_typing|solve_extract|solve_typing]. +``` +Here, `solve_typing` and `solve_extract` do some advanced +*automation* in Coq behind the scene. + +The second goal is to actually create the type context, +which consists of the two mutable references. +You can split the goal into the goals for each mutable reference +by the tactic `iSplitL "Vo Bor"; [|iSplitL; [|done]]`. +Let's focus on proof for the first mutable reference, +since proof for the second one just goes similarly. + +The goal is as follows. +``` +... +"In" : α ⊑ ty_lft ty +"⧖" : ⧖(S d) +"⧖'" : ⧖(S d') +--------------------------------------□ +"Vo" : .VO[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] (fst ∘ pπ') d' +"Bor" : &{α} (∃ (vπ': proph (𝔄 ↾ prval_to_inh (fst ∘ pπ))) (dx': nat), + ⧖(S dx') ∗ + .PC[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] vπ' dx' ∗ + l ↦∗: ty_own ty vπ' dx' tid) +--------------------------------------∗ +tctx_elt_interp tid (#l ◁ &uniq{α} ty) qπ +``` +By `rewrite (tctx_hasty_val #l)` and `iExists (S d')`, +the main goal turns into: +``` +⧖(S d') ∗ ty_own (&uniq{α} ty) qπ (S d') tid [#l] +``` +After `iFrame "⧖' In"` and `iExists d', ξi`, the main goal is: +``` +⌜(S d' <= S d') ∧ + snd ∘ qπ = (λ π, π (PrVar (𝔄 ↾ prval_to_inh (fst ∘ qπ)) ξi))⌝ ∗ +.VO[PrVar (𝔄 ↾ prval_to_inh (fst ∘ qπ)) ξi] (fst ∘ qπ) d' ∗ +&{α} (∃ (vπ': proph (𝔄 ↾ prval_to_inh (fst ∘ pπ))) (dx': nat), + ⧖(S dx') ∗ + .PC[PrVar (𝔄 ↾ prval_to_inh (fst ∘ pπ)) ξi] vπ' dx' ∗ + l ↦∗: ty_own ty vπ' dx' tid) +``` +You can end the proof by rewriting +`prval_to_inh (fst ∘ pπ)` into `prval_to_inh (fst ∘ qπ)`! +That is possible using the lemma `proof_irrel` for (axiom-free) +proof irrelevance, because `prval_to_inh ...` is actually a proof +witness of a proposition. +You just need to do +``` +move: Eq. rewrite (proof_irrel (prval_to_inh (fst ∘ pπ)) + (prval_to_inh (fst ∘ qπ)))=> ?. +``` +Now `by iFrame` ends the proof for the first mutable reference. +As mentioned earlier, proof for the second mutable reference just goes similarly. + +Now the remaining goal is: +``` +... +"Obs" : ⟨π, let '(a, a') := pπ π in let '(b, b') := pπ' π in + a' = b → b' = a → postπ π ()⟩ +... +--------------------------------------□ +⟨π, (pπ π).2 = (pπ' π).1 → (pπ' π).2 = (pπ π).1 → postπ π ()⟩ +``` +You can directly use `"Obs"` to get the remaining goal. +However, `done` doesn't work because Coq can't turn directly +equate the pattern matching `let '(a, a') := pπ π in` +with `let a = (pπ π).1 in let a' = (pπ π).2 in`. + +You can use the lemma `proph_obs_impl` to solve a situation like this. +By `iApply proph_obs_impl; [|done]=>/= π`, the goal turns into: +``` +(let '(a, a') := pπ π in let '(b, b') := pπ' π in + a' = b → b' = a → postπ π ()) → +(pπ π).2 = (pπ' π).1 → (pπ' π).2 = (pπ π).1 → postπ π () +``` +You can end the proof by the tactic `by case (pπ π), (pπ' π)`. + +It was a long journey, but now you have proved `swap_type`! +Woo-hoo! -- GitLab