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 runse1ande2concurrently and returns the pair of their results; -
the
wp_parlemma which lifts pairs of WP specifications for the two threads to a WP specification of their composition; -
the
SpawnG GFclass capturing the resourcesparneeds.
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