The Iris Tutorial in Lean

5. Specifications🔗

5.1. Introduction🔗

Now that we have seen basic separation logic in Iris and introduced a suitable language, HeapLang, we are finally ready to start reasoning about programs. HeapLang ships with a program logic defined using Iris. We can access the logic through the proof-mode package, which also defines tactics to alleviate working with the logic.

The program logic for HeapLang relies on a basic notion of a resource: the resource of heaps. Recall that GF specifies the available resources. To make the resource of heaps available, we require an instance of HeapLangGS hlc GF throughout this section. We declare it as a variable so that every definition and theorem below has the resource of heaps available.

open Iris Iris.BI Iris.HeapLang namespace Specifications variable {hlc} {GF : BundledGFunctors} [HeapLangGS hlc GF]

5.2. Weakest Precondition🔗

The first construct for specifying program behaviour is the weakest precondition. In Iris, a weakest precondition has the form WP e {{ v, Φ v }}. This asserts that if the HeapLang program e terminates at some value v, then v satisfies the predicate Φ. The double curly brackets are the postcondition.

A natural first example is a pure arithmetic expression. The wp_pure and wp_pures tactics symbolically execute pure steps, including integer arithmetic.

def arith : Exp := hl(#1 + #2 * #3 + #4 + #5) theorem arith_spec : ⊢@{IProp GF} WP arith {{ v, ⌜v = hl_val(#16)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP arith {{ v, ⌜v = hl_val(#16)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl((#1 + ((#2 * #3) + (#4 + #5)))) {{ v, ⌜v = hl_val(#16)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl((#1 + ((#2 * #3) + #(4 + 5)))) {{ v, ⌜v = hl_val(#16)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ |={⊤}=> ⌜hl_val(#(1 + (2 * 3 + (4 + 5)))) = hl_val(#16)⌝ All goals completed! 🐙

The same tactic also handles β-reduction and conditionals:

def boolish : Exp := hl( (λ b, if b then #1 else #0) #true) theorem boolish_spec : ⊢@{IProp GF} WP boolish {{ v, ⌜v = hl_val(#1)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP boolish {{ v, ⌜v = hl_val(#1)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl(let b := #true; if b then #1 else #0) {{ v, ⌜v = hl_val(#1)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ |={⊤}=> ⌜hl_val(#1) = hl_val(#1)⌝ All goals completed! 🐙

The tactic wp_pures symbolically executes all pure steps for which a PureExec instance exists. Each step internally applies the wp_pure_step_fupd rule from Iris.ProgramLogic.Lifting, which says (informally):

e₁ →pure e₂
──────────────────────────────────
WP e₂ {{ Φ }} ⊢ WP e₁ {{ Φ }}

After every pure step has fired, the goal is reduced to proving the postcondition behind a fancy-update modality |={⊤}=>; itrivial discharges the residual proposition ⌜#1 = #1⌝.

The lambda program from the previous chapter combines lambda application with arithmetic:

def lambda : Exp := hl(let add5 := (λ x, x + #5); let double := (λ x, x * #2); let compose := (λ f g, λ x, g (f x)); compose add5 double #5) theorem lambda_spec : ⊢@{IProp GF} WP lambda {{ v, ⌜v = hl_val(#20)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP lambda {{ v, ⌜v = hl_val(#20)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl(let add5 := (λ x, (x + #5)); let double := (λ x, (x * #2)); let compose := (λ f g x, g (f x)); compose add5 double #5) {{ v, ⌜v = hl_val(#20)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ |={⊤}=> ⌜hl_val(#((5 + 5) * 2)) = hl_val(#20)⌝ All goals completed! 🐙

Here is a higher-order example using only β-reduction:

def hofun : Exp := hl(let myId := (λ x, x); let myApply := (λ f x, f x); myApply myId #5) theorem hofun_spec : ⊢@{IProp GF} WP hofun {{ v, ⌜v = hl_val(#5)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hofun {{ v, ⌜v = hl_val(#5)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl(let myId := (λ x, x); let myApply := (λ f x, f x); myApply myId #5) {{ v, ⌜v = hl_val(#5)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ |={⊤}=> ⌜hl_val(#5) = hl_val(#5)⌝ All goals completed! 🐙

5.3. Resources🔗

In this section, we introduce our first notion of a resource: the resource of heaps. As mentioned in the basics chapter, propositions in Iris describe / assert ownership of resources. To describe resources in the resource of heaps, we use the points-to predicate, written l ↦ some v. The value carries an Option because the heap model also tracks deallocated locations: some v means the location currently holds v. Intuitively, l ↦ some v describes all heap fragments that have value v stored at location l. The proposition l1 ↦ some hl_val(#1) ∗ l2 ↦ some hl_val(#2) then describes all heap fragments that map l1 to 1 and l2 to 2.

A running example for this section is

let: "x" := ref #1 in
"x" <- !"x" + #2 ;;
!"x"

which both touches the heap and performs an addition.

def prog : Exp := hl( let x := ref(#1); x ← !x + #2; !x) theorem prog_spec : ⊢@{IProp GF} WP prog {{ v, ⌜v = hl_val(#3)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP prog {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl(let x := ref(#1); x ← (!x + #2); !x) {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#1) ⊢ WP hl(let x := #l; x ← (!x + #2); !x) {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#1) ⊢ WP hl(#l ← (!#l + #2); !#l) {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#1) ⊢ WP hl(#l ← (#1 + #2); !#l) {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#1) ⊢ WP hl(#l ← #(1 + 2); !#l) {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#(1 + 2)) ⊢ WP hl(!#l) {{ v, ⌜v = hl_val(#3)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#(1 + 2)) ⊢ |={⊤}=> ⌜hl_val(#(1 + 2)) = hl_val(#3)⌝ All goals completed! 🐙

The allocation tactic names the fresh location and its points-to hypothesis. The load and store tactics find that hypothesis in the proof-mode context and update it as the program executes.

For comparison, here is a program that allocates and immediately reads.

def writeread : Exp := hl( let x := ref(#7); !x) theorem writeread_spec : ⊢@{IProp GF} WP writeread {{ v, ⌜v = hl_val(#7)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP writeread {{ v, ⌜v = hl_val(#7)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl(let x := ref(#7); !x) {{ v, ⌜v = hl_val(#7)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#7) ⊢ WP hl(let x := #l; !x) {{ v, ⌜v = hl_val(#7)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#7) ⊢ WP hl(!#l) {{ v, ⌜v = hl_val(#7)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#7) ⊢ |={⊤}=> ⌜hl_val(#7) = hl_val(#7)⌝ All goals completed! 🐙

The surrounding wp_pures advances past the let β-reductions.

HeapLang also provides cmpXchg(_, _, _). The wp_cmpxchg tactic creates success and failure branches and names their equality and inequality assumptions.

def cmpXchg_0_to_10 (l : Loc) : Exp := hl(cmpXchg(#l, #0, #10)) theorem cmpXchg_0_to_10_spec (l : Loc) (v : Val) : l ↦ some v -∗ WP (cmpXchg_0_to_10 l) {{ _u, (⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10)) ∨ (⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v) }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:Val⊢ ⊢ l ↦ some v -∗ WP (cmpXchg_0_to_10 l) {{ _u, ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:Val⊢ ∗Hl : l ↦ some v ⊢ WP (cmpXchg_0_to_10 l) {{ _u, ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:Val⊢ ∗Hl : l ↦ some v ⊢ WP hl(cmpXchg(#l, #0, #10)) {{ _u, ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:Val⊢ v.compareSafe hl_val(#0) = truehlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHeq:v = hl_val(#0)⊢ ∗Hl : l ↦ some hl_val(#10) ⊢ |={⊤}=> ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some vhlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHne:v ≠ hl_val(#0)⊢ ∗Hl : l ↦ some v ⊢ |={⊤}=> ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:Val⊢ v.compareSafe hl_val(#0) = true hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:Val⊢ (v.isUnboxed || true) = true All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHeq:v = hl_val(#0)⊢ ∗Hl : l ↦ some hl_val(#10) ⊢ |={⊤}=> ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHeq:v = hl_val(#0)⊢ ∗Hl : l ↦ some hl_val(#10) ⊢ ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHeq:v = hl_val(#0)⊢ ∗Hl : l ↦ some hl_val(#10) ⊢ ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHeq:v = hl_val(#0)⊢ ⊢ ⌜v = hl_val(#0)⌝ All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHne:v ≠ hl_val(#0)⊢ ∗Hl : l ↦ some v ⊢ |={⊤}=> ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHne:v ≠ hl_val(#0)⊢ ∗Hl : l ↦ some v ⊢ ⌜v = hl_val(#0)⌝ ∗ l ↦ some hl_val(#10) ∨ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHne:v ≠ hl_val(#0)⊢ ∗Hl : l ↦ some v ⊢ ⌜v ≠ hl_val(#0)⌝ ∗ l ↦ some v hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Locv:ValHne:v ≠ hl_val(#0)⊢ ⊢ ⌜v ≠ hl_val(#0)⌝ All goals completed! 🐙

The points-to predicate is not duplicable. That is, for every location l, there can only exist one full-fraction points-to associated with it. iris-lean exposes this via the HeapView-level disjointness lemmas; we omit the detailed proof.

5.4. Composing Programs and Proofs🔗

The tactic wp_apply finds the evaluation context in which a specification applies, focuses on that sub-expression, and applies the specification. A postcondition-generic WP lemma is one way to expose a program's result to its caller:

theorem writeread_spec_2 (Φ : Val → IProp GF) : (∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v) -∗ WP writeread {{ v, Φ v }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GF⊢ ⊢ (∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v) -∗ WP writeread {{ v, Φ v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GF⊢ ∗HΦ : ∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v ⊢ WP writeread {{ v, Φ v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GF⊢ ∗HΦ : ∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v ⊢ WP hl(let x := ref(#7); !x) {{ v, Φ v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GFl:Loc⊢ ∗HΦ : ∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v ∗Hl : l ↦ some hl_val(#7) ⊢ WP hl(let x := #l; !x) {{ v, Φ v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GFl:Loc⊢ ∗HΦ : ∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v ∗Hl : l ↦ some hl_val(#7) ⊢ WP hl(!#l) {{ v, Φ v }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GFl:Loc⊢ ∗HΦ : ∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v ∗Hl : l ↦ some hl_val(#7) ⊢ |={⊤}=> Φ hl_val(#7) hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GFl:Loc⊢ ∗HΦ : ∀ v, ⌜v = hl_val(#7)⌝ -∗ Φ v ∗Hl : l ↦ some hl_val(#7) ⊢ Φ hl_val(#7) hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFΦ:Val → IProp GFl:Loc⊢ ∗Hl : l ↦ some hl_val(#7) ⊢ ⌜hl_val(#7) = hl_val(#7)⌝ All goals completed! 🐙 theorem writeread_add_2_spec : ⊢@{IProp GF} WP hl(&writeread + #2) {{ v, ⌜v = hl_val(#9)⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl((&writeread + #2)) {{ v, ⌜v = hl_val(#9)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFw:Valhw:w = hl_val(#7)⊢ ⊢ WP hl((v(&w) + #2)) {{ v, ⌜v = hl_val(#9)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ WP hl((#7 + #2)) {{ v, ⌜v = hl_val(#9)⌝ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GF⊢ ⊢ |={⊤}=> ⌜hl_val(#(7 + 2)) = hl_val(#9)⌝ All goals completed! 🐙

5.5. Hoare Triples🔗

Having studied weakest preconditions, we shift our focus onto another construct for specifying program behaviour: Hoare triples. The weakest precondition does not explicitly specify which conditions must hold before executing the program; it only talks about the postcondition. Hoare triples build on weakest preconditions by requiring us to explicitly mention the precondition.

A Hoare triple is written {{ P }} e {{ x .. y, RET v; Q }}. It desugars to

□ (∀ Φ, P -∗ ▷ (∀ x .. y, Q -∗ Φ v) -∗ WP e {{ w, Φ w }})

Inside an Iris proposition, the notation includes the outer □, so triples are persistent. As a Lean theorem statement it elaborates to the universally quantified WP rule, introduced with iintro %Φ.

Consider a function that swaps two values.

def swap : Val := hl_val(λ x y, let v := !x; x ← !y; y ← v) theorem swap_spec (l1 l2 : Loc) (v1 v2 : Val) : {{ l1 ↦ some v1 ∗ l2 ↦ some v2 }} hl(&swap #l1 #l2) {{ RET hl_val(#()); l1 ↦ some v2 ∗ l2 ↦ some v1 }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:Val⊢ ⊢ ∀ Φ, l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ ▷ (l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#())) -∗ WP hl(v(&swap) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v1 ∗H2 : l2 ↦ some v2 ∗HΦ : ▷ (l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#())) ⊢ WP hl(v(&swap) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v1 ∗H2 : l2 ↦ some v2 ∗HΦ : ▷ (l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#())) ⊢ WP hl((v(λ x y, let v := !x; x ← !y; y ← v)) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v1 ∗H2 : l2 ↦ some v2 ∗HΦ : l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#()) ⊢ WP hl(let v := !#l1; #l1 ← !#l2; #l2 ← v) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v1 ∗H2 : l2 ↦ some v2 ∗HΦ : l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#()) ⊢ WP hl(let v := v(&v1); #l1 ← !#l2; #l2 ← v) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v1 ∗H2 : l2 ↦ some v2 ∗HΦ : l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#()) ⊢ WP hl(#l1 ← v(&v2); #l2 ← v(&v1)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H2 : l2 ↦ some v2 ∗HΦ : l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#()) ∗H1 : l1 ↦ some v2 ⊢ WP hl(#l2 ← v(&v1)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗HΦ : l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#()) ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ |={⊤}=> Φ hl_val(#()) hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗HΦ : l1 ↦ some v2 ∗ l2 ↦ some v1 -∗ Φ hl_val(#()) ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ Φ hl_val(#()) hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ l1 ↦ some v2 ∗ l2 ↦ some v1 All goals completed! 🐙 theorem swap_swap_spec (l1 l2 : Loc) (v1 v2 : Val) : {{ l1 ↦ some v1 ∗ l2 ↦ some v2 }} hl(&swap #l1 #l2; &swap #l1 #l2) {{ RET hl_val(#()); l1 ↦ some v1 ∗ l2 ↦ some v2 }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:Val⊢ ⊢ ∀ Φ, l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ ▷ (l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ Φ hl_val(#())) -∗ WP hl(v(&swap) #l1 #l2; v(&swap) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H : l1 ↦ some v1 ∗ l2 ↦ some v2 ∗HΦ : ▷ (l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ Φ hl_val(#())) ⊢ WP hl(v(&swap) #l1 #l2; v(&swap) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗HΦ : l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ Φ hl_val(#()) ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ WP hl(#(); v(&swap) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗HΦ : l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ Φ hl_val(#()) ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ WP hl(v(&swap) #l1 #l2) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ l1 ↦ some ?v1 ∗ l2 ↦ some ?v2hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗HΦ : l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ Φ hl_val(#()) ∗H : l1 ↦ some ?v2 ∗ l2 ↦ some ?v1 ⊢ Φ hl_val(#())hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ Valhlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ Valhlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ Valhlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ Valhlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ Valhlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ Val hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗H1 : l1 ↦ some v2 ∗H2 : l2 ↦ some v1 ⊢ l1 ↦ some ?v1 ∗ l2 ↦ some ?v2 All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl1:Locl2:Locv1:Valv2:ValΦ:Val → IProp GF⊢ ∗HΦ : l1 ↦ some v1 ∗ l2 ↦ some v2 -∗ Φ hl_val(#()) ∗H : l1 ↦ some v1 ∗ l2 ↦ some v2 ⊢ Φ hl_val(#()) All goals completed! 🐙

A convention in Iris is to write reusable specifications as Hoare triples and prove them by introducing the postcondition and executing program steps. The second proof uses wp_apply to compose two calls without unfolding swap.

5.6. Concurrency🔗

We finish this chapter with a final example that illustrates how ownership of resources can be transferred between threads. The program forks two threads that each write to a separate location.

iris-lean's port of the par library (Iris.HeapLang.Lib.Par) provides:

  • the parallel-composition operator e1 ‖ e2, which runs e1 and e2 concurrently and returns the pair of their results;

  • the wp_par lemma which lifts pairs of WP specifications for the two threads to a WP specification of their composition;

  • the SpawnG GF class capturing the resources par needs.

section ParExamples variable [Spawn.SpawnG GF] open Iris.HeapLang.Par def parWrite (l1 l2 : Loc) : Exp := hl((v(#l1) ← #21) ‖ (v(#l2) ← #2)) theorem parWrite_spec (l1 l2 : Loc) (v1 v2 : Val) : l1 ↦ some v1 -∗ l2 ↦ some v2 -∗ WP (parWrite l1 l2) {{ _v, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) }} := hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ⊢ l1 ↦ some v1 -∗ l2 ↦ some v2 -∗ WP (parWrite l1 l2) {{ _v, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl1 : l1 ↦ some v1 ∗Hl2 : l2 ↦ some v2 ⊢ WP (parWrite l1 l2) {{ _v, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl1 : l1 ↦ some v1 ∗Hl2 : l2 ↦ some v2 ⊢ WP hl(v(&par) (λ _, #l1 ← #21) (λ _, #l2 ← #2)) {{ _v, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl1 : l1 ↦ some v1 ∗Hl2 : l2 ↦ some v2 ⊢ WP hl(v(&par) (v(λ _, #l1 ← #21)) (v(λ _, #l2 ← #2))) {{ _v, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl1 : l1 ↦ some v1 ⊢ WP hl(#l1 ← #21) {{ x, l1 ↦ some hl_val(#21) }}hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl2 : l2 ↦ some v2 ⊢ WP hl(#l2 ← #2) {{ x, l2 ↦ some hl_val(#2) }}hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ⊢ ∀ v1 v2, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) -∗ ▷ (l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2)) hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl1 : l1 ↦ some v1 ⊢ WP hl(#l1 ← #21) {{ x, l1 ↦ some hl_val(#21) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl1 : l1 ↦ some hl_val(#21) ⊢ |={⊤}=> l1 ↦ some hl_val(#21) All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl2 : l2 ↦ some v2 ⊢ WP hl(#l2 ← #2) {{ x, l2 ↦ some hl_val(#2) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ |={⊤}=> l2 ↦ some hl_val(#2) All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Val⊢ ⊢ ∀ v1 v2, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) -∗ ▷ (l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2)) hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFl1:Locl2:Locv1:Valv2:Valv1':Valv2':Val⊢ ∗H1 : l1 ↦ some hl_val(#21) ∗H2 : l2 ↦ some hl_val(#2) ⊢ ▷ (l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2)) All goals completed! 🐙

Each thread's WP is a separate subgoal of iapply wp_par. The $$ [Hl1] [Hl2] [] annotation explicitly partitions the spatial context: Hl1 goes to the first thread, Hl2 to the second, and the postcondition-handler subgoal receives nothing (it is closed purely from the values returned by the two threads).

A larger client allocates both locations, runs the writes in parallel, and multiplies the final values. Its triple returns both locations and the computed integer, together with ownership of the final heap.

def par_client : Exp := hl% let l1 := ref(#0); let l2 := ref(#0); ((l1 ← #21) ‖ (l2 ← #2)); let life := !l1 * !l2; (l1, l2, life) theorem par_client_spec : {{ True }} par_client {{ (l1 l2 : Loc) (life : Int), RET hl_val((#l1, #l2, #life)); l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ }} := hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GF⊢ ⊢ ∀ Φ, True -∗ (▷ ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life))) -∗ WP par_client {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GF⊢ ∗HΦ : ▷ ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ⊢ WP par_client {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GF⊢ ∗HΦ : ▷ ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ⊢ WP hl(let l1 := ref(#0); let l2 := ref(#0); v(&par) (λ _, l1 ← #21) (λ _, l2 ← #2); let life := (!l1 * !l2); (l1, l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Loc⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#0) ⊢ WP hl(let l1 := #l1; let l2 := ref(#0); v(&par) (λ _, l1 ← #21) (λ _, l2 ← #2); let life := (!l1 * !l2); (l1, l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Loc⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#0) ⊢ WP hl(let l2 := ref(#0); v(&par) (λ _, #l1 ← #21) (λ _, l2 ← #2); let life := (!#l1 * !l2); (#l1, l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#0) ∗Hl2 : l2 ↦ some hl_val(#0) ⊢ WP hl(let l2 := #l2; v(&par) (λ _, #l1 ← #21) (λ _, l2 ← #2); let life := (!#l1 * !l2); (#l1, l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#0) ∗Hl2 : l2 ↦ some hl_val(#0) ⊢ WP hl(v(&par) (v(λ _, #l1 ← #21)) (v(λ _, #l2 ← #2)); let life := (!#l1 * !#l2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗Hl1 : l1 ↦ some hl_val(#0) ⊢ WP hl(#l1 ← #21) {{ x, l1 ↦ some hl_val(#21) }}hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗Hl2 : l2 ↦ some hl_val(#0) ⊢ WP hl(#l2 ← #2) {{ x, l2 ↦ some hl_val(#2) }}hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ⊢ ∀ v1 v2, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) -∗ ▷ WP hl(v((&v1, &v2)); let life := (!#l1 * !#l2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗Hl1 : l1 ↦ some hl_val(#0) ⊢ WP hl(#l1 ← #21) {{ x, l1 ↦ some hl_val(#21) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗Hl1 : l1 ↦ some hl_val(#21) ⊢ |={⊤}=> l1 ↦ some hl_val(#21); All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗Hl2 : l2 ↦ some hl_val(#0) ⊢ WP hl(#l2 ← #2) {{ x, l2 ↦ some hl_val(#2) }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ |={⊤}=> l2 ↦ some hl_val(#2); All goals completed! 🐙 hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Loc⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ⊢ ∀ v1 v2, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) -∗ ▷ WP hl(v((&v1, &v2)); let life := (!#l1 * !#l2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ ▷ WP hl(v((&r1, &r2)); let life := (!#l1 * !#l2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ WP hl(v((&r1, &r2)); let life := (!#l1 * !#l2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ WP hl(let life := (!#l1 * !#l2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ WP hl(let life := (!#l1 * #2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ WP hl(let life := (#21 * #2); (#l1, #l2, life)) {{ Φ }} hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ |={⊤}=> Φ hl_val((#l1, #l2, #(21 * 2))) hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗HΦ : ∀ l1 l2 life, l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜life = 42⌝ -∗ Φ hl_val((#l1, #l2, #life)) ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ Φ hl_val((#l1, #l2, #(21 * 2))) hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ∗Hl1 : l1 ↦ some hl_val(#21) ∗Hl2 : l2 ↦ some hl_val(#2) ⊢ l1 ↦ some hl_val(#21) ∗ l2 ↦ some hl_val(#2) ∗ ⌜21 * 2 = 42⌝ hlc:HasLCGF:BundledGFunctorsinst✝¹:HeapLangGS hlc GFinst✝:Spawn.SpawnG GFΦ:Val → IProp GFl1:Locl2:Locr1:Valr2:Val⊢ ⊢ ⌜21 * 2 = 42⌝ All goals completed! 🐙 end ParExamples end Specifications