Iris Lecture Notes
Iris Lecture Notes
With contributions by: Kristoffer Just Andersen (Aarhus University), Johan Bay
(Aarhus University), Daniel Gratzer (Carnegie Mellon University / Aarhus Univer-
sity), Dan Frumin (Radboud University Nijmegen), Mathias Høier (Aarhus Uni-
versity), Robbert Krebbers (Aarhus University / Delft University of Technology),
Marit Edna Ohlenbusch (Aarhus University), Amin Timany (KU Leuven / Aarhus
Univeristy), Simon Friis Vindum (Aarhus University), Felix Wiemuth (Aarhus Uni-
versity), Jonas Kastberg Hinrichsen (Aarhus University).
Contents
1 Introduction 3
2 Programming Language 3
A Overview 159
A.1 Operational Semantics (Section 2) . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
A.2 Typing Rules (Section 3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
A.3 Separation Logic Rules (Section 3) . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
A.4 Program Logic Rules (Section 4) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
A.5 Rules for the Later Modality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
A.6 Rules for the Persistently Modality (Section 7) . . . . . . . . . . . . . . . . . . . . 165
A.7 Rules for Invariants (Section 8) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
A.8 Rules for Ghost State (Section 8) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
A.9 Rules for the Update Modality (Section 8) . . . . . . . . . . . . . . . . . . . . . . 166
Preface
These lecture notes are intended to serve as an introduction to Iris, a higher-order concurrent
separation logic framework implemented and verified in the Coq proof assistant.
Iris has been developed over several years in joint research involving the Logic and Seman-
tics group at Aarhus University, led by Lars Birkedal, and the Foundations of Programming
Group at Max Planck Institute for Software Systems, led by Derek Dreyer. Lately, the devel-
opment has involved several other international research groups, in particular the group of
Robbert Krebbers at TU Delft.
The main research papers describing the Iris program logic framework are three conference
papers [9, 7, 10] and a longer journal paper with more details on the semantics and the latest
developments of the logic [8]. These papers, and several other Iris related research papers, can
all be found on the Iris Project web site:
iris-project.org
At this web site one can also get access to the Coq implementation of Iris.
Design Choices It is not obvious how one should introduce a sophisticated logical framework
such as Iris, especially since Iris is a framework in more than one sense: Iris can be instantiated
to reason about programs written in different programming languages and, moreover, Iris has a
base logic, which can be used to define different kinds of program logics and relational models.
We now describe some of the design choices we have made for these lecture notes.
These lecture notes are aimed at students with no prior knowledge of program logics. Hence
we start from scratch and we focus on a particular instantiation of Iris to reason about a core
concurrent higher-order imperative programming language, λref,conc . (As Martin Hyland once
put it [5]: “One good example is worth a host of generalities”.)
We start with high-level concepts, such as Hoare triples and proof rules for those, and then,
gradually, as we introduce more concepts, we show, e.g., how proof rules that were postulated
at first can be derived from simpler concepts. Moreover, new logical concepts are introduced
with concrete, but often artificial, verification examples. The lecture notes also include larger
case studies which show the logic can be used for verification of realistic programs. A word of
caution to the reader. The beginning of the lecture notes, until about Section 4, is rather formal
and abstract. Do not be disheartened by it. This part is needed in order to fix notation, and
explain the basic structure of reasoning used in concrete examples of program verification later
on.
Since the Iris logic involves several new logical modalities and connectives, we present ex-
ample proofs of programs in a fairly detailed style (instead of the often-used proof outlines).
We hope this will help readers learn how the novel aspects of the logic work.
We have included numerous exercises of varying degree of difficulty. Some exercises intro-
duce reasoning principles used later in the notes. Thus the exercises are an integral part of the
lecture notes, and should not be skipped.
When we introduce the logic, we only use intuitive semantics to explain why proof rules are
sound. For the time being we refer the reader to a research paper [8] for an extensive description
of the model of Iris. There are several reasons for this choice: the formal semantics is non-trivial
(e.g., it involves solutions to recursive domain equations); the semantics is really defined for the
base logic, which is only introduced later in the notes; and, finally, our experience from teaching
a course based on the these lecture notes is that students can learn to use the logic without being
exposed to the formal semantics of the logic.
1
Since Iris comes with a Coq implementation, it would perhaps be tempting to teach Iris
using the Coq implementation from the beginning. However, we have decided against doing
so. The reason is that our students do not have enough experience with Coq to make such an
approach viable and, moreover, we believe that, for most readers, there would be too many
things to learn at the same time. We do include a section on the Coq implementation and
also describe all the parts of Iris needed in order to work with the Coq implementation. The
examples in the notes have been formalized in the Iris Coq implementation and are available at
the Iris Project web site.
We have not attempted to include references to original research papers or to include his-
torical remarks. Please see the Iris research papers for references to earlier work.
Acknowledgements We thank the students of the Program Analysis and Verification course at
Aarhus University for providing feedback on an early version of these notes. We thank Ambal
Guillaume, Jonas Kastberg Hinrichsen, Marianna Rapoport, and Lily Tsai for valuable feedback.
2
1 Introduction
The goal of these notes is to introduce a powerful logic, called Iris, for proving partial functional
correctness of concurrent higher-order imperative programs. Partial correctness refers to the fact
that when a program is proved correct with respect to some specification this only guarantees
that if the program terminates then its result will satisfy the stated property. If a program does
not terminate then the specification says nothing about its behaviour, apart from that it does
not get stuck. (Knowing that an infinite computation does not get stuck can also be useful: it
means that the program is safe and thus in particular that there are no memory errors such as
trying to read from a location which does not exist in memory.)
Iris is a higher-order logic. This means in particular that program specifications can be
parametrized by arbitrary propositions. One of the main benefits of this is that the generality
of higher-order logic specifications support modularity: libraries and modules can be specified
and proved correct once and for all, and different clients, or users, of the library can be verified
in isolation, using only the specification (and not the implementation) of the library the clients
are using.
Iris supports verification of concurrent programs, by the use of so-called ghost state, and
invariants. Invariants are a mechanism that allows different program threads to access shared
resources, e.g., read and write to the same location, provided they do not invalidate properties
other threads depend on, i.e., provided they maintain invariants. Ghost state is a mechanism
which allows the invariants to evolve over time. It allows the logic to keep track of additional
information that is not present in the program code being verified, but is nonetheless essential
to proving its correctness, such as relationships between values of different program variables.
In these notes, we introduce Iris gradually, starting with the necessary ingredients for rea-
soning about simple sequential programs, and refining and extending it until the logic is ca-
pable of reasoning about higher-order, concurrent, imperative programs. After that we show
how the logic can be simplified into a minimal base logic in which all the rules which were used
previously can be derived as theorems.
The examples used to introduce and explain the rules of the logic are often minimal and
somewhat contrived. However the notes also contain larger case studies, in separate sections,
which show that the logic can be used also to verify and reason about larger, and more realistic,
programs. These case studies also illustrate modularity of the logic more clearly. More case
studies can be found on the Iris project home page.1
2 Programming Language
A program logic is used to reason about programs, that is, to specify their behaviour. The pre-
cise rules of the logic depend on the constructs present in the programming language. Iris is
really a framework, which can be instantiated to a range of different programming languages,
but in order to learn about Iris, it is useful first to get some experience with one particular
choice of programming language. Thus in this section we fix a concrete programming lan-
guage, which we will use throughout the notes. Our language of choice, denoted λref,conc , is an
untyped higher-order ML-like language with general references and concurrency. Concurrency
is supported via a primitive, fork {e}, for forking a new thread to compute e, and via a primitive
compare and set, CAS, operation. This is the only synchronization primitive in the language.
Other synchronization constructs, such as locks, semaphores, etc., can be defined in the lan-
guage, and indeed we implement and give specifications to two different kinds of locks. The
1 iris-project.org
3
syntax and the operational semantics is shown in Figure 1.
Syntax sugar In addition to the given constructs we will use additional syntax sugar when
writing examples. We will write λx.e for the term rec f x := e where f is some fresh variable
not appearing in e. Thus λx.e is a non-recursive function with argument x and body e. We
will also write let x := e1 in e2 for the term (λx.e2 )e1 . This is the standard let expression, whose
operational meaning (see the operational semantics below) is to evaluate the term e1 to a value
v, and then evaluate e2 [v/x], i.e., the term e2 with the value v substituted for the variable x.
Referring to function defintiions can be quite verbose, e.g., loop , rec loop () := (); loop (). As
such, we often collapse the outer-most binding with the function binding, and simply write
loop () = (); loop ().
Let us call it e.
Exercise 2.1. Come up with at least two different executions of this program. ♦
4
Syntax
x, y, f ∈ Var
` ∈ Loc
n ∈ Z
} ::= + | − | ∗ | = | < | ···
Val v ::= () | true | false | n | ` | (v, v) | inj1 v | inj2 v | rec f (x) := e
Expr e ::= x | n | e } e | () | true | false | if e then e else e | `
| (e, e) | π1 e | π2 e | inj1 e | inj2 e | match e with inj1 x ⇒ e | inj2 y ⇒ e end
| rec f (x) := e | e e
| ref (e) | ! e | e ← e | CAS(e, e, e) | fork {e}
ECtx E ::= − | E } e | v } E | if E then e else e | (E, e) | (v, E) | π1 E | π2 E | inj1 E | inj2 E
| match E with inj1 x ⇒ e | inj2 y ⇒ e end | E e | v E | ref (E) | ! E | E ← e | v ← E
| CAS(E, e, e0 ) | CAS(v, E, e) | CAS(v, v 0 , E)
fin
Heap h ∈ Loc −−*Val
fin
TPool E ∈ N −−* Expr
Config ς ::= (h, E)
Pure reduction
pure
v } v0 v 00 if v 00 = v } v 0
pure
if true then e1 else e2 e1
pure
if false then e1 else e2 e2
pure
πi (v1 , v2 ) vi
pure
match inji v with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end ei [v/xi ]
pure
(rec f x := e) v e[(rec f x := e)/f , v/x]
Configuration reduction
5
language, etc. These things need to be written in some language. In the case of Iris the under-
lying language of “things” is simple type theory with a number of basic constants. These basic
constants are given by the signature S.
Note that this language of “things” is different from the programming language introduced in
Section 2. In fact terms of the programming language are one of the “things” we reason about.
It is regrettable that the notation is often very similar, e.g.,, both the language of terms of Iris as
well as the programming language have lambda abstraction, pairs, sums. We hope the reader
will get used to the distinction.
We introduce the different notions of Iris step by step, starting with a minimal separation
logic useful for a sequential language.
Syntax. Iris syntax is built up from a signature S and a countably infinite set Var of variables
(ranged over by metavariables x, y, z). The signature in particular contains a list of function
symbols F with their arities, i.e., their types. For a function symbol F we write F : τ1 , . . . , τn →
τn+1 ∈ F to mean that it can be applied to a tuple of terms of types τ1 , . . . , τn , and the result is of
type τn+1 . An example of a function symbol is addition of integers. Its arity is Z, Z → Z where
Z is the type of integers.
The types of Iris are built up from the following grammar, where T stands for additional
base types which we will add later, Val and Expr are types of values and expressions in the
language, and Prop is the type of Iris propositions.
τ ::= T | Z |Val | Expr | Prop | 1 | τ + τ | τ × τ | τ → τ
The corresponding terms of Iris are defined below. They will be extended later when we intro-
duce new concepts of Iris, and some of the terms that we treat as primitive now will turn out to
be defined concepts.
t, P ::= x | n | v | e | F(t1 , . . . , tn ) |
() | (t, t) | πi t | λx : τ. t | t(t) |
inl t | inr t | case(t, x.t, y.t) |
False | True | t =τ t | P ⇒ P | P ∧ P | P ∨ P | P ∗ P | P −∗ P |
∃x : τ. P | ∀x : τ. P |
P | .P |
{P } t {P } |
t 7→ t
where x are variables, n are integers, v and e range over values of the language (i.e., they are
primitive terms of typesVal and Expr), and F ranges over the function symbols in the signature
S.
The term () is the only term of the unit type 1, (t, t) are pairs, πi t is the projection, λx : τ. t
is the lambda abstraction and t(t) denotes function application. Next there are introduction
forms for sums (inl and inr) and the corresponding elimination form case.
The rest of the terms are logical constructs. Most of them are standard propositional con-
nectives. The additional constructs are separating conjunction (∗) and magic wand (−∗), which
will be explained in Section 3. Then there are the later modality . P , explained in Section 6, and
the persistently modality P , explained in Section 7. Finally we have the Hoare triples {P } t {P }
and the points-to predicate t 7→ t, which are explained in Section 4.
The typing rules of the language of terms are shown in Figure 2. The judgments take the
form Γ `S t : τ and express when a term t has type τ in context Γ , given signature S. The variable
6
context Γ assigns types to variables of the logic. It is a list of pairs of a variable x and a type τ
such that all the variables are distinct. We write contexts in the usual way, e.g., x1 : τ1 , x2 : τ2 is
a context.
Γ `t:τ Γ , x : τ0, y : τ0 ` t : τ Γ1 , x : τ 0 , y : τ 00 , Γ2 ` t : τ
x:τ `x:τ Γ , x : τ0 ` t : τ Γ , x : τ 0 ` t[x/y] : τ Γ1 , x : τ 00 , y : τ 0 , Γ2 ` t[y/x, x/y] : τ
Γ ` t : τ1 Γ ` u : τ2 Γ ` t : τ1 × τ2 i ∈ {1, 2} Γ , x : τ ` t : τ0
Γ ` () : 1 Γ ` (t, u) : τ1 × τ2 Γ ` πi t : τi Γ ` λx. t : τ → τ 0
Γ ` t : τ → τ0 u:τ
0
Γ ` t(u) : τ
7
Terminology A word about terminology. We generally use “proposition” for terms of type
Prop and “predicate” for terms of type τ → Prop for types τ. However the distinction is not
so clear since, if P : τ → Prop is a predicate and x : τ, then P x is a proposition. Moreover,
propositions can be thought of as nullary predicates, that is, predicates on the type 1. Thus the
decision of when to use which term is largely a matter of convention and is not significant.
Intuition for Iris propositions Intuitively, an Iris proposition describes a set of resources.
A canonical example of a resource is a heap fragment. So far we have not introduced any
primitives for talking about resources. One such primitive is the “points-to” predicate x 7→ v,
which we will use extensively in Section 4 in connection with Hoare triples. For now it is
enough to think of x 7→ v as follows. It describes the set of all heap fragments that map location
x to value v (in particular location x needs to exist in the heap).
In addition there is another reading, another intuition, for Iris propositions. Propositions
assert ownership of resources. For example, x 7→ v asserts that we have the sole authority, i.e.,
exclusive ownership, of the portion of the heap which contains the location x. This means that
we are free to read and modify the value stored at x. This intuition will become clearer in con-
nection with Hoare triples, and the ownership reading of propositions will become particularly
useful when programs contain multiple threads.
With this intuition the proposition P ∗ Q describes the set of resources which can be split up
into two disjoint parts, with one part described by P and the other part described by Q.
For example, x 7→ u ∗ y 7→ v describes the set of heaps with two disjoint locations x and y, the
first stores u and the second v.
The proposition P −∗ Q describes those resources r which satisfy that, if we combine r with
a disjoint resource described r 0 by P , then we get a resource described by Q. For example, the
proposition
x 7→ u −∗ (x 7→ u ∗ y 7→ v)
describes those heap fragments that map y to v, because when we combine it with a heap
fragment mapping x to u, then we get a heap fragment mapping x to u and y to v.
In the following section it suffices to think of resources as heap fragments. Later on, we will
see much more sophisticated notions of resource and much more refined notions of ownership,
including shared ownership, than that captured by simple points-to predicate. When r is a
resource described by P , we also say that r satisfies P or that r is in P .
Entailment relation Entailment rules are often presented using a sequence of formulas to-
gether with a number of structural rules, which manipulate such sequences of assumptions.
Here instead, the assumption of the entailment rules consists of only a single formula, and
the structural rules are replaced by appropriate uses of transitivity of entailment together with
properties of conjunction and separating conjunction, such as associativity, commutativity, and
weakening. We have chosen this single-assumption style of presentation because otherwise we
would have needed two ways of extending the sequence of formulas, one corresponding to or-
dinary conjunction and one corresponding to separating conjunction. The intuition reading of
an entailment P ` Q is that, for all resources r, if r is in P , then r is also in Q.
8
and r2 satisfying Q. What it means to split a resource is dependent on the particular notion of
a resource. For example, if the resources are heap fragments then splitting means splitting the
heap: some locations go to one subheap, the others to the other.
P1 ∗ P2 ` P1
states that we can forget about resources. This makes Iris an affine separation logic.3 With the
resource reading of propositions this rule restricts the kind of sets that can be allowed as sets
of resources.
For instance, if resources are heaps then the points-to predicate x 7→ v contains those heaps
which map x to v, but other locations can contain values as well. Then, for instance, the rule
x 7→ u ∗ y 7→ v ` x 7→ u
is clear: On the left-hand side we have heaps which map the location x to u and the location
y to v. Any such heap in particular maps x to u, so is in the set of resources described by the
right-hand side.
The associativity and commutativity rules
∗-assoc ∗-comm
P1 ∗ (P2 ∗ P3 ) a` (P1 ∗ P2 ) ∗ P3 P1 ∗ P2 a` P2 ∗ P1
are basic structural rules. The symbol a` is used to indicate that the rule can be used in both
directions (so that we do not have to write two separate rules for associativity). The above rules
hold because “to separate” is a commutative and associative operation.
states that to prove a separating conjunction Q1 ∗ Q2 we need to split the assumptions as well
and decide which ones to use to prove Q1 and which ones to use to prove Q2 . Compared to
the introduction rule for ordinary conjunction (∧I), this splitting of assumptions restricts the
basic structural properties of ∗. For instance, P ` P ∗ P is not provable in general. However, this
“limitation” allows us to state additional properties in combination with primitive resource
assertions. For instance, if resources are heaps then we have the following basic property of the
points-to predicate
x 7→ v ∗ x 7→ u ` False.
Note that if we used ordinary conjunction in the above axiom, then we would be able to derive
¬(x 7→ v) for all x and v, making the points-to predicate useless.
3 Sometimes called intuitionistic separation logic, but that terminology is ambiguous since it can also refer to the
absence of the law of excluded middle or other classical axioms.
9
Magic wand introduction and elimination
−∗I −∗E
R∗P ` Q R1 ` P −∗ Q R2 ` P
R ` P −∗ Q R1 ∗ R2 ` Q
The magic wand P −∗ Q is akin to the difference of resources in Q and those in P : it is the set of
all those resources which when combined with any resource in P are in Q. With this intuition
the introduction rule should be intuitively clear.
The elimination rule is similar to the elimination rule for implication (⇒E), except that we
need to split the assumptions and decide which ones to use to prove the magic wand and which
ones to use to prove the premise of the magic wand.
Example derivation of a derivable rule We show one derivation of a derivable rule here to
illustrate how the rules for separating conjunction and magic wand are used, and how these
connectives interact. We leave the others as exercise for the reader.
Example 3.1. We show
∗-∨-comm
P ∗ (Q ∨ R) a` P ∗ Q ∨ P ∗ R
The direction from right to left is immediate since we have P ∗Q ` P ∗(Q∨R) and P ∗R ` P ∗(Q∨R)
by monotonicity of ∗ and ∨-introduction.
The direction from left to right relies on the existence of the wand.4
Consider the following proof tree (where we omit use of structural rules such as commuta-
tivity of ∗)
P ∗Q ` P ∗Q P ∗R ` P ∗R
P ∗Q ` P ∗Q∨P ∗R P ∗R ` P ∗Q∨P ∗R
Q ` P −∗ (P ∗ Q ∨ P ∗ R) R ` P −∗ (P ∗ Q ∨ P ∗ R)
Q ∨ R ` P −∗ (P ∗ Q ∨ P ∗ R)
.
P ∗ (Q ∨ R) ` P ∗ Q ∨ P ∗ R
Notice we made essential use of the following two rules
−∗I −∗E’
R∗P ` Q P ` Q −∗ R
R ` P −∗ Q P ∗Q ` R
The first is the introduction rule for −∗, and the second one is easily derivable. We make use of
the −∗ to “move” the part of the context into the conclusion. This allows us to use the elimination
rule for disjunction.
The other rules are derivable in an analogous way.
Exercise 3.2. Following the example above derive the following two rules:
∗-∃-comm ∧-∃-comm
x < FV (P ) x < FV (P )
P ∗ ∃x. Φ a` ∃x. P ∗ Φ P ∧ ∃x. Φ a` ∃x. P ∧ Φ
♦
4 Indeed, for those familiar with adjoint functors, it is a consequence of the general categorical fact that left adjoints
preserve colimits.
10
We have the usual η and β laws for projections, λ and µ.
unit-η λ−η
Γ `t:1 λ−β x not free in e Γ ` e : τ1 → τ2
Γ ` t ≡ () Γ ` (λx : τ. e1 )(e2 ) ≡ e1 [e2 /x] Γ ` (λx.e(x)) ≡ e
π−η
π−β Γ ` e : τ1 × τ2 inl-β
Trans Eq
Asm P `Q Q`R Γ , x : τ ` Q : Prop Γ | P ` Q[t/x] Γ | P ` t =τ t 0 Eq-Refl
P `P P `R Γ | P ` Q[t 0 /x] P ` t =τ t
Eq-Symm Eq-Trans ⊥E ∧I
P ` t =τ u P ` t1 =τ t2 P ` t2 =τ t3 Q ` False >I R`P R`Q
P ` u =τ t P ` t1 =τ t3 Q`P Q ` True R ` P ∧Q
⇒I ⇒E ∀I ∀E
R∧P ` Q R`P ⇒Q R`P Γ ,x : τ | Q ` P Γ | Q ` ∀x : τ. P Γ `t:τ
R`P ⇒Q R`Q Γ | Q ` ∀x : τ. P Γ | Q ` P [t/x]
∃I ∃E
Γ | Q ` P [t/x] Γ `t:τ Γ | R ` ∃x : τ. P Γ ,x : τ | R ∧ P ` Q
Γ | Q ` ∃x : τ.P Γ |R`Q
−∗I −∗E
R∗P ` Q R1 ` P −∗ Q R2 ` P
R ` P −∗ Q R1 ∗ R2 ` Q
The intuitive reading of the Hoare triple {P } e {Φ} is that if the program e is run in a heap
h satisfying P , then the computation does not get stuck and, moreover, if it terminates with a
value v and a heap h0 , then h0 satisfies Φ(v). Note that Φ has two purposes. It describes the value
v, e.g., it could contain the proposition v = 3, and it describes the resources after the execution
of the program, e.g., it could contain x 7→ 15. At this stage the precondition P must describe
the set of resources necessary for e to run safely, if we are to prove the triple. Informally, we
sometimes say that P must include the footprint of e, those resources needed for e to run safely.
For example, if the expression e uses a location during evaluation (either reads to or writes from
it), then any heap fragment in P must contain a value at that location. As a consequence, if the
expression is run in a heap with location `, which is not mentioned by P , then the value at that
location will not be altered. This is one intuition for why the frame rule below is sound.
Later on, in Section 8, not all resources needed to execute e will need to be in the precon-
dition. Resources shared by different threads will instead be in invariants, and only resources
that are local to the thread will be in preconditions. But that is for later.
Laws for the points-to predicate As we described above the basic predicate x 7→ v describes
those heap fragments that map the location x to the value v.
12
The essential properties of the points-to predicate are that (1) it is not duplicable, which
means that
` 7→ v ∗ ` 7→ v 0 ` False
and (2) that it is a partial function in the sense that
` 7→ v ∧ ` 7→ v 0 ` v =Val v 0 .
Other properties of the points-to predicate come into play in connection with Hoare triples
and language constructs which manipulate state.
Laws for Hoare triples The basic axioms for Hoare triples are listed in Figure 5 on page 16.
They are split into three groups. First there are structural rules. These deal with transforming
pre- and postconditions but do not change the program. Next, for each elimination form and
basic heap operation of the language, there is a rule stating how the primitive operation trans-
form pre- and postconditions. In the third group we list two more structural rules which allow
us to move persistent propositions, that is, propositions which do not depend on any resources,
in and out of preconditions.
In postconditions we use v.Q to mean λv.Q.
In most rules there is an arbitrary proposition/assumption S. Some structural rules, such
as Ht-Eq do change it, but in most rules it remains unchanged. This proposition is necessary.
It will contain, e.g., equalities or inequalities between natural numbers, and other facts about
terms appearing in triples. We now explain the rules.
13
False precondition The following rule
Ht-False
S ` {False} e {v.Q}
The Ht-ret rule is simple: Since a computation consisting of a value does not compute further,
it does not require any resources and the return value is just the value itself.
The rule Ht-bind is more interesting and will be used extensively in order to transform the
verification of a big program K[e] to the verification of individual steps for which we have basic
axioms for Hoare triples. To illustrate consider the following example.
Suppose we are to prove (using let expressions, which are definable in the language)
{P } let x := e in e2 {v.R}
{P } e {v.Q}
for some Q. The rule Ht-bind states that we only need to verify
for all values u. Typically, Q will restrict the set of values; it will be those values which are
possible results of evaluating e.
Exercise 4.3. Use Ht-bind to show {True} 3 + 4 + 5 {v.v = 12}. ♦
Persistent propositions Some of the propositions, namely Hoare triples and equality, do not
rely on any exclusive resources, exclusive in the sense that they cannot be shared. We call such
propositions persistent and we will see more examples, and a more uniform treatment, later
on. For now the essential properties of persistent propositions are the rules Ht-Eq and Ht-Ht,
together with the following axiom for any persistent propositions P and any proposition Q.
P ∧Q ` P ∗Q if P is persistent.
Intuitively, if P is persistent, then it does not depend on any exclusive resources. Thus if P ∧ Q
holds, then r ∈ P ∧ Q is shareable, thus can be shared between P and Q, i.e., r in P ∗ Q. Later on
this intuition will be slightly refined and we will see exactly what shareable means in Iris.
Note that we always have the entailment
P ∗Q ` P ∧Q
and thus, if one of the propositions is persistent, then there is no difference between conjunction
and separating conjunction.
Exercise 4.4. Prove the derived rule P ∗ Q ` P ∧ Q for any propositions P and Q. ♦
14
The rule of consequence
Ht-csq
S `P ⇒P0 S ` P 0 e v. Q0 S ` ∀u. Q0 [u/v] ⇒ Q[u/v]
S persistent
S ` {P } e {v. Q}
The rule of consequence states that we can strengthen the precondition and weaken the post-
condition. It is important that the context in which strengthening and weakening occur is
persistent, that is, that it does not rely on any resources (hence the context must not contain
predicates such as the points-to predicate). The rule of consequence is used very often, most of
the time implicitly.
Rules for basic constructs of the language. The first rule appearing is the rule Ht-op inter-
nalising the operational semantics of binary operations.
Next is the rule for reading a memory location.
Ht-load
S ` {` 7→ u} ! ` {v.v = u ∧ ` 7→ u}
To read we need resources: we need to know that the location ` we wish to read from exists
and, moreover, that a value u is stored at that location. After reading we get the exact value and
we still have the resources we started with. Note that it is crucial that the postcondition still
contains ` 7→ u. Otherwise it would be impossible to use the same location more than once if
we wished to verify a program.
Allocation does not require any resources, i.e., it is safe to run ref (u) in any heap. Hence the
precondition for allocation is True.
Ht-alloc
S ` {` 7→ −} ` ← w {v.v = () ∧ ` 7→ w}
5 Note that each of those two rules is two ordinary rules, one where the top is the premise and the bottom the
conclusion, and one where the bottom is the premise and top the conclusion.
15
Structural rules.
Ht-frame Ht-ret
S ` {P } e {v.Q} Ht-False w is a value
S ` {P ∗ R} e {v.Q ∗ R} S ` {False} e {v.Q} S ` {True} w {v.v = w}
Ht-bind
K is an eval. context S ` {P } e {v. Q} S ` ∀v. {Q} K[v] {w. R}
S ` {P } K[e] {w. R}
Ht-csq
S `P ⇒P0 S ` P 0 e v. Q0 S ` ∀u. Q0 [u/v] ⇒ Q[u/v]
S persistent
S ` {P } e {v. Q}
Ht-disj Ht-exist
S ` {P } e {v. R} S ` {Q} e {v. R} x < FV (Q) S ` ∀x. {P } e {v. Q}
S ` {P ∨ Q} e {v. R} x < FV (Q) S ` {∃x. P } e {v. Q}
Ht-op
v 00 = v } v 0 Ht-load
{True} v } v 0 r.r = v 00
S ` {` 7→ u} ! ` {v.v = u ∧ ` 7→ u}
Ht-alloc Ht-store
Ht-Rec
Γ , g :Val | S ∧ ∀y. ∀v. {P } gv {u.Q} ` ∀y. ∀v. {P } e[g/f , v/x] {u.Q}
Γ | S ` ∀y. ∀v. {P } (rec f x := e)v {u.Q}
Ht-Match
Ht-Proj S ` {P } ei [u/xi ] {v.Q}
S ` {True} πi (v1 , v2 ) {v.v = vi } S ` {P } match inji u with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {v.Q}
Ht-If
{P ∗ b = true} e2 {u.Q} {P ∗ b = false} e3 {u.Q}
{P } if b then e2 else e3 {u.Q}
The following two rules allow us to move persistent propositions in and out of preconditions.
Ht-Eq Ht-Ht
S ∧ t =τ t 0 ` {P } e {v.Q} S ∧ {P1 } e1 {v.Q1 } ` {P2 } e2 {v.Q2 }
S ` P ∧ t =τ t 0 e {v.Q}
S ` {P2 ∧ {P1 } e1 {v.Q1 }} e2 {v.Q2 }
16
requires resources. Namely that the location exists in the heap (` 7→ − is shorthand for ∃u.` 7→
u). Note that with the ownership reading of Iris propositions the requirement that ` points-to
some value means that we own the location. Hence we can change the value stored at it (without
violating assumptions of other modules or concurrently running threads).
Remark 4.6. Note that it is essential that ` 7→ − is in the precondition of the Ht-store, even
though we do not care what is stored at the location. A rule such as
S ` {True} ` ← w {v.v = () ∧ ` 7→ w}
It mimics the intuitive meaning of the conditional expression. The rules for eliminating values
of the product and sum types
Ht-Match
Ht-Proj S ` {P } ei [u/xi ] {v.Q}
S ` {True} πi (v1 , v2 ) {v.v = vi } S ` {P } match inji u with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {v.Q}
It states that to prove that the recursive function application satisfies some specification it suf-
fices to prove that the body of the recursive function satisfies the specification under the as-
sumption that all recursive calls satisfy it. The variable v is the programming language value
to which the function is applied. The variable y is the logical variable, which will typically be
connected to the programming language value v in the precondition P . As an example, which
we will see in detail later on, the value v could be a pointer to a linked list, and the variable y
could be the list of values stored in the linked list. What type precisely y ranges over depends
on the precise application in mind. In some examples we will not need the variable y, i.e., we
shall use the rule
Γ , g :Val | S ∧ ∀v. {P } gv {u.Q} ` ∀v. {P } e[g/f , v/x] {u.Q}
(1)
Γ | S ` ∀v. {P } (rec f x := e)v {u.Q}
This rule is derivable from the rule Ht-Rec by choosing the variable y to range over the singleton
type 1 using the logical equivalence ∀y : 1. Φ ⇐⇒ Φ, provided y does not appear in Φ.
17
Example 4.7. The recursion rule perhaps looks somewhat intimidating. To illustrate how it
is used we use it to verify a very simple program, the program computing the factorial. The
factorial function can be implemented in our language as follows.
rec fac n := if n = 0 then 1 else n ∗ fac(n − 1).
The specification we wish to give it is, of course,
∀n.{n ≥ 0} fac n {v. v =Val n!}
where n! is the factorial of the number n.
Let us now sketch a proof of it. There is no logical variable y, so we will use the simplified
rule (1). Using the rule we see that we must show the entailment (the context S is empty)
∀n. {n ≥ 0} f n {v. v =Val n!} ` ∀n. {n ≥ 0} if n = 0 then 1 else n ∗ f (n − 1) {v. v =Val n!}
So let us assume
∀n. {n ≥ 0} f n {v. v =Val n!}. (2)
To prove ∀n. {n ≥ 0} if n = 0 then 1 else n ∗ f (n − 1) {v. v =Val n!} we start by using the Ht-If rule.
Thus we have to prove two triples
{n ≥ 0 ∗ (n = 0) =Val true} 1 {v. v =Val n!}
{n ≥ 0 ∗ (n = 0) =Val false} n ∗ f (n − 1) {v. v =Val n!}
We leave the first one as an exercise and focus on the second. Using the Ht-bind with the eval-
uation context being n ∗ − and the intermediate assertion Q being Q ≡ v = (n − 1)! we have to
prove the following two triples
{n ≥ 0 ∗ (n = 0) =Val false} f (n − 1) {v.v =Val (n − 1)!}
∀v. {v = (n − 1)!} n ∗ v {u.u =Val n!}
The first triple follows by the rule of consequence and the assumption (2). Indeed n ≥ 0 ∗ (n =
0) =Val false implies n − 1 ≥ 0, and instantiating the assumption (2) with n − 1 we get
{n − 1 ≥ 0} f (n − 1) {v. v =Val (n − 1)!}
as needed. The second triple follows easily by Ht-Eq and basic properties of equality and the
factorial function.
Notice that rules above are stated in very basic form with values wherever possible, which
means they are often needlessly cumbersome to use in their basic form. The following exercises
develop some derived rules.
Exercise 4.8. Prove the following derived rule. For any value u and expression e we have
S ` {P } e {v.Q}
.
S ` {P } π1 (e, u) {v.Q}
It is important that the second component is a value. Show that the following rule is not valid
in general if e1 is allowed to be an arbitrary expression.
S ` {P } e {v.Q}
.
S ` {P } π1 (e, e1 ) {v.Q}
18
Hint: What if e and e1 read or write to the same location?
The problem is that we know nothing about the behaviour of e1 . But we can specify its
behaviour using Hoare triples. Come up with some propositions P1 and P2 or some conditions
on P1 and P2 such that the following rule
S ` {P } e {v.Q} S ` {P1 } e1 {v.P2 }
.
S ` {P } π1 (e, e1 ) {v.Q}
is derivable. ♦
Exercise 4.9. From Ht-If we can derive two, perhaps more natural, rules, which are simpler to
use. They require us to only prove a specification of the branch which will be taken.
Ht-If-True Ht-If-False
{P ∗ v = true} e2 {u.Q} {P ∗ v = false} e3 {u.Q}
{P ∗ v = true} if v then e2 else e3 {u.Q} {P ∗ v = false} if v then e2 else e3 {u.Q}
Derive Ht-If-True and Ht-If-False from Ht-If. ♦
Exercise 4.10. Show the following derived rule for any expression e and i ∈ {1, 2}.
S ` {P } e {v.v = inji u ∗ Q} S ` {Q [inji u/v]} ei [u/xi ] {v.R}
S ` {P } match e with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {v.R}
♦
19
Define the sequencing expression e1 ; e2 such that when this expression is run first e1 is eval-
uated to a value, the value is discarded, and then e2 is evaluated. Show the following specifica-
tions for the defined construct.
Ht-seq
S ` {P } e1 {v.Q} S ` {∃x. Q} e2 {u.R}
S ` {P } e1 { .Q} S ` {Q} e2 {u.R}
S ` {P } e1 ; e2 {u.R}
S ` {P } e1 ; e2 {u.R}
♦
Exercise 4.14. Derive the following rule.
Ht-bind-det
K is an eval. context S ` {P } e {x.x = u ∧ Q} S ` {Q[u/x]} K[u] {w. R}
S ` {P } K[e] {w. R}
♦
When proving examples, one constantly uses the rule of consequence Ht-csq, the bind rule
Ht-bind and its derived versions, such as Ht-bind-det, and the frame rule Ht-frame. We now
prove a specification of a simple program in detail to show how these structural rules are used.
In subsequent examples in the following we will use these rules implicitly most of the time.
Example 4.15. We want to show the following triple
We start by using the rule Ht-let-det which means we have to show two triples (recalling
that equality is a persistent proposition)
for some intermediate proposition Q. We choose Q to be y 7→ − and using the frame rule Ht-
frame and the value rule Ht-ret we have the first triple.
For the second triple we use the deterministic bind rule Ht-bind-det together with the frame
rule. First we prove
{y 7→ −} 3 + 2 {v.v = 5 ∗ y 7→ −}
{y 7→ −} y ← 5 {v.v = () ∗ y 7→ 5}.
In the following we will not show all the individual steps of proofs since then proofs be-
come very long and tedious. Instead our basic steps will involve Hoare triples at the level of
granularity exemplified by the following exercise.
20
Exercise 4.16. Show the following triples and entailments in detail.
•
{R ∗ ` 7→ m} ` ← ! ` + 5 {v.R ∗ v = () ∗ ` 7→ (m + 5)}
Example 4.17. A slightly more involved example involving memory locations involves the fol-
lowing function. Let
swap = λx y.let z := ! x in x ← ! y; y ← z
To prove it, we will use Ht-let-det and hence we need to prove the following two triples:.
{`1 7→ v1 ∗ `2 7→ v2 } ! `1 {v.v = v1 ∧ `1 7→ v1 ∗ `2 7→ v2 }
The first one follows immediately from Ht-frame and Ht-load. For the second we use the se-
quencing rule Ht-seq, and hence we need to show the following two triples:
{`1 7→ v1 ∗ `2 7→ v2 } `1 ← ! `2 { .`1 7→ v2 ∗ `2 7→ v2 }
{`1 7→ v2 ∗ `2 7→ v2 } `2 ← v1 {v.v = () ∧ `1 7→ v2 ∗ `2 7→ v1 }
Recall that `2 7→ v2 implies `2 7→ −. Hence the second specification follows by Ht-frame, Ht-csq
and Ht-store.
For the first we need to again use the deterministic bind rule Ht-bind-det. After that the
proof consists of parts analogous to the parts we already explained.
Remark 4.18. Note that swap will work even if the locations `1 and `2 are the same. Hence
another specification of swap is
This specification is incomparable (neither of them is derivable from the other) with the pre-
vious one with the rules we have. In fact, with the rules we have, we are not able to show this
specification.
21
4.2 Reasoning about Mutable Data Structures
In the following examples we will work with linked lists – chains of reference cells with forward
links. In order to specify their behaviour we assume (as explained in Section 3.3) that we have
sequences and operations on sequences in our logic together with their expected properties. We
write [] for the empty sequence and x :: xs for the sequence consisting of an element x and a
sequence xs. We define a predicate isList to tie concrete program values to logical sequences.
Our representation of linked lists will mimic that of inductively defined lists, which are either
empty (inj1 ()) or a pointer to a pair of a value and another list (inj2 ` where ` 7→ (h, t)). Notice,
however, that this is not an inductively defined data structure in the sense known from stati-
cally typed funtional languages like ML or Coq – it mimics the shape, but nothing inherently
prevents the formation of, e.g., cyclical lists.
The isList lxs predicate relates values l to sequences xs; it is defined by induction on xs:
Notice that while our data representation in itself does not ensure the absence of, e.g., cyclic
lists, the isList lxs predicate above does ensure that the list l is acyclic, because of separation of
the hd pointer and the inductive occurrence of the predicate.
Exercise 4.19. Explain why the separating conjunction ensures lists are acyclic. What goes
wrong if we used ordinary conjunction? ♦
To specify the next example we assume the map function on sequences in the logic. It is the
logical function from sequences to sequences that applies f at every index of the sequence: it is
defined by the following two equations
map f [] ≡ []
map f (x :: xs) ≡ f x :: map f xs
Example 4.20. We now have all the ingredients to write and specify a simple program on linked
lists. Let inc denote the following program that increments all values in a linked list of integers:
We proceed by the Ht-Rec rule and we consider two cases: we use the fact that the sequence xs
is either empty [] or has a head x followed by another sequence xs0 .
In the first case we need to show
22
and in the second
∀x, xs0 .∀l. isList l(x :: xs0 ) match l with... v.v = () ∧ isList l(map(1+)(x :: xs0 ))
where in the body we have replaced inc with the function f for which we assume the triple (the
assumption of the premise of the Ht-Rec rule)
In both cases we proceed by the derived match rule from Exercise 4.10, as the isList predicate
tells us enough information to determine the chosen branch of the match statement.
In the first case we have isList l[] ≡ l = inj1 (), and thus we easily prove
which follows by Ht-ret after framing away isList l[], which is allowed as map(+1)[] ≡ [].
In the case where the sequence is not empty we have that
l
r.r = inj2 hd ∗ l = inj2 hd ∗ hd 7→ (x, l 0 ) ∗ isList l 0 xs0
for some l 0 and hd, using the rule Ht-exist, the frame rule, the Ht-Pre-Eq rule, and the Ht-ret
rule.
Exercise 4.21. Prove this Hoare triple in detail using the rules just mentioned. ♦
This is enough to determine that the match takes the second branch, and using the derived
match rule from Exercise 4.10 we proceed to verify the body of the second branch. Using the
rules Ht-let-det and Ht-Proj repeatedly we quickly prove
let h := π1 !hd in
let t := π2 !hd in
hd ← (h + 1, t)
l = inj2 hd ∗ hd 7→ (x + 1, l 0 ) ∗ isList l 0 xs0 ∗ t = l 0 ∗ h = x
Now
23
clearly implies
and thus, by the sequencing rule Ht-seq and the rule of consequence Ht-csq, we are left with
proving
ft
r.r = () ∗ isList l(map(+1)(x :: xs0 ))
which follows from the induction hypothesis (3), and the definition of the isList predicate.
Remark 4.22 (About functions taking multiple arguments). The programming language λref,conc
only has primitive functions which take a single argument. Functions taking multiple argu-
ments can be encoded as either higher-order functions returning functions, or as functions
taking tuples as arguments.
Therefore we use some syntactic sugar to write functions taking multiple arguments in a
more readable way. We write
rec f x, y := e
for the following term
rec f p := let x := π1 p in let y := π2 p in e.
This notation is generalised in an analogous way to three and more arguments. The correspond-
ing derived Hoare rule is the following
Ht-Rec-multi
Γ , g :Val | S ∧ ∀z. ∀v1 . ∀v2 . {P } g(v1 , v2 ) {u.Q} ` ∀z. ∀v1 . ∀v2 . {P } e[g/f , v1 /x, v2 /y] {u.Q}
Γ | S ` ∀z. ∀v. {∃v1 , v2 . v = (v1 , v2 ) ∧ P } (rec f x, y := e)v {u.∃v1 , v2 . v = (v1 , v2 ) ∧ Q}
24
• Is the following specification also valid?
♦
Exercise 4.25. The append function in the previous exercise is not tail recursive and hence its
space consumption is linear in the length of the first list. A better implementation of append
for linked lists is the following.
In the function go the value p is the last node of the list l we have seen while traversing l. Thus
go traverses the first list and once it reaches the end it updates the tail pointer in the last node
to point to the second list, l 0 .
Prove for append’ the same specification as for append above. You need to come up with a
strong enough invariant for the function go, relating h, p and xs and ys. ♦
Exercise 4.26. Implement, specify and prove correct a length function for linked lists. ♦
Exercise 4.27. Using the above specifications, construct a program using append and length,
give it a reasonable specification and prove it. ♦
For the following example, we need to relate a linked list to the reversal of a mathematical
sequence. The reverse function on sequences is defined as follows:
reverse[] ≡ []
reverse(x :: xs) ≡ reverse xs ++ [x]
Example 4.28. Consider the following program which performs the in-place reversal of a linked
list using an accumulator to remember the last element which the function visited:
25
Intuitively we wish to give it the following specification:
The resulting induction hypothesis is, however, not strong enough, and we need to strengthen
the specification. On the one hand, we are now proving a stronger statement, which requires
more work. On the other, we get to leverage a much stronger induction hypothesis, eventually
allowing the proof to go through.
We generalize the specification of rev to the following specification:
∀vs, us.∀hd, acc.{isList hd vs ∗ isList acc us} rev(hd, acc) {r. isList r(reverse vs ++ us)}
reverse’[]acc ≡ acc
reverse’(x :: xs)acc ≡ reverse’ xs(x :: acc)
Convince yourself that ∀xs. reverse’ xs[] ≡ reverse xs is not directly provable by induction as the
resulting induction hypothesis is too weak. ♦
Exercise 4.30. Prove that in-place reversing twice yields the original list. ♦
We here proceed with the proof of the general specification. The strategy is the same as
in the case of the inc function: we use the Ht-Rec rule and case analysis on the sequence vs,
followed by the derived match rule.
If vs = [], we argue that
and thus by using the derived rule for match from Exercise 4.10 we need to show the following
triple for the first branch of the match.
and we are thus in the second branch of the match. We start by showing
let h := π1 !hd in
let t := π2 !hd in
hd ← (h, acc)
l = inj2 hd ∗ hd 7→ (v, acc) ∗ isList l 0 vs0 ∗ isList acc us ∗ h = v ∗ t = l 0
26
by repeated applications of Ht-let-det and Ht-Proj. Clearly the proposition
which is simply
by definition of the isList predicate. Finally by using the induction hypothesis (the assumption
of Ht-Rec) we have
rev(t, l)
r. isList r(reverse vs0 ++ v :: vs)
and we are done, observing that (reverse vs0 ++ v :: vs) ≡ reverse(v :: vs0 ) ++ vs.
Thus combining all of these using the sequencing rule Ht-seq and the rule of consequence
Ht-csq we have proved
let h := π1 !hd in
let t := π2 !hd in
hd ← (h, acc); rev(t, l)
r. isList r(reverse(v :: vs0 ) ++ vs)
as required.
27
The above specification is unsatisfactory since it completely exposes the internals of the
counter, which means that it is not modular: if we verify a client of the counter relative to the
above specification and then change the implementation of the counter, then the specification
will likely also change and we will have to re-verify the client. A more abstract and modular
specification is the following, which existentially quantifies over the “counter representation
predicate” C, thus hiding the fact that the return value is a location.
∃C :Val → N → Prop.
{True} mk counter() {c.C(c, 0)}∗
(4)
∀c. {C(c, n)} inc counter c {v.v = () ∧ C(c, n + 1)}∗
∀c. {C(c, n)} read counter c {v.v = n ∧ C(c, n)}.
This approach is not ideal either, because the code, consisting of three separate functions, does
not provide any abstraction on its own (a client of this code would be able to modify directly
the contents of the reference cell reprensenting the counter rather than only through the read
and increment methods) and is not the kind of code that realistically would be written. In a
typed language, the three functions would typically be sealed in a module, and the return type
of the mk counter method would be abstract, only supporting read and write operations.
In our untyped language, we can also hide the internal state and only expose the read and
increment methods as follows:
The idea is that the counter method returns a pair of methods which have a hidden internal
state variable x. The specification of this counter needs nested Hoare triples. One possibility is
the following. To aid readability we use v. inc in place of π1 v and analogously for v. read.
` 7→ 0∗
{True} counter() v.∃`.
∀n. {` 7 → n} v. inc() {u.u = () ∧ ` 7 → (n + 1)}∗
∀n. {` 7→ n} v. read() {u.u = n ∧ ` 7→ n}
A disadvantage of this specification is, again, that it exposes the fact that the internal state is a
single location which contains the value of the counter.
Exercise 4.32. Show that the counter method satisfies the above specification. ♦
A better, modular, specification completely hides the internal state in an abstract predicate:
C(0)∗
{True} counter() v.∃C : → Prop. (5)
N ∀n. {C(n)} v. inc() {u.u = () ∧ C(n + 1)}∗
∀n. {C(n)} v. read() {u.u = n ∧ C(n)}
Exercise 4.33. Show this specification of the counter method. Can you derive it from the pre-
vious one? What about conversely? Can you derive the previous specification from the current
one? Hint: Think about another implementation of counter which satisfies the second specifi-
cation, but not the first. ♦
Exercise 4.34. Define the counter method with the help of the methods mk counter, inc counter
and read counter and derive specification (5) from specification (4). ♦
28
Ideally we would want to use this combination of code and specification. However, it has
some practical usability downsides when used in the accompanying Coq formalization. Chief
among them is that it is easier to define, e.g., an is counter predicate, and use that instead of
always having to deal with eliminating an existentially quantified predicate. To make the proofs
more manageable, we will therefore usually write modules and specifications in the style of (4)
and understand that we let go of abstraction at the level of the programming language.
As long as we are only using the specifications of programs it does not matter that the actual
implementation exposes more details. And as we will see in examples, this is how we prove
clients of modules. When working in Coq, for instance, we can ensure this mode of use of code
and specifications using Coq’s abstraction features.
Here isStack(s, xs) is an assertion stating that s is a stack, which contains the list of elements xs
(in the specified order). The specifications of push and pop are self-explanatory. This specifi-
cation is useful for verification of certain clients, namely those that push and pop pure values,
such as numbers, strings, and integers, to and from the stack. However, it is not strong enough
to verify clients which operate on stacks of, e.g., locations.
Exercise 4.35. Using the stack specification above, show that the program e defined as
29
There are several things to notice about this specification. First is the universal quantification
over arbitrary predicates Φ and lists of predicates Φs, this makes the specification higher-order.
The second thing to notice is the ownership transfer in the specifications of push and pop
methods. This is related to the ownership reading of assertions explained in Section 3.1 above.
The idea is that, when pushing, the client of the stack transfers the resources associated with
the element x (the assertion Φ(x)) to the stack. Conversely, when executing the pop operation
the client of the stack gets the value v and the resources associated with the value (the assertion
Φ(v)).
An example of a resource that can be transferred is the points to assertion ` 7→ 3. In this
case, the Φ predicate would be instantiated with λx.x 7→ 3.
Exercise 4.36. Derive specification (6) from the more general specification (7). ♦
To see where this more general specification is useful let us consider an example client.
let s := mk stack() in
let x1 := ref (1) in
let x2 := ref (2) in
push(x1 , s); push(x2 , s); pop(s);
let y := pop(s) in (8)
match y with
inj1 () ⇒ ()
| inj2 ` ⇒ ! `
end
The stronger specification also gives us additional flexibility when specifying functions
working with stacks. For instance, we can specify a function f which expects a stack of lo-
cations pointing to prime numbers and returns a prime number as follows.
where PrimeLoc(Φ) asserts that all predicates in Φs are of a particular form, namely that they
hold only for locations which point to prime numbers. And isPrime(v) asserts that v is a prime
number. We omit its definition here.
The assertion PrimeLoc(Φs) can be defined by recursion on the list Φs by the following two
cases.
PrimeLoc([]) ≡ True
PrimeLoc(Φ :: Φs) ≡ (Φ(v) −∗ ∃n. v 7→ n ∗ isPrime(n)) ∗ PrimeLoc(Φs)
Such a use case where we want a certain property to hold for all elements of a stack is
common. Thus it is useful to have a less general stack module specification tailored for the use
30
case. The specification we have in mind is
The idea is that isStack(s, xs, Φ) asserts that s is a stack whose values are xs and all of the values
x ∈ xs satisfy the given predicate Φ. The difference from the specification (7) is that there is
a uniform predicate Φ which all the elements have to satisfy, as opposed to having a list of
predicates, one for each element of the stack. We can derive the specification (9) from the
specification (7) as follows.
Let isStackg : Val → list(Val → Prop) → Prop be the predicate associated with the general
stack specificaton (7). We define isStacku :Val → listVal → (Val → Prop) → Prop as
Φs([]) ≡ []
Φs(x :: xs) ≡ (λy.x = y ∧ Φ(y)) :: Φs(xs)
Exercise 4.38. Using isStacku derive the specifications of mk stack, push and pop as stated
in (9). ♦
The first argument f is a function taking a pair as an argument, the second argument a is the
result of foldr on the empty list, and l is the linked list.
31
The specification of the foldr function is as follows.
isList l xs ∗ all P xs ∗ I [] a ∗
∀P , I. ∀f ∈Val. ∀xs. ∀l.
∀x ∈Val. ∀a0 ∈Val. ∀ys. {P x ∗ I ys a0 } f (x, a0 ) {r.I(x :: ys)r}
foldr(f , a, l)
{r. isList l xs ∗ I xs r}
where the predicate all P xs states that P holds for all elements of the list. It is defined by
induction on the list as
all P [] ≡ True
all P (x :: xs) ≡ P x ∗ all P xs
We explain the specification by how it is used in the proof of foldr, which is detailed below. In
the proof of foldr we are only going to use the specification for f when l represents the sequence
(x :: xs), where the sequence xs is represented by some implementation t. In this case we are
going to instantiate the specification of f with foldr(f , a, t) as a0 , xs as ys and x as x.
In this sense the specification for f simply says that if x satisfies P and the result a0 of folding
f over the subsequence xs satisfies the invariant, then applying f to (x, a0 ) (which is exactly what
foldr does on the sequence (x :: xs)) gives a result, such that I relates it to the sequence (x :: xs).
32
Remark 5.1. The only place, where our concrete implementation l of the list (as a linked list)
is used is in isList l xs where it is linked to a mathematical sequence xs. The predicates P , I
and all are all defined on mathematical sequences instead. In this sense we may say that the
mathematical sequences are abstractions or models of our concrete lists and P , I and all oper-
ate on these models, hence the distinction between implementation details and mathematical
properties becomes clear.
into the context i.e. we may assume it. We thus have to prove
{isList l xs ∗ all P xs ∗ I [] a}
∀x. ∀a . ∀ys. P x ∗ I ys a0 f (x, a0 ) {r.I (x :: ys)} `
0
foldr(f , a, l)
{r. isList l xs ∗ I xs r}
We proceed by Ht-Rec i.e. we have to prove the specification for the body of foldr in which
we have replaced any occurrence of foldr with the function g for which
is assumed.
By the definition of the isList predicate we have that the list l points to is either empty [] or
has a head x followed by another list xs0 .
In the first case we need to show
In both cases we proceed by the derived match rule from Exercise 4.10.
In the first case we have isList l [] ≡ l = inj1 () hence the first case is taken (follows by Ht-
frame, Ht-Pre-Eq and Ht-ret), thus we have to prove
{I [] a} a {r.I [] r}
{I [] a} a {r.r = a ∗ I [] a}
33
In the second case we have isList l (x :: xs0 ) ≡ ∃hd, l 0 . l = inj2 hd ∗ hd 7→ (x, l 0 ) ∗ isList l 0 xs0 . By
exercise 4.21 we get that the second case is taken, thus we need to prove
let h := π1 ! x2 in
let t := π2 ! x2 in
f (h, (g(f , a, t)))
r. isList l (x :: xs0 ) ∗ I (x :: xs0 ) r
By simple applications of Ht-let-det, Ht-frame, Ht-bind, Ht-load, Ht-Proj and Ht-ret we need to
show:
isList l 0 xs0 ∗ all P (x :: xs) ∗ I [] a ∗ l = inj2 hd ∗ hd 7→ (x, l 0 )
f (x, g(f , a, l 0 ))
r. isList l (x :: xs0 ) ∗ I (x :: xs0 ) r
g(f , a, l 0 )
r. isList l 0 xs0 ∗ I xs0 r ∗ P x ∗ l = inj2 hd ∗ hd 7→ (x, l 0 )
3. the specification
f (x, v)
r. isList l (x :: xs0 ) ∗ I (x :: xs0 ) r
f xv
r. isList l (x :: xs0 ) ∗ I (x :: xs0 ) r
34
Client: sumList
The following client is a function that computes the sum of a list of natural numbers by making
a right-fold on +, 0 and the list. The code below is a slightly longer as it has to take into account
that f takes the arguments as a pair.
∀l. ∀xs. {isList l xs ∗ all isNat xs} sumList l {r. isList l xs ∗ r = Σx∈xs x}
where
True if x ∈ N
isNat x ≡
False otherwise
Proof of the sumList specification Let l and xs be given. By Ht-let-det it suffices to show
as f , l as l, and xs as xs we get
n o n o
∀x, a. ∀ys. isNat x ∗ a = Σy∈ys y (λp, let x := π1 p in let y := π2 p in x + y)(x, a) r.r = Σy∈(x::ys) y
∗ isList l xs ∗ all isNat xs ∗ 0 = Σ x
x∈[]
foldr ((λp, let x := π1 p in let y := π2 p in x + y), a, l)
{r. isList l xs ∗ r = Σx∈xs x}
which is almost what we want. The difference being the precondition. By Ht-csq it suffices to
show
n o
isNat x ∗ a = Σ y∈ys y
∀x, a. ∀ys. (λp, let x := π p in let y := π p in x + y)(x, a)
1 2
isList l xs ∗ all isNat xs ⇒
n o
r.r = Σy∈(x::ys) y
35
n o n o
1. ∀x, a. ∀ys. isNat x ∗ a = Σy∈ys y (λp, let x := π1 p in let y := π2 p in x+y)(x, a) r.r = Σy∈(x::ys) y
2. 0 = Σx∈[] x
without assuming anything.
The second item is immediate, and we leave the first as an exercise.
Exercise 5.2. Prove the specification (1) above. ♦
Client: filter
The following client implements a filter of some boolean predicate p on a list l, i.e., it creates a
new list whose elements are precisely those elements of l that satisfy p.
Specification
where
listFilter P [] ≡ []
(x :: (listFilter P xs)) if P x = true
listFilter P (x :: xs) ≡
listFilter P xs
otherwise
The specification
in the precondition of the specification of filter states that p implements the boolean predicate
P . The specification of filter states that given such an implementation p and a list l, whose
elements corresponds to the mathematical sequence xs, then filter returns (without changing
the original list) a list r whose elements are those that satisfy P .
Proof of the specification of filter Let P , l and xs be given. By Ht-let-det it suffices to show
36
By applying the specification for foldr with True as P x, isList a (listFilter P xs) as I xs a,
(λy, let x := π1 y in let xs := π2 y in if p x then inj2 (ref (x, xs)) else xs)
λy, let x := π1 y in let xs := π2 y in if p x then inj2 (ref (x, xs)) else xs.
S ` {P } e [v/x] {u.Q}
S ` {P } (λx.e)v {u.Q}
37
Given a value F, the call-by-value Turing fixed-point combinator ΘF is the following term
ΩF = λr.F(λx.rrx)
ΘF = ΩF ΩF
ΘF v F(λx.ΘF x)v
Ht-Turing-fp
Γ | S ∧ ∀v. {P } ΘF v {u.Q} ` ∀v. {P } F(λx.ΘF x)v {u.Q}
Γ | S ` ∀v. {P } ΘF v {u.Q}
But at present there is nothing in the logic which would allow us to do so. In contrast to the
specifications of the linked list methods above, there is no tangible structure, such as the math-
ematical sequence representing a list, which could be used as a crutch to prove the specification
by induction, for example.
We are thus led to extend the logic with a new construct, the . (pronounced “later”) modal-
ity. The main punch of the later modality is the Löb rule:
Löb
Q ∧ .P ` P
Q`P
which is similar to a coinduction principle. It states that (in any context Q), if from . P we can
derive P , then we can also derive P without any assumptions. When using this rule we will
often say we “proceed by Löb induction”, or that we are going to use “Löb induction”.
Note that the . modality is necessary for the rule to be sound: if we admit the rule
Q∧P ` P
(10)
Q`P
to the logic, then the logic would become inconsistent, in the sense that we could then prove
True ` False.
Exercise 6.1. Assuming (10) derive True ` False. ♦
Intuitively, the . modality in the premise . P in the Löb rule ensures that when proving P ,
we need to “do some work” before we can use P as an assumption.
The rest of the rules for the . modality, listed in Figure 6, essentially ensure that that we can
get the later modality at the correct place in order to use the Löb rule.
7 The rule is simplified by the removal of the “logical” variables y in the rule Ht-Rec. These are not essential for
understanding this example and would only complicate the reasoning.
38
later-mono later-weak Löb .-∃ ∃-.
Q`P Q`P Q ∧ .P ` P τ is inhabited Q ` . ∃x : τ. P Q ` ∃x. . P
.Q ` .P Q ` .P Q`P Q ` ∃x : τ. . P Q ` . ∃x. P
Figure 6: Laws for the later modality. A type τ is inhabited if ` t : τ is derivable for some t.
Strengthening the Hoare rules In order for the later modality to be useful for proving speci-
fications (Hoare triples) we will need to connect it in some way to the steps a program can take.
Let us see how this comes up by trying to show Ht-Turing-fp.
We proceed by Löb induction so we assume . ∀v. {P } ΘF v {u.Q} and we are to show
∀v. {P } ΘF v {u.Q}.
Let v be a value. By using the Ht-bind and Ht-beta it suffices to show {P } F(λx.ΘF x)v {u.Q} and
thus by the assumption of the rule we are proving it suffices to show
∀v. {P } ΘF v {u.Q}.
However we only have the Löb induction hypothesis
. ∀v. {P } ΘF v {u.Q}
from which we cannot get what is needed. The issue is that we have not connected the later
modality to the programming language constructs in any way. One way to do that is to have a
stronger Ht-beta rule, which only assumes . P in the precondition of the triple in the conclusion
of the rule:
Ht-beta
S ` {P } e [v/x] {u.Q}
S ` {. P } (λx.e)v {u.Q}
The intuitive explanation for why this rule is sensible is that the term (λx.e)v takes one more
step to evaluate than e [v/x]; hence any resources accessed by the body e will only be needed
one step later.
Exercise 6.2. Convince yourself that the old beta rule is derivable from the new one using the
rules presented thus far. ♦
The final piece we need is that if P is a persistent proposition (e.g., a Hoare triple or equality)
then . P is also a persistent proposition, which implies that we can move it in and out of the
preconditions (c.f. Ht-Ht).
Let us prove the rule Ht-Turing-fp. We proceed by Löb induction so we assume
. ∀v. {P } ΘF v {u.Q}
and we are to show
∀v. {P } ΘF v {u.Q}.
39
Let v be a value. By using later-weak and the rule of consequence Ht-csq it suffices to show
{. P } ΘF v {u.Q}. Since Hoare triples are persistent propositions this is equivalent to showing
{.(∀v. {P } ΘF v {u.Q} ∧ P )} ΘF v {u.Q}
By the bind rule and the stronger rule Ht-beta introduced above it thus suffices to show
{∀v. {P } ΘF v {u.Q} ∧ P } F(λx.ΘF x)v {u.Q}
which again is equivalent (rule Ht-Ht) to showing {P } F(λx.ΘF x)v {u.Q} assuming ∀v. {P } ΘF v {u.Q}.
But this is exactly the premise of the rule Ht-Turing-fp, and thus the proof is concluded.
S ` {. ` 7→ u} ! ` {v.v = u ∧ ` 7→ u} S ` {. ` 7→ −} ` ← w {v.v = () ∧ ` 7→ w}
Ht-Rec
Γ , g :Val | S ∧ ∀y. ∀v. {P } gv {u.Q} ` ∀y. ∀v. {P } e[g/f , v/x] {u.Q}
Γ | S ` ∀y. ∀v. {. P } (rec f x := e)v {u.Q}
Ht-Match
S ` {P } ei [u/xi ] {v.Q}
S ` {. P } match inji u with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {v.Q}
Exercise 6.3. Derive the rules in Section 4 apart from Ht-Rec, for which see Exercise 7.4 below,
from the rules just listed. ♦
Exercise 6.4. This is a variant of Exercise 4.12 above deriving stronger rules for Hoare triples
which allow us to remove more later modalities.
Show the following rules
Ht-let
S ` {P } e1 {x. . Q} S ` ∀v. {Q[v/x]} e2 [v/x] {u.R}
S ` {P } let x := e1 in e2 {u.R}
Ht-let-det
S ` {P } e1 {x. . x = v ∧ . Q} S ` {Q[v/x]} e2 [v/x] {u.R}
S ` {P } let x := e1 in e2 {u.R}
Ht-seq Ht-If
S ` {P } e1 {v. . Q} S ` {∃x. Q} e2 {u.R} {P ∗ b = true} e2 {u.Q} {P ∗ b = false} e3 {u.Q}
S ` {P } e1 ; e2 {u.R} {. P } if b then e2 else e3 {u.Q}
♦
In the rest of the document we use these stronger variants of the rules. (For instance, when
in future sections we refer to the sequencing rule, we refer to the one above involving a ..)
40
6.2 Recursively defined predicates
With the addition of the later modality we can extend the logic with recursively defined predi-
cates. The terms of the logic are thus extended with
t ::= · · · | µx : τ. t
with the side-condition that the recursive occurrences must be guarded: in µx. t, the variable x
can only appear under the later . modality.
The typing rule for the new term is as expected
Γ ,x : τ ` t : τ x is guarded in t
Γ ` µx : τ. t : τ
where x is guarded in t if all of its occurrences in t are under the . modality. For example, P is
guarded in the following terms (where Q is a closed proposition)
.(P ∧ Q) (. P ) ⇒ Q Q
P ∧ .Q P ⇒Q P ∨ Q.
We have a new rule for using guarded recursively defined predicates, which expresses the fixed-
point property:
Mu-fixed
Q ` µx : τ. t =τ t [µx : τ. t/x]
41
example is also related to the notion of invariants we introduce later) we can imagine a slight
refinement of heaps as a notion of resource. Suppose that each heap is split into two parts
(hr , hw ), where hr is read-only, and hw is an ordinary heap which supports reading and writing.
Composition of (hr , hw ) and (h0r , h0w ) is only defined when the components hr and h0r are the same,
and moreover the composition of hw and h0w is defined as composition of ordinary heaps. Then
the duplicable part of the element (hr , hw ) is the element (hr , ∅), where ∅ is the empty heap.
Duplicable resources are important because we can always create a copy of them to give
away to other threads. One way to state this precisely is the following rule
persistently-dup
P a` P ∗ P
This rule is derivable from the axioms for the persistently modality, which are shown in Fig-
ure 7. We discuss these below.
We call a proposition P persistent if it satisfies P ` P . Persistent propositions are important
because they are duplicable, see Exercise 7.2.
An example of a persistent proposition is the equality relation. A non-example of a persistent
proposition is the points-to predicate x 7→ v. Indeed, any heap in x 7→ v contains at least one
location, namely x. Hence the empty heap is not in x 7→ v. By the same reasoning we have
(x 7→ v) a` False.
Given the above intuitive reading of P , the first three axioms for in Figure 7, persistently-
mono, persistently-E and persistently-idemp, are immediate. These three rules together allow us
to prove the following “introduction” rule for the persistently modality.
persistently-intro
P ` Q
P ` Q
Exercise 7.1. Prove the above introduction rule. ♦
The rules in the right column of Figure 7 state that True is persistent. If resources are heaps
then True is the set of all heaps. Hence True in particular contains the empty heap and thus
True = True. The rest of the rules state that commutes with the propositional connectives.
We do not explain why that is the case since that would require us to describe a model of the
logic.
Next comes the rule persistently-sep and the derived rule persistenly-sep-derived. They gov-
ern the interaction between separating conjunction, conjunction, and the persistently modality.
persistently-sep
S ` P ∧ Q
S ` P ∗ Q
The intuitive reason for why this rule holds is that any resource r in P and Q can be split into
a duplicable resource s in P (and since it is duplicable also in P ) and r. Hence r is in P ∗ Q.
There is an important derived rule which states that under the persistently modality con-
junction and separating conjunction coincide. The rule is
persistenly-sep-derived
S ` (P ∧ Q)
S ` (P ∗ Q)
which is equivalent to the entailment
(P ∧ Q) ` (P ∗ Q).
42
To derive this entailment we first show the following:
P ` P
. (11)
P ` P ∗P
Indeed, using P ` P we have
P ` P ` P ∧ P ` P ∗ P ` P ∗ P
using the rule persistently-sep in the third step, and P ` P and monotonicity of separating
conjunction in the last.
Next, by the fact that commutes with conjunction and (11), we have
P ∧ Q ` (P ∧ Q) ` (P ∧ Q) ∗ (P ∧ Q)
Now, using the fact that P ∧ Q ` P and P ∧ Q ` Q and monotonicity of and ∗, we have
(P ∧ Q) ∗ (P ∧ Q) ` P ∗ Q.
Hence we have proved
P ∧ Q ` P ∗ Q. (12)
With this we can finally derive persistenly-sep-derived as follows
(P ∧ Q) ` (P ∧ Q) ` ( P ∧ Q) ` ( P ∗ Q) ` (P ∗ Q)
where in the last step we again use P ` P and monotonicity of separating conjunction.
Exercise 7.2. Using similar reasoning show the following derived rules.
1. P ` P
2. (P ⇒ Q) ` P ⇒ Q
3. P ⇒ Q ` P −∗ Q
4. (P −∗ Q) ` (P ⇒ Q)
5. (P −∗ Q) ` P −∗ Q
6. P ⇒ Q ` P −∗ Q
7. if P ` Q then P ` Q ∗ P .
8. (P −∗ (Q ∗ R)) ∗ P ` (P −∗ (Q ∗ R)) ∗ P ∗ R
The last two items are often useful. They state that we can get persistent propositions without
consuming resources. ♦
Exercise 7.3. Derive (x 7→ v) ` False using the rules in Figure 7 and the basic axioms of the
points-to predicate. ♦
Exercise 7.4. Show the following derived rule for recursive function calls from the rule Ht-Rec
above.
Ht-rec-lob
Γ , f :Val | Q ∧ ∀y. ∀v. {. P } f v {Φ} ` ∀y. ∀v. {P } e[v/x] {Φ}
Γ | Q ` ∀y. ∀v. {. P } (rec f x := e)v {Φ}
Hint: Start with Löb induction. Use the rule Ht-persistently to move the Löb induction hypoth-
esis into the precondition. Then use the recursion rule Ht-Rec, and again Ht-persistently. You
are almost there. ♦
43
persistently-mono persistently-sep
persistently-dup P `Q persistently-E persistently-idemp S ` P ∧ Q
P a` P ∗ P P ` Q P ` P P ` P S ` P ∗ Q
persistently-∀ persistently-∃
∀x. P a` ∀x. P ∃x. P a` ∃x. P
We have the following rule Ht-persistently which, combined with the rules above, generalizes
Ht-Ht, Ht-Eq and Ht-False.
Ht-persistently
Q ∧ S ` {P } e {v. R}
S ` {P ∧ Q} e {v. R}
44
waits until the value at the given location is Some x for some value x, i.e., until the flag is set.
Finally, we define new infix notation for the par construct. It wraps the given expressions into
thunks:
In words the functions do the following. We spawn a new thread and run the function f1 there.
In the current thread we execute f2 and then wait until f1 finishes (the call to join). The notation
e1 || e2 is then a wrapper around par. We need to make thunks out of expressions because our
language is call-by-value. If we were to pass expression e1 and e2 directly to par, then they
would be evaluated in the current thread before being passed to spawn, hence defeating the
purpose of par.
The || construct satisfies the following specification which we derive in Section 8.8 from the
primitive fork {} specification.
Ht-par
S ` {P1 } e1 {v.Q1 } S ` {P2 } e2 {v.Q2 }
S ` {P1 ∗ P2 } e1 || e2 {v.∃v1 v2 . v = (v1 , v2 ) ∗ Q1 [v1 /v] ∗ Q2 [v2 /v]}
The rule states that we can run e1 and e2 in parallel if they have disjoint footprints and that in
this case we can verify the two components separately. Thus this rule is sometimes also referred
to as the disjoint concurrency rule.
With the concepts introduced so far we can verify simple examples of concurrent programs.
Namely those where threads do not communicate. We hasten to point out that there are many
important examples of such programs. For instance, list sorting algorithms such as quick sort
and merge sort, where the recursive calls operate on disjoint lists of elements.
Exercise 8.1. Prove the following specification.
45
where e is the program ` ← ! ` + 1. The problem here is that we cannot split the ` 7→ n predicate
to give to the two subcomputations.
Note that we cannot hope to prove
{` 7→ n} (e || e); ! ` {v.v = n + 2}
since the command ` ← ! ` + 1 is not atomic: both threads could first read the value stored at `,
which is n, and then write back the value n + 1.
The best we can hope to prove is
However this specification is considerably harder to prove than (13). To avoid having to intro-
duce too many concepts at once, we first focus on describing the necessary concepts for proving
(13). We return to proving the specification (14) in Example 8.31 after we introduce the neces-
sary concepts.
What we need is the ability to share the predicate ` 7→ n among the two threads running
in parallel. Invariants enable such sharing: they are persistent resources, thus duplicable, and
hence sharable among several threads.
8.2 Invariants
To introduce invariants we need to add a type of invariant names InvName to the logic. Invari-
ants are associated with names and names are used to ensure that we do not open an invariant
more than once. We explain why this is needed later on.
ι
We add a new term P to the logic, which should be read as invariant P named ι, or associated
with the name ι.
The typing rule for the new construct is as follows.
Γ ` P : Prop Γ ` ι : InvName
ι
Γ ` P : Prop
That is, we can make an invariant out of any proposition and any name. Notice in particular
0
ι ι
that we can form nested invariants, e.g., terms of the form P .
The rules for invariants are listed in Figure 8 on page 50. As mentioned above we need
to make sure that we do not open the same invariant more than once (see Example 8.3 for
an example of what goes wrong if we allow opening an invariant twice). For this reason we
need to annotate Hoare triples with an infinite set of invariant names E. This set identifies the
invariants we are allowed to use; see the rule Ht-inv-open. If there is no annotation on the Hoare
triple then E = InvName, the set of all invariant names. With this convention all the previous
rules are still valid.
With the addition of invariant names to Hoare triples there is a need to relate Hoare triples
with different sets of invariant names. We just have one rule for that:
Ht-mask-weaken
S ` {P } e {v.Q}E1 E1 ⊆ E2
S ` {P } e {v.Q}E2
This weakening rule allows us to add more invariant names. Intuitively it is sound, because if
we are allowed to use more invariants then surely we can prove more specifications.
We now explain the rules for invariants.
46
Invariants are persistent The essential property of invariants is that they can be shared by
different threads. The precise way to state this property in Iris is that invariants are persistent
(the rule Inv-persistent).
has the following interpretation. To verify a program e, which will typically contain either
fork or parallel composition ||, we want to share the resources described by P between different
threads. To this end we give away the resources to an invariant, i.e., we lose the resources P , but
ι
we obtain an invariant P for some ι. We can only specify that ι comes from some infinite set of
names, but no more. The ability to choose E is needed when we wish to use multiple invariants.
We want different invariants to be named differently so that we can open multiple invariants at
the same time; see the explanation of the Ht-inv-open rule below.
Invariants are persistent, so giving away resources to invariants is not without cost. The cost
is that invariants can only be used in a restricted way, namely by the invariant opening rule.
Footprint reading of Hoare triples With the introduction of invariants, the “minimal foot-
print” reading of Hoare triples mentioned in Section 4 must be refined. Now the resources
needed to run the program e can either be in the precondition P of the triple {P } e {v.Q} or
they can be governed by one or more invariants. Thus we will often prove triples of the form
{True} e {v.Q}, for some Q, where e accesses shared state governed by an invariant. See Exam-
ple 8.4, in particular the proof of the triple (15) on page 51.
Graphically, we can depict the situation as follows.
I1 I2
P1 P2
The heap (and other resources) is split between local state owned by the two threads (resources
P1 owned by the first thread, and resources P2 owned by the second thread) and some shared
state owned by invariants (in this case I1 and I2 ). Individual threads can access the state owned
47
by invariants and temporarily transfer it to their local state, using the invariant opening rule
we will see below. Thus if, for instance, the first thread opens invariant I2 , we can depict the
state as follows.
I1 I2
P1 P2
The resources owned by the invariant are temporarily transferred to the local state of the first
thread. This is the essence of the invariant opening rule.
is the only way to get access to the resources governed by an invariant. The rule states that
ι
if we know an invariant P exists, we can temporarily, for one atomic step, get access to the
resources. This rule is the reason we need to annotate the Hoare triples with sets of invariant
names E. This set contains names of those invariants which we are allowed to open. We refer
to E as a mask. In particular, we cannot open the same invariant twice (see Example 8.3 for an
example of what goes wrong if we allow opening an invariant twice).
Note that the reader might perhaps be puzzled as to why we need the set of invariant names
when we could perhaps just have the rule
removing the knowledge about the invariant once we open it. The reason is that this would not
ι
prevent opening invariants in a nested way because the invariant assertion P is persistent. For
this reason from the rule just mentioned we can easily derive
ι
e is an atomic expression S ∧ P ` {. P ∗ Q} e {v. . P ∗ R}
ι
S ∧ P ` {Q} e {v.R}
48
which would lead to a contradiction as shown in Example 8.3.
The restriction of the term e to be an atomic expression is also essential; see Example 8.6. An
expression e is atomic if it steps to a value in a single execution step.
Existing Hoare triple rules The existing rules for Hoare triples, e.g., those in Figure 5, are all
still valid with arbitrary masks, but the premises and conclusions of the rules must be annotated
with the same mask. For example, the rule Ht-beta becomes
Ht-beta
S ` {P } e [v/x] {u.Q}E
S ` {P } (λx.e)v {u.Q}E
for an arbitrary invariant mask E.
The rules in Figure 8 will be considerably generalised and simplified later, but for that we
need concepts we have not yet introduced.
Before proceeding with an example we need one more rule, namely a stronger frame rule,
which is only applicable in certain cases. The rule is needed because opening invariants only
gives access to the resources later. This is essential. The logic would be inconsistent otherwise,
though proof of this fact is not yet within our reach.8
The stronger frame rule is the following
Ht-frame-atomic
e is an atomic expression S ` {P } e {v.Q}
S ` {P ∗ . R} e {v.Q ∗ R}
This rule is useful because typically an invariant will contain something akin to ` 7→ v, plus
some additional facts about v, and the expression e will be either reading from or writing to the
location `. This rule, together with the invariant opening rule, allows us to get the facts about
v now (note that there is no . on R in the postcondition) after reading the value.
Exercise 8.2 (Later False). We will often use an invariant to tell us that some cases are impos-
sible. For instance an invariant will often encode a transition system, e.g., encoding a commu-
nication protocol, and different threads will hold tokens which will be used to guarantee that
the transition system can only be in certain states, meaning, for instance, that a certain message
has not yet been sent. Thus we will have some resources now that are incompatible with those
held by the invariant. But the invariant gives us those resources, and hence the inconsistency,
later. Using Ht-frame-atomic show the following triples.
{.(False)} ` ← v {v.Q} {.(False)} !` {v.Q} {.(False)} ref (v) {v.Q}
8 The precise statement and proof of this property can be found in [8], although it uses concepts we have not yet
introduced here.
49
Ht-inv-alloc
Inv-persistent ι
E infinite S ∧ ∃ι ∈ E. P ` {Q} e {v.R}E
ι ι
P ` P S ` {. P ∗ Q} e {v.R}E
Ht-inv-open
ι
e is an atomic expression S ∧ P ` {. P ∗ Q} e {v. . P ∗ R}E
ι
S ∧ P ` {Q} e {v.R}E]{ι}
Example 8.3 (Opening an invariant twice leads to an inconsistency). This example demon-
strates a problem with opening an invariant more than once. Suppose the invariant opening
rule Ht-inv-open did not remove the name ι from the possible set of invariants to open, i.e.,
suppose the rule was instead
ι
e is an atomic expression S ∧ P ` {. P ∗ Q} e {v. . P ∗ R}E]{ι}
ι
S ∧ P ` {Q} e {v.R}E]{ι}
{` 7→ 0} ! ` {v.v = 3}.
Indeed, using the invariant allocation rule Ht-inv-alloc we just need to prove
ι
∃ι ∈ InvName. ` 7→ 0 ` {True} ! ` {v.v = 3}.
Example 8.4. We now have sufficient rules to prove specification (13) from page 45.
We start off by allocating an invariant. One might first guess that the invariant is ` 7→ n. How-
ever this does not work since the value at location ` does in fact change, so is not invariant.
Technically, we can see that, to use the invariant opening rule, we need to reestablish the in-
variant (the . P in the post-condition).
Instead, we use the weaker predicate I = ∃m. m ≥ n ∧ ` 7→ m, which is an invariant. To
show (13) we first allocate the invariant I using Ht-inv-alloc. This we can do by the rule of
consequence Ht-csq since ` 7→ n implies I, and so also . I.
50
Thus we have to prove
ι
I ` {True} (e || e); ! ` {v.v ≥ n} (15)
for some ι.
Using the derived sequencing rule Ht-seq we need to show the following two triples
ι
I ` {True} (e || e) { .True}.
ι
I ` {True} ! ` {v.v ≥ n}.
We show the first one; during the proof of that we will need to show the second triple as
well. Using the rule Ht-par, the proof of the first triple reduces to showing
ι
I ` {True} e { .True}
where, recall, e is the term ` ← ! ` + 1. Note that we cannot open the invariant now since the
expression e is not atomic.
Using the bind rule we first show
ι
I ` {True} ! ` {v.v ≥ n}.
Note that this is exactly the second premise of the sequencing rule mentioned above. To show
this triple, we use the invariant opening rule Ht-inv-open, and thus it remains to show
{. I} ! ` {v.v ≥ n ∧ . I}InvName\{ι} .
Using the rule Ht-frame-atomic together with the rule Ht-load and structural rules we have
{. I} ! ` {v.v = m ∧ m ≥ n ∧ ` 7→ m}InvName\{ι} .
To show this we again use the invariant opening rule and Ht-frame-atomic.
Exercise 8.5. Show this claimed specification in detail. ♦
This concludes the proof.
Example 8.6 (Restriction to atomic expressions in Ht-inv-open is necessary.). The restriction on
atomic expressions in the invariant opening rule is necessary. Consider the following program,
call it e
(` ← 4; ` ← 3) || ! `
and the invariant I = ` 7→ 3. Suppose the rule Ht-inv-open did not restrict expressions e to be
ι
atomic. Then we could allocate the invariant I and then use the rule Ht-par. Without the
atomicity restriction it is easy to show (exercise!)
ι
I ` {True} ` ← 4; ` ← 3 { .True}
51
and
ι
I ` {True} ! ` {v.v = 3}
However, the pair ((), 4) is also a possible result of executing e (the second thread could read `
just after it was set to 4 by the first thread). Thus the logic would not be sound with respect to
the operational behaviour of the programming language.
where e is again the program ` ← ! ` + 1 is sound. However the logic we have introduced thus
far does not allow us to prove it. Invariants allow us to make resources available to different
threads, but exactly because they are shared by different threads, the resources governed by
them need to be preserved, i.e., the invariant has to be reestablished after each step of execution.
Thus, for instance, when the invariant is ` 7→ n the location ` must always point to the value n.
We could allow the state to change by using an invariant such as ` 7→ n∨` 7→ (n+1). However
with the concepts introduced until now we cannot have an invariant that would ensure that
once ` 7→ (n + 1) holds, the location ` will never point to n again.
One way to express this is using ghost state. In Iris, ghost state is an additional kind of
primitive resource, analogous to the points-to predicate. Other names for the same concept
are auxiliary state, logical state, or abstract state, to contrast it with concrete state, which is the
concrete program configuation, i.e., a heap and threadpool.
Iris supports a uniform treatment of ghost state, but in this subsection we start out more
concretely, with just enough ghost state to prove specification (16).
To work with ghost state we extend Iris with a new type GhostName of ghost names, which
we typically write as γ. Ghost names are to be thought of as analogous to concrete locations
in the heap, but for the abstract state of the program. Hence ghost names are also sometimes
referred to as ghost variables. There are no special operations on ghost names. Ghost names
can only be introduced by ghost name allocation, which we explain below.
To prove (16) we need two additional primitive resource propositions, indexed by GhostName:
γ γ
S and F . These satisfy the following basic properties:
F-duplicable S-S-incompatible S-F-incompatible
γ γ γ γ γ γ γ
F ` F ∗F S ∗S ` False S ∗F ` False
γ
The way to think about these propositions is that S is the “start” token. The invariant will
γ
start out in this “state”. The proposition F is the “finished” token. Once the invariant is in
γ
this state, it can never go back to the state S .
Conceptually, the tokens are used to encode the following transition system.
S F
52
Additionally, we need rules relating these tokens to Hoare triples:
Using these rules, we now prove (16). The invariant we pick is the following predicate, parametrised
by γ ∈ GhostName.
γ γ
I(γ) = ∃m. ` 7→ m ∗ S ∧ m ≥ n ∨ F ∧ m ≥ (n + 1)
The idea is as explained above. The invariant can be in two “states”. It will be allocated in the
first state, with the “start” token S, since we know that the current value stored at ` is at least
n. Then, when a thread increases the value stored at `, we will update the invariant, so that
it is in the “finished” state. This pattern of using special ghost state tokens and disjunction to
encode information about the execution of the program in the invariant is typical, and we shall
see more of it later.
Example 8.7. So, let us start proving. We start off by using the rule Ht-token-alloc plus Ht-exist,
so we have to prove
n γ o
S ∗ ` 7→ n (e || e); ! ` {v.v ≥ n + 1}.
We then again use the sequencing rule Ht-seq, but this time the intermediate proposition is not
γ
True, but F , i.e., we prove the following two triples
n γ o n γo
S ∗ ` 7→ n e || e v. F (17)
n γo
F ! ` {v.v ≥ n + 1}. (18)
We begin by showing the first triple. Start by using the invariant allocation rule Ht-inv-alloc.
γ
This is allowed by an application of the rule of consequence Ht-csq since S ∗ ` 7→ n implies
I(γ). Hence we have to prove
ι n γo
I(γ) ` {True} e || e v. F .
γ γ γ
Since F ∗ F implies F it suffices (by the rule of consequence) to use the parallel composi-
tion rule Ht-par and prove
ι n γo
I(γ) ` {True} e v. F .
Exercise!
For the other premise of the bind rule, we now have to show
ι n γo
I(γ) ` {m ≥ n} ` ← (m + 1) . F .
53
To open the invariant we need an atomic expression. We use the rule Ht-bind-det to evaluate
m + 1 to a value using the rule Ht-op. We then open the invariant and after using the rule Ht-disj
and other structural rules we need to prove the following two triples
ι n γ o n γo
I(γ) ` .(` 7→ m ∗ S ∧ m ≥ n) ` ← (m + 1) . . I(γ) ∗ F
ι n γ o n γo
I(γ) ` .(` 7→ m ∗ F ∧ m ≥ (n + 1)) ` ← (m + 1) . . I(γ) ∗ F
We only show the first one, and leave the second one as an exercise. We note however that
γ
duplicability of F is essential.
Using the rules Ht-frame-atomic and Ht-store we derive the following entailment.
ι n γ o n γ o
I(γ) ` .(` 7→ m ∗ S ∧ m ≥ n) ` ← (m + 1) v.v = () ∧ ` 7→ (m + 1) ∗ S ∧ m ≥ n
from which it is easy to derive the wanted triple using F-duplicable to create another copy of
γ
F . One of the copies is used to reestablish the invariant I(γ), and the other remains in the
postcondition.
To conclude the proof of this example we now need to show (18)
ι n γo
I(γ) ` F ! ` {v.v ≥ n + 1}
Using the invariant opening rule Ht-inv-open together with structural rules we need to prove
ι n γ γ o
I(γ) ` F ∗ .(` 7→ m ∗ S ∧ m ≥ n) ! ` {v.v ≥ (n + 1) ∧ . I(γ)}
ι n γ γ o
I(γ) ` F ∗ .(` 7→ m ∗ F ∧ m ≥ (n + 1)) ! ` {v.v ≥ (n + 1) ∧ . I(γ)}
By S-F-incompatible, the precondition of the first triple is equivalent to . False, that is, this case
is impossible, and the triple holds by Ht-later-false. We leave the second triple as an exercise.
To recap, the high-level idea of the proof is that the invariant can be in two “states”. It starts
off in the state where we know that the value at ` is at least n and then, when incrementing, we
γ
transition to a new state, but we also get out a new token, i.e., we get F in the postcondition.
This token is then used to decide in which case we are when opening the invariant again.
54
Definition 8.8. A commutative semigroup is a set M together with a function (·) : M × M → M,
called the operation such that the operation is associative and commutative.
A commutative semigroup is called a commutative monoid if there exists an element ε (called
the unit) which is the neutral element for the operation (·): for all m ∈ M, the property m · ε =
ε · m = m holds.
The set M is called the carrier of the semigroup (resp. monoid).
Every semigroup can be made a preorder by defining the extension order a 4 b as
a 4 b ⇐⇒ ∃c, b = a · c.
In words, a 4 b if a is a part of b.
Exercise 8.9. Show that the relation 4 is transitive for any semigroup M. Show that it is reflex-
ive if and only if for every element a there exists an element b ∈ M such that a · b = a. Conclude
that if M is a commutative monoid then 4 is reflexive. ♦
Certain kinds of commutative semigroups and monoids serve as good abstract models of
resources. Resources can be composed using the operation. Commutativity and associativity
express that the order in which resources are composed does not matter. The unit of the monoid
represents the empty resource, which exists in many instances.
Finally, we also need the ability to express that certain resources cannot be combined to-
gether. This can be achieved in many ways. The way we choose to do it is to have a subset V of
so-called valid elements. Thus, for now, our notion of resources are the resource algebras defined
as follows.9
Definition 8.10 (Resource algebra). A resource algebra is a commutative semigroup M together
with a subset V ⊆ M of elements called valid, and a partial function | · | : M → M, called the core.
The set of valid elements is required to have the closure property
a · b ∈ V ⇒ a ∈ V,
A resource algebra is unital if M is a commutative monoid with unit ε and the following
properties hold.
ε∈V |ε| = ε.
55
Exercise 8.11. Show that for any resource algebra M, and any element a ∈ M, the core of a, if
defined, is duplicable, i.e., for any a, show
♦
Exercise 8.12. Show that in a unital resource algebra the core is always defined. Hint: ε 4 a for
any element a. ♦
Example 8.13. A canonical example of a unital resource algebra is the one of heaps. More
precisely, the carrier of the resource algebra is the set of heaps plus an additional element,
call it ⊥, which is used to define composition of incompatible heaps. Composition of heaps
is disjoint union, and if the heaps are not disjoint, then their composition is defined to be ⊥.
Composing ⊥ with any other element yields ⊥. The core is the constant function, mapping
every element to the empty heap, which is the unit of the resource algebra. Every heap is valid,
the only non-valid element being ⊥.
Example 8.14. We now present an example, which we will use later, and where the core is
non-trivial. (It is closely related to the agreement construction, which we will also use later on.)
Given a set X, the carrier of the resource algebra is the set X ∪ {⊥}, for some element ⊥ not in X.
The operation (·) is defined by the following rules. The non-trivial compositions are only
m·m = m
and otherwise (when m and n are distinct) m · n = ⊥. The core can be defined as the identity
function, and every element apart from ⊥ is valid. The definition of the core as the identity
function is possible since every element of the resource algebra is duplicable.
Example 8.15 (Finite subsets of natural numbers). The carrier of this resource algebra is the
set of finite subsets of natural numbers and an additional element ⊥. The operation is disjoint
union, i.e.:
x ∪ y if x ∩ y = ∅
x·y =
⊥
otherwise
The unit of this operation is ∅. Valid elements are all finite subsets of natural numbers and
the core operation maps every valid element to ∅.
Example 8.16. If we take the resource algebra M to have the carrier {S, F, ⊥} with multiplication
defined as F · F = F and otherwise x · y = ⊥ then, with the rules presented above, we can recover
the rules ftok-duplicable, S-S-incompatible and S-F-incompatible, which were postulated in
the previous section. The core of the resource algebra M is always undefined.
Example 8.17 (Resource algebra of fractions). An often used resource algebra is the one of frac-
tions Q01 . Its carrier is the set of (strictly) positive rational numbers q with addition as the oper-
ation. However the valid elements are only those q less than or equal to 1, i.e., V = {q | 0 < q ≤ 1}.
The core is always undefined.
Example 8.18 (Exclusive resource algebra). Given a set X the exclusive resource algebra Ex(X)
has as carrier the set X with an additional element ⊥. The operation is defined such that x·y = ⊥
for all x and y. The core is the always undefined function, and the valid elements are elements
of X, i.e., every element of the resource algebra except the ⊥.
56
Perhaps it does not seem that this resource algebra is very interesting. In fact it does appear
in verification of certain programs, but it can also be used as a building block of other resource
algebras, as shown in the following exercise.
The following examples are generic constructions. They construct resource algebras com-
bined from a variety of smaller ones. This makes it easier to build more complex notions of
resources needed in verification, since a lot of the infrastructure can be reused. However it does
take some practice to get used to thinking in terms of decomposition of the desired resource
algebra in terms of the smaller ones. We hope the reader will get some intuition for this by
working through the example verifications in the rest of these notes.
Example 8.19 (Products of resource algebras). If M1 and M2 are resource algebras with cores
| · |1 and | · |2 and sets of valid elements V1 and V2 then we can form the product resource algebra
M× . Its carrier is the product M1 × M2 , and its operation is defined component-wise as
(a, b) · (a0 , b0 ) = (a · a0 , b · b0 )
and the set of valid elements
V× = {(a, b) | a ∈ V1 , b ∈ V2 } .
The core is similarly defined component-wise as
(|a|1 , |b|2 ) if |a|1 and |b|2 defined
|(a, b)|× =
undefined otherwise
It is easy to see (exercise!) that if both resource algebras are unital then so is the product re-
source algebra.
This product example can be extended to a product of arbitrary many resource algebras.
Example 8.20 (Finite map resource algebra). Let (M, V , | · |) be a resource algebra. We can form
fin
a new resource algebra N * M whose carrier is the set of partial functions from N to M with
finite domain, and the operation is defined as
f (n) · g(n) if f (n) and g(n) defined
f (n) if f (n) defined andg(n) undefined
(f · g)(n) =
g(n)
if g(n) defined andf (n) undefined
undefined otherwise
fin
Note that N * M is always a unital resource algebra, its unit being the always undefined finite
partial function.
57
fin
Exercise 8.21. Show that when restricted to valid elements, the resource algebra N * Ex (Val)
is the same as the unital resource algebra of heaps described in Example 8.13. More precisely,
fin
show that the valid elements of N * Ex (Val) are precisely the heaps, and composition of these
is exactly the same as the composition of heaps, if it is a valid element. ♦
Example 8.22 (Option resource algebra). Given any resource algebra (not necessarily unital) M,
we define the unital resource algebra M? . Its carrier is the set M together with a new element
?. The operation on elements of M is inherited, and we additionally set ? · x = x·? = x, i.e., ? is
the unit. The set of valid elements is that of M and ?. Finally, the core operation is defined as
|?|M? = ?
|x|
if |x| defined
|x|M? =
?
otherwise
Extending Iris with resource algebras With these concepts, we can extend Iris with a general
notion of resources, a single unital resource algebra. Strictly speaking the logic is extended with
a family of chosen resource algebras Mi , which we leave open, so that new ones can be added
when they are needed in the verification of concrete examples. We add the resource algebras,
and its elements, the core function, and the property of the element being valid, as new types
and new terms of the logic, together with all the equations for the operations. In addition to this
we also add the notion of ghost names. These are used to be able to refer to multiple different
instances of the same resource algebra element, analogous to how different locations in a heap
are used to contain different values.
Thus we extend the logic with the following constructs
The first two are self-explanatory, they internalise the notions of the resource algebra into the
logic, i.e., they allow us to reason about elements of the resource algebras in the logic. The last
γ
rule introduces a new construct, the ghost ownership assertion a : Mi , which we will write as
γ
a when the resource algebra Mi is clear from the context. This assertion states that we own
an instance of a ghost resource a named γ.
The rules of the ghost ownership assertion are as follows.
Own-op Own-valid
γ γ γ γ
a : Mi ∗ b : Mi a` a · b : Mi a : Mi ` a ∈ Vi
And the final rule, which shows why the core is useful, is related to the persistently modality
with the following law of the logic.
Persistently-core
Γ ` a : Mi |a|i defined
γ γ
a : Mi ` |a|i : Mi
58
Ghost updates We now consider how to update the ghost resources. This ability will be used
to evolve the ghost state along with the execution of the program. When the ghost state changes,
it is important that it remains valid – Iris always maintains the invariant that the ghost state
obtained by composing the contributions of all threads is well-defined and valid, i.e., that all the
contributions of all threads are compatible. We call state changes that maintain this invariant
frame-preserving updates.
Definition 8.23 (Frame preserving update). For any resource algebra M with the set of valid
elements V we define a relation, the frame preserving update a B, where a ∈ M and B ⊆ V is a
non-empty subset of valid elements. It states that any element compatible with a is compatible
with some element in B. Precisely,
frame-preserving-update
a B ⇐⇒ ∀x ∈ M, a · x ∈ V ⇒ ∃b ∈ B, b · x ∈ V .
If B is the singleton set {b}, we write a b for a {b}.
To support modification of ghost state in the logic we introduce a new update modality V | P,
with associated frame preserving updates. The typing rules and basic axioms of the update
modality are show in Figure 9. The intuition is that V | P holds for a resource r, if from r we
can do a frame-preserving update to some r 0 that satisfies P . Thus the update modality V | P
provides a way, inside the logic, to talk about the resources we could own after performing an
update to what we do own. With this intuitive reading of V | P , the laws in Figure 9 should
make sense. For instance, the upd-frame axiom holds because if r satisfies P ∗ V | Q, then r can be
split into r1 and r2 with r1 in P and r2 in V
| Q, and the latter means that r2 can be updated in a
frame-preserving way to some r20 in Q, i.e., r2 r20 . But then also r = (r1 · r2 ) (r1 · r20 ) and hence
| (P ∗ Q).
r ∈V
upd-mono
P `Q upd-intro upd-idemp upd-frame
Γ ` P : Prop
| P `V
V | Q P `V
| P | P `V
| V
V | P P ∗V | (P ∗ Q)
| Q`V
| P : Prop
Γ `V
2.
upd-sep
P1 ` V
| Q1 P2 ` V
| Q2
| (Q1 ∗ Q2 )
P1 ∗ P2 ` V
3.
upd-bind
P2 ` V
| Q P1 ∗ Q ` V
| R
P1 ∗ P2 ` V
| R
59
♦
Remark 8.25. Note that the rule upd-bind is a kind of bind or let rule. Indeed, it may be in-
structive to compare the rule upd-bind with the typing rule for a let construct in an ML-like
language
Γ ` e1 : τ Γ , x : τ ` e2 : σ
Γ ` let x := e1 in e2 : σ
The difference is that because of the use of separating conjunction we need to separate the
resources needed to prove V | Q from those needed to prove the V | R. Thus we cannot use P2 any-
more when proving V | R. Instead upd-bind very closely corresponds to the let rule in a language
with an affine or linear type system.
The following exercise shows that if a more standard let-like rule is added to the logic then
the update modality would become significantly weaker.
Exercise 8.26. Show that if the rule
P `V
| Q P ∗Q `V
| R
P `V
| R
P ∗ P ` False
P `V
| R
In particular P ` V
| False for P such that P ∗ P ` False. ♦
The update modality allows us to allocate and update ghost resources, as explained by the
following rules.
Ghost-alloc Ghost-update
a∈V a b
γ γ γ
| ∃γ. a
True ` V a | b
`V
Finally, we connect the update modality with Hoare triples. The idea is that ghost state is
abstract state used to keep track of auxiliary facts during proofs. So we should be able to update
the ghost state in pre- and postconditions of Hoare triples, since whether or not the program is
safe to run, and its return value, only depends on the physical state.
A uniform way to do this is to generalise the consequence rule Ht-csq. We first define the
view shift P V Q as
P V Q = (P ⇒ V
| Q)
Exercise 8.27. Derive the previous rule of consequence from the one just introduced. ♦
60
Exercise 8.28. Derive the following.
•
a∈V
γ
P `V
| ∃γ. a ∗ P
•
a∈V
γ ♦
` P V ∃γ. a ∗ P
γ γ γ
In particular, we have True V ∃γ. a , for all valid a, and if a b then a V b .
Exercise 8.29. Derive the rest of the rules for start and finish tokens used in the previous section
for the resource algebra from Example 8.16. That is, show the rules Ht-token-update-post, Ht-
token-update-pre, and Ht-token-alloc. ♦
Exercise 8.30 (Allocating invariants in the post-condition). It will often be the case that we
need to allocate an invariant in the post-condition, using the following derivable rule.
Ht-inv-alloc-post
E infinite S ` {P2 } e {v.Q}E
n ιo
S ` {(. P1 ) ∗ P2 } e v.Q ∧ ∃ι ∈ E. P1
E
Derive the rule using Ht-inv-alloc, the fact that invariants are persistent and the generalised
rule of consequence introduced above. ♦
Example 8.31. In this example we show how to use slightly more complex reasoning using
resource algebras to show the specification (14) (page 46) from the parallel increment exam-
ple. We are going to use two resource algebras: the one of fractions defined in Example 8.17
together with the resource algebra encoding the transition system with states S and F defined
in Example 8.16 and demonstrated in Example 8.7.
The proof proceeds similarly to the proof in Example 8.7, but with a different invariant. The
invariant we are going to use is
γ1
I(γ1 , γ2 , n) = ` 7→ n ∗ S ∨
γ1 γ2
1
` 7→ (n + 1) ∗ F ∗ 2 ∨
γ1 γ2
` 7→ (n + 2) ∗ F ∗1
That is, we are in essence encoding a three state transition system. The tokens F and S are
used to distinguish the initial state from the rest of the states, and the fractions 21 and 1 can be
thought of as the price needed to get from the initial state to the current state. We can depict
this in the following way
1 1
2 2
(S, 0) F, 12 (F, 1)
61
The resources on the transitions can also be viewed as coming from the environment. To make
γ2
a transition from one state to another the environment will have to give up ownership of 12
and transfer it to the invariant.
With this invariant let us proceed to the proof of the specification
for some ι. Using the rule Ht-seq we now verify the two parts, showing the following two triples
ι n γ o n γ o
I(γ1 , γ2 , n) ` 1 2 (e || e) . F 1
ι n γ o
I(γ1 , γ2 , n) ` F 1 ! ` {v.v = n + 1 ∨ v = n + 2}.
The proof of the second triple is completely analogous to the one in Example 8.7, so we omit it
here. To show the first triple we first use the rule Own-op to get
γ2 γ2 γ2
1 1
1 ⇐⇒ 2 ∗ 2
and
γ1 γ1 γ1
F ⇐⇒ F ∗F
which means we can use the parallel composition rule Ht-par, and we have to show
γ n
ι 2 γ o
I(γ1 , γ2 , n) ` 12 e .F 1 .
Recall that e is the program ` ← ! ` + 1. Using the Ht-bind rule we show the following two
entailments.
γ γ2
ι 2 γ
I(γ1 , γ2 , n) ` 12 ! ` v. v = n ∨ v = (n + 1) ∗ F 1 ∗ 12 (19)
γ2
ι γ n γ o
I(γ1 , γ2 , n) ` ∀v. v = n ∨ v = (n + 1) ∗ F 1 ∗ 12 ` ← v+1 . F 1 (20)
62
γ
Note the F 1 token we chose to add in the postcondition for the case of reading n + 1. As in
γ
Example 8.7, this is essential to show the second triple: with F 1 being duplicable, this allows
us to remember that the invariant is not in the initial state anymore. Then, when storing, we
can exclude the impossible case where we store n + 2 (and thus must have read n + 1) while the
γ
invariant is still in the initial state S 1 (in which case we would not be able to reestablish the
γ2
invariant, as we only have one 12 fraction). As in this case we will have ownership of both
the S and the F token (which are incompatible), we will be able to reach a contradiction.
We first show (19) and start by opening the invariant I(γ1 , γ2 , n). We thus get
γ2 γ2
γ γ γ γ
. ` 7→ n ∗ S 1 ∨ ` 7→ (n + 1) ∗ F 1 ∗ 12 ∨ ` 7→ (n + 2) ∗ F 1 ∗ 1 2 ∗ 12
in the precondition which, using the distributivity laws of the logic, together with Own-op sim-
plifies to
γ γ2
. ` 7→ n ∗ S 1 ∗ 12 ∨
γ γ2
γ1 1 2
. ` 7→ (n + 1) ∗ F ∗ 2 ∗ 12 ∨
γ2
γ
. ` 7→ (n + 2) ∗ F 1 ∗ 32
The last disjunct is equivalent to . False by Own-valid and the fact that the only valid fractions
are those which are not greater than 1. Using the disjunction rule Ht-disj we have to show
further three triples, all with postcondition
γ γ2
v = n ∨ v = (n + 1) ∗ F 1 ∗ 12 ∗ . I(γ1 , γ2 , n)
and with three preconditions corresponding to the three disjuncts above. The last is the easiest
one and follows directly from Ht-later-false. The first two we leave as exercises, since they are
direct applications of rules we have seen many times.
Exercise 8.32. Show the following two triples.
γ2 γ2
γ γ
. ` 7→ n ∗ S 1 ∗ 12 ! ` v. v = n ∨ v = (n + 1) ∗ F 1 ∗ 12 ∗ . I(γ1 , γ2 , n)
γ2 γ2 γ2
γ γ
. ` 7→ (n + 1) ∗ F 1 ∗ 12 ∗ 12 ! ` v. v = n ∨ v = (n + 1) ∗ F 1 ∗ 12 ∗ . I(γ1 , γ2 , n)
♦
Let us now turn to showing the specification (20). Using the ∀ introduction rule, distribu-
tivity of ∗ over ∨, and Ht-disj this means showing two specifications.
γ2
ι n γ o
I(γ1 , γ2 , n) ` v = n ∗ 12 ` ← v+1 . F 1 (21)
γ2
ι γ n γ o
I(γ1 , γ2 , n) ` v = (n + 1) ∗ F 1 ∗ 12 ` ← v+1 . F 1 (22)
We show the first and leave the second as an exercise. Using Ht-persistently and ordinary equa-
tional reasoning the first triple is equivalent to
γ
ι 2
n γ o
I(γ1 , γ2 , n) ` 12 ` ← n+1 . F 1
63
To be completely precise we first need to use the bind rule to compute n + 1 to a value, but this
is completely straighforward, so let us just assume we have done it. We then open the invariant
and after simplifying we have
γ2 γ2
γ γ γ γ
. ` 7→ n ∗ S 1 ∗ 12 ∨ . ` 7→ (n + 1) ∗ F 1 ∗ 1 2 ∨ . ` 7→ (n + 2) ∗ F 1 ∗ 32
in the precondition. As before, the last disjunct is equivalent to . False and the correspond-
ing triple follows from Ht-later-false. Using Ht-disj we thus need to prove the following two
specifications.
γ2
γ n γ o
. ` 7→ n ∗ S 1 ∗ 12 ` ← n + 1 . F 1 ∗ . I(γ1 , γ2 , n) (23)
n γ γ o n γ o
. ` 7→ (n + 1) ∗ F 1 ∗ 1 2 ` ← n + 1 . F 1 ∗ . I(γ1 , γ2 , n) (24)
We show the first and leave the second as an exercise. We proceed by forward reasoning and
start by using Ht-load and Ht-frame-atomic to get
γ2 γ2
γ γ
. ` 7→ n ∗ S 1 ∗ 12 ` ← n + 1 ` 7→ n + 1 ∗ S 1 ∗ 12 (25)
We then use the fact that S F together with the rule Ghost-update and upd-frame to get
γ2 γ2
γ γ
` 7→ n + 1 ∗ S 1 ∗ 12 V ` 7→ n + 1 ∗ F 1 ∗ 12
γ1 γ1 γ1
Moreover F = F · F and thus Own-op gives us F V F ∗F and thus together we have
γ2 γ2
γ1 1 γ1 γ1 1
` 7→ n + 1 ∗ S ∗ 2 V ` 7→ n + 1 ∗ F ∗F ∗ 2 .
γ1 γ2
1
Clearly ` 7→ n + 1 ∗ F ∗ 2 V . I(γ1 , γ2 , n) and thus we have
γ2
γ1 1
γ1
` 7→ n + 1 ∗ S ∗ 2 V F ∗ . I(γ1 , γ2 , n)
and thus finally, by the (generalized) rule of consequence, we get (23) from (25), as needed.
Exercise 8.33. Following similar reasoning illustrated above show specifications (24) and (22).
♦
The proof is somewhat complex, and perhaps the key new point, compared to previous
examples (in particular Example 8.7), is lost to the reader. The key new way of reasoning in this
example is the use of the fraction 12 and how we transferred it from the ownership of a thread to
the ownership of the invariant. Technically this can be seen from the fact that if when reading
γ γ γ2
! ` the invariant is in state ` 7→ n ∗ S 1 , then after writing it is in state ` 7→ (n + 1) ∗ F 1 ∗ 12 .
γ1 γ2
1
Analogously, if the invariant is in state ` 7→ (n + 1) ∗ F ∗ 2 when reading, then after writing
γ1 γ2 γ2
it is in state ` 7→ (n + 2) ∗ F ∗ 1 . Finally, we have used the fact that the threads own 12 at
the beginning, to discount the possibility that we have read the value n + 2 before writing, i.e.,
γ γ
that the invariant was in state ` 7→ (n + 2) ∗ F 1 ∗ 1 2 .
64
Exercise 8.34. For the same program e as in the preceding example, define an invariant which
would allow you to prove the following specification
♦
Example 8.35. This example is a generalization of the parallel increment example 8.31. Instead
of for a fixed number of threads, we will prove a specification for any number of threads adding
to a location. We also generalize it to the addition of any number instead of just 1 for each
thread. This is slightly more interesting, because the possible result values in the location
are not just n + 1, n + 2, . . . , n + k, but any combination of the numbers the threads are adding.
As we will see, this does not allow us to use the same reasoning as in the previous example.
Instead, we present another way of combining state-tracking resource algebras (multiple S-F
instances), which at the same time represents an alternative way of proving the specification of
Example 8.31, without using the fractional resource algebra.
Another new aspect of this example is that we show a specification for an inductively con-
structed program, namely a program consisting of any number of nested threads. As we will
see in the proof, induction on the list of threads while having an invariant about those threads
is not trivial.
The program and specification for k threads with numbers a1 , . . . , ak to add is the following:
P
{` 7→ n} (e1 || (e2 || · · · (ek−1 || ek ) · · · )) ; ! ` {v.∃A ⊆ {1, . . . , k} , A , ∅ ∧ v = n + i∈A ai } (26)
where ei = ` ← ! ` + ai .
The proof is structured as the one in Example 8.31: We will have an invariant that governs
the location ` and which can be in different states, logically representing which threads have
contributed. One might think that we could proceed with fractions similarly to the example
with two threads where instead of a fraction 12 , each thread would get a fraction corresponding
a
to its contribution, i.e., P j a for thread j, and the fractions collected in the invariant would
i=1,...,k i
represent the amount added to the location. However, this will not work out, as we will not be
able to distinguish between the addition of an ai and two times the addition of a2i , for instance.
This would allow to represent sums that in reality are not possible.
Luckily, it turns out that the problem is much simpler and we do not even need fractions.
What we actually want to track is whether a thread has contributed or not, to discount the
possibility of a thread’s number being contributed twice. This can be achieved with the two-
state transition system with states S and F from Example 8.16 which we also have used in the
previous proofs of parallel increment. We will simply use k instances of this resource algebra,
one for each thread. We use the ghost names γ = γ1 , . . . , γk to distinguish them. Additionally we
will – as before – use the same transition system to encode the global state of execution, namely
whether we are in the starting state S or a final state F. This instance is represented by the ghost
name γ. With this idea, our invariant looks as follows:
I(γ, γ, n) = ` 7→ n ∗ S
γ
∨ ∃A ⊆ {1, . . . , k} , A , ∅ ∧ ` 7→ n +
P
i∈A ai ∗ ∗ i∈A F
γi
∗F
γ
γ
The invariant can be in two states. The first state, represented by the token S , is the same as
in the example with just two threads: the value at the location is unchanged, as no thread has
γ
written to the location yet. The second state, represented by the token F , represents all pos-
sible final states of the program, i.e., where all threads have executed and thus the contribution
of at least one thread has to have been added to `. For each thread whose number is included
65
γ
in the sum, we additionally have the thread-specific final state token F i . This allows us to
exclude the impossible case where a thread’s number is already part of the sum when it makes
its contribution.
Let us now show how we can prove specification (26) using this invariant. We start by
γ γ
allocating ghost state: the global S token and the thread-specific S i tokens. Then the spec-
ification to show is
γ
∗ γ o
n
` 7→ n ∗ S ∗ i∈{1,...,k} S i
(e1 || (e2 || · · · (ek−1 || ek ) · · · )) ; ! `
n P o
v.∃A ⊆ {1, . . . , k} , A , ∅ ∧ v = n + i∈A ai
which allows us to allocate the invariant in the starting state, leaving us to show
∗ γi o
n
i∈{1,...,k} S
ι
I(γ, γ, n) ` (e1 || (e2 || · · · (ek−1 || ek ) · · · )) ; ! `
n P o
v.∃A ⊆ {1, . . . , k} , A , ∅ ∧ v = n + i∈A ai
Following the same pattern as in the examples 8.7 and 8.31, for the postcondition of the parallel
γ
part of the program we choose ownership of the F token. This is enough to get our eventual
postcondition when reading the location in the second triple, as the token implies that the
invariant must be in the final state. This part is completely analogous to the proofs before, so
we leave showing (28) as an exercise.
To prove the main part, (27), we need a specification for the individual threads, which we
can then compose to prove the whole program:
ι n γ o n γo
I(γ, γ, n) ` S i ei . F (29)
γ
That is, given the invariant and the thread-specific S i token, after executing ei , we will have
γ
the global F token. This – obtained from the invariant’s final state – represents the fact that
after executing any one thread, the value stored at ` is a possible final value. Note that we do
γ
not need an F i token here; these are only used in the invariant.
With this specification, showing (27) seems trivial: we repeatedly use Ht-par and will end
γ
up with k F tokens, where one is enough for the required postcondition. Note however, that
formally, we have to show this using induction on the number of threads k. This will not work
directly, as k also occurs in the invariant, namely in the list of ghost names γ = γ1 , . . . , γk . This
means that the induction hypothesis would work with a different invariant (for the threads
1, . . . , k − 1) and thus the two premises for the Ht-par rule would have a different left side of
the entailment and we would be stuck. The solution is to keep the invariant over the list of all
threads, while doing induction on a suffix of the list of threads not having considered yet (for
γ
which there must be an S i token and which at the beginning happen to be all threads). Note
that the Ht-par rule allows us to consider (prove) the threads in any order. Intuitively we can do
it this way because the invariant’s proposition for some of the threads also holds for all threads
66
(if the result value is the sum of contributions from a subset of some of the threads, then it is also
the sum of contributions from a subset of all threads). Technically it works because specification
and proof for a single thread (29) just talk about that single thread and are independent of the
other threads. Formally, instead of showing (27), we show the following triple for any q ≤ k:
ι n γ o
∗ γo
n
I(γ, γ, n) ` i∈{1,...,q} S i eq || (eq−1 || · · · (e2 || e1 ) · · · ) .F (30)
Induction is then straightforward. The base case q = 1 is exactly specification (29) with i = 1.
In the induction case, the induction hypothesis gives the specification (30) for q − 1, the one for
eq is again (29) (with i = q) and thus we can use the Ht-par rule to obtain (30), just with post
γ γ γ
condition F ∗ F , which we simplify to F using Ht-csq. For q = k we then get (27) from (30).
We conclude the proof by showing the essential part, the specification (29) for a single thread
using the invariant. Recall that thread ej is the program ` ← ! ` + aj . Using the Ht-bind rule we
show the following two entailments:
n γj o
S
ι
I(γ, γ, n) ` !`
γj
∗ γi γ o
n P
v. S ∗ v = n ∨ ∃A ⊆ {1, . . . , k} \ {j} , A , ∅ ∧ v = n + i∈A ai ∗ i∈A F ∗F
(31)
γj
∗ γi γ o
n P
S ∗ v = n ∨ ∃A ⊆ {1, . . . , k} \ {j} , A , ∅ ∧ v = n + i∈A ai ∗ i∈A F ∗F
ι
I(γ, γ, n) ` ∀v. ` ← v + aj
n γo
.F
(32)
The idea is that if we read a value which includes the contributions from some threads, we
want to remember that the invariant was in the corresponding state. This is done in (31) via
γ γ
the tokens F and F i for all i where the contribution ai is included in the read value. It is
moreover important to include the fact that aj is not part of the read sum, which cannot be the
case because the current thread has not written yet. We will see that this is needed in the proof
of (32). To show (31) we open the invariant I(γ, γ, n) and get two disjuncts in the precondition.
Using the Ht-disj rule we have to show two triples:
n γj γ o
S ∗ .(` 7→ n ∗ S )
!`
γj
∗ γj γ
n P o
v. S ∗ v = n ∨ ∃A ⊆ {1, . . . , k} \ {j} , A , ∅ ∧ v = n + i∈A ai ∗ i∈A F ∗F ∗ . I(γ, γ, n)
γj
∗ γi γ o
n P
S ∗ . ∃A ⊆ {1, . . . , k} , A , ∅ ∧ ` 7→ n + i∈A ai ∗ i∈A F ∗F
!`
γj
∗ γj γ
n P o
v. S ∗ v = n ∨ ∃A ⊆ {1, . . . , k} \ {j} , A , ∅ ∧ v = n + i∈A ai ∗ i∈A F ∗F ∗ . I(γ, γ, n)
The first follows easily by choosing the left disjunct in the postcondition as well as the first state
of the invariant via Ht-csq and then applying Ht-frame-atomic and Ht-load. For the second, we
use Ht-csq and then Ht-disj to distinguish the cases j ∈ A and j < A in the precondition. The
triple with j < A follows similarly as the first triple (as then we have A ⊆ {1, . . . , k}\{j}). However,
67
γ γ
here we have to duplicate the tokens F j and F via ftok-duplicable because we need one
copy for the actual post condition (which allows us to “remember” the state of the invariant for
later) and one to reestablish the invariant. The triple with j ∈ A in the precondition actually
γ
represents an impossible case, as intended. We obtain a contradiction by combining the S j
γj
token with the F (which we have because we assume j ∈ A): with S-F-incompatible, we get
. False in the precondition and the triple holds by Ht-later-false. We have thereby shown (31).
We continue by showing the second part for the bind rule, the specification for the writing
part (32). Using the rule Ht-disj, we have to show two triples for the two different values of v.
For each, we will open the invariant and, using Ht-disj, have to show two triples. In total, we
have to show the following four triples (where parts not needed for the proof are in grey):
n γj γ o
S ∗ .(` 7→ n ∗ S )
` ← n + aj (33)
n γj γ o
.F ∗F ∗ . I(γ, γ, n)
γj
∗ γi γ o
n P
S ∗ .(∃A ⊆ {1, . . . , k} , A , ∅ ∧ ` 7→ n + i∈A ai ∗ i∈A F ∗F )
` ← n + aj (34)
n γj γ o
.F ∗F ∗ . I(γ, γ, n)
∀A ⊆ {1, . . . , k} \ {j} , A , ∅.
∗
n γj γ γ γ o
S ∗ i∈A F i ∗ F ∗ .(` 7→ n ∗ S )
P
` ← n + i∈A ai + aj (35)
n γ γ o
. F j ∗ F ∗ . I(γ, γ, n)
∀A ⊆ {1, . . . , k} \ {j} , A , ∅.
∗ ∗
n γj γ γ γi γ o
S ∗ i∈A F i ∗ F ∗ .(∃A0 ⊆ {1, . . . , k} , A0 , ∅ ∧ ` 7→ n + i∈A0 ai ∗
P
i∈A0 F ∗F )
P
` ← n + i∈A ai + aj (36)
n γ γ o
. F j ∗ F ∗ . I(γ, γ, n)
For each triple we first use the bind rule to compute the value of the binary operation.
The first two triples represent the cases where we read n from the location. In the first, we
opened the invariant in the starting state for writing, whereas in the second the invariant was
γ γ
already in a final state. In both triples we use Ht-token-update-pre to update S j to F j in the
γ γ
precondition, in the first we additionally have to update the global S token to F , which is
not necessary in the second as the invariant is already in the corresponding state and provides
the token. In both cases, we want to reestablish the invariant in the final state with A = {j}.
γ γ
To do so, we duplicate the tokens F j and F via ftok-duplicable, choose the corresponding
disjunct in the invariant via Ht-csq, use Ht-frame-atomic and then get the triple via Ht-store.
The third triple represents an impossible case: the invariant cannot be in the starting state
if we have read a value from the final state before. We can prove the triple by combining the
γ γ
invariant’s S token with the F token we remembered from reading to obtain a . False in the
precondition.
68
The fourth triple represents the case where we have read a value which already includes
some contributions from other threads. Therefore the invariant must be in a final state (we
have just excluded the other case), but it can be a different one (represented by the set A0 which
we don’t use). To the sum we obtained from reading, we add aj , so we have to reestablish the
invariant with a sum represented by the set A00 = A ∪ {j}. Note that it is essential that this is
a disjoint union as otherwise we would get a different sum. This is why we needed to require
j < A when choosing the precondition for (32) with the bind rule. To prove the triple, we update
γ γ γ
the current thread’s S j to F j . Then we have the F i token for all i ∈ A00 (as we remembered
those from A when reading) which is exactly what we need for the invariant. The rest follows
by Ht-csq, Ht-frame-atomic and Ht-store.
Hereby we have concluded the proof of (32) and have thereby shown our specification for
the parallel addition example.
To summarize, this example showed how we can make use of multiple instances of the
S-F state transition system resource algebra to track progress for multiple threads as well as
keeping track of the global invariant state. We have moreover shown how we can prove a
specification for any number of threads, with an invariant that governs logical state for all of
those threads.
Exercise 8.36. In Example 8.35 we considered the parallel, non-atomic addition to a location
by k threads. Assuming we had a primitive which atomically adds a number to a location, how
would the specification for the program change? How would an invariant look like with which
this specification can be proved? Hint: Take a look at Example 8.15. ♦
Ht-CAS-fail
Exercise 8.37. Derive the rules Ht-CAS-succ and Ht-CAS-fail from Ht-CAS. ♦
8.6 Examples
69
Example 8.38 (Spin lock). For our first example of a concurrent module with shared state we
will show a specification for a spin lock module. The module consists of three operations,
isLock, acquire and release, with the following implementations:
Concretely, the lock is a boolean flag, which must be set atomically to indicate that a thread
is entering a critical region. We will give an abstract specification, which does not expose the
concrete implementation of the lock. Therefore, we specify the operations on the lock using
an abstract, i.e., existentially quantified, isLock predicate. The specification we desire for the
module as a whole is:
The specification expresses that the isLock predicate is persistent, hence duplicable, which
means that it can be shared among several threads. When a new lock is created using the
newLock method, the client obtains an isLock predicate, and the idea is then that since it is
duplicable, it can be shared among two (or more) threads which will use the lock to coordinate
access to memory shared among the threads. The newLock, acquire, and release methods are all
parameterized by a predicate P which describes the resources the lock protects. The postcon-
dition of acquire expresses that once a thread acquires the lock, it gets access to the resources
protected by the lock (the P in the postcondition). Moreover, it gets a locked(γ) predicate (think
of it as a token), which indicates that it is the current owner of the lock – to call release one needs
to have the locked(γ) token. To call release, a thread needs to have the resources described by
P , which are then, intuitively, transferred over to the lock module – the postcondition of release
does not include P . Finally, the locked(γ) token is not duplicable, because if it was, it would
defeat its purpose of ensuring that only the thread owning the lock would be able to call release.
We will discuss an example of a client of the lock module below.
We thus proceed to prove that the spin lock implementation meets the above lock module
specification.
We need ghost state to record whether the lock is in a locked or an unlocked state. The
resource algebra we use is {ε, ⊥, K}, with the operation defined as ε · x = x · ε = x and otherwise
x · y = ⊥.
To define the isLock predicate, we will make use of an invariant – that will allow us to show
that the isLock predicate is persistent, as required by the specification above.
The invariant we use is:
γ
I(`, P , γ) = ` 7→ false ∗ K ∗ P ∨ ` 7→ true.
70
With this we define the isLock and locked predicates as follows.
ι
isLock(v, P , γ) = ∃` ∈ Loc, ι ∈ InvName. v = ` ∧ I(`, P , γ)
γ
locked(γ) = K
The idea of the invariant is as follows. If the location ` contains false, then the lock is
unlocked. In this case it “owns” the resources P , together with the token K. The K token can be
thought of as the “key”, which is needed to release, or unlock, the lock. In the post-condition of
acquire we obtain locked(γ), and together with the fact that locked(γ) is not duplicable we can
ensure that only the thread that acquired the lock has control over releasing it and, moreover,
that the lock can only be released once.
We can imagine the resource algebra and the invariant as encoding the following two state
labelled transition system.
unlocked locked
The label on the transition means that the transition is only valid when we have the token K,
i.e., we can only unlock a lock if we have the key.
There are now five proof obligations, one for each of the conjuncts in the specification, and
we treat each in turn.
The first says that isLock(v, P , γ) is persistent, which it is because invariants and equality are
persistent, and conjunction and existential quantification preserves persistency.
The second says that locked(γ) is not duplicable. This follows as K·K = ⊥ by definition of the
γ γ γ
resource algebra: K ∗ K ` K · K by Own-op which yields False by Own-valid. By transitivity
of ` we are done.
The third is the specification of allocating a new lock, and hence needs the allocation of a
lock invariant. We proceed to show the following triple:
We allocate new ghost state using Ghost-alloc, as in Exercise 8.28, use the rule of consequence
and then use Ht-exist. We are left with proving
for some γ.
Exercise 8.39. Prove this. (Hint: Use the derived invariant allocation rule Ht-inv-alloc-post,
and Ht-bind with an empty context -). ♦
The fourth is the specification of the acquire operation. It is a recursive definition, so we
proceed with the derived rule for recursive functions from Exercise 7.4. That is, assuming
71
we show the following triple
{isLock(v, P , γ)} if CAS(v, false, true) then () else acquire(v) { .P ∗ locked(γ)}.
The isLock predicate gives us that v is a location ` governed by an invariant, which we can move
into the context as follows:
ι
I(`, P , γ) ` {True} if CAS(`, false, true) then () else acquire(`) { .P ∗ locked(γ)}
We next evaluate the CAS expression with the Ht-bind rule. As our intermediate step we proceed
to show the following triple:
ι
I(`, P , γ) ` {True} CAS(`, false, true) {u.(u = true ∗ P ∗ locked(γ)) ∨ (u = false)}.
As CAS is atomic, we open the invariant to get at `, using Ht-inv-open, and it suffices to show
that
{. I(`, P , γ)}
ι
I(`, P , γ) ` CAS(`, false, true) .
ι
I(`, P , γ) ` CAS(`, false, true) .
ι
I(`, P , γ) ` CAS(`, false, true) .
72
The first follows by the rule for the unit expressions, the second by our induction hypothesis
(37). This concludes the proof that acquire satisfies its specification.
The fifth and final is the specification of the release operation. We proceed to show the
following triple:
To perform the assignment we must obtain ` as a resource from the invariant, which we do
by opening it with the Ht-inv-open rule. As invariants are persistent, we can move it into our
assumptions before opening, leaving us with the following triple:
ι
I(`, P , γ) ` {. I(`, P , γ) ∗ P ∗ locked(γ)} ` ← false { . . I(`, P , γ)}
We consider two cases, based on the disjunction in I(`, P , γ) in the precondition. The first case
is
ι
I(`, P , γ) ` {. (` 7→ false ∗ locked(γ) ∗ P ) ∗ P ∗ locked(γ)} ` ← false { . . I(`, P , γ)}
and in the postcondition we chose the first disjunct by Ht-csq – i.e., we will show the following
triple:
ι
I(`, P , γ) ` {.(` 7→ true) ∗ .(P ∗ locked(γ))} ` ← false { . .(` 7→ false) ∗ .(locked(γ) ∗ P )}
73
non-empty list.
let newBag = λ . (ref (None), newLock())
let insert = λx. λv. let ` := π1 x in
let lock := π2 x in
acquire lock;
` ← Some(v, ! `);
release lock
let remove = λx. let ` := π1 x in
let lock := π2 x in
acquire lock;
let r = match ! ` with
None ⇒ None
| Some p ⇒ ` ← π2 p; Some(π1 p)
end
in release lock; r
We would like to have a specification of the bag which will allow clients to use it in a concurrent
setting, where different threads insert and remove elements from a bag.
A weak, but still useful specification is the following. Given a predicate Φ, the bag contains
elements x for which Φ(x) holds. When inserting an element we give away the resources, and
when removing an element we give back an element plus the knowledge that it satisfies the
predicate. The specification is:
∃ isBag : (Val → Prop) ×Val → Prop.
∀(Φ :Val → Prop).
(∀b. isBag(Φ, b) ⇒ isBag(Φ, b))
∧ {True} newBag() {b. isBag(Φ, b)}
∧ ∀bu. {isBag(Φ, b) ∗ Φ(u)} insert b u { .True}
∧ ∀b. {isBag(Φ, b)} remove b {v.v = None ∨∃x. v = Some x ∧ Φ(x)}
With this specification, the only thing we get to know after calling remove is that the returned
element, if we get one out, satisfies the chosen predicate Φ. In fact, giving a stronger specifi-
cation is hard. The reason is that the isBag predicate is freely duplicable. And we do want the
isBag to be duplicable, since this allows us to share it between as many threads as we need,
which in turn allows us to specify and prove concurrent programs. The consequence of isBag
being duplicable is that concurrently running threads will be able to add and remove elements,
so each thread has no guarantee which particular elements it will get back when calling remove.
We now proceed to show that the implementation meets the specification. The isBag predi-
cate is defined as follows:
isBag(Φ, b) = ∃`vγ. b = (`, v) ∧ isLock(v, ∃xs. ` 7→ xs ∗ bagList(Φ, xs), γ)
where bagList is defined by guarded recursion as the unique predicate satisfying
bagList(Φ, xs) = xs = None ∨∃x. ∃r. xs = Some(x, r) ∧ Φ(x) ∗ .(bagList(Φ, r)).
Let Φ :Val → Prop be arbitrary.
74
Exercise 8.41. Prove that isBag(Φ, b) is persistent for any b. ♦
Exercise 8.42. Prove the newBag specification:
Note that since isBag(Φ, b) is persistent for any b we can derive, by using the frame rule
(exercise!), the following specification
∀b. {isBag(Φ, b)} remove b {v. (v = None ∨∃x. v = Some x ∧ Φ(x)) ∗ isBag(Φ, b)}
from the one claimed above, i.e., we do not lose the knowledge that b is a bag.
Let us now prove the specification of the remove method. We are proving
{isLock(lock, ∃xs. ` 7→ xs ∗ bagList(Φ, xs), γ)} remove(`, lock) {u.u = None ∨∃x. u = Some x ∧ Φ(x)}
for some `, lock and γ. Using Ht-beta and Ht-let-det we reduce to showing
{isLock(lock, ∃xs. ` 7→ xs ∗ bagList(Φ, xs), γ)} e {u.u = None ∨∃x. u = Some x ∧ Φ(x)}
acquire lock;
let r = match ! ` with
None ⇒ None
| Some p ⇒ ` ← π2 p; Some(π1 p)
end
in release lock; r
Using the sequencing rule Ht-seq we use the specification of acquire as the first triple, and thus
we have to prove
Exercise 8.43. Prove the above triple (possibly after looking at the proof of the second case
below). ♦
75
In the second case, after using rules in Figure 4 and Ht-exist, we get the following proof
obligation.
{locked(γ) ∗ ` 7→ xs ∗ xs = Some(x, r) ∗ Φ(x) ∗ . bagList(Φ, r)} e0 {u.u = None ∨∃x. u = Some x ∧ Φ(x)}
We use the let rule Ht-let-det. For the first premise we show
(note the omission of . on bagList in the postcondition). We start with the bind rule, then the
Ht-load rule and then Ht-Match which means we have to show
{locked(γ) ∗ ` 7→ Some(x, r) ∗ Φ(x) ∗ . bagList(Φ, r)}
` ← π2 (x, r); Some(π1 (x, r))
{u.u = Some x ∧ ` 7→ r ∗ Φ(x) ∗ locked(γ) ∗ bagList(Φ, r)}
which by using Ht-bind and Ht-Proj simplifies to showing
76
We can use the sequencing rule together with the release specification to give away the resources
` 7→ r, locked(γ) and bagList(Φ, r) back to the lock. We are left with proving
{Φ(x)}
Some x
{u.∃x. u = Some x ∧ Φ(x)}
which is immediate.
Remark 8.44. Note that even if we did not call release we could have proved the same triple,
by using weakening, i.e., the rule P ∗ Q ` P . Such an implementation would indeed be safe,
but we could never remove more than one element from the bag, since we would not be able
to acquire a lock more than once. This is a general observation about an affine program logic
such as Iris. However in Iris it is possible to give a stronger specification to the lock module
which can ensure that locks which are acquired must be released, however this requires a more
sophisticated use of the Iris base logic, so we do not dwell on it here, and instead focus only on
safety specifications.
Following the discussion above, when reading the value of the counter it might be larger than
that known to us via the isCounter predicate, i.e., other threads might have increased its value
without us knowing it.
77
The counter implementation we have in mind is the following. The newCounter method
creates the counter, which is simply a location containing the counter value.
The incr method increases the value of the counter by 1. Since ` ← ! ` + 1 is not an atomic
operation we use a CAS loop, as seen in examples before.
read ` = ! `.
Now, what should the isCounter predicate be? A first attempt might be simply
isCounter(`, n) = ` 7→ n.
However this clearly cannot work, since such an isCounter predicate is not persistent. A second
attempt might be to put the points-to assertion into the invariant as
ι
isCounter(`, n) = ∃ι. ` 7→ n .
This gets us closer, but the problem is that ` 7→ n is not an invariant of all the methods, i.e., not
all the methods maintain this invariant. In particular the increment method does not. Recall
ι
that we can never change the assertion stored in the invariant, e.g., we cannot change ` 7→ n
ι
into ` 7→ (n + 1) . An invariant would be ∃m. ` 7→ m, but now we need to somehow relate m to
n which is the parameter of the isCounter predicate. An idea might be to use the invariant
∃m. ` 7→ m ∧ m ≥ n
This is an invariant, however it is not strong enough. We cannot prove the increment method
using this invariant since we cannot update isCounter(`, n) to isCounter(`, n + 1), because we
cannot change the assertion in the invariant. We can conclude that any attempt which mentions
n in the invariant directly must fail, so we need some other way to relate the real counter value
m and the lower bound n. We will use ghost state. The idea is that in the invariant we will have
γ
some ghost state dependent on m, let us call it •m , whereas we will keep some other piece
γ
of ghost state in the isCounter(`, n) predicate outside the invariant. Let us call this piece ◦n .
Let us see what we need from the resource algebra to be able to verify the counter example by
using
γ γ ι
isCounter(`, n, γ) = ◦n ∗ ∃ι. ∃m. ` 7→ m ∗ •m (38)
as the abstract predicate. First, to verify the read method we will open the invariant, and after
γ
some simplification we will have •m · ◦n and the value we are going to return is m. From this
78
we should be able to conclude that m ≥ n. Using Own-valid we have •m · ◦n ∈ V , where V is
the set of valid elements of the resource algebra we are trying to define. Hence we need to be
able to conclude m ≥ n from •m · ◦n ∈ V . Next, if isCounter(`, n, γ) is to be persistent, it must be
γ
that ◦n is persistent, which is only the case (see Persistently-core) if |◦n| = ◦n, where | · | is the
core operation of the resource algebra we are defining. In particular this means that ◦n must be
duplicable for any n.
Finally, let us see what we need to verify the incr method. As we have seen many times by
now, we have to update the ghost state when the CAS operation succeeds. Just before the CAS
operation succeeds the following resources are available
γ
` 7→ k ∗ •k · ◦n
isCounter(`, n + 1, γ).
The only way to do this is to update the ghost state using Ghost-update. Thus it would suffice to
have the frame preserving update
|◦n| = ◦n (39)
•m · ◦n ∈ V ⇒ m ≥ n (40)
•m · ◦n •(m + 1) · ◦(n + 1) (41)
We now define a resource algebra which allows us to achieve these properties. Let M = N⊥,> ×N
where N⊥,> is the set of natural numbers with two additional elements ⊥ and >. Define the
operation · as
(y, max(n, m)) if x = ⊥
(x, n) · (y, m) =
(x, max(n, m)) if y = ⊥
(>, max(n, m)) otherwise
It is easy to see (exercise!) that this makes M into a commutative semigroup. Moreover it has a
unit, which is the element (⊥, 0).
For m, n ∈ N let us write •m for (m, 0) and ◦n for (⊥, n). Using the definition of the operation
we clearly see •m · ◦n = (m, n). Thus to get property (40) we should require that if (m, n) ∈ V
for natural numbers n and m then m ≥ n. Moreover, the closure condition of the set of valid
elements states that subparts of valid elements must also be valid. Thus, since we wish (n, n) =
◦n · •n ∈ V we must also have ◦n = (⊥, n) ∈ V . With this in mind we define the set of valid
elements as
V = {(x, n) | x = ⊥ ∨ x ∈ N ∧ x ≥ n} .
79
In particular note that elements of the form (>, n) are not valid. With this definition we can see
that property (40) holds.
Requirement (39) defines the core on elements ◦n. The only way to extend it to the whole
M so that it still satisfies all the axioms of the core is to define
It is easy to see that this definition makes (M, V , | · |) into a unital resource algebra.
Finally, let us check we have property (41). Recall Definition 8.23 (on page 59) of frame
preserving updates. Let (x, y) ∈ M be such that (•m · ◦n) · (x, y) is valid. This means in particular
that x = ⊥ and that m ≥ max(n, y). Hence m + 1 ≥ max(n + 1, y) and thus (•(m + 1) · ◦(n + 1)) · (x, y)
is also valid, as needed.
In particular note how it was necessary that •m · •k = (>, 0) is not valid to conclude that x
must be ⊥, which is why we define the operation in this rather peculiar way.
Exercise 8.46. Let isCounter :Val → N → GhostName → Prop be the predicate
γ γ ι
isCounter(`, n, γ) = ◦n ∗ ∃ι. ∃m. ` 7→ m ∗ •m .
Using the specification of the counter module from the preceding exercise show the following
specification for e.
{True} e {v.v ≥ 1}. ♦
80
The increment has an analogous specification, apart from it being parametrized by q, and the
newCounter method creates a counter which is owned in full by the thread that created it.
The next question is how do we share the counter. As explained above, the isCounter pred-
icate cannot be persistent. Instead, when splitting the counter we must record that somebody
else can also use it. We achieve this by parameterising the counter predicate with an additional
fraction p ∈ (0, 1]. This fraction indicates our degree of knowledge about the counter. If p = 1
we have the full ownership of the counter, and thus can know its exact value. Otherwise we
only know its lower bound as with the previous counter specification. By using the following
splitting property of the new isCounter predicate
we can create the counter, share it among different threads, and then once all of those threads
are finished executing we can combine all the counters and read off the exact value.
In light of this rule there is another way to read the assertion isCounter(v, n, γ, p). We can
read it as that the contribution of this thread to the total value of the counter is exactly n. If p
is 1 then this is the only thread, and so the value of the counter is exactly n.
To define the desired isCounter predicate and to prove the desired specification we will
need a resource algebra similar to the one used for the first counter specification, but more
involved. To achieve it we need to generalize the resource algebra we have defined above to the
construction called authoritative resource algebra.
Example 8.48 (Authoritative resource algebra). Given a unital resource algebra M with unit ε,
set of valid elements V and core | · |, let Auth(M) be the resource algebra whose carrier is the set
M⊥,> × M (recall that M⊥,> is the set M together with two new elements ⊥ and >) and whose
operation is defined as
(y, a · b) if x = ⊥
(x, a) · (y, b) =
(x, a · b) if y = ⊥
(>, a · b) otherwise
The core function is defined as (recall that the core is total in a unital resource algebra; see
Exercise 8.12)
VAuth(M) = {(x, a) | x = ⊥ ∧ a ∈ V ∨ x ∈ M ∧ x ∈ V ∧ a 4 x}
81
• •x · •y < VAuth(M) for any x and y
• ◦x · ◦y = ◦(x · y)
• •x · ◦y ∈ V ⇒ y 4 x
• if x · z is valid in M then
•x · ◦y •(x · z) · ◦(y · z)
in Auth(M).
• if x · z is valid in M and w 4 z then
•x · ◦y •(x · z) · ◦(y · w)
in Auth(M).
♦
Recall the isCounter predicate we used previously
γ γ ι
isCounter(`, n, γ) = ◦n ∗ ∃ι. ∃m. ` 7→ m ∗ •m .
We wish to incorporate into it the fraction p indicating how much of the counter this thread
owns. We do this as follows.
γ γ ι
isCounter(`, n, γ, p) = ◦(p, n) ∗ ∃ι. ∃m. ` 7→ m ∗ •(1, m) .
Thus the invariant stores the exact value of the counter, and since it knows its exact value the
γ
fraction is 1. The assertion ◦(p, n) connects the actual value of the counter to the value that
is known to the particular thread. Now, to be able to read the exact value of the counter when
p is 1 we need the property that if •(1, m) · ◦(1, n) is valid then n = m. Further, we need the
property that if •(1, m) · ◦(p, n) is valid then m ≥ n. Finally, we wish to get isCounter(`, n +
k, γ, p + q) a` isCounter(`, n, γ, p) ∗ isCounter(`, k, γ, q). The way to achieve all this is to take the
resource algebra Auth ((Q01 × N)? ) where
• Q01 is the resource algebra of fractions from Example 8.17,
• N is the resource algebra of natural numbers with addition as the operation, and every
element is valid,
• and (Q01 × N)? is the option resource algebra on the product of the two previous ones.
Exercise 8.51. Show the following properties of the resource algebra (Q01 × N)? .
• (p, n) · (q, m) = (p + q, n + m)
• if q ≤ 1 then (p, n) 4 (q, m) if and only if
– p ≤ q and n ≤ m (where ≤ is the standard ordering on naturals and rationals)
– if p = 1 then q = 1 and n = m.
And show the following properties of the resource algebra Auth ((Q01 × N)? ).
• ◦(p, n) · ◦(q, m) = ◦(p + q, n + m)
82
• if •(1, m) · ◦(p, n) is valid then n ≤ m and p ≤ 1
• if •(1, m) · ◦(1, n) is valid then n = m
• •(1, m) · ◦(p, n) •(1, m + 1) · ◦(p, n + 1). ♦
Using the results from the preceding exercise about ghost updates the following exercise is
a straightforward adaptation of Exercise 8.46.
Exercise 8.52. Let isCounter be the predicate
γ γ ι
isCounter(`, n, γ, p) = ◦(p, n) ∗ ∃ι. ∃m. ` 7→ m ∗ •(1, m) .
Next show the following specifications for the methods defined above.
Using the specification from the previous exercise we can now revisit Exercise 8.47 to give
it the most precise specification.
Exercise 8.53. Let e be the program
Using the specification of the counter module from the preceding exercise show the following
specification for e.
{True} e {v.v = 2}. ♦
The key point is that we only require the forked-off thread to be safe, we do not care about its
return value, hence the post-condition True in the premise.
Recall how parallel composition is defined in Section 8.1. It uses two auxiliary methods
spawn, which creates a new thread and a “shared channel” which is used to signal when it is
done, and the join method which listens on the channel until the spawned thread is done. These
methods are of independent interest since, in contrast to the plain fork {}, they allow us to wait
for a forked-off thread to finish. Since we are using a shared channel we will use an invariant
to allow the two threads to access it.
83
The method spawn has a function f as an argument. Let us assume that f has the following
specification for some predicates P and v.Q.
{P } f () {v.Q}.
where we are using the exclusive resource algebra over the singleton set (see Example 8.18).
The idea is that the spawn method returns this handle, which has information about the state
of the forked-off thread. In the beginning the invariant is in the first state, with the location
` pointing to None. Once the spawn terminates the invariant will intuitively be in the second
state, with ` 7→ Some x and Q(x).
γ
When the join method is waiting for the location to become Some x it will have the ()
token, allowing it to know that the invariant is either in the first or the second state. Once it is
γ
in the second the join method will close the invariant in the third state, transferring () into it,
and obtaining Q(x).
Exercise 8.54. Following the description above show the following specifications for the spawn
and join methods in detail.
Ht-spawn
{P } f () {v.Q} Ht-join
Using these specifications derive the Ht-par specification of the parallel composition operation.
♦
Before giving a specification to myrec we will try to get an intuition of how Landin’s knot
works. To this end we consider what a client implementing the factorial of a natural number
could look like and how it would compute the factorial of 2. First we define the f we are going
to use as:
F = λf .λx.if x = 0 then 1 else x ∗ (f (x − 1))
Next we define
fac = myrec F
Hence we have that
84
Now we consider how fac 2 reduces. After some steps, we have that r 7→ (λx. F (! r) x) and the
computation continues as
(! r) 2 (λx. F (! r) x) 2
F (! r) 2
F (λx. F (! r) x) 2
λx.if x = 0 then 1 else x ∗ ((λx. F (! r) x) (x − 1)) 2
if 2 = 0 then 1 else 2 ∗ ((λx. F (! r) x) (2 − 1))
2 ∗ ((λx. F (! r) x) (2 − 1))
2 ∗ ((λx. F (! r) x) 1)
..
.
2 ∗ if 1 = 0 then 1 else 1 ∗ ((λx. fac (! r) x) (1 − 1))
..
.
2 ∗ 1 ∗ ((λx. F (! r) x) 0)
2 ∗ ((λx. F (! r) x) 0)
2 ∗ (F (! r) 0)
..
.
2 ∗ if 0 = 0 then 1 else 0 ∗ (λx. F (! r) x) (0 − 1)
2∗1
2
Now it should be clear that the trick is that r is mapped to a λ-abstraction, which refers to r in
it’s body. Another important observation is that the value r points to never changes after it has
been updated to λx. F (! r) x in the beginning.
The specification we want myrec to satisfy is:
Ht-myrec
Γ , f :Val, g :Val | S ∧ ∀y. ∀v. {P } gv {u.Q} ` ∀y. ∀v. {P } f g v {u.Q}
Γ | S ` ∀y. ∀v. {P } myrec f v {u.Q}
This specification is similar to the first recursive rule from Section 4. The only differences is
that here f is assumed to be a value and that we have f g v in the premise instead of e[g/f , v/x].
holds.
85
The Hoare-triple we want to prove is
∀y, ∀v.{P } (λf .let r := λx. x in r ← (λx. f (! r) x) ; ! r) f v {u.Q}
We proceed by using ∀I twice, Ht-beta, Ht-let, Ht-frame and Ht-alloc. Hence it suffices to prove
∀r.{∃l.P ∗ l = r ∗ l 7→ (λx. x)} r ← (λx. f (! r) x) ; (! r) v {u.Q}
By ∀I, Ht-exist, ∀I and Ht-Pre-Eq it suffices to show
{P ∗ l 7→ (λx. x)} l ← (λx. f (! l) x) ; (! l) v {u.Q}
Now by Ht-seq, Ht-frame and Ht-store it suffices to show
{P ∗ l 7→ (λx. f (! l) x)} (! l) v {u.Q}
It the follows by Ht-bind-det and Ht-load that it suffices to show
{P ∗ l 7→ (λx. f (! l) x)} (λx. f (! l) x) v {u.Q} (44)
Thus by Ht-beta, Ht-bind-det and Ht-load it suffices to show
{P ∗ l 7→ (λx. f (! l) x)} f (λx. f (! l) x) v {u.Q} (45)
How do we proceed from here?
First attempt – “Naive approach” Looking at triple (45) and comparing it to the triple at the
right hand side of the ` in (42), we see that they are similar except:
1. the precondition of the current triple is P ∗ l 7→ (λx. f (! l) x) instead of just P
2. in (45) we have λx. f (! l) x instead of g.
Regarding 1: As P ∗ l 7→ (λx. f (! l) x) ⇒ P , it follows by Ht-csq that in order to prove a triple
with precondition P ∗ l 7→ (λx. f (! l) x) it suffices to prove the same triple with precondition P
instead.
Regarding 2: As the g is actually quantified by a ∀10 , we have that if we can show that
∀y. ∀v. {P } (λx. f (! l) x) v {u.Q} holds then {P } f (λx. f (! l) x) v {u.Q} follows by the assumption
that (42) holds.
Combining the two we get that it suffices to prove
∀y. ∀v. {P } (λx. f (! l) x) v {u.Q} (46)
We proceed by using ∀I twice and Ht-beta. It thus suffices to prove
{P } f (! l) v {u.Q}
Now by Ht-bind we need to show
1. f − v is an Evaluation context.
2. {P } ! l {w.R}
3. ∀w. {R} f w v {u.Q}
for some R of our choice. Item (1) is clear since f is assumed to be a value. For item (2) we get
stuck, as we no longer have l 7→ (λx. f (! l) x) in our precondition and thus we can not dereference
l.
10 see (43).
86
Second attempt – “Löb induction” In the beginning of Section 6, a fixed-point combinator
for defining recursive functions was defined. In the proof of the rule for this fixed-point com-
binator the Löb rule played a crucial role. As we are doing something similar it might be useful
to apply Löb induction here as well.
An immediate challenge is then: when should we apply the rule? The rule needs to be
applied at a stage, which we will return to later, such that we may use the assumption we get at
this later time. The two best places seem to be either where we ended (45)
Clearly, we won’t return to the exact same code later (if this was the case, then we would loop
indefinitely). When we return to either of these stages, the value v will be different, hence we
of course want our Löb induction hypothesis to work for all values, not just a particular one.
In the second case this poses no problem at all; we added v to the context when we used ∀I
in the beginning, hence we can use ∀E before using Löb induction to get a sufficiently general
assumption.
In the first case, we can do the same trick for the value v (the second argument), but we can’t
do so for the first argument of f . Looking at item (3) in our first attempt, we see that it seems
likely that we would need to consider a general value for the first argument to f as well.
Therefore, we decide to use Löb induction in the second case.
We start from (44). By ∀E it suffices to prove
Hence by Löb induction we may assume that . (∀v. {P ∗ l 7→ (λx. f (! l) x)} (λx. f (! l) x) v {u.Q})
holds.
First we get rid of the ∀ quantification by using ∀I. We then proceed by Ht-beta, Ht-bind-det
and Ht-load and thus it suffices to show
Now we proceed as in our first attempt. When we reach the point where we need to prove (46),
we are almost able to conclude the proof. We need to prove
The ∀y we can get rid of by ∀I, but the assumption is not strong enough, as it requires us to
have l 7→ (λx. f (! l) x) in the precondition, which we don’t have. We are thus stuck again.
11 Notice that the . has been stripped from the assumption we got by the Löb induction. The reason for this is that the
operational semantics has taken at least one step. Concretely, one could reason as follows; the assumption is persistent,
as Hoare-triples are, and since . commutes with . We can therefore use Ht-persistently to move the assumption
into the precondition of the Hoare-triple. Each time the operational semantics takes a step, we may use Ht-bind
to bind the atomic step, and then use Ht-frame-atomic to remove the . from the assumption. As the assumption
without the later is still persistent, we may move it back into our assumptions by using Ht-persistently in the other
direction.
87
If we instead had forgotten l 7→ (λx. f (! l) x), before using Löb induction, then we would have
gotten a stronger assumption that doesn’t require l 7→ (λx. f (! l) x) to be in the precondition. Our
problem would then be, that giving up l 7→ (λx. f (! l) x), we almost immediately get stuck: we
could go from
{P } (λx. f (! l) x) v {u.Q}
to
{P } f (! l) v {u.Q}
but then the loss of the right to dereference l stops us from going any further.
Third attempt – “Invariants” When computing the factorial of 2, we noticed that the location
kept pointing to the same value after it had been initialized and updated once, i.e., that it was
invariant from this point on and onwards. Let us therefore try to state this as an invariant and
see how things go. For this attempt, we will try to see if we can succeed without using Löb
induction. Let E be the current mask12 .
As we have that l 7→ (λx. f (! l) x) and hence clearly . (l 7→ (λx. f (! l) x)) it suffices by Ht-inv-
alloc to prove
ι
∃ι ∈ E. l 7→ (λx. f (! l) x) ` {P } f (λx. f (! l) x) v {u.Q}E
We proceed as in the first attempt. When we get to the place, where we got stuck before, we
can now use Ht-bind-det instead of Ht-bind, as this time we know, what the result of dereferenc-
ing l is. We thus need to show
1. f − v is an evaluation context.
ι
2. ∃ι ∈ E. l 7→ (λx. f (! l) x) ` {P } ! l {w.w = z ∧ R}E
ι
3. ∃ι ∈ E. l 7→ (λx. f (! l) x) ` {R[z/w]} f z v {u.Q}E
for some z and R of our choice. We consider each item in turn:
1. As before in the first attempt.
2. As ! l is atomic, we may use Ht-inv-open and thus it suffices to show
{P ∗ . (l 7→ (λx. f (! l) x))}
ι
∃ι ∈ E. l 7→ (λx. f (! l) x) ` ! l
{w.w = (λx. f (! l) x) ∧ R ∗ . (l 7→ (λx. f (! l) x))}E\{ι}
Now we are stuck again. f is a value and w is a value, hence the next reduction step would
be to apply f to w, but we don’t have any information about what then happens.
12 We didn’t explicitly annotate the Hoare-triples with masks before, as we didn’t use any invariants.
88
Fourth attempt – “Invariants + Löb induction” Before beginning our fourth attempt, let us
try to examine our previous attempts to see if they might give any clues about how to proceed.
In the first attempt we more or less just tried to brute-force the proof; this did not work out.
In the second attempt we tried to use Löb induction; this did not work either. The assump-
tion, we got to deal with the recursive call was either too weak or came at the cost of the resource
l 7→ (λx. f (! l) x), which was needed in order to get to the point, where the assumption had to be
used.
In the third attempt, we proceeded mostly as in the first attempt, but tried to be a bit more
clever by using an invariant to maintain the resource l 7→ (λx. f (! l) x). We got a bit further than
in the first attempt, but we still did not have any way to deal with the recursive call.
From this it seems that we had two main challenges:
ι
∃ι ∈ E. l 7→ (λx. f (! l) x) ` {P } (λx. f (! l) x) v {u.Q}E
By reasoning as in the third attempt, including opening the invariant in order to dereference l,
we obtain that it suffices to show
ι
∃ι ∈ E. l 7→ (λx. f (! l) x) ` ∀y. ∀v. {P } (λx. f (! l) x) v {u.Q}E
This triple now follows by using ∀I to instantiate ∀y and then using the assumption we got by
the Löb induction. So, finally, we managed to prove the desired specification for myrec.
Exercise 9.1. Is it possible to prove the specification if one instead used Löb induction on the
other case discussed in the second attempt? That is, can one prove
ι
∃ι ∈ E. l 7→ (λx. f (! l) x) ` ∀v. {P } f (λx. f (! l) x) v {u.Q}E
89
Client: fac
In this subsection we prove that the client fac does indeed compute the factorial of its argument.
Recall the definition of fac
fac = myrec F
where
F = λf .λx.if x = 0 then 1 else x ∗ (f (x − 1))
For the specification of fac we will use the standard mathematical definition of the factorial of
a natural number:
factorial 0 ≡ 1
factorial (n + 1) ≡ (n + 1) ∗ factorial n
Now we can state the specification of fac as
∀n. {n ≥ 0} fac n {v.v = factorial n}
90
Implementation The abstract data type that we are implementing is that of a stack. There-
fore, it will have two operations, push and pop. The main complication of our data structure is
that push and pop must be thread-safe. One way to achieve this would be to use a lock to guard
access to the stack, but this is too coarse-grained and slow when many threads wish to modify
the stack concurrently.
Instead, we use a fine-grained implementation of the stack which optimistically attempts
to operate on the stack without locking and then uses the compare and set primitive to check
whether another thread interfered – if another thread interfered, the operation is restarted. If
many threads operate on the stack concurrently it is quite likely that some of them will try to
push, and some of them try to pop elements. In such a situation we can omit actually adding
the element to a stack and instantly removing it. We can simply pass it from one thread to
another.
This is achieved by introducing a side-channel for threads to communicate along. Then, if
a thread attempts to get an element from a stack, it will first check whether the side-channel
contains an element (which will be called an offer). If so, it will take the offer, but if not, the
thread which wishes to get an element will instead try to get an element from the actual stack.
A thread wishing to push an element will act dually; it will offer its value on the side-channel
temporarily in an attempt to avoid having to compete for the main stack. The idea is that this
scheme reduces contention on the main atomic cell and thus improves performance. Note that
this means that a push operation helps a pop operation to complete (and vice versa); hence we
refer to this style of implementation as using helping or cooperation.14
91
accept
(-, 1)
(-, 0)
(-, 2)
revoke
The pattern of offering something, immediately revoking it, and returning the value if the
revoke was successful is sufficiently common that we can encapsulate it in an abstraction called
a mailbox. The idea is that a mailbox is built around an underlying cell containing an offer
and that it provides two functions which, respectively, briefly put a new offer out and check
for such an offer. The code for this is shown below. Note a small difference in the style from
the three methods above. When the mailbox is created it does not return the underlying store,
but rather returns two closures which manipulate it. This simplifies the process of using a
mailbox for stacks where we only have one mailbox at a time, but is otherwise not an important
difference.
We will call the first part of the tuple the put method, and the second one the get method.
Note that in a real implementation we could, depending on contention, insert a small delay
between making an offer and revoking it in the put method so that other threads would have
more of a chance to accept it. Observe that the put method will return None if another thread
has accepted an offer, and Some v otherwise.
92
will make a temporary offer using the put method, and check the resulting value for whether
the offer was accepted or not. The code for the stack is as follows. Note that this too is written
in a similar style to that of mailboxes, a make function which returns two closures for the
operations rather than having them separately accept a stack as an argument.
stack , λ .
let mb := mailbox() in
let put := π1 mb in
let get := π2 mb in
let r := ref (None) in
(rec pop := match get() with
None ⇒ match ! r with
None ⇒ None
| Some hd ⇒ let h := π1 hd in
let t := π2 hd in
if CAS(r, Some hd, t)
then Some h
else pop()
end
| Some x ⇒ Some x
end,
rec push := match put() with
None ⇒ ()
| Some n ⇒ let r 0 := ! r in
let r 00 := Some(n, r 0 ) in
if CAS(r, r 0 , r 00 ) then ()
else push()
end)
p = (pop, push) ∗
∀Φ.{True} stack() p.∃ pop push . {True} pop() {v.v = None ∨∃v 0 . v = Some v 0 ∗ Φ(v 0 )} ∗
∀v. {Φ(v)} push v {u.u = () ∗ True}
93
Rather than directly verifying this specification, the proof depends on several helpful lem-
mas verifying the behavior of offers and mailboxes. By proving these simple sublemmas, the
verification of concurrent stacks can respect the abstraction boundaries constructed by isolating
mailboxes as we have done.
Having defined this, the representation predicate is offer is now within reach. An offer is a pair
of a location containing an integer, and a value, which is the value being offered. Since multiple
threads will share access to the offer, we use an invariant.
ι
is offerγ (v) , ∃v 0 , `. v = (v 0 , `) ∗ ∃ι. stagesγ (v 0 , `)
Notice that both of these propositions are parameterized by a ghost name, γ. Each γ should
uniquely correspond to an offer and represents the ownership the creator of an offer has over it,
namely the right to revoke it. This is expressed in the specification of mk offer:
n γ o
∀v. {Φ(v)} mk offer(v) u. ∃γ. ex(()) ∗ is offerγ (u)
γ
This reads as that calling mk offer will allocate an offer as well as returning ex(()) which
represents the right to revoke an offer. The fact that ex(()) represents the right to revoke an
offer can be seen in the specification for revoke offer:
n γo
∀γ, v. is offerγ (v) ∗ ex(()) revoke offer(v) u.u = None ∨∃v 0 . u = Some(v 0 ) ∗ Φ(v 0 )
γ
The specification for accept offer is similar except that it does not require ownership of ex(()) .
This is because multiple threads may call accept offer even though it will only successfully
return once.
n o
∀γ, v. is offerγ (v) accept offer(v) u.u = None ∨ ∃v 0 . u = Some(v 0 ) ∗ Φ(v 0 )
Proofs of these specifications are entirely straightforward based on what we have seen up until
now, so we leave them to the reader.
94
involve nested Hoare triples.
∃ put get .
u = (put, get) ∗
{True} mailbox() u. (47)
∀v. {Φ(v)} put(v) {w.w = None ∨∃v 0 . w = Some(v 0 ) ∗ Φ(v 0 )} ∗
{True} get() {w.w = None ∨∃v 0 . w = Some(v 0 ) ∗ Φ(v 0 )}
Note that the proof of this specification is made with no reference to the underlying implemen-
tation of offers, only to the specification described above. Throughout the proof an invariant
is maintained governing the shared mutable cell that contains potential offers. This invariant
enforces that when this cell is full, it contains an offer. It looks like this
is mailbox(`) , ` 7→ None ∨∃vγ. ` 7→ Some(v) ∗ is offerγ (v)
This captures the informal notion described above: either the mailbox is empty, or it contains
an offer. As above, we do not show a proof of the specification and leave it to the reader.
Having verified mailboxes already only a small amount of additional preparation is needed be-
fore we can prove this specification. Specifically, we need an invariant governing the shared
memory cell containing the stack. The predicate is stack(v) used to form the invariant is de-
fined as by guarded recursion as the unique predicate satisfying
is stack(v) , v = None ∨∃h, t. v = Some(h, t) ∗ Φ(h) ∗ . is stack(t)
It states that all elements of the given list satisfy the predicate Φ. Having defined this, it is
straightforward to define an assertion enforcing that a location points to a stack.
stack inv(`) , ∃v 0 . ` 7→ v 0 ∗ is stack(v 0 )
We will allocate an invariant containing this assertion during the proof of the stack specifica-
tion. With this we can now turn to proving the specification.
To start the proof we use the Ht-let rule several times, and then the memory allocation rule,
together with the specification of mailboxes, and thus we end up having to show
p = (pop, push) ∗
{r 7→ None} (pop, push)
p.∃ pop push . {True} pop() {v.v = None ∨∃v 0 . v = Some v 0 ∗ Φ(v 0 )} ∗
∀v. {Φ(v)} push v {u.u = () ∗ True}
where (pop, push) are the two methods in the body of the stack method. We should show this
in a context where we have
∀v. {Φ(v)} put(v) w.w = None ∨∃v 0 . w = Some(v 0 ) ∗ Φ(v 0 )
95
the specification of the mailbox. Before verifying the specifications we allocate an invariant
containing stack inv which we can, since r 7→ None implies stack inv. Having done this let us
verify the first method, and leave the second one for the reader. That is, let us show
where, of course, pop is the first method in the pair returned by stack. Recall that we are
verifying this a context with stack inv and the above two specifications of put and get methods.
Since we are proving the correctness of a recursive function, we proceed by Löb induction,
assuming
Using the specification of the get method we consider two cases. If the result of get() is Some x
we are done, since the specification of get gives us precisely what we need. This corresponds
to the fact that if an offer was made on the side-channel, then we can simply take it and we are
done. Otherwise the result of get() is None and we need to use the invariant to continue with
the proof. We thus open the invariant stack inv to read ! r, after which, by using the definition
of the stack inv predicate, we have to consider two cases. If the stack is empty (r 7→ None) we
are done, since the stack was empty, and thus we return None, indicating that. Otherwise we
know r pointed to a pair, where Φ holds for h and t satisfies . stack inv(t). To proceed we need
to open the invariant again, and after that we again consider two cases.
• If now r 7→ None, then CAS fails and we simply use the Löb induction hypothesis to
proceed.
• Otherwise, r 7→ Some(h0 , t 0 ), where Φ(h0 ) and . stack inv(t 0 ) hold. If the pair (h0 , t 0 ) is equal
to (h, t) then CAS succeeds and we are done, since Φ(h) holds and we can close the invariant
since stack inv(t) holds. Otherwise CAS fails and we are again done by the Löb induction
hypothesis.
96
11.1 Setup
The ticket lock has three methods, newLock for creating a new ticket lock, and acquire and
release for acquiring and releasing the lock respectively. Moreover, we have an auxiliary wait
method used by the acquire method. In order to make the verification more readable we write
it as a separate public method.
As we explained above, the lock uses two mutable counters, the first for keeping track of who
is currently permitted to take the lock and thereby gain access to the resources protected by it,
and the second for tracking which ticket to give out to the next thread. We will refer to them as
owner and next respectively.
In the acquire method we first try to obtain a ticket. This is the intention of the first two
lines. However since another thread might try to obtain a ticket at the same time we need to use
CAS to atomically increment, and thus might need to retry this operation. Once we succeed,
we obtain a ticket in the queue, and proceed to wait our turn to get access to the resources
protected by the lock by using the wait method.
Remark 11.1. Note that it is indeed possible that we never obtain a ticket, since the scheduler
might decide to schedule another thread every time just after we have read the value of the
second counter. Thus we might always fail to acquire the lock. However, getting a ticket is
a cheap operation, and we have to be exceedingly unlucky to always be preempted just after
reading the counter. In contrast, threads can spend a significant amount of time in the critical
region, hence the spin lock is more likely to fail and always be prevented from acquiring the
lock.
In a real implementation we would use a primitive fetch-and-add operation supported by
many modern processors instead of the CAS loop. This would provide an even stronger guar-
antee. We have chosen to keep things simple in these notes and not complicate the language
too much, and hence we stick with only the CAS primitive.
The wait method waits until it is the given client’s turn to enter the critical region. Note
that the method does not need to use any synchronisation primitives since only the thread that
acquired the lock should release it, and thus the value of n will not change once it becomes n;
this is the invariant maintained by all the methods of the ticket lock module.
Analogously, we do not need to use synchronisation primitives when releasing the lock (the
release method). Only the method which is in the critical region will increment the first counter,
and thus there will be no interference between reading its value, and writing it.
As mentioned in the introduction, we can give this lock the same specification as the spin
lock. The only addition is the specification of the wait method, together with the auxiliary
97
assertion issued used by it.
The abstract predicate isLock(v, P , γ) expresses that the value v (a pair of two locations) is a
ticket lock protecting resources described by the predicate P using the ghost resources associ-
ated with γ. As before, the isLock predicate is persistent, so different threads can use the lock
simultaneously.
We will start by defining the resource algebra, invariant, and predicates
we
need, and then
explain our choices. The resource algebra we use is: Auth(Ex(N)? × Pf in (N) . All of the con-
structs of this RA have been previously introduced in Section 8.4. The lock invariant we will
use in our proof is:
γ
lockInv(γ, `o , `n , P ) = ∃o, n. `o 7→ o ∗ `n 7→ n ∗ •(o, {i | 0 ≤ i < n})
γ γ
∗ ◦(o, ∅) ∗ P ∨ ◦(ε, {o}) .
With this we define the isLock, locked and issued predicates as follows (where E is some
chosen infinite set of invariant names)
ι
isLock(v, P , γ) = ∃`o , `n , ι ∈ E. v = (`o , `n ) ∗ lockInv(γ, `o , `n , P )
γ
locked(γ) = ∃o. ◦(o, ∅)
γ
issued(γ, n) = ◦(ε, {n})
Our lock invariant keeps track of the values of the owner and next counters. Since these
change when the lock is being used, they need to be existentially quantified. The exclusive
construction in our RA expresses the fact that only one thread is allowed to take or hold the
lock at any given time. Moreover, the invariant holds an authoritative piece of ghost state, which
holds both the value of the owner counter and a subset of natural numbers. This set keeps track
of which tickets have been given out. The combination of the authoritative construction and
the exclusive one is needed in order to conclude equivalent values when combining different
parts of our ghost state. The disjunction in our invariant intuitively describes whether the lock
is currently acquired or not. If the left disjunct holds then the thread with ticket number o is
allowed to take the lock, but has not yet done so. This is also the reason why the lock in this
case holds the predicate P . If the right disjunct holds then the lock is held by the thread with
ticket number o.
The locked (γ) predicate expresses that the lock associated with ghost name γ is currently
locked with some ticket number. The predicate issued (γ, n) states that ticket number n has
been given out.
98
These two predicates correspond to the two sides of the disjunction in the invariant. The
intuition behind this is that when we know either locked or issued, then we can conclude that
the other side of the disjunction must currently be in the invariant, since the composition would
otherwise be invalid.
11.2 Proofs
There are five proof obligations in the specification. The first three are derived directly from the
properties of the chosen resource algebra, and the definitions of the relevant predicates. The
isLock predicate is persistent since it is a separating conjunction of two persistent propositions.
The locked predicate is not duplicable due to the use of the exclusive part of our RA. Finally,
the issued predicate is not duplicable, because the composition of two sets in our RA is only
valid if these sets are disjoint.
11.2.1 newLock
The fourth obligation is the specification for newLock and will therefore include the allocation
of ghost state and the lock invariant. We need to show the following triple:
We first apply the Ht-bind-det rule twice with ref (0) in both cases. We then allocate the ghost
γ γ γ γ
state (•(0, ∅)) · ◦(0, ∅) . Keeping in mind that (•(0, ∅)) · ◦(0, ∅) ` •(0, ∅) ∗ ◦(0, ∅) , we get the
two pieces of ghost state we need. Together with P from the precondition and the two locations
we got from the two ref (0), we now have all the resources needed for the isLock predicate and
are done.
11.2.2 wait
The next proof obligation is the specification for wait. We want to show:
This is a recursive definition, so we make use of the derived rule from Exercise 7.4 together
with the Ht-beta and Ht-csq rules. This means we assume:
By unfolding the isLock predicate, we get the concrete pair of locations `o , `n we can substitute v
with. Moreover, we can move our invariant into the context. We will refer to lockInv(γ, `o , `n , P )
as I for the remainder of the proof in order to keep the notation shorter. We now use the Ht-Proj
rule followed by the Ht-let-det and the Ht-bind-det rules with the intermediate postcondition
{w.(w = n ∗ locked(γ) ∗ P ) ∨ (w , n ∗ issued(γ, n))}. So we first need to see what ! `o evaluates to.
In order to do this, we need to open our invariant with the Ht-inv-open rule, since it holds the
99
`o 7→ o information we need. We then proceed by casing on whether n = o. In the case where
n , o, we need to show:
ι
I ` {. I ∗ n , o ∗ issued(γ, n)} ! `o {w.((w = n ∗ locked(γ) ∗ P ) ∨ (w , n ∗ issued(γ, n))) ∗ . I}
We can now use the Ht-load rule and choose to prove the right side of the disjunction in the
postcondition, since we directly get this information from our precondition. In the case where
n = o, we need to show:
ι
I ` {. I ∗ n = o ∗ issued(γ, n)} ! `o {w.((w = n ∗ locked(γ) ∗ P ) ∨ (w , n ∗ issued(γ, n))) ∗ . I}
Again we start with the Ht-load rule. In this case, we need to prove the left side of the disjunc-
tion. We can now unpack issued and I and replace n with o. The composition of the different
pieces of ghost state we now own is only valid if the left side of the disjunction in the precondi-
tion is currently true, i.e. we get:
γ γ γ
`o 7→ o ∗ `n 7→ n ∗ •(o, {i | 0 ≤ i < n}) ∗ ◦(o, ∅) ∗ P ∗ ◦(ε, {o})
We can now reestablish the invariant, this time with the resources for the right side of the dis-
γ
junction. This leaves us with: ◦(o, ∅) ∗ P , which is exactly what we need for the postcondition
locked(γ) ∗ P .
The remaining proof obligation at this point is:
ι
I ` {((o = n ∗ locked(γ) ∗ P ) ∨ (o , n ∗ issued(γ, n)))} if n = o then () else wait n l { .P ∗ locked(γ)}
Since our precondition is a disjunction, we will need to show the triple twice assuming the left
and the right side respectively. If we assume the left side of the disjunction we need to show:
ι
I ` {o = n ∗ locked(γ) ∗ P } if n = o then () else wait n l { .P ∗ locked(γ)}
Since our precondition gives us the result of the if statement, we can directly use Ht-If-True and
notice that the postcondition follows directly from our assumptions, so we are done. Assuming
the right side, we have to show:
ι
I ` {o , n ∗ issued(γ, n)} if n = o then () else wait n l { .P ∗ locked(γ)}
Again, our precondition gives us the result to the if, so we can use the Ht-If-False rule and end
up with having to reason about the recursive call to wait. The triple matches our outermost
assumption, so we apply the induction hypothesis (i.e., the assumption of the premise of the
rule in Exercise 7.4) and are done.
11.2.3 acquire
With the specification for wait in place, we are now able to show the one for acquire. We need
to show the following triple:
Since acquire uses wait internally, the proof of this specification will make use of the one for
wait. Notice how the postconditions of both specifications are exactly the same. This means
our proof intuitively consists of getting from our current precondition to the one for wait at the
point where we call wait, since we can then apply its specification and be done.
100
We begin the same way as with wait, since we are dealing with a recursive definition again.
So we assume:
11.2.4 release
The final obligation is the specification for the release method. We need to show the following
triple:
101
We now apply the Ht-bind-det rule with ! `o . In order to reason about this expression, we need
the information stored in our invariant. Since reading from a location is an atomic operation
we can open our invariant at this point which will give us
γ γ γ
`o 7→ o ∗ `n 7→ n ∗ •(o, {i | 0 < i ≤ n}) ∗ (( ◦(o, ∅) ∗ P ) ∨ ◦(ε, {o}) )
for some numbers o and n. So we can use the Ht-load rule now that we have `o 7→ o specifically.
γ
Unfolding the locked(γ) predicate gives us ◦(o0 , ∅) for some number o0 . Since the compo-
sition of this and the authoritative piece of ghost state must be valid, we get that o = o0 and we
can therefore substitute one of them away. We now close our invariant again.
We then bind o + 1, use the Ht-op rule and are left with showing:
n γ o
lockInv(γ, `o , `n , P ) ∗ ◦(o, ∅) ∗ P `o ← (o + 1) { .True}.
At this point, we can open the invariant again, since storing a value is atomic. As before, this
gives us access to the needed location and we can therefore use the Ht-store rule. We moreover
again conclude that the two o values in our ghost state must be the same. Apart from this, we
γ
can see that we have ◦(o, ∅) , which means that the disjunction part of the invariant must hold
γ
◦(ε, {o}) , since the composition would otherwise not be valid.
At this point we can update
γ γ
•(o, {i | 0 ≤ i < n}) · ◦(o, ∅)
to
γ γ
•((o + 1), {i | 0 ≤ i < n}) · ◦((o + 1), ∅) ,
and then we can close the invariant again with these two pieces of ghost state along with P .
11.3 Discussion
As mentioned earlier, the lock presented in this section is based on two counters. Earlier in
the notes, we have already given different specifications for counters, but rather than utilizing
these, the ticket lock implementation and proof refers to the implementation of the counters.
Hence it is not modular — ideally, we should be able to verify the ticket lock relative to an ab-
stract specification of the counter module. This point will be addressed further in the following
Section 13.
102
12.1 Arrays in λref,conc
As the name of the array-based queuing lock implies, its implementation relies on arrays. We
therefore first introduce how arrays are supported in λref,conc .
One may wonder why arrays are necessary, given that we have already seen how lists can be
implemented in λref,conc . The key difference is that arrays offer constant time random-access to
elements in the array. In addition to the performance benefits of this, it also means that reading
and writing elements in an array is an atomic expression.
We need to be able to create arrays of a given size, access elements in an array at a specific
index, and update values at an index in an array. To this end λref,conc includes the following
syntactic constructs:
} ::= · · · | +l
Expr e ::= · · · | allocN (e, e)
ECtx E ::= · · · | allocN (E, e) | allocN (v, E)
In order to express that a location ` points to an array corresponding to a list v, we use the
predicate ` 7→∗ v with the typing rule
If n is a number and v is a value, then allocN (n, v) represents the allocation of an array that is
n long and which is initialized with the value v at every index. We use replicate(n, v) to denote
a mathematical list consisting of the value v repeated n times. The behavior of allocN is then
captured by the following rule:
Ht-allocN
To index into an array the +l operator is used. If ` points to the start of an array then ` +l i is a
location pointing to the value of the i’th element, counting from 0. If xs is a mathematical list,
we use the notation xsi to denote the i’th entry in the list.
Ht-load-offset
The operator +l can be thought of as pointer arithmetic and then this way of using arrays is
similar to how arrays are implemented in many low-level programming languages. Handling
arrays in this manner in Iris has the benefit that we only need to add +l and can reuse the load
and store operators in the context of arrays.
103
The lock consists of a natural number next and an array of booleans array. The number
represents the next ticket available and the array contains false at every entry, except for one,
which contains true. The index of the true value in the array represents which ticket currently
grants access to the lock.
Constructing an ABQL takes as argument a natural number cap, specifying the capacity of
the lock. An array of length cap is then initialized with true at the first entry and false at all
other entries. The natural number which represents the next ticket is initially set to 0.
To acquire the lock, a ticket is received using FAA . The primitive FAA atomically returns the
number stored at a location and adds a number to the stored value. The received ticket, modulo
the capacity of the lock, is an index into the array. By spinning on the value at this index in the
array until it becomes true, the lock is acquired.
To release the lock, the entry corresponding to the ticket from which the lock was acquired
is updated from true to false. Then, the next index in the array, modulo cap, is set to true. This
signals to the next thread that it can acquire the lock.
In the implementation we use a triple to represent the lock. We use (−, −, −) as notation for
((−, −), −) and abuse notation slightly by letting π1 , π2 , and π3 denote the projections of a
triple denoted in this manner. The first element in the triple is the array, the second the next
number, and the third is the length of the array, which we store since arrays in λref,conc do not
have a length operation. The implementation is then:
104
12.3 The specification
The specification can be seen as an extension of the specification for the ticket lock. The signif-
icant changes specific to the ABQL are highlighted in red in the presentation below.
The isLock(γ, ι, κ, l, cap, R) predicate represents the knowledge that the value l is a lock with a
capacity of cap, and which protects the resource R. Since the lock is supposed to be used in
a concurrent setting the isLock predicate is persistent, as we have seen before. The predicates
locked(γ, κ, o) and issued(γ, t) serve the same purpose as they did in the ticket lock.
The precondition for newLock requires that a lock must be created with a capacity greater
than zero. This is required as the implementation performs modulo with cap, which is not
defined for zero.
The biggest change is the addition of invitations. Since every thread waiting to acquire the
lock is spinning on an entry in the array, and since the array is cap long, at most cap threads
may simultaneously attempt to acquire the lock.
The predicate invitation(ι, n, cap) denotes the ownership of n invitations where cap invita-
tions exists in total. When the lock is created, cap invitations are also constructed, see the
postcondition for newLock. In order to acquire the lock, one invitation is required and is sur-
rendered to the lock. When a thread later releases the lock, an invitation is given back, as seen
in the postcondition for release, such that the thread can then acquire the lock again.
Since invitations are not duplicable, we ensure that at most cap threads can simultaneously
attempt to acquire the lock. Invitations can be split and combined, meaning that the ownership
of n + m invitations implies the separate ownership of n and m invitations.
M = (N × N) ∪ ⊥.
The first number in the pair represents how many invitations are owned, and the second how
many invitations exist in total.
It should not be possible to have more invitations than the total number of invitations, and
hence the valid elements are defined as
V = {(n, m) | n ≤ m}.
105
Since it should be possible to combine and separate invitations, we define the operation as
(a + b, m) if n = m
(a, n) · (b, m) =
.
⊥
otherwise
It only makes sense to combine invitations with the same upper bound, and hence the operation
returns ⊥ if the two upper bounds are not equal. Invitations should not be duplicable, and to
this end the core function is always undefined.
With this resource algebra in place, we now define invitations as
γ
invitation(γ, a, n) = (a, n) .
Exercise 12.1. Show that the resource algebra defined above satisfies the requirements given in
the definition of a resource algebra (Definition 8.10). ♦
In the following subsections we describe these predicates at a high-level and identify several
useful and central properties as lemmas, which we then refer back to when we present the
proofs. Hopefully this makes it easier for the reader to understand how the various pieces fit
together.
106
We use disjoint union such that partial knowledge about the existence of a ticket can only
exist once. Hence we have the following lemma:
Lemma 12.2. Only a single thread can know that a certain ticket has been issued.
γ γ
◦(ε, {t}) ∗ ◦(ε, {t}) −∗ ⊥
Similarly, the exclusive construct around the value of o ensures that this information can
also only exist once.
Lemma 12.3. Only a single thread can have the partial information about o.
γ γ
◦(o, ∅) ∗ ◦(o, ∅) −∗ ⊥
The predicates left(κ), right(κ), and both(κ) are defined using the resource algebra Ex(1)? ×
Ex(1)? where 1 denotes the unit resource algebra with the single element ():
κ κ κ
left(κ) = ((), ε) right(κ) = (ε, ()) both(κ) = ((), ()) .
Lemma 12.4. The resource both(κ) splits into left(κ) and right(κ), and left(κ) and right(κ) can be
combined back together into both(κ).
Lemma 12.5. Any other combination of left(κ), right(κ), and both(κ) except for those in Lemma 12.4
is invalid.
Both lemmas follow directly from the definition of the resource algebra and the rules Own-op
and Own-valid.
n ,→ o + i.
As one would expect, n points to the ticket currently granting access to the lock added together
with the number of threads waiting for the lock.
107
Inside the invariant we have the authoritative part
γ
•(o, seq(o, i)) .
The lock always knows the value of o and of all the tickets in existence. Here we have made an
important change compared to the equivalent part in the ticket lock. In the ticket lock the set
of tickets always starts from 0, and it is only expanded when new tickets are issued. In some
sense, tickets are not “cleaned up” after their usage. For the ABQL it is important that the set of
tickets starts at o. This means that whenever o increases, we know that all tickets smaller than
o no longer exist. Hence, we have the following lemma.
Lemma 12.6. From issued(γ, o) and the content of the invariant one can conclude o ≤ t < o + i. More
precisely,
γ γ γ γ
◦(ε, {t}) ∗ •(o, seq(o, i) −∗ ◦(ε, {t}) ∗ •(o, seq(o, i) ∗ o ≤ t < o + i.
The lemma is straightforward to show. Suppose the two ghost states are owned. By Own-
op and Own-valid the product of the two elements of the resource algebra is also owned and
is valid. From the definition of the authoritative resource algebra and the disjoint set resource
algebra this implies {t} ⊆ seq(o, i). Which per the definition of seq means that we have the bound
in the lemma. This tighter bound is not needed for the proofs of the ticket lock to go through
but for the ABQL it is necessary.
The invariant also contains ghost state for invitations, which we have already described. It
is used as
invitation(ι, i, cap)
inside the invariant. This establishes the connection that i is the number of invitations owned
by the lock. If i threads are currently waiting for the lock then i invitations are currently owned
by the lock. This corresponds to the specification where, when calling acquire, a thread surren-
ders an invitation and when calling release the invitation is handed back. In the meantime the
invitation is “stored” in the lock. As discussed previously the inclusion of invitations serves to
constrain how many threads can wait for the lock. Indeed we have the following key property:
Lemma 12.7. The invitation(ι, i, cap) in the invariant establishes the inequality i ≤ cap.
This lemma follows from the definition of invitation, Own-valid, and the definition of valid-
ity of the resource algebra. This lemma is essential when proving the specification for wait.
108
open closed
left right
clopen
In the first and third disjunct nthTrue(cap, o rem cap) denotes a mathematical sequence of
booleans of length cap with false at every index except for index o rem cap which contains
true.
12.6 Proofs
We now prove the specification for each of the functions.
109
12.6.2 Proof of acquire
For the acquire function we must prove the following specification:
From the isLock predicate we know that l is a triple and that there exists a location n which is
the second element of the triple. Using these facts and structural rules we can step through the
projection and the first let-expression. We now apply the bind rule on the expression FAA n 1.
Since FAA is an atomic expression we can open the lock invariant. From opening the lock
invariant we know that there exists natural numbers o and i such that n points to o + i. With
this points-to predicate, we can apply the Ht-faa rule after which we know
n ,→ o + i + 1
The only part of the invariant we no longer have is n ,→ o +i. Instead, we now have n ,→ o +i +1.
Thus, when we close the invariant we have to provide either o + 1 as a witness for o or i + 1 as a
witness for i. Since o represents the index of who may currently access the lock and i represents
how many threads are waiting, it only makes sense to increment i. Hence, we provide o as a
witness for o and i + 1 as a witness for i. For xs we provide the same value we got when opening
the invariant. Since we only changed i we can frame away the part of the invariant that does
not depend on i. The points-to assertion n ,→ o + i + 1 can also be framed away since we picked
i exactly such that it matched the points-to assertion we had after the fetch-and-add.
In order to close the invariant we are now left with showing
γ
invitation(ι, i + 1, cap) ∗ •(o, seq(o, i + 1) .
From opening the invariant we have invitation(ι, i, cap) and from the precondition in the speci-
fication we have invitation(ι, 1, cap). We can combine these two facts to get invitation(ι, i + 1, cap)
and frame the same thing away in the postcondition.
To show the second item we must update the authoritative ghost state we got when opening
the invariant, namely •(o, seq(o, i)). We can update it to •(o, seq(o, i)∪{o +i})·◦(ε, {o +i}). The first
part of the product is exactly what we need to close the invariant, and the second is issued(ε, o+i)
which we will need later when calling wait.
After closing the invariant we can evaluate the let-expression and are then left with
wait l (o + i); o + i
110
Since the code is a sequencing of two expressions we apply the sequencing rule. We have the
isLock predicate, issued(ε, o+i), and the code wait l (o+i). This is fits the wait specification, which
we apply. The postcondition for the specification gives us the resource R and locked(γ, κ, o + i).
Additionally, the last expression evaluates to o+i. This matches precisely with the postcondition
for the specification, and we are done by framing.
The definition is recursive, so we apply the Ht-Rec rule, and assume that the specification
holds for any recursive call.
From the isLock predicate we know that l is a triple and that there exists a value a, which
is the first element of the triple, and a natural number cap, which is the third element of the
triple. With this information we can step through the projections and the let-expressions. We
then focus on the condition
!(array +l i)
with the bind rule. We now open the invariant which contains the information that there exists
an xs such that array ,→∗ xs. In order for the expression to make sense, the index must be
smaller than the length of the array. Fortunately, we know from the invariant that the length of
the array is cap and t rem cap is certain to be smaller than cap. From this we can show that the
load expression evaluates to a boolean b where
We cannot step forward through the code until after we close the invariant. We still have every-
thing from opening the invariant, so we simply use the exact same values as witnesses to close
the invariant. We then apply Ht-If-False which leaves us with the code
wait l t
This is a recursive call of wait and we can now apply the specification we assumed when we
used the Ht-Rec rule earlier. This concludes the first case.
In the second case we have b = true. Intuitively, we are in this case because the lock is open.
Not only is the lock open, our ticket t should be equal to the ticket which now grants access
to the lock, namely o. If we can show this, then we have issued(γ, o), and we can then use
Lemma 12.8 to conclude that the lock is open.
111
Consider the state(γ, κ, cap, o, R, xs) part of the invariant:
γ
( ◦(o, ∅) ∗ R ∗ both(κ) ∗ xs = nthTrue(cap, o rem cap))∨
(issued(γ, o) ∗ right(κ) ∗ xs = replicate(false, cap))∨
(issued(γ, o) ∗ left(κ) ∗ xs = nthTrue(cap, o rem cap)).
xs = replicate(false, cap)
This states that xs is a list containing only false. This is a contradiction, as we have just read
true at an index in the array. In particular, we know that b = true and b = xs(t rem cap) . It is easy
to show that xs = replicate(false, cap) implies that xsi = false for any i smaller than cap. And,
since t rem cap is certainly smaller than cap we have the desired contradiction.
Both of the remaining cases contains the equality xs = nthTrue(cap, o rem cap). Combining
this with Equation 49 we get
Recall that nthTrue(cap, o rem cap) denotes a list that only contains true at index o rem cap.
Thus the above implies
o ≡ t (mod cap). (50)
This is, unfortunately, not enough to prove t = o. It could, for instance, still be the case that
t = o − cap or t = o + cap. However, considering how the ABQL works neither of these can
actually happen.
• We can never have a ticket that is smaller than o. Then it would already have been our
turn to acquire the lock.
• Our ticket can not be larger than o +cap. Intuitively, t −o represents how many threads are
currently in front of us in the queue to acquire the lock. But, because at most cap threads
can ever wait for the lock, our position in the queue can be no larger than cap.
Fortunately, both of these intuitions are encoded in the definitions and can be shown as follows.
γ γ
We have issued(γ, t) = ◦(ε, {t}) and •(o, seq(o, i) . By using Lemma 12.6 we have
o ≤ t < o+i
This shows the first item above, that t can not be smaller than o.
From opening the invariant we have invitation(ι, i, cap). We can thus use Lemma 12.7 to
conclude that i ≤ cap. Put together with the above, we have the second item.
t < o + i ≤ o + cap.
This means that in both of the remaining cases we have issued(γ, o) and per Lemma 12.2 we
now know that the lock must be in the state corresponding to open.
112
In other words, from opening the lock invariant we now also have both(κ), the resource
γ
R, and the partial information ◦(o, ∅) . We can split both(κ) into a left(κ) and a right(κ). This
means that we have issued(γ, o), left(κ), and, from opening the invariant, xs = nthTrue(cap, o rem cap).
These are exactly the things needed to show the closed state of the disjunction in the invariant.
Thus we can close the invariant providing the same variables we initially got for o, i, and xs
followed by framing.
We have now closed the invariant and can step further into the code. We use the Ht-If-True
rule and are left with
()
Hence, the only thing left to show is the postcondition. This includes R and locked(γ, κ, o) =
γ
◦(o, ∅) ∗ right(κ). Both of these follow from framing as we have these from the invariant.
We can evaluate the two let expressions using the facts from the isLock predicate which leaves
us with
Before we proceed any further, let us take a step back and consider how the proof must
proceed. In the code above, there are two store operations that write into the array. Since the
points-to predicate for the array is inside the invariant we must open the invariant twice. Each
time we open the invariant we must consider the state(γ, κ, cap, o, R, xs) disjunction inside the
invariant.
Since the lock is closed when we open the invariant for the first time, we should be able to
conclude that it is in the closed state. We then set the single true value in the array to false.
Hence, we must close the invariant in the clopen state. When we open the invariant for the sec-
ond time, we should be able to conclude that the disjunction is still in the clopen state. Finally,
after the last store operation we should be able to close the invariant in the open state.
We use the bind rule to focus on the first store operation.
We now open the invariant and get the existence of o0 , i, and xs such that
113
Since we have right(κ) we can conclude that the state(γ, κ, cap, o0 , R, xs) disjunction is in the
last state as the first branch contains both(κ) and the second branch contains right(κ)—both of
which lead to a contradiction when combined with right(κ). We therefore additionally know.
When closing the invariant we want to show the middle part of the state disjunction, and hence
our goal is to show
issued(γ, o0 ) ∗ right(κ) ∗ xs = replicate(false, cap).
The only thing we must work for is xs = replicate(false, cap) as the other properties can be
framed away. Fortunately, this is exactly what we expect to be able to show after stepping
through the store as this operation is supposed to overwrite the single true in the array with a
false. But, notice that we are setting the array using the value o, but, that the information from
the invariant describes where the true value is in the list in terms of o0 . We therefore want to
γ
show that o and o0 are in fact equal. To do this we combine ◦(o, ∅) from the locked(γ, κ, o)
γ
predicate in the precondition with •(o0 , seq(o0 , i)) from the invariant.
We now have xs = nthTrue(cap, o0 rem cap) and the code
From this we can show the postcondition xs = replicate(false, cap). This is what we need to close
the invariant in the clopen state. We frame everything else away.
We now focus on the next store operation. We still have the resources we started with except
that instead of right(κ) we now have left(κ), and the code is
We again open the invariant. Similarly to how we previously used right(κ) to determine the
state of the disjunction in the lock we now use left(κ) to determine that the lock is still in the
clopen state. We therefore have xs = replicate(false, cap) and array ,→ replicate(false, cap). After
executing the store operation we thus have
We are now ready to close the invariant for the last time. When closing the invariant in the
open state we must provide three witnesses and show the following.
For o we provide o + 1, since we have now effectively moved the true value in the array one
element to the left. For xs we provide nthTrue(cap, (o + 1) rem cap). When we opened the
invariant every entry in xs was false but we have updated one entry to true. For i we provide
i − 1. We do this because invariant contains n ,→ o + i and since we are providing o + 1 in the
place of o we have to establish n ,→ (o + 1) + i. But, we have not changed what n points to so we
still only have n ,→ o + i. The only way we can make this work is to use i − 1 as the witness for i.
With this choice of witnesses we can immediately frame away R and the points-to predicate
for the array. The equality involving the length is trivially true. We show both(κ) by combining
the left(κ) and the right(κ) that we have with Own-op followed by framing.
114
For the points-to predicate we have n ,→ o + i and are to show n ,→ o + 1 + (i − 1). Since we
are working with natural numbers (o + 1) + (i − 1) is only equal to o + i if i is greater than 0. We
thus have the points-to predicate if we can show that i is greater than 0.
γ γ
From opening the invariant we have both issued(γ, o) = ◦(ε, {o}) and •(o, seq(o, i)) . We
can combine these using Own-op and then conclude that
is valid from the Own-valid rule. From the definition of valid in the authoritative resource
algebra, this implies that o ∈ seq(o, i). If i was 0 then seq(o, i) would be the empty set so this can
not be the case. With this fact we can rewrite the goal n ,→ ((o + 1) + (i − 1)) into n ,→ o + i which
we can then frame away.
Notice that since we decremented the value of i when we closed the invariant we only have
to show invitation(ι, i − 1, cap). On the other hand, the postcondition of release requires us to
show invitation(ι, 1, cap). This adds up nicely. By using Own-op we split the i invitations we
have into one ghost state with 1 invitation and one ghost state with i − 1 invitations. We can
then frame both the aforementioned goals away.
The only thing that remains to show is:
γ γ
•(o + 1, seq(o + 1, i − 1)) ∗ ◦(o + 1, ∅)
γ γ γ
To do that we have ◦(ε, {o}) and •(o, seq(o, i)) from opening the invariant and ◦(o, ∅) from
the locked(γ, κ, o) predicate in the original precondition. It is clear that we must make a frame
preserving update in order to achieve this. Specifically we need the frame preserving update
◦(o, ∅) · ◦(ε, {o}) · •(o, seq(o, i)})) ◦(o + 1, ∅) · •(o + 1, seq(o + 1, i − 1))).
12.7 Discussion
We have now completed the proof of the specification for the ABQL, a lock that can be used by at
most a fixed number of participating threads. In particular, we have seen how the specification
of the ticket lock can be extended to express the restrictions using the concept of invitations
and we have verified that the implementation meets the specification using a suitable resource
algebra for invitations.
115
concurrent modules that are sufficiently general that they can be instantiated by many diverse
clients. In particular, we present a new specification of the concurrent counter module, which
is more modular, in the sense that the earlier given specifications can be derived from it (without
reference to the code of the counter, only using the abstract predicates). Moreover, we also show
how this modular specification of the counter module allows us to give a modular proof of the
ticket lock, i.e., a proof of the ticket lock which only depends on the specification of the counter
module, not on the concrete implementation. We include this section for the obvious reason
that modularity is a key point we have been striving for all along, but also because it gives us an
additional opportunity to show how Iris’s higher-order logic supports quite advanced modular
specifications.
The methodology we present is a higher-order approach to modular specifications of concurrent
modules. It stems from [15], which was based on [6]. It is closely related to the notion of
logical atomicity from the TaDa logic [1]. The examples, the concurrent counter module and
the ticket lock, are from [3], which contains a presentation and discussion of these examples
using TaDa-style logical atomicity. This section is supposed to be an introduction to the topic of
modular specifications for concurrent modules, please see the mentioned references for further
discussion.
We now outline the overall idea of the methodology; it is perhaps a little tricky to under-
stand at first, so it may be easiest to read this description quickly at first, and then study the
examples below and return to the description again.
We consider concurrent modules which have some state and some methods operating on the
state. A concrete example could be a concurrent stack module. To specify a concurrent module,
we decide on what the mathematical model of the abstract state should be, and in particular
how the model allows for sharing. For the concurrent stack module, a natural choice is to
model the abstract state of the stack by a mathematical sequence of natural numbers (assuming
that the elements of the stack are simply natural numbers). We will use a ghost variable to
keep track of the contents of the abstract state of the module, so for the concurrent stack we
will have a ghost resource whose contents will be a mathematical sequence of numbers. Now
consider the specification of a method. Typically, it will involve a modification of the abstract
state of the module. For example, for the concurrent stack, a push method is supposed to
change the abstract state of the module, by inserting the element being pushed into the front of
the mathematical sequence modeling the abstract state of the stack.
Since we are in a concurrent setting, it matters when the state of the module changes, and
when the abstract state changes, a client will typically also have to update some invariants
and protocols of its own. However, the module, of course, cannot know how different clients
wish to update their invariants when the abstract state of the module changes. Therefore we
parameterize the method specification by a view shift, which (1) describes how the abstract state
is supposed to change and (2) describes how other invariants should be updated. The idea
is that, when we prove the specification of the method of the module, then we can use this
view shift to update the abstract state of the module; typically, we will then also show that
the concrete state of the module matches the new abstract state of the module. Thus it is the
client of the module who has to prove that the abstract state of the module can be changed as
described by the view shift, since the client has to provide a proof of the view shift. (Perhaps
it is surprising that the client can prove that the abstract state of the module can be changed,
but notice that the client only considers the abstract state of the module, which is tracked using
ghost state — the modifications to the actual concrete state of the module are proved to match
the abstract state change when we prove the module method specification.) Since the module
cannot know which other invariants the client has, we also parameterize the specification by
predicates intended to describe those.
116
13.1 Modular Specification of Concurrent Counter Module
Counter Implementation The counter implementation we will consider is the same as in Sec-
tion 8.7, except we add an additional weak increment (wk incr) method. Its definition is the
following
wk incr ` = ` ← 1 + ! `.
The intention is that clients should only call the weak increment method when the client knows
that it is safe to do so, i.e., when the client knows that only one thread will increment the
counter. Since the increment is not atomic
A Resource Algebra for the Abstract State of the Counter We model the abstract state of the
counter by a natural number. We will use a resource algebra for keeping track of the abstract
state of the counter module. The resource algebra is the authoritative resource algebra (from
Example 8.48) over the product of the resource algebra of fractions (from Example 8.17) and
the agreement resource algebra (from Example 8.14) on the set of natural numbers N (the type
of the model of the abstract state of the counter). The product of the two last resource algebra
is not a unital resource algebra. In order to apply the authoritative resource algebra construc-
tion we wrap the product in the option resource algebra (from Example 8.22). The resulting
construction is Auth((Q01 × N)? ).
q γ
We write γ Z⇒◦ m for the ghost ownership assertion ◦(q, m) of the fragmental element
◦(q, m) to reflect the intuitive reading of this ghost ownership assertion as “there is a ghost
heap, which maps γ to m with fraction q”. In particular, we write γ Z⇒ 1k m when the fraction q is
1 γ
k . In case when q = 1 we write γ Z⇒◦ m. We write γ Z⇒• m for the ownership assertion •(1, m)
of the authoritative element •(1, m). It is a simple exercise to verify that for all n, m and p, q we
have the following entailments.
q q
γ Z⇒◦ m ∗ γ Z⇒◦ n ` n = m (51)
p
γ ⇒
Z ◦ m ∗ γ Z⇒• n ` n = m (52)
p q p+q
γ ⇒
Z ◦ m∗γ ⇒
Z ◦ m a` γ ⇒
Z ◦ m (53)
γ Z 1◦
⇒ m ∗ γ Z⇒• m ` V
| γ Z 1◦
⇒ n ∗ γ Z⇒• m (54)
The first property means that everybody in possession of the partial knowledge (fraction q less
than 1) agrees on the value of the counter, the second property states that anyone in possession
of the partial knowledge agrees with the authoritative part on the value of the counter, the
third property states how the abstract predicate can be split, and the last property states that
anybody in full possession of the fragmental state of the counter and the authoritative state of
the counter can update both.
q
Exercise 13.1. Prove the preceding properties of the assertions γ Z⇒◦ m and γ Z⇒• m. ♦
117
wk incr, P and Q range over Prop, v over Val, q over fractions Q01 , and m over N.
∃ Cnt : Val → GhostName → InvName → Prop.
(∀v, γ, c. Cnt(v, γ, c) ⇒ Cnt(v, γ, c))
n o
∧ {True} newCounter() v.∃γ, c. Cnt(v, γ, c) ∗ γ Z⇒1◦ 0
E
∧ ∀γ, c, P , Q, v. ∀m. (γ Z⇒• m ∗ P ) VE\{c} (γ Z⇒• m ∗ Q(m)) ⇒
{Cnt(v, γ, c) ∗ P } read(v) {u. Cnt(v, γ, c) ∗ Q(u)}E
∧ ∀γ, c, P , Q, v. ∀m. (γ Z⇒• m ∗ P ) VE\{c} (γ Z⇒• (m + 1) ∗ Q(m)) ⇒
{Cnt(v, γ, c) ∗ P } incr(v) {u. Cnt(v, γ, c) ∗ Q(u)}E
q
∧ ∀γ, c, P , Q, v, q, m. γ Z⇒• m ∗ γ Z⇒◦ m ∗ P VE\{c} γ Z⇒• (m + 1) ∗ Q ⇒
n q o
Cnt(v, γ, c) ∗ γ Z⇒◦ m ∗ P wk incr(v) {u.u = () ∗ Cnt(v, γ, c) ∗ Q}E
The idea is that the abstract predicate Cnt(v, γ, c) expresses that v represents a counter,
whose abstract state is kept in the ghost variable γ, and which uses invariant name c. As usual,
Cnt(v, γ, c) is persistent so that we can share it among several threads.
The postcondition of newCounter says that a counter is created and, moreover, that the
abstract state of the counter is 0. The client of the counter gets ownership of the fragmental
part (γ Z⇒1◦ 0) of the abstract state, which means that if the client gets access to the authoritative
part (γ Z⇒• 0), which is kept by the counter module, then it can update the abstract state of
the counter. The fragmental and authoritative parts together represent the abstract state of the
counter from different angles. The authoritative part γ Z⇒• 0 provides the module view of the
abstract state, and the fragmental part γ Z⇒1◦ 0 provides the client view of the abstract state.
Those two views have to be synchronized. This means that the counter module cannot update
the abstract state of the counter “on its own”, just from the module view (remember that the
idea of the methodology is that the module cannot know what should happen when the abstract
state changes and hence it delegates updating of the abstract state to the client of the module).
Now consider the specification of incr. To use this specification, the client must show the
view shift
∀m. (γ Z⇒• m ∗ P ) VE\{c} (γ Z⇒• (m + 1) ∗ Q(m)),
and then it gets the Hoare triple {Cnt(v, γ, c) ∗ P } incr(v) {u. Cnt(v, γ, c) ∗ Q(u)}E . The view shift
expresses that the incr will increment the abstract state of the counter; the predicates P and
Q are universally quantified and can thus be instantiated by the client to coordinate updates
to invariants held by the client. The Hoare triple expresses that one may call incr if one has a
Cnt(v, γ, c) resource.
The specification of read is similar to the specification of incr, except that the abstract state
does not change. Even though the abstract state does not change, we still parameterize the
specification by a view shift, because that will allow a client to update its own invariants ap-
propriately when it learns about the abstract state of the counter (by instantiating P and Q as
necessary). We will show examples of how this can be done below.
Note that the quantification over the abstract state m of the counter in the view shifts for
incr and read captures the point that a client cannot know (if the counter is shared by different
threads) what the abstract state is — because other threads may call methods on the counter
concurrently.
In the specification of wk incr, the value of the counter m is quantified over both the view
shift and the Hoare triple. Moreover, to call wk incr, the client must have fragmental ownership
118
q
of the abstract state of the counter (note the γ Z⇒◦ m in the precondition of the Hoare triple); this
captures the idea that no other thread can have full ownership of the abstract state and hence
cannot update the abstract state “under our feet”, which is in accordance with the idea that a
client should only call wk incr when it knows that no other thread can modify the counter. In
the specifications for incr and read, the predicate Q is parameterized by the abstract state of the
counter (because we do not know up front what the abstract state is), but in the specification for
wk incr, the predicate Q need not be parameterized by the abstract state of the counter, since
q
the client already keeps track of it (γ Z⇒◦ m).
Finally, we comment on the mask annotation on the view shifts: since the mask is E \ {c}, the
client may use (open and close) all the invariants in E when showing the view shift, except the
invariant named c used by the counter module. That is also the reason why we parameterize
Cnt by c (rather than hiding c behind an existential, as we have done in earlier examples). (In
Coq, we use invariant name spaces to keep tract of these invariant names, see Section 14.5.)
Showing that the Implementation meets the Modular Counter Specification We now out-
line the proof that the counter implementation meets the above modular specification. We
naturally use an invariant to share the state of the counter. The invariant connects the concrete
value of the counter to the abstract state of the counter, which in this case is simply the same
value as the concrete value stored in the reference of the counter module. 15
With this definition of the abstract Cnt predicate, it is not hard to show that the different meth-
ods meet the specifications. Here we just outline the proof for the incr method, and leave the
other (easier) ones as an exercise.
For incr we first assume the given view shift, and the proceed to show the Hoare triple. To
that end, since incr is a recursive function we proceed, as usual, by Löb induction. To deref-
erence the reference we open the invariant and then close it again. Then we get to the CAS
instruction. We open the invariant and thus get ownership of the authoritative part of the ab-
stract state, i.e., we get γ Z⇒• m for some m. The interesting case is when it succeeds (otherwise
we just end up recursing so the proof succeeds by applying the Löb induction hypothesis). In
this case we get that the reference now points to m + 1 (since the CAS succeeded). Now we want
to apply the view shift, so we instantiate it with m, and then we can apply it. This we can do
since we both have γ Z⇒• m and P (we have P from the precondition in the Hoare triple). By
the view shift we have γ Z⇒• (m + 1) and Q(m). Thus, since we now both have that the reference
points to m + 1 and we also have γ Z⇒• (m + 1), we can close the counter invariant, and thus we
obtain the required postcondition. So, in summary, the key point to notice is that the abstract
state of the counter is updated by an application of the view shift which the specification is
parameterized by.
Exercise 13.2. Show the specifications for newCounter, read, and wk incr. ♦
13.1.1 Deriving Counter with Contributions from the Modular Counter Specification
In this subsection we sketch how we may use the modular counter specification from above to
derive a counter-with-contributions specification from Exercise 8.52 in Section 8.7.
15 Generally, in this methodology, the abstract state is an appropriate mathematical abstraction of the contents of the
module, e.g., the abstract state for a concurrent stack module could be a mathematical sequence of values.
119
The idea is to proceed much as in Exercise 8.52, except that now we have to use the abstract
state of the counter (as a client of the modular counter specification that is all we can use!). Thus
we let isCounter be the predicate
γ1 γ1 ι
isCounter(`, n, γ1 , γ2 , c, p) = ◦(p, n) ∗ ∃ι ∈ E \ {c}. ∃m. γ2 Z⇒◦ m ∗ •(1, m) ∗ Cnt(`, γ2 , c).
where we use the same authoritative resource algebra as we did for the verification of the
counter with contributions previously. Note the similarity to the earlier definition in Exer-
cise 8.52! In the definition of isCounter, the predicate Cnt(`, γ2 , c) expresses that ` is a counter,
whose abstract state is tracked by γ2 , and in the invariant we use γ2 Z⇒◦ m to record that the
abstract state of the counter is m (note how this ghost state plays a role similar to the role played
by ` 7→ m in Exercise 8.52). Also note that isCounter(`, n, γ1 , γ2 , c, p) is persistent.
With this definition in place, we can prove the following specifications:
We sketch the proof for incr, and the leave the others as an exercise. We assume the pre-
condition, which gives us Cnt(v, γ2 , c), as is necessary for using the modular counter specifica-
γ
tion for incr. We instantiate P and Q in the modular incr specification by P = ◦(p, n) 1 and
γ1
Q = λx. ◦(p, n + 1) . Now we need to show the view shift
γ1 γ1
γ2 Z⇒• m ∗ ◦(p, n) VE\{c} γ2 Z⇒• (m + 1) ∗ ◦(p, n + 1) .
γ
We do this by opening the invariant ι, which gives us γ2 Z⇒◦ k ∗ •(1, k) 1 , for some k. By the
properties for resource algebra for abstract state (52) we conclude that k = m. Using (54), we
can update the abstract state γ2 Z⇒◦ k ∗ γ2 Z⇒• k to γ2 Z⇒◦ k + 1 ∗ γ2 Z⇒• k + 1. By the properties
γ γ γ
of the authoritative resource algebra we can also update ◦(p, n) 1 ∗ •(1, k) 1 to ◦(p, n + 1) 1 ∗
γ1
•(1, k + 1) . With this we can close the ι invariant again. Recalling that k = m, the resources
γ
we have left are exactly the γ2 Z⇒◦ (m + 1) ∗ ◦(p, n + 1) 1 , as required for completing the proof of
the view shift.
Since we have shown the view shift, we now get the Hoare triple
n γ o n γ o
Cnt(v, γ2 , c) ∗ ◦(p, n) 1 incr(v) u.u = () ∗ Cnt(v, γ2 , c) ∗ ◦(p, n + 1) 1
γ
By the definition of isCounter, we not only have Cnt(v, γ2 , c), but also ◦(p, n) 1 . Hence we
conclude from the Hoare triple above that we can indeed call incr(v) and obtain Cnt(v, γ2 , c) ∗
γ
◦(p, n + 1) 1 , which, together with the invariant ι, suffices to conclude isCounter(v, n+1, γ1 , γ2 , c, p),
as required.
120
13.1.2 Deriving Sequential Counter from Modular Counter Specification
Here is a specification for a counter that can be used in a sequential context only:
∃ SeqCnt : Val → GhostName → InvName → N → Prop.
∀n. {True} newCounter() {v.∃γ, c. SeqCnt(v, γ, c, 0)}
∧ ∀γ, c, v, n. {SeqCnt(v, γ, c, n)} read(v) {u.u = n ∗ SeqCnt(v, γ, c, n)}
∧ ∀γ, c, v, n. {SeqCnt(v, γ, c, n)} incr(v) {u.u = n ∗ SeqCnt(v, γ, c, n + 1)}
121
As you can see, the only change (compared to the earlier specification in Section 11) is the
additional ghost name and invariant name arguments to the abstract predicates and invariant.
That is because we use the modular counter specification, which is also parameterized by ghost
names and invariant names.
To prove that the implementation above meets the specification, we define the abstract pred-
icates as follows:
γ
lockInv(γ, γo , γn , P ) = ∃o, n. γo Z⇒ 21 o ∗ γn Z⇒◦ n ∗ •(o, {i | 0 ≤ i < n})
γ γ
∗ (( ◦(o, ∅) ∗ γo Z⇒ 21 o ∗ P ) ∨ ◦(ε, {o}) ).
ι
isLock(v, P , γ, γo , γn , co , cn ) = ∃`o , `n , ι ∈ InvName. v = (`o , `n ) ∗ lockInv(γ, γo , γn , P )
∗ Cnt(`o , γo , co ) ∗ Cnt(`n , γn , cn )
γ
locked(γ, γo , γn ) = ∃o. ◦(o, ∅) ∗ γo Z⇒ 21 o
γ
issued(γ, n) = ◦(ε, {n})
Compared to the earlier non-modular proof of the ticket lock, the change is that our defini-
tions are now given in terms of the abstract modular counter predicates (rather than relying on
information about the actual implementation of counters).
As in the derivation of the counter with contributions from the modular counter, we use
abstract state predicates γo Z⇒ 12 o and γn Z⇒◦ n to track the values of the owner and the next
counter. The reason for using different fractions for the owner counter and the next counter
is that we want to be able to call wk incr on the owner counter when releasing the lock and
the specification of wk incr requires some fragmental ownership of the corresponding ghost
state in its precondition, and hence we split the non-authoritative ownership of γo into two
halves, one of which we keep in the “basic” part of the invariant and the other of which we
keep in the left side of the disjunction and in the locked predicate. Remember how this left side
corresponds to the lock not being in use at the moment, so having this ghost resource in both
the invariant and the predicate makes sense, since only one of these will ever be true.
13.2.1 wait-loop
The wait method now calls read on the owner counter instead of loading its contents directly,
so we need to utilise the counter module’s read specification now. Apart from this, the proof
follows the same structure as before.
Recall that the intuition in the entire method is that we have a ticket with a specific number
and read the owner counter until its value matches the one on our ticket. When these two
values match, we “hand in” our ticket and actually acquire the lock, which means that we
need to change our state. As long as they do not match, we start over and do not change any
state. This intuition corresponds to the instantiation of P and Q in the counter module’s read
specification:
P = issued(γ, n)
Q =λv.(locked(γ, γo , γn ) ∗ P ∨ issued(γ, n))
Note that Q is the same as the one we chose as intermediate postcondition for our bind rule in
the old proof.
122
This means we need to show the view shift:
Opening the invariant gives us γo Z⇒◦ o for some o. As in the preceding section, we then con-
clude that o = m. As in the old proof, we proceed by casing on whether n = o. If they are not
the same, we choose to show the right side of the disjunction in Q, which we can do by sim-
ply closing the invariant again. If they are the same, however, we need to show that we can
update our abstract state to locked. This is accomplished the same way as in the old proof,
i.e., by concluding that the left side of the disjunction in the invariant must have been true be-
fore and then closing the invariant again with our ticket instead, thereby releasing exactly the
resources corresponding to locked and the lock resources needed for the postcondition of our
specification.
13.2.2 acquire
The proof of the acquire specification is even closer to the old one than wait. The CAS operation
is now replaced by the incr method, so we make use of its specification instead. We instantiate
P = True and Q = λv. issued(γn , v) in the counter module’s incr specification and have to prove
the view shift
Opening the invariant gives us the full non-authoritative permission for γn containing some m0
and, as earlier, we can conclude that m = m0 . Moreover, we get the authoritative part of the
ticket lock resources and can update them as we did in the earlier proof, leaving us with the
issued(γn , m), as needed for the postcondition of the view shift. With this ticket we can now use
our wait specification from above and are done with the proof.
13.2.3 release
Release makes use of the wk incr method, so we need to be able to provide some fragmental
ownership of γo . This is contained in the locked predicate, which is part of our precondition.
Intuitively, it makes sense to use wk incr rather than incr, since only the thread currently hold-
ing the lock is allowed to call release. This is reflected in the formal specifications in the way
that the only way to get the locked predicate is to have succeeded with acquire.
γ
We instantiate P by ◦(o, ∅) ∗ R and Q by γo Z⇒ 12 (m + 1) in the counter module’s wk incr
specification. The R in P are the resources the lock protects and we therefore currently own,
since we have the lock. At this point, we have to prove the view shift
γ
γo Z⇒• m ∗ γo Z⇒ 21 m ∗ ◦(m, ∅) ∗ R VE\{c} γo Z⇒• (m + 1) ∗ γo Z⇒ 12 (m + 1).
Note how m is not universally quantified in this specification. This corresponds to the fact that
we know that no other threads can change the value of the owner counter since we have our
fragmental ownership of the corresponding ghost resource. Opening the invariant will give us
the second half of non-authoritative ownership of γo . Again, we conclude the value it holds
must be the same as m, so we can update γo to contain m + 1. For the ticket lock ghost state
we proceed as we did before in order to update it. We can then close the invariant with the
resources for the left side of the disjunction and are done.
123
13.3 Summary
Above we have presented a higher-order approach to modular specifications of concurrent mod-
ules, where method specifications are parameterized by view shifts expressing (1) how the ab-
stract state of the module changes by calling the method and (2) how client invariants should
be updated when the abstract state of the module changes. We have exemplified the method
by presenting modular specifications of counters and shown how they can be used to verify
modular implementations of ticket locks. In the accompanying Coq examples, you can find
more examples, including a modular specification of the ticket lock and the concurrent bag and
concurrent runner examples from [15].
As mentioned in the introduction to this chapter, our methodology for modular specifica-
tions of concurrent modules is related to the notion of logical atomicity from the TaDa logic.
Indeed, the examples we have presented thus far can also be specified and verified using the
Iris formalization of TaDa-style logical atomicity from [9].
The original presentation of the TaDa approach focuses on atomicity and was aimed at giv-
ing logically atomic specifications to methods that, to a client, appear to be atomic. The higher-
order approach we have presented here focuses on changes to the abstract state and thus also
applies to operations that are not logically atomic. For example, consider the following opera-
tion:
incr twice(`) = incr `; ; incr `
This method does not have a single linearization point, so the fact that we cannot give it a
logically atomic specification should not be a surprise. We can, however, use our higher-order
approach if we use two view shifts, one for each modification of the abstract state. The specifi-
cation looks as follows:
∀γ, P , Q,0 Q, `. ∀n. γ Z⇒• n ∗ P VE\{c} γ Z⇒• (n + 1) ∗ Q0 (n) ⇒
∀n. γ Z⇒• n ∗ (∃m. Q0 (m)) VE\{c} γ Z⇒• (n + 1) ∗ Q(n) ⇒
{Cnt(`, γ, c) ∗ P } incr twice(`) {r. Cnt(`, γ, c) ∗ Q(r)}
This said, one can use a modification of the Iris formalization of TaDa-style logically atomic
triples to give a specification of incr twice. Indeed, the two approaches are essentially equiva-
lent.
124
minimal connection between the operational semantics of the program and its logical prop-
erties. It will turn out that, especially when using the Iris logic in the Coq proof assistant, it
is often easier and more direct to use the weakest precondition assertion instead of (derived)
Hoare triples.
Without further hesitation here is the typing rule for the new assertion.
In the same way that we write v.Q in postconditions of Hoare triples we will write v.Q instead
of λv.Q in wpE e {v.Q}. As evident in the typing rule, in the assertion wpE e {Φ} e is a closed
term, Φ is an assertion, and E is the set of invariant names, playing the same role as it does in
Hoare triples.
Remark 14.1. One can think of the mapping Φ 7→ wpE e {Φ} as the semantics of the term cap-
turing those aspects of the behaviour we care about, e.g., safety. In this way it embeds the terms
of the programming language into assertions of the logic.
The intended meaning of the weakest precondition becomes clearer when we define Hoare
triples in terms of it as
{P } e {Φ}E , P −∗ wpE e {Φ} .
Thus, wpE e {Φ} is indeed the weakest (i.e., implied by any other) precondition such that e runs
safely and if it terminates with a value v, the assertion Φ(v) holds. Further, the use of the
modality is crucial. Indeed, without it the Hoare triple assertion would not be duplicable,
which would mean that we could not use the specification of the method we proved more than
once. Using the modality guarantees that all the non-persistent resources required by e are
contained in P , i.e., that all exclusive resources e needs to run safely are in P .
This is consistent with the reading of Hoare triples explained in Section 8.2, where we ex-
plained that the resource required to run e are either in the precondition P , or owned by invari-
ants, and invariants are persistent assertions.
The basic rules of this new assertion are listed in Figure 10 on page 127. The first part of
the figure are basic structural rules. The rule wp-mono is analogous to the rule of consequence
for Hoare triples, whereas the rule wp-frame is analogous to the frame rule Ht-frame, and the
rule wp-frame-step is analogous to the rule Ht-frame-atomic. In fact, these rules for weakest
precondition are used to derive the corresponding rules for Hoare triples. Next we have the
expected rule wp-val, and the important rule wp-bind which, analogously to the rule Ht-bind,
allows one to deconstruct the term into an evaluation context and a basic term for which we
can use one of the basic rules for the weakest precondition assertion.
The rules for basic language constructs are stated in a style akin to the continuation passing
style of programs, with an arbitrary postcondition Φ. This style allows for easy symbolic ex-
ecution of programs, and circumvents the constant use of the rules wp-mono and wp-frame. To
see why this is so let us look at an alternative formulation of wp-alloc. This formulation is much
closer to the Hoare triple rule Ht-alloc.
.(` 7→ v) ` wp ! ` {u.u = v ∗ ` 7→ v}
125
is equivalent to the rule wp-load.
First we derive wp-load from wp-load-direct. Assuming wp-load-direct we have by wp-frame-
step
where in the last step we used the rule wp-load with Φ(u) being u = v ∗ ` 7→ v.
Exercise 14.3. Suppose we only had Hoare triples as a primitive in the logic, and we did not
have the weakest precondition assertion. It turns out we can define, in the logic, an assertion
wpE e {v.Q} which satisfies the rules in Figure 10 as follows.
• Show that {P } e {Φ}E ∗ P entails wpE e {Φ}, i.e., if the Hoare triple {P } e {Φ}E holds then the
precondition P implies wpE e {Φ}, i.e., show the following entailment
• Show the rules in Figure 10 for wpE e {Φ} as defined here from the rules for Hoare triples
described in the preceding sections. ♦
This exercise shows that the notions of Hoare triples and weakest preconditions are, at least
with respect to the rules in Figure 10 and analogous rules for Hoare triples, essentially equiva-
lent. The weakest precondition is the more minimal of the two, however, since it factors out the
precondition. Further, we shall see in the next sections that some of the interactions with in-
variants can be more easily stated for the weakest precondition assertion. This leads to smaller,
more manageable, and principal rules.
Finally, notice that the rules in Figure 10 do not support working with invariants. Opening
and closing of invariants is an operation that is of independent interest, e.g., the ability to open
and close invariants independently of Hoare triples is needed to define the concept of logically
atomic triples16 , so it should not be tied to Hoare triples or the weakest precondition assertion
directly. To support it we introduce a new concept, the fancy update modality.
16 Logically atomic triples allow some reasoning principles, such as opening of invariants, also around programs
which are “logically atomic”, e.g., they use locks, but are not atomic in the sense that they evaluate to a value in a single
execution step.
126
Structural rules.
wp-mono wp-frame
wp-val wp-bind
wp-frame-step
e < Val n o
Φ(v) ` wpE v {Φ} wpE e v. wpE K[ v ] {Φ} ` wpE K[ e ] {Φ}
. P ∗ wpE e {Φ} ` wpE e {P ∗ Φ}
. Φ() ∗ . wpE e {v. True} ` wpE fork {e} {Φ} .(∀`. ` 7→ v −∗ Φ(`)) ` wpE ref (v) {Φ}
wp-load wp-store
.(` 7→ v) ∗ .(` 7→ v −∗ Φ(v)) ` wpE ! ` {Φ} .(` 7→ v) ∗ .(` 7→ w −∗ Φ()) ` wpE (` ← w) {Φ}
wp-CAS-suc
wp-CAS-fail
. wpE e[v/x][(rec f x := e)/f ] {Φ} ` wpE (rec f x := e)v {Φ} . wpE vi {Φ} ` wpE πi (v1 , v2 ) {Φ}
wp-if-true wp-if-false
. wpE e1 {Φ} ` wpE if true then e1 else e2 {Φ} . wpE e2 {Φ} ` wpE if false then e1 else e2 {Φ}
wp-match
. wpE ei [u/xi ] {Φ} ` wpE match inji u with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {Φ}
127
14.2 Fancy update modality
The fancy update modality allows us to get resources out of knowledge that an invariant exists,
ι
i.e., to get P from P , and to put resources back into an invariant, i.e., to close the invariant. As
we explained in Section 8.2 invariants are persistent, in particular duplicable. Thus we cannot
ι ι
simply get resources out of invariants in the sense of the rule P ` P or P ` . P ; this would
lead to inconsistency. We need to keep track of the fact that we were allowed to open this
particular invariant, and that we are not allowed to open this particular invariant again until
we have closed it. Thus, the rule for opening invariants will be
ι∈E
ι
P ` EV
| E\{ι} . P
where E1 V| E2 is the fancy update modality, and E1 and E2 are masks, i.e., sets of invariant names
(cf. Section 8.2).
The intuition behind the modality E1 V | E2 P is that it contains resources r which, together with
resources in invariants named E1 , can be updated (via frame preserving update) to resources
which can be split into resources satisfying P and resources in invariants named E2 . Thus in
particular the fancy update modality subsumes the update modality V | introduced in Section 8,
in the sense that V | E P , i.e., if the set of invariant names available does not change. The
| P ` EV
rules for the fancy update modality are listed in Figure 11. We describe the rules now, apart
from the rule Fup-timeless, which we describe in the next section, when we introduce the notion
of timelessness.
Inv-open
ι∈E
ι
| E\{ι} . P ∗ . P −∗ E\{ι}V
P ` EV | E True
Introduction and structural rules of the fancy update modality The following rules are
analogous to the rules for the update modality introduced in Section 8.2.
Fup-mono Fup-intro-mask Fup-trans
P `Q E2 ⊆ E 1
| E2 P
E1 V | E2 Q
` E1 V P ` E1 V | E1 P
| E2 E2 V | E3 P
| E2 E2 V
E1 V | E3 P
` E1 V
The rule Fup-intro-mask is perhaps a bit surprising since it introduces two instances of the fancy
update modality, with swapped masks. This generality is useful since, in general, we do not
128
have P ` E1 V
| E2 P . Indeed, if, for example, P ` ∅V
| {ι} P was provable it would mean that any
resource in P could be split into a resource satisfying the invariant named ι, and a resource
satisfying P . This cannot hold in general, of course. However, using Fup-trans together with
Fup-intro-mask, we can derive the following introduction rule where the masks are the same:
Fup-intro
P ` EV
| EP
We will write V| E P for E V
| EP.
Next we have a rule relating the modality with separating conjunction, analogous to upd-
frame, but in addition to framing of resources, we can also frame on additional invariant names
Ef .
Fup-frame
Ef disjoint from E1 ∪ E2
Q ∗ E1 V | E2 ]Ef (Q ∗ P )
| E2 P ` E1 ]Ef V
The rule perhaps looks daunting. The following derived rules are perhaps more natural, since
they only manipulate a single concept (either the frame, or the masks) at a time.
E1 ⊆ E 2
Q ∗ E1 V | E2 (Q ∗ P )
| E2 P ` E1 V | E1 P ` V
V | E2 P
| P `V
V | EP
Note that in combination with the previous rules for | E2 ,
E1 V the rules Ghost-alloc and Ghost-
update remain valid if we replace V
| with V
| E , for any mask E.
Fancy update modality and invariants Finally, we have rules for allocation and opening of
invariants:
Inv-alloc Inv-open
E1 infinite ι∈E
ι ι
. P ` E2 V
| E2 ∃ι ∈ E1 . P P ` EV
| E\{ι} . P ∗ . P −∗ E\{ι}V
| E True
The allocation rule should not be surprising, perhaps apart from the two different sets of in-
variant names. An intuitive reason for why the two sets E1 and E2 of invariant names are not
required to be related is that we only allocate a new invariant – the mask E2 has to do with
opening and closing of invariants, as can be seen in Inv-open.
An equivalent rule to Inv-alloc is the following
Inv-alloc-empty
E1 infinite
ι
. P ` ∅V
| ∅ ∃ι ∈ E1 . P
129
Exercise 14.5. Derive Inv-alloc from Inv-alloc-empty. ♦
The rule Inv-open is used not just to open invariants, but also to close them. It implies the
following two rules
ι∈E ι∈E
ι ι
P ` EV
| E\{ι} . P P ` EV
| E\{ι} . P −∗ E\{ι}V
| E True
The first one of which is the pure invariant opening rule. It states that we can get resources
out of an invariant, but only later. Removing the later from the rule would be unsound. The
second rule is the invariant closing rule. It shows how resources can be transferred back into
invariants. The crucial parts in this rule are the invariant masks. In particular, the assertion
| E True is not equivalent to True. It contains only those resources which can be combined
E\{ι}V
with resources in invariants named E \ {ι} to get resources in invariants named E, i.e., it contains
the resources in the invariant named ι.
Exercise 14.6. Show the following property of the fancy update modality.
| E2 (P
E1 V −∗ Q) ` P −∗ E1 V
| E2 Q
♦
wp-atomic
wp-vup e is an atomic expression
n o
| E wpE e {v.|VE Φ(v)} ` wpE e {Φ}
V | E2 wpE2 e v. E2 V
E1 V | E1 Φ(v) ` wpE1 e {Φ}
wp-frame-step
e < Val E2 ⊆ E 1
| E2 . E2 V
E1 V | E1 P ∗ wpE2 e {Φ} ` wpE1 e {P ∗ Φ}
Figure 12: Rules connecting fancy view shifts to the weakest precondition assertion.
and inside the weakest precondition assertion. This is important because in general we do not
have V| E P ` P , and so proving V
| E P is weaker than proving P . The rule wp-vup states that this
is not the case for the weakest precondition assertion. We can use this rule to, for example, do
frame preserving updates inside the weakest precondition assertion.
Exercise 14.7. Derive the following rule.
a b
n γo n γo
wpE e v.Φ(v) ∗ a ` wp e v.Φ(v) ∗ b
♦
130
Next is the rule wp-atomic, which is similar to the rule Ht-inv-open. It is crucial here that e is
an atomic expression. If it was not then a similar counterexample as the one for the rule Ht-inv-
open, which is explained in Example 8.6, would apply, and the weakest precondition assertion
would not be sound for the operational semantics of the language. The rule is very general, so
let us see how it allows us to recover some rules for working with invariants.
Example 14.8. Let E be a set of invariant names and ι ∈ E and e an atomic expression. We derive
the following rule for accessing invariants using the weakest precondition assertion.
wp-inv-open
e is an atomic expression
ι
I ∗ . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)} ` wpE e {Φ}
We have
ι
I ∗ . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)} ` E V
| E\{ι} . I ∗ . I −∗ E\{ι}V
| E True ∗ . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)}
(Inv-open)
| E\{ι} . I ∗ . I −∗ E\{ι}V
` EV | E True ∗ . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)}
(Fup-frame)
| E\{ι} . I −∗ E\{ι}V
` EV | E True ∗ wpE\{ι} e {v. . I ∗ Φ(v)} (−∗E)
n o
| E True ∗ (. I ∗ Φ(v))
| E\{ι} wpE\{ι} e v. . I −∗ E\{ι}V
` EV
(wp-frame)
n o
| E\{ι} wpE\{ι} e v. E\{ι}V
` EV | E True ∗ Φ(v) (wp-mono and −∗E)
n o
| E\{ι} wpE\{ι} e v. E\{ι}V
` EV | E True ∗ Φ(v) (Fup-frame)
n o
| E\{ι} wpE\{ι} e v. E\{ι}V
` EV | E Φ(v) (Fup-mono)
` wp e {Φ} (wp-atomic)
The rule derived in the preceding example can be strengthened somewhat.
Exercise 14.9. Derive the following rules.
ι
|
V
E E I ∗ . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)} ` wpE e {Φ} (55)
ι
EV| E I ∗ . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)} ` wpE e {Φ} (56)
ι
I ∗ EV | E . I −∗ wpE\{ι} e {v. . I ∗ Φ(v)} ` wpE e {Φ} (57)
ι
I ∗ . I −∗ E\{ι}V | E\{ι} wpE\{ι} e {v. . I ∗ Φ(v)} ` wpE e {Φ} (58)
These rules perhaps look rather strange, however they show that as long as we are proving
a weakest precondition we can most of the time strengthen the assumptions by removing the
fancy update modalities, provided the masks match. The rules are thus quite crucial in concrete
proofs, and are often used implicitly. In particular when Iris is used in Coq via the interactive
proof mode (see Section 15) these, and related rules, are used by the tactics behind the scenes.
♦
As we mentioned above the rule wp-atomic is similar to the rule Ht-inv-open. In fact, the
latter is derivable from the rule we derived in Example 14.8, as we now demonstrate.
131
Example 14.10 (Derivation of Ht-inv-open from wp-inv-open). Let e be an atomic expression. We
are to show
Ht-inv-open
ι
S ∧ I ` {. I ∗ P } e {v. . I ∗ Φ(v)}E\{ι}
ι
S ∧ I ` {P } e {Φ}E
recalling that we have defined {P } e {Φ}E as P −∗ wpE e {Φ} . Let us show it. Since invariants
and Hoare triples are persistent we have
ι ι ι
S ∧ I ` (S ∧ I ) ∧ I
ι
` ({. I ∗ P } e {v. . I ∗ Φ(v)}E\{ι} ) ∧ I
ι
` {. I ∗ P } e {v. . I ∗ Φ(v)}E\{ι} ∗ I
wpE e {Φ}
132
Fancy view shifts Finally, we define the fancy view shift P E1 VE2 Q from the fancy update
modality as
P E1 VE2 Q , (P −∗ E1 V
| E2 Q).
If E1 = E2 we write P VE1 Q for P E VE Q. This concept is analogous to how view shifts are
1 1
defined from the update modality in Section 8. The concept makes it easier to state some of the
(derived) rules involving Hoare triples.
Exercise 14.12. Derive the following rules for the fancy view shift.
•
Fvs-refl Fvs-trans
S `P E1 VE2 Q S `Q E2 VE3 R
· ` P VE1 P S `P E1 VE3 R
•
Fvs-imp Fvs-wand
S ` (P ⇒ Q) S ` (P −∗ Q)
S ` P VE Q S ` P VE Q
•
Fvs-frame Fvs-mask-frame
S `P E1 VE2 Q
S `P E1 VE2 Q (E1 ∪ E2 ) ∩ Ef = ∅
S ` P ∗R E1 V E2 Q∗R S ` P ∗R E1 ]Ef VE2 ]Ef Q∗R
•
Fvs-timeless
` P timeless
· ` . P VE P
•
Fvs-alloc-I Fvs-open-I
E infinite
ι ι
· ` .P ∅V∅ ∃ι ∈ E. P P ` True {ι}V∅ .P
Hoare triples and fancy view shifts With the new concepts we can present the final general-
isation of the rules for Hoare triples. The most general rule of consequence we consider is the
following
Ht-csq-fvs
P0 S ` P 0 e v. Q0 E S ` ∀u. Q0 [u/v] E VE Q[u/v]
S persistent S `P E VE
S ` {P } e {v. Q}E
133
Exercise 14.13. Derive the above rule of consequence. ♦
The next rule is a generalisation of Ht-frame-atomic.
Ht-frame-step
e < Val S ` {P } e {v.Q}E2 S ` R1 E1 VE2 . R2 S ` R2 E2 VE1 R3 E2 ⊆ E 1
S ` {P ∗ R1 } e {v.Q ∗ R3 }E1
It allows us to remove the later modality from the frame in cases where the term e is not a value.
The side-condition in the rule corresponds to the side-condition in the rule wp-frame-step.
Exercise 14.14. Derive the rule Ht-frame-step from the rule wp-frame-step. ♦
Example 14.15 (Improved Specfication for the Spin Lock). In this example we will show how
to use the fancy update modality to give a better specification of the spin lock module from
Section 8.6.
First, recall the specification we gave earlier for the spin lock module:
Notice that the resource invariant, the predicate P , is in the precondition for the newLock
method. This means that that a client of the lock module must allocate the resources, which
the lock is going to protect, before calling the newLock method. For example, we cannot use
the above specification to verify safety of the following simple client, where the intention, of
course, is that the lock should protect the reference r.
The problem is that when newLock is called, reference r is not in scope and thus we cannot
instantiate P with our intended resource invariant ∃n.r 7→ n when attempting to verify the call
to newLock. Thus with the specification given above, we are forced to rewrite the client code as
follows:
C2 = let r := ref (0) in let l := newLock() in acquire l; r ← ! r + 1; release l
so that the resource (here the reference r) is allocated before newLock is called. In general this
is undesirable since the two programs are completely equivalent, and thus the logic should not
force us to make trivial changes to the program in order to verify them.
Let us instead consider the following specification of the newLock method:
This specification expresses that we can always call newLock (since the precondition is True)
and, moreover, that when newLock returns, we get the assertion
| ∅ isLock(v, P , γ).
∀P . P −∗ V (60)
134
The idea is that we can use this assertion when we know what the resource invariant P should
be instantiated with. Then, when we have the resources P , we can use the assertion (60) to
obtain V
| ∅ isLock(v, P , γ), which is equivalent to isLock(v, P , γ) if it appears in the pre- or post-
condition of a Hoare triple, i.e., the following inference rules are valid.
Exercise 14.16. Derive the preceding two rules from the rule of consequence. ♦
Remark 14.17. Notice that the P −∗ V
| ∅ isLock(v, P , γ) is almost a fancy view shift, except for the
missing modality. The specification with a fancy view shift, i.e.,
would be unsound in the sense that isLock(v, P , γ) would not protect the resources as explained
in the following exercise. It would allow us to conjure up many different independent locks
protecting the same resource, meaning none of the locks would actually protect the resource.
let v := newLock() in
let ` := ref (0) in
acquire v;
release v;
acquire v;
let n := ! ` in release v; n
Assuming (61) as the specification for newLock show the following specification.
The specifications of the acquire and release methods stay the same.
Exercise 14.19.
1. Use the new lock module specification to verify the safety of the first client program, by
showing the following specification:
{True} C1 {True}
2. Prove that the newLock method for the spin lock implementation in Section 8.6 satisfies
the specification in (59). ♦
135
14.4 Timeless propositions
We have already mentioned timeless propositions in the previous section. One of the rules for
the fancy update modality is the rule Fup-timeless
Fup-timeless
` P timeless
. P ` EV
| EP
which allows us to remove a later provided the proposition P is timeless, and the conclusion is
under the fancy update modality. Note that if we wanted . P ` P for timeless propositions then
the only timeless proposition would be True. This follows from the Löb induction principle.
Now, what exactly is a timeless proposition? Recall the intuition behind the later modality.
The proposition . P holds if P holds in the future. Now, some propositions do not depend on
time. For example, if n and m are natural numbers then n = m is either always true, or always
false. These are the propositions which we call timeless. The technical definition is as follows.
Definition 14.20. A proposition P is timeless if the following entailment holds
. P ` P ∨ . False
We write
` P timeless
for the judgement stating that P is timeless, or
Γ ` P timeless
if the variable context Γ is important.
There is a perhaps curious . False appearing in the definition. In order to have the power-
ful Löb induction rule we must have that if . P ` P , then P is necessarily equivalent to True.
Semantically, this means there has to be a “final time”, where there is no future. In order for
. P to be well-defined it must be that . P holds in this final world. The proposition . False is a
proposition which holds exactly at this final time, but does not hold otherwise.
All ordinary propositions are timeless. By ordinary propositions we mean such things as
equality on all base types apart from Prop, basic relations such as ≤ or ≥ on natural numbers
and so on. Moreover being timeless is preserved by almost all the constructs of logic, as stated
in Figure 13. A general guiding principle we can discern from these rules is that if a predicate
does not involve a later or update modality, or an arbitrary predicate P , then it is timeless.
Properties of timeless propositions In the examples in the preceding sections we have seen
that opening invariants leads to some complications with the later modality. When opening the
ι
invariant P we only get the proposition . P in the precondition of the Hoare triple, as opposed
to P . We worked around this by using Ht-frame-atomic together with stronger rules for Hoare
triples from Section 6.1, but this is often quite inconvenient, especially since we often need to
remove . from propositions which, intuitively, do not depend on time.
The essence of why timelessness is a useful property is captured by the following rule, which
relates timeless propositions to Hoare triples.
Ht-timeless-pre-post
` P1 timeless ` Q1 timeless {P1 ∗ P2 } e {v. . Q1 ∗ Q2 }E
{. P1 ∗ P2 } e {v.Q1 ∗ Q2 }E
136
` P timeless ` Q timeless
` True timeless ` False timeless ` P ∨ Q timeless
Γ , x : τ ` Φ timeless ` P timeless
Γ ` ∃x. Φ timeless ` P timeless
The rule states that we can remove one . from pre- and postconditions provided the proposi-
tions are timeless. Note that there is no restriction on the expression e being atomic, as there
is in Ht-frame-atomic. However Ht-timeless-pre-post is in general incomparable with Ht-frame-
atomic since the latter applies to arbitrary frames P , not just to timeless ones.
Exercise 14.21. Derive the rule Ht-timeless-pre-post from the generalized rule of consequence
involving the fancy view shifts introduced in the previous section. ♦
Exercise 14.22. Derive the following rules for timeless propositions.
Ht-timeless-pre Ht-timeless-post
` P1 timeless {P1 ∗ P2 } e {v.Q}E ` Q1 timeless {P } e {v. . Q1 ∗ Q2 }E
{. P1 ∗ P2 } e {v.Q}E {P } e {v.Q1 ∗ Q2 }E
♦
In the examples we have done thus far the new rules would not help to reduce complexity
noticeably. Later on, however, we will see that it is crucial for examples involving complex ghost
state and invariants. Moreover, when using Iris in Coq it simplifies its use significantly, since
tactics can automatically derive that propositions are timeless, and thus automatically remove
the later modality in many places. The reason it was not needed until now is that we have strong
rules for Hoare triples in the sense that, for basic stateful operations, it suffices to have .(` 7→ v)
in the precondition. Typically, when using invariants we will get such an assertion after opening
an invariant. But when we use more complex ghost state, then we shall get propositions of the
γ
form . a in the precondition. Using such is difficult without timelessness.
A conceptual reason for why timelessness is a useful and needed concept is the following.
Iris supports nested and higher-order invariants. For this reason it is crucial, as we shall see
later on, that when opening an invariant we do not get access to the resources now, but only
137
later, i.e., opening an invariant gives us . P in the precondition. However this is only needed
if P refers to other invariants, or is a higher-order predicate. For first order-predicates, which
do not refer to other invariants, it is safe to get the resources immediately. Using the notion of
timelessness, we can recover some of the convenience of logics which support only first-order,
predicative invariants, but retain the ability to form and use higher-order invariants.
Exercise 14.23. Derive the following rules for invariant opening. We assume E is a set of in-
variant names and ι ∈ E.
wp-inv-timeless
e is an atomic expression ` I timeless ι∈E
ι
I ∗ I −∗ wpE\{ι} e {v.I ∗ Φ(v)} ` wpE e {Φ}
Ht-inv-timeless
ι
e is an atomic expression ` I timeless ι∈E S ∧ I ` {I ∗ P } e {v.I ∗ Q}E\{ι}
ι
S ∧ I ` {P } e {v.Q}E
♦
The exercise establishes some properties of timeless assertions which are used implicitly
when the Iris logic is used in the Coq proof assistant.
Exercise 14.24. Assuming P is timeless derive the following rules.
Q∗P `V
| ER Q ∗ P ` wpE e {Φ}
♦
Q ∗ .P ` V
| ER Q ∗ . P ` wpE e {Φ}
Then to use invariants I1 and I2 at the same time we need to know ι1 , ι2 ∈ E and that ι1 , ι2 . The
reason we need to know the last inequality is that after opening the first invariant we need to
prove
ι1 ι2
P1 ∧ P2 ` {. I1 ∗ P } e {v. . I1 ∗ Φ(v)}E\{ι1 }
and thus if ι1 and ι2 were the same, then we could not open them again. So how can we know
that ι1 , ι2 ? The only way we can guarantee it is by using suitable sets of invariant names when
allocating invariants. Recall (one variant of) the invariant allocation rule
Inv-alloc
E1 infinite
ι
.P ` V
| ∅ ∃ι ∈ E1 . P
We can choose an infinite set of invariant names from which ι is drawn. Hence we can use
different sets in different parts of the proof in order to guarantee name inequalities. Invariant
namespaces are used to denote these infinite sets of invariant names.
138
There are different ways to encode them, but one way to think of them is to think of invari-
ant names as strings. An invariant namespace is then also a string N , but it denotes the set of
all strings whose prefix is N . We write this set as N ↑ . With this encoding, if N is a namespace,
then, say, N .lock and N .counter are two other namespaces, and, importantly, they denote dis-
joint sets of invariant names, i.e., the sets (N .lock)↑ and (N .counter)↑ are disjoint. To make use
of namespaces we define some abbreviations. We define17
N ι
P , ∃ι ∈ N ↑ . P .
for any chosen namespace N . Other rules for working with invariants need to change slightly
N
to use P . The rule for opening invariants becomes
Inv-open-namespace
N↑ ⊆E
N
P | E\N ↑ . P ∗ . P −∗ E\N ↑ V
` EV | E True
and the rule for opening invariants in connection with the weakest precondition assertion is
thus
wp-inv-open-namespace
e is an atomic expression N↑ ⊆E
N
P ∗ . I −∗ wpE\N ↑ e {v. . I ∗ Φ(v)} ` wpE e {Φ}
Unfortunately, with the rules we have presented thus far we cannot derive Inv-open-namespace
from Inv-open. We will be able to do this once we define the fancy update modality in terms of
other connectives, but for now we remark that the rule is sound, and derivable from a general
property of the fancy update modality stated in the following exercise.
Exercise 14.25. Assume the following property of the fancy update modality, for any masks
E1 , E2 , and Ef such that E1 is disjoint from Ef .
|
V
E1 E2 P ∗ (Q −∗ |
V
E2 E1 R) ` |
V
E1 ∪Ef E2 P ∗ (Q −∗ |
V
E2 E1 ∪Ef R) .
139
both of them at the same time. We do not need to keep track of additional name inequalities
elsewhere in our context.
Invariant namespaces are well supported in the Iris proof mode in Coq. Hence, most of the
time, when working with invariants, the side-conditions on masks are discharged automatically
in the background. This simplifies proofs and enables the user to focus on the interesting parts
of the verification.
Interactive proof mode Interactive proof mode is a set of tactics to manipulate judgements
S ` Q. For various reasons it has proved useful to split the context S into three parts. The
first part are the pure facts, such as equality of values, comparison of natural numbers, etc., the
second part are the persistent Iris assertions, and the last part are general Iris assertions. So to
be more precise, the interactive proof mode tactics manipulate such judgements and the tactics
are aware, for instance, that the assertions in the persistent context can be duplicated. Let us
see how this looks on an example proof. We are proving
P ∗ Q ` (P ∗ Q) ∗ P .
140
Ignoring Σ, which has to do with specifying which resource algebras are available, this is an
ordinary Coq goal. The assumptions are that P and Q are Iris propositions, and the goal is to
prove the entailment. Note that the ` is replaced with −∗, for reasons which are not important.
It is simply different notation for the same thing.
We then enter the proof mode at which point our goal looks as follows.
The Coq context, above the double line, stays the same, but the goal is different. It consists
of two contexts, and a conclusion. The first context contains one assumption P , named HP.
Assumptions are named so that they can be referred to by tactics, analogously how assump-
tions are named in ordinary Coq proofs, except that for engineering reasons names of Iris as-
sumptions need to be quoted as strings. This is the context of persistent assumptions. Every
assumption in this context implicitly has an modality around it.
The second context also contains one assumption, Q, and the assumption has name HQ. This
is a context of arbitrary Iris assertions.
To prove the goal, if we were using the rules of the logic directly, we would duplicate the
assumption P , and then use the separating conjunction introduction rule. There is a tactic
which corresponds to the separating conjunction introduction rule,20 and the tactic knows that
persistent assertions can be duplicated. Thus using this tactic we get the following two goals.
Notice how in the first goal we have assertions P and Q available, whereas in the second we
only have P available, since Q is not persistent.
In the accompanying Coq example files we explain how to use the tactics and manipulate
contexts to achieve this.
20 There are in fact two, isSplitL, and iSplitR.
141
Hoare triples in Iris Coq One point of difference of the Iris logic in Coq as opposed to the one
presented in this paper is the definition of Hoare triples. Recall that we defined Hoare triples
as
{P } e {Φ} , (P −∗ wp e {Φ}) .
In Coq they are defined slightly differently, using the similar mode of use of weakest precondi-
tion specifications with an arbitrary postcondition. To wit, they are defined as
{P } e {Φ}E , ∀Ψ , P −∗ .(∀v, Φ(v) −∗ Ψ (v)) −∗ wpE e {Ψ } .
If there was no later modality the two definitions would be rather trivially equivalent. The
reason for introducing the later modality is technical, and it is there purely for reasons of con-
venience.
Exercise 15.1. Show that for expressions e which are not values the two definitions are logically
equivalent. ♦
Thus, the only place where they differ slightly is for values. But since these triples are in
practice never used for values, they are only used for top-level specifications, it does not matter.
Preview We have already seen two ways of defining predicates on τ, which we now call to
mind. First, recall the isList predicate defined in Section 4. There we explained that isList l xs
relates a programming language value l to a mathematical sequence of values xs, i.e., isList
is of type Val → Seq(Val) → Prop, and that it was defined by induction on the mathematical
sequence xs. Observe: Seq(Val) is an Iris type of standard mathematical sequences and we use
that to define a new predicate isList. Now, what if we do not care about which mathematical
sequence l represents, but just want to express that l is a linked list representing some unknown
sequence of elements ? Then we can, of course, define a new predicate MList : Val → Prop by
setting MList l = ∃xs. isList l xs. But we could also define a predicate by guarded recursion by
letting
GList = µϕ : Val → Prop. λl.l = inj1 () ∨ ∃hd, x, l 0 . l = inj2 (hd) ∗ hd 7→ (x, l 0 ) ∗ . ϕ(l 0 ).
21 Formal proofs in Iris in Coq can be found in the accompanying coq file fixpoint.v.
142
Note that GList is well-defined since ϕ (only) occurs under a later . modality. Intuitively,
GList(l) holds if either l is the empty list or if l points to a pair with a head and tail element
such that Glist holds for the tail element later.
Since all of our linked-list operations in Section 4 always take a step to access the tail of a
linked list, this definition would also allow us to prove safety of the linked list operations.
Exercise 16.1. Use GList to prove the following Hoare triple that expresses that the increment
function from Section 4 is safe:
Use Löb induction and take care to note how we get to remove the later modality we get from
the definition of GList. ♦
Now take a closer look at the definition of GList and rewrite it as follows. First, define
function F : (V al → Prop) → (Val → Prop) by
Then GList = µϕ. F(. ϕ). But since ϕ only occurs positively in F(ϕ), the function F : (V al →
Prop) → (Val → Prop) is in fact a monotone function and hence it has a least and a greatest fixed
point — we will define what monotonicity means and prove that such least and greatest fixed
points do indeed exist below. The least fixed point of F is an inductive predicate; indeed, by
an inductive predicate we mean a predicate defined as the least fixed point of a monotone function.
Likewise, the greatest fixed point of F is a coinductive predicate; and, indeed, by an coinductive
predicate we mean a predicate defined as the greatest fixed point of a monotone function.
Thus we could also have defined a predicate IList as the least fixed point of F and a pred-
icate CoIList as the greatest fixed point of F. Then we would have three candidate formal
definitions of predicates corresponding to the intuitive linked list predicate. In this particular
case, all three predicates, the inductive IList and the coinductive CoIList predicates coincide
(in the sense that IList a` CoIList) and is closely related to the guarded-recursive GList pred-
icate (in the sense that CoIList ` GList and if True ` GList then True ` CoIList). This is not
entirely trivial to see, but can be proved using the model of Iris; it rests on the fact that the
programming language values and the heap are finite, and that the definition of F does not
allow for cycles in the heap because of the use of ∗. However, in general, inductive, coinduc-
tive and guarded recursive predicates (obtained from the same monotone operator) are not the
same; we will present an example that illustrates the differences in the following. We will do
so by working entirely in the Iris logic, i.e., without having to understand the semantics of Iris.
Before we begin on the more formal treatment, however, we hasten to point out one key differ-
ence between guarded-recursive predicates and inductive / coinductive predicates: to define a
guarded-recursive predicate, we do not need to require that the induced function F is mono-
tone; as long as the recursion variable (the variable ϕ above) occurs under a later . modality,
then the predicate is well-defined. We make use of this flexibility in Section 18 to define a log-
ical relations interpretation of recursive types. (For a program verification example that relies
on this flexibility, see the event loop example in the iCap logic [16] – iCap is a precursor to Iris.)
143
points. Consider the special case where L is the powerset of some given set X, i.e., L = X →T Prop,
and the ordering ≤ is the subset ordering. In this case the least fixed point, e.g., is given by {ϕ |
∀x. f ϕx ⇒ ϕx}. In higher-order logic, one can prove that the higher-order logic rendition of this
formula, i.e., ∀ϕ. (∀x. f ϕx ⇒ ϕx) ⇒ ϕ is a fixed point of f : (X → Prop) → (X → Prop) when f is
monotone. Similarly, one can obtain a higher-order logic definition of the greatest fixed point
based on Tarski’s greatest fixed point formula. In our situation, with our Iris resource logic,
we will use the persistence modality to ensure that the notions of monotonicity, prefixed point,
and postfixed point do not depend on any resources.
A More Systematic Account Let τ be an Iris type and let F : (τ → Prop) → (τ → Prop) be an
endofunction on the type of predicates on τ.
Definition 16.2. We say that F is monotone if, for all ϕ, ψ : τ → Prop, it holds that
Definition 16.3. Suppose F : (τ → Prop) → (τ → Prop) is monotone. We then define lfp F : τ →
Prop, gfp F : τ → Prop, and grd F : τ → Prop by
As the names suggest, lfp F is the least fixed point of F, gfp F is the greatest fixed point of F,
and grd F is the guarded recursive predicate defined by F. The latter is by definition, whereas
the least and greatest fixed point properties require proof. We now state the claims precisely
using a series of lemmas, which are instructive to prove. We leave the proofs as exercises to the
reader. As mentioned above, by the inductive predicate defined by F we mean lfp F, and by the
coinductive predicate defined by F we mean gfp F.
Lemma 16.4 (lfp F is a fixed point of F, up to provability).
144
To get a better intuitive understanding of inductive, coinductive, and guarded recursive
predicates, and how they relate, we consider an example, namely reachability in the following
infinite graph G.
T
F
F
F F
F
··
·
F F F F
··
·
F F F
B(2, 2) B(3, 2)
··
·
F F
B(3, 3)
··
·
F
··
·
The graph G is an element of a type of graphs with nodes A or B(n, m) (with m ≤ n) and with
edges labelled with a boolean T or F. We assume that this type of graphs is available in Iris,
just like we assumed that the type of finite mathematical sequences is, see Section 4. We also
assume that the type of nodes is available and denote it by Node. Moreover, we assume that
we have an Iris type of mathematial streams (finite or infinite sequences) available; we write
Stream(B) for the type of streams of booleans B = {T , F}, and we write [] for the empty stream,
and l :: ls for the stream with head l and tail ls.
We will now consider different ways of defining reachability in the graph G. Specifically, we
will define predicates that express that, starting from some specific node, a stream of booleans
is reachable in G. (For example, looking at the picture of G above, since there is a loop from
node A to A labelled T , it is intuitively clear that the constant infinite stream of T ’s is reachable
from A.) Thus we will be interested in predicates on the type τ = Node × Stream(B).
Consider now the following function F : (Node × Stream(B) → Prop) → (Node × Stream(B) →
Prop):
where Edge(G, x, y, l) means that there is an edge from node x to node y in the graph G with
label l.
Lemma 16.9. F is monotone.
145
Since F is monotone, the least and greatest fixed points lfp F and gfp F of F exist, as does the
guarded recursive predicate grd F. Each of these define a notion of reachability.
Lemma 16.10.
The following lemmas express that the inductive notion of reachability only contains finite
paths.
Lemma 16.11. Let ls be a finite sequence of constant T ’s or constant F’s. Then lfp F(A, ls) holds.
Lemma 16.12. Let ls be an infinite sequence of constant T ’s or constant F’s and let n be any node.
Then lfp F(n, ls) does not hold, (i.e., lfp F(n, ls) ` False).
The following lemmas express that the coinductive notion of reachability not only includes
finite paths, but also infinite paths.
Lemma 16.13. Let ls be the infinite sequence of constant T ’s. Then gfp F(A, ls) holds.
Lemma 16.14. Let ls be the infinite sequence of constant F’s and let n be any B node. Then
gfp F(n, ls) does not hold (i.e., gfp F(n, ls) ` False).
Lemma 16.15. Let ls be the infinite sequence of constant F’s and let n be any node. Then gfp F(n, ls)
does not hold (i.e., gfp F(n, ls) ` False).
Intuitively, the following lemma captures that guarded-recursive predicates are “closed un-
der limits”: by Lemmas 16.10 and 16.11 every finite path of constant F’s is in the guarded-
recursive notion of reachability; this is used to prove the following lemma, which says that also
the limit, the infinite sequence of constant F’s, is in the guarded-recursive notion of reachability.
Lemma 16.16. Let ls be the infinite sequence of constant F’s. Then grd F(A, ls) holds.
We finally remark that guarded-recursive predicates are in fact unique (we did not include
a proof rule expressing that in Section 6.2) and that is exactly because their behaviour at limit
points is determined by what happens at finite approximants.
146
Definition 17.1. Given a proposition P we say that P holds for k steps if and only if the follow-
ing holds:
This makes intuitive sense: if P holds under the assumption that False holds after k steps,
then P must hold for k steps. In addition, we have that for a proposition P ,
This again makes intuitive sense: P holds if and only if it holds for arbitrarily many steps. The
only if direction of the above statement (62) is trivial. The if direction, on the other hand, is
not. It follows, rather easily (left as an exercise), from the following:
The statement (63) above, which might be a bit surprising at first sight, is in fact logically
equivalent to Löb induction. Before showing this however, let us ponder the intuitive meaning
of (63). It says that there is exists a number k such that False holds after k many steps. Recall
that Iris is a step-indexed logic where the intuitive meaning of ` P is that for any j, P holds for
j steps. With this intuition in mind, (63) essentially says that for any j the following holds for j
steps: there exists a number k such that after k steps False holds. Since we are considering the
truth of the statement “there exists a number k . . . ” only up to j steps, we can simply take k
to be any number greater than j. Then, by definition, we don’t care about what happens after
k steps because it is already beyond the j many steps that we care about. The crucial point
here is the interpretation of the existential quantifier in our step-indexed logic. It allows us
to pick different k’s for the different number of steps being considered. A deep and thorough
consideration of the intuitive meaning behind the proof of the following theorem should reveal
the intuitive reasoning we just discussed for why (63) holds.
Remark 17.2. Note that the statement (63) holding, i.e., ` ∃k. .k False, does not imply that there
is in fact some number n for which ` .n False holds. The key point here is that the existential
quantifier can be instantiated to be a different natural number depending on the current step-
index. The proposition ` .n False cannot hold (i.e., cannot hold for all step-indices) for any fixed
n because for any fixed n it would not hold for step-indices greater than n.
Theorem 17.3. Statement (63) is equivalent to the principle of Löb induction.
Proof. [(63) is implied by Löb] We need to show ` ∃k. .k False. By Löb induction it suffices to
show that . ∃k. .k False ` ∃k. .k False. Now, we can use the .-∃ rule. Hence, we have to show that
∃k. .k+1 False ` ∃k. .k False. We then eliminate the existential quantifier as the natural number
l which leaves us to show .l+1 False ` ∃k. .k False. We simply take k to be l + 1, or any number
larger than that, to finish the proof.
[(63) implies Löb] We need to show that Q ` P given that Q ∧ . P ` P . Now, by (63) we can
add ∃k. .k False to our hypotheses, since we are assuming it holds. Thus it suffices to show that
Q ∧ ∃k. .k False ` P . We proceed by eliminating the existential quantifier as the natural number
l which leaves us to show Q ∧ .l False ` P . We proceed by induction on l. The base case is trivial:
we have to show that Q∧False ` P . For the inductive case, we can assume (induction hypothesis)
that Q ∧ .l False ` P and have to show Q ∧ .l+1 False ` P . Now, by our starting hypothesis, (the
147
antecedent of Löb) we have that Q ∧ . P ` P . Hence, it suffices to show that Q ∧ .l+1 False ` Q ∧ . P
to conclude the proof. Obviously, Q ∧ .l+1 False ` Q holds. Therefore, we only need to show
Q ∧ .l+1 False ` . P . We proceed by weakening Q by putting it under a later modality. That
is, we need to show . Q ∧ .l+1 False ` . P . And since later commutes with conjunction (later-∧)
we simply need to show .(Q ∧ .l False) ` . P . Finally, the later-mono rule can be applied which
leaves us to show Q ∧ .l False ` P . This is precisely our induction hypothesis (the induction we
performed on l) which concludes the proof. QED
To summarize, what we have seen so far captures, in the formal language of the logic, what
we intuitively think when we think about step-indexing in Iris. The proposition .k False holds
if we are proving things only up to k steps. When we are proving a proposition P in Iris we
are essentially showing that for any k the proposition P holds for k steps (62). And that there
is always a step-index k such that we are proving things only up to k steps (63). Note how
the latter intuition essentially tells us that the reason why Löb induction holds is that we can
perform induction on the step-index; this, i.e., induction on the step-index, is exactly how one
proves the principle of Löb induction sound in the model of Iris [8].
Now, let us consider the following program which is not safe. It crashes, i.e., gets stuck, but
only after two steps of computation.
Since Iris ties physical steps of computation (in the sense of small-step operational semantics)
to logical steps, i.e., the later modality, we should be able to show the following:
n o
` .2 False twoSteps {x. False}
And we can. After applying the rule Ht-beta corresponding to the function application step we
need to show
Now we can use the rule Ht-If for conditionals which leaves us to show
and
Both of which hold vacuously. Note how in this proof the postcondition is irrelevant as the
program never terminates within the two steps of computation that we are considering it.
148
In the words of Reynolds [14], a type system “is a syntactic discipline for enforcing levels of
abstraction”. One of the fundamental properties of a type system is type soundness, i.e., that
“syntactically well-typed programs don’t go wrong” [12]. In contrast to the properties we have
considered earlier in these notes, where we so far have focused on properties of individual pro-
grams, type soundness is a language property: it is a property that depends on the operational
semantics and the type system for the whole programming language and it is a property that
one proves once and for all for a programming language. There are different approaches to
proving type soundness, most notably the syntactic approach based on progress and preserva-
tion lemmas (for textbook treatments, see, e.g., [13, 4]), and the semantic approach where one
gives a semantic model of the types and proves that any syntactically well-typed program is in
the semantic interpretation of its type.
An advantage of the semantic approach is that it gives a semantic account of the invariants
enforced by the syntactic type system. This means that it allows one to combine syntactically
well-typed programs with programs which are semantically, but not necessarily syntactially,
well-typed. This is important in practise since most realistic statically typed programming
languages include some facility for interacting with programs that are not syntactically well
typed (e.g., through foreign function call interfaces, or through an “unsafe” construct []).
An advantage of the syntactic approach using progress and preservation lemmas is that it
scales well to advanced type systems including impredicative polymorphism, recursive types,
and general reference types. In contrast, an often-cited challenge with the semantic approach
is that it is non-trivial to define semantic interpretations of advanced type systems (because the
semantic models need to support recursive definitions and impredicative invariants).
In this section we show that by interpreting types as Iris propositions it is straightforward to
give a semantic interpretation of types, and prove that all syntactically well-typed programs are
in the appropriate semantic interpretation. Moreover, we can also use Iris to show semantic
well-typedness of programs that are not syntactically well-typed.
The key point is that we can exploit Iris features such as invariants, persistence, and the
possibility of defining predicates by guarded recursion to give a simple inductive definition
of the interpretation of all the types of Fµ,ref ,conc , which include challenging types such as im-
predicative polymorpic types, recursive types, and general reference types. We focus on type
soundness in Subsection 18.2.
Another advantage of the semantic approach is that it can be generalized to give a relational
interpretation of types, which is useful for proving contextual refinement and data abstraction
results. It is well-known that relational models for showing contextual refinement are also
non-trivial to construct. Moreover, it is particularly challenging to construct relational models
which are sufficiently powerful to allow one to prove relatedness of programs that use state
and concurrency in very different ways. Using Iris, however, we can addresss both of these
challenges. Indeed, in [17] a relational interpretation of the types of Fµ,ref ,conc is also defined,
by interpreting types as Iris relations. Moreover, it is shown that the relational interpretation
is expressive enough to allow one to prove challenging examples of program refinements. For
example, it is proved that a fine-grained concurrent stack module is a contextual refinement of
a coarse-grained stack module. In light of the fact that we so far only used Iris for reasoning
about a single program at a time, it is perhaps somewhat surprising that we can also use Iris
to relate two different programs. Thus, in Subsection 18.3 we sketch the key idea of how this
can be done. However, we do not include a full description of the relational model, but instead
refer the reader to [17].
149
18.1 The language Fµ,ref ,conc
Fµ,ref ,conc is mostly as λref,conc , the main exception being that it is a typed languages. We have
all the same values and expressions as before with a few additions:
• fold and unfold respectively fold and unfold expressions of recursive types.
• Λ is a type-level lambda abstraction.
Syntax
Val v ::= · · · | fold v | Λ e
Expr e ::= · · · | fold e | unfold e | Λ e | e
ECtx E ::= · · · | fold E | unfold E | E
T ypes τ ::= X | 1 | N | B | τ → τ | ∀X. τ | τ × τ | τ + τ | µX.τ | ref τ
We have the same reductions as λref,conc with the addition of the following two pure reductions:
pure
(Λ e) e
pure
unfold (fold v) v
As the weakest precondition is defined from the operational semantics, we get the same wp-
rules as in Figure 10 with the addition of the following two rules, corresponding to our two new
reductions:
wp-Tlam wp-fold
. wpE e {Φ} ` wpE (Λ e) {Φ} . wpE v {Φ} ` wpE unfold (fold v) {Φ}
To make Fµ,ref ,conc a typed language, we naturally need some typing rules. These are fairly
standard (see [13, 4]) and given in Figure 14.
Our goal is to prove type safety, hence we need a notion of safety. Here we will use the
following definition:
Safety We say a program e is safe, written Safe(e), if it does not get stuck. In more detail, e is
safe if, for all expressions e0 that e or one of the child thread of e has evaluated to, e0 is either
a value or it can be evaluated further by making a head step, or by forking a thread. This is
formally defined as follows:
150
T-var T-Bool T-rec
x:τ ∈Γ T-Unit T-Nat v ∈ {true, false} Ξ | Γ , x : τ, f : τ → τ 0 ` e : τ 0
Ξ|Γ `x:τ Ξ | Γ ` () : 1 Ξ|Γ `n:N Ξ|Γ `v:B Ξ | Γ ` rec f x := e : τ → τ 0
T-if T-pair
Ξ|Γ `e:B Ξ | Γ ` ei : τ i ∈ {1, 2} Ξ | Γ ` e 1 : τ1 Ξ | Γ ` e 2 : τ2
Ξ | Γ ` if e then e1 else e2 : τ Ξ | Γ ` (e1 , e2 ) : τ1 × τ2
T-proj T-inj
Ξ | Γ ` e : τ1 × τ2 i ∈ {1, 2} Ξ | Γ ` e : τi i ∈ {1, 2}
Ξ | Γ ` π i e : τi Ξ | Γ ` inji e : τ1 + τ2
T-match T-fold
Ξ | Γ ` e : τ1 + τ2 Ξ | Γ , x : τi ` ei : τ3 i ∈ {1, 2} Ξ | Γ ` e : τ[µX. τ/X]
Ξ | Γ ` match e with inji x ⇒ ei end : τ3 Ξ | Γ ` fold e : µX. τ
T-store
Ξ | Γ ` e1 : ref (τ) Ξ | Γ ` e2 : τ
Ξ | Γ ` e1 ← e2 : 1
T-CAS
Ξ | Γ ` e1 : ref (τ) Ξ | Γ ` e2 : τ Ξ | Γ ` e3 : τ EqType(τ) EqTyp-unit EqTyp-nat
Ξ | Γ ` CAS(e1 , e2 , e3 ) : B EqType(1) EqType(N)
T-fork
EqTyp-bool EqTyp-ref Ξ|Γ `e:τ
EqType(B) EqType(ref(τ)) Ξ | Γ ` fork {e} : 1
151
the logical relation for a type have well-defined behavior, i.e., they do not get stuck. The type
soundness theorem is a direct consequence of these two facts.
We define the logical relations in three stages. We first define a relation for closed values
of a type by induction on types. We then use the value relations to define relations on closed
expressions. Intuitively, a closed expression is in the relation for a type τ if it is a computation
that results in a value that is in the value relation for the type τ. Finally, we define the logical
relation for (open) expressions based on the expression relations and the value relations above.
Fµ,ref ,conc features polymorphism. Hence, types can have free type variables. Thus, we index
the relations on closed values and expressions with a map, ∆, which assigns a semantic type (a
value relation) to each free type variable. That is, for each type τ we define the value relation
JΞ ` τK∆ : Val → Prop where ∆ : Ξ → Val → Prop. The full definition of the value relations for
types is given in Figure 15. We will discuss them in detail below. Before that we discuss how
value relations are extended to expression relation on closed and subsequently open expres-
sions.
Intuitively, a closed expression is in the expression relation for a type τ if it computes a
result that is in the value relation for the type τ. We define the expression relation, JΞ ` τKE∆ :
Expr → Prop, using weakest preconditions, as follows:
In order to formally define the logical relation for open expressions we first define a relation,
JΞ ` ·KG∆ , for typing contexts. Intuitively, a list a values, #»
v , is in the relation for a typing context,
Γ , if each value in #»v is in the value relation for the type corresponding to it in Γ . The formal
definition of the typing-context relation is given below. Here, ε is the empty list of values.
Let Ξ, Γ , e and τ be such that all free variables of e are in the domain of Γ and all free type
variables that appear in Γ or τ are in Ξ. Then, we write Ξ | Γ e : τ to express that the expression
e is in the logical relation for type τ under the typing contexts Γ and Ξ. This relation is defined
as follows:
Ξ | Γ e : τ , ∀∆, #»
v . JΞ ` Γ KG∆ ( #»
v ) ` JΞ ` τKE∆ (e[ #»
v / #»
x ])
The value relation for types are given in Figure 15. One important aspect of the type system
of Fµ,ref ,conc is that it is intuitionistic, i.e., values, e.g., function arguments, can be used multiple
times. Thus, it is crucial that the value relation of all types are persistent and hence duplicable.
The persistence modality and the side-condition persistent(Ψ ) in Figure 15 are added to ensure
the persistence of value relations.
The value relation for type variables is given by ∆. A value is in the relation for the unit
type if it is (). A values is in the relation for the type of natural numbers if it is simply a natural
number; similarly for booleans. A value is in the relation for the product type if it is a pair of
values each in their respective types. A value of the sum type τ + τ 0 , on the other hand, is either
a value in the relation for τ or one in the relation for τ 0 . The value relation for recursive types
is defined using Iris’s guarded recursive predicates. A value is in the relation for a recursive
type if it is of the form fold w such that the value w is, one step of the computation later, in the
relation for the recursive type. Notice, however, that unfolding a folded value takes a step of
computation. A memory location is in the relation for a reference type, ref(τ), if it invariantly
stores a value that is in the value relation for τ.
152
JΞ ` XK∆ , ∆(X)
JΞ ` 1K∆ (v) , v = ()
JΞ ` NK∆ (v) , ∃n ∈ N. v = n
JΞ ` BK∆ (v) , v ∈ {true, false}
JΞ ` τ1 × τ2 K∆ (v) , ∃v1 , v2 . v = (v1 , v2 ) ∗ JΞ ` τ1 K∆ (v1 ) ∗ JΞ ` τ2 K∆ (v2 )
_
JΞ ` τ1 + τ2 K∆ (v) , ∃w. v = inji w ∗ JΞ ` τi K∆ (w)
i∈{1,2}
JΞ ` τ → τ K∆ (v) , ∀w. JΞ ` τK∆ (w) −∗ JΞ ` τ 0 KE∆ (v w)
0
Figure 15: The unary value relation for types of Fµ,ref ,conc .
A value v in the relation for the function type τ → τ 0 if whenever v is applied to a value w, in
the relation for τ, the resulting expression, v w, is in the expression relation for τ 0 . A value is in
the relation for the type ∀X. τ if, when instantiated, the resulting expression is in the expression
relation for τ where the interpretation for X is taken to be any persistent predicate.
Lemma 18.1. Let τ be a type such that all its free type variables appear in Ξ. Furthermore, let X be
a type variable such that X < Ξ and let ∆ be an interpretation for type variables in Ξ. It follows that
Proof. By induction on the typing derivation. All cases follow from the inference rules of weak-
est preconditions presented in Section 14.1. Here, we present a few cases of this proof.
153
– Case T-alloc: For this case, given ∆ : Ξ → Val → Prop and a list of values #»
v such that
G #»
JΞ ` Γ K∆ ( v ), we need to show assuming
wp e[ #»
v / #»
x ] {JΞ ` τK∆ } (64)
wp ref (e[ #»
v / #»
N .`
x ]) x.∃`. x = ` ∧ ∃w. ` 7→ w ∗ JΞ ` τK∆ (w)
We use the rule wp-bind together with the assumption (64) above. Consequently, we need
to show that given some arbitrary value v such that
we have
N .`
wp ref (v) x.∃`. x = ` ∧ ∃w. ` 7→ w ∗ JΞ ` τK∆ (w)
This follows easily from the rules wp-val and Inv-alloc together with assumption (65)
above.
– Case T-rec: For this case, given ∆ : Ξ → Val → Prop and a list of values #» v such that
G #»
JΞ ` Γ K∆ ( v ), we need to show assuming
wp rec f x := e[ #»
v / #»
n o
x ] x. ∀w. JΞ ` τK∆ (w) −∗ JΞ ` τ 0 KE∆ (x w)
Note the operational semantics pertaining to calling a recursive functions. The expression
(rec f x := e[ #»
v / #»
x ]) w
(e[ #»
v / #»
x ])[w, rec f x := e[ #»
v / #»
x ]/x, f ]
154
Hence, to finish the proof we use the Löb rule. Consequently we get to assume the follow-
ing Löb induction hypothesis (IH).
We finish the proof using the rule wp-rec which requires us to show the following for some
arbitrary value w for which we have JΞ ` τK∆ (w):
. wp (e[ #»
v / #»
x ])[w, rec f x := e[ #»
v / #»
x ]/x, f ] JΞ ` τ 0 K∆
This follows easily from our assumptions and the Löb induction hypothesis, (IH), above.
– Case T-tlam: For this case, given ∆ : Ξ → Val → Prop and a list of values #»
v such that
G #»
JΞ ` Γ K∆ ( v ), we need to show assuming
∀Ψ . JΞ, X ` Γ KG∆,X7→Ψ ( #»
v ) ` wp e[ #»
v / #»
x ] JΞ, X ` τ 0 K∆,X7→Ψ
(67)
wp Λ e[ #»
v / #»
n o
x ] x. ∀Ψ . persistent(Ψ ) ⇒ JΞ, X ` τKE∆,X7→Ψ (v )
Since Λ e[ #»
v / #»
x ] is a value, we use the rule wp-val. Hence, it suffices to show the following
for some arbitrary but fixed Ψ such that persistent(Ψ ):
Note that here we can introduce the persistence modality as none of our assumptions
assert any ownership. Unfolding the expression relation in the above formula reveals that
we need to show:
wp (Λ e[ #»
v / #»
x ]) JΞ, X ` τK∆,X7→Ψ
To prove this, we proceed by applying the rule wp-Tlam and as a result need to show:
. wp e[ #»
v / #»
x ] JΞ, X ` τK∆,X7→Ψ
Finally, we can finish the proof by appealing to assumption (67). We only need to show
JΞ, X ` Γ KG∆,X7→Ψ ( #»
v ) while we have JΞ ` Γ KG∆ ( #»
v ). However, this follows from Lemma 18.2.
Lemma 18.4 (Adequacy of unary logical relations). Let e be an expression such that JΞ ` τKE∆ (e).
Then, e is safe, Safe(e).
Proof. This lemma is a direct consequence of the adequacy theorem, see [8].
Theorem 18.5 (Soundness of unary logical relations). All closed well-typed programs of Fµ,ref ,conc
are safe:
If · | · ` e : τ then Safe(e)
Proof. By the fundamental theorem of unary logical relation, Theorem 18.3, we know that
·|·e:τ
155
Expanding the definition of unary logical relations we get
∀∆, #»
v . J· ` ·KG∆ ( #»
v ) ` J· ` τKE∆ (e[ #»
v / #»
x ])
We take ∆ = ∅ and #»
v = ε which gives us
which immediately simplifies to J· ` τKE∆ (e). By Theorem 18.4, we get Safe(e), as required.
The logical relations model presented in this section is modular. For instance, a well-typed
function of type τ → τ 0 (which by the fundamental theorem falls in the logical relation) can
be applied to any expression that is the logical relations for τ and the result is in the logical
relation for τ 0 . On the other hand, our logical relations model is defined in terms of untyped
expressions. This means that our logical relations model can be used to prove safety of pro-
grams that mix well-typed code with untyped code as long as we show that the untyped code is
semantically well-typed, i.e., the untyped code is in the logical relations for the appropriate type.
As an example of a program that is not well-typed but is nonetheless semantically well-typed
consider the following:
where
but it semantically does. This program allocates a boolean reference and uses it to non-determi-
nistically call f or g. In each case it changes the reference that it has already allocated with the
given value of the appropriate type before passing it to the chosen function. In case the decision
is made to call the first function, i.e., the other thread has not succeeded in the race, it waits for
the other thread to finish. This is to ensure that the other thread writing to the reference l is not
going to destroy the contents of l at some later point. Despite not being syntactically well-typed
one can show that the program above is semantically of the type given. Hence, this program
can be safely linked against any other (syntactically or semantically) well-typed program with
compatible type.
156
In the relational model, the goal is to define a logical relation, such that if e is logically
related to e0 then e contextually refines e0 . We often refer to the expression “on the left”, e, as the
implementation side expression and the expression “on the right”, e0 , as the specification side
expression. To define the relational models, we need a way to refer to (the heap and different
threads of) the program on the specification side “that is about to be executed”. The intuitive
definition of two expressions being related is as follows:
An expressions e (the implementation side) is related to an expression e0 (the specification
side) if we have:
This relation between e and e0 reads as follows: if thread j is about to execute e0 under some
evaluation context K on the specification side and e reduces to a value, then there is a value v 0
such that the specification side is about to execute K[v 0 ]. In other words, whenever e reduces
to a value we know that e0 has also been reduced to v 0 . The reason for explicit quantification
over the thread j under which the specification side is being executed is to enable thread-local
reasoning. We quantify over the evaluation context K under which the expression is about to
be executed to enable modular reasoning with respect to evaluation contexts.
The goal is now to be able to formalize that ”thread j is about to execute K[e0 ]”. For this, we
first define the monoids:
fin
Heap , Auth(Loc −−* (Ex(Val)))
fin
Tpool , Auth(N −−* (Ex(Expr)))
SpecConf(σ , #» ∗ •fpfnOf( #»
γh0 0
γtp
e ) , •res(σ ) e)
γh0
` 7→s v , ◦[` 7→ v]
0
γtp
j Z⇒s e , ◦[j 7→ e]
where
157
With these tools, it is possible to define the expression relation as:
The expression relation above states that e and e0 are related if the following holds: for any
thread j which is about to execute e0 under some evaluation context K, it is safe to evaluate e
and whenever e reduces to a value v, we know that e0 has also been evaluated to some value v 0
in thread j under the evaluation context K. Furthermore, we know that v and v 0 will be related
as values of the type relating e and e0 . Thus, essentially, two expressions e and e0 are related at
type τ if, whenever e reduces to a value, so does e0 (no matter under which circumstances it is
being evaluated), and the resulting values will be related at type τ.
One can now define the binary logical relations for Fµ,ref ,conc , written Ξ | Γ e ≤log e0 : τ, as
follows:
#» #» #»
Ξ | Γ e ≤log e0 : τ , ∀ #»
v , v 0 , ∆. JΞ ` Γ KG∆ ( #»
v , v 0 ) ` JΞ ` τKE∆ (e[ #»
v / #»
x ], e0 [v 0 / #»
x ])
References
[1] Pedro da Rocha Pinto, Thomas Dinsdale-Young, and Philippa Gardner. TaDA: A logic for
time and data abstraction. In ECOOP, volume 8586 of LNCS, pages 207–231, 2014. 116
[2] B.A. Davey and H. Priestly. Introduction to Lattices and Order. Cambridge Univ. Press, 2nd
edition, 2002. 143
[3] Thomas Dinsdale-Young, Pedro da Rocha Pinto, and Philippa Gardner. A perspective on
specifying and verifying concurrent modules. Journal of Logical and Algebraic Methods in
Programming, 98:1 – 25, 2018. 116
[4] Robert Harper. Practical Foundations for Programming Languages. Cambridge University
Press, 2 edition, 2016. 149, 150
[5] J.M.E. Hyland. The effective topos. In A.S. Troelstra and D. van Dalen, editors, The L.E.J.
Brouwer Centenary Symposium, volume 110 of Studies in Logic and The Foundations of Math-
ematics, pages 165–216, Amsterdam, 1982. North-Holland. 1
[6] Bart Jacobs and Frank Piessens. Expressive modular fine-grained concurrency specifica-
tion. In POPL, pages 271–282, 2011. 116
[7] Ralf Jung, Robbert Krebbers, Lars Birkedal, and Derek Dreyer. Higher-order ghost state.
In ICFP, pages 256–269, 2016. 1
[8] Ralf Jung, Robbert Krebbers, Jacques-Henri Jourdan, Aleš Bizjak, and Derek Dreyer
Lars Birkedal. Iris from the ground up: A modular foundation for higher-order concurrent
separation logic. Journal of Functional Programming, 2018. 1, 49, 140, 142, 146, 148, 155
158
[9] Ralf Jung, David Swasey, Filip Sieczkowski, Kasper Svendsen, Aaron Turon, Lars Birkedal,
and Derek Dreyer. Iris: Monoids and invariants as an orthogonal basis for concurrent
reasoning. In POPL, pages 637–650, 2015. 1, 124
[10] Robbert Krebbers, Ralf Jung, Aleš Bizjak, Jacques-Henri Jourdan, Derek Dreyer, and Lars
Birkedal. The essence of higher-order concurrent separation logic. In ESOP, LNCS, pages
696–723, 2017. 1
[11] Robbert Krebbers, Amin Timany, and Lars Birkedal. Interactive proofs in higher-order
concurrent separation logic. In Proceedings of the 44th ACM SIGPLAN Symposium on Prin-
ciples of Programming Languages, POPL 2017, pages 205–217, New York, NY, USA, 2017.
ACM. 140
[14] John C. Reynolds. Types, abstraction, and parametric polymorphism. Information Process-
ing, 1983. 149
[15] K. Svendsen, L. Birkedal, and M. Parkinson. Modular reasoning about separation of con-
current data structures. In Proceedings of ESOP, 2013. 116, 124
[16] Kasper Svendsen and Lars Birkedal. Impredicative concurrent abstract predicates. In Eu-
ropean Symposium on Programming Languages and Systems, pages 149–168. Springer, 2014.
143
[17] A. Timany and L. Birkedal. Type soundness and contextual refinement via logical relations
in higher-order concurrent separation logic. 2018. Manuscript. 148, 149, 156, 158
[18] Aaron Joseph Turon, Jacob Thamsborg, Amal Ahmed, Lars Birkedal, and Derek Dreyer.
Logical relations for fine-grained concurrency. In The 40th Annual ACM SIGPLAN-SIGACT
Symposium on Principles of Programming Languages, POPL ’13, Rome, Italy - January 23 - 25,
2013, pages 343–356, 2013. 4
A Overview
This appendix contains an overview of the operational semantics, typing rules, and logical rules
(and derivations thereof) presented throughout the lecture notes.
The appendix subsections are denoted with the sections of the lecture notes that they cover.
When doing exercises you should only use the rules corresponding to the current section and
sections before it. Additionally, make sure to only use rules that are marked as derived if you
have already carried out the example/exercise to derive them.
159
A.1 Operational Semantics (Section 2)
Syntax
x, y, f ∈ Var
` ∈ Loc
n ∈ Z
} ::= + | − | ∗ | = | < | ···
Val v ::= () | true | false | n | ` | (v, v) | inj1 v | inj2 v | rec f (x) := e
Expr e ::= x | n | e } e | () | true | false | if e then e else e | `
| (e, e) | π1 e | π2 e | inj1 e | inj2 e | match e with inj1 x ⇒ e | inj2 y ⇒ e end
| rec f (x) := e | e e
| ref (e) | ! e | e ← e | CAS(e, e, e) | fork {e}
ECtx E ::= − | E } e | v } E | if E then e else e | (E, e) | (v, E) | π1 E | π2 E | inj1 E | inj2 E
| match E with inj1 x ⇒ e | inj2 y ⇒ e end | E e | v E | ref (E) | ! E | E ← e | v ← E
| CAS(E, e, e0 ) | CAS(v, E, e) | CAS(v, v 0 , E)
fin
Heap h ∈ Loc −−* Val
fin
TPool E ∈ N −−* Expr
Config ς ::= (h, E)
Pure reduction
pure
v } v0 v 00 if v 00 = v } v 0
pure
if true then e1 else e2 e1
pure
if false then e1 else e2 e2
pure
πi (v1 , v2 ) vi
pure
match inji v with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end ei [v/xi ]
pure
(rec f x := e) v e[(rec f x := e)/f , v/x]
Configuration reduction
160
A.2 Typing Rules (Section 3)
Γ `t:τ Γ , x : τ0, y : τ0 ` t : τ Γ1 , x : τ 0 , y : τ 00 , Γ2 ` t : τ
x:τ `x:τ Γ , x : τ0 ` t : τ Γ , x : τ 0 ` t[x/y] : τ Γ1 , x : τ 00 , y : τ 0 , Γ2 ` t[y/x, x/y] : τ
Γ ` t : τ1 Γ ` u : τ2 Γ ` t : τ1 × τ2 i ∈ {1, 2} Γ , x : τ ` t : τ0
Γ ` () : 1 Γ ` (t, u) : τ1 × τ2 Γ ` πi t : τi Γ ` λx. t : τ → τ 0
Γ ` t : τ → τ0 u:τ
0
Γ ` t(u) : τ
161
Trans Eq
Asm P `Q Q`R Γ , x : τ ` Q : Prop Γ | P ` Q[t/x] Γ | P ` t =τ t 0 Eq-Refl
P `P P `R Γ | P ` Q[t 0 /x] P ` t =τ t
Eq-Symm Eq-Trans ⊥E ∧I
P ` t =τ u P ` t1 =τ t2 P ` t2 =τ t3 Q ` False >I R`P R`Q
P ` u =τ t P ` t1 =τ t3 Q`P Q ` True R ` P ∧Q
⇒I ⇒E ∀I ∀E
R∧P ` Q R`P ⇒Q R`P Γ ,x : τ | Q ` P Γ | Q ` ∀x : τ. P Γ `t:τ
R`P ⇒Q R`Q Γ | Q ` ∀x : τ. P Γ | Q ` P [t/x]
∃I ∃E
Γ | Q ` P [t/x] Γ `t:τ Γ | R ` ∃x : τ. P Γ ,x : τ | R ∧ P ` Q
Γ | Q ` ∃x : τ.P Γ |R`Q
−∗I −∗E
R∗P ` Q R1 ` P −∗ Q R2 ` P
R ` P −∗ Q R1 ∗ R2 ` Q
Structural rules.
Ht-frame Ht-ret
S ` {P } e {v.Q} Ht-False w is a value
S ` {P ∗ R} e {v.Q ∗ R} S ` {False} e {v.Q} S ` {True} w {v.v = w}
Ht-bind
K is an eval. context S ` {P } e {v. Q} S ` ∀v. {Q} K[v] {w. R}
S ` {P } K[e] {w. R}
162
Ht-csq
S `P ⇒P0 S ` P 0 e v. Q0 S ` ∀u. Q0 [u/v] ⇒ Q[u/v]
S persistent
S ` {P } e {v. Q}
Ht-disj Ht-exist
S ` {P } e {v. R} S ` {Q} e {v. R} x < FV (Q) S ` ∀x. {P } e {v. Q}
S ` {P ∨ Q} e {v. R} x < FV (Q) S ` {∃x. P } e {v. Q}
Ht-op
v 00 = v } v 0 Ht-load
{True} v } v 0 r.r = v 00
S ` {` 7→ u} ! ` {v.v = u ∧ ` 7→ u}
Ht-alloc Ht-store
Ht-Rec
Γ , g : Val | S ∧ ∀y. ∀v. {P } gv {u.Q} ` ∀y. ∀v. {P } e[g/f , v/x] {u.Q}
Γ | S ` ∀y. ∀v. {P } (rec f x := e)v {u.Q}
Ht-Match
Ht-Proj S ` {P } ei [u/xi ] {v.Q}
S ` {True} πi (v1 , v2 ) {v.v = vi } S ` {P } match inji u with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {v.Q}
Ht-If
{P ∗ b = true} e2 {u.Q} {P ∗ b = false} e3 {u.Q}
{P } if b then e2 else e3 {u.Q}
The following two rules allow us to move persistent propositions in and out of preconditions.
Ht-Eq Ht-Ht
S ∧ t =τ t 0 ` {P } e {v.Q} S ∧ {P1 } e1 {v.Q1 } ` {P2 } e2 {v.Q2 }
S ` P ∧ t =τ t 0 e {v.Q}
S ` {P2 ∧ {P1 } e1 {v.Q1 }} e2 {v.Q2 }
Ht-let
S ` {P } e1 {x.Q} S ` ∀v. {Q[v/x]} e2 [v/x] {u.R}
S ` {P } let x := e1 in e2 {u.R}
Ht-let-det Ht-seq
S ` {P } e1 {x.x = v ∧ Q} S ` {Q[v/x]} e2 [v/x] {u.R} S ` {P } e1 {v.Q} S ` {∃x. Q} e2 {u.R}
S ` {P } let x := e1 in e2 {u.R} S ` {P } e1 ; e2 {u.R}
Ht-beta
S ` {P } e [v/x] {u.Q}
S ` {P } (λx.e)v {u.Q}
163
A.5 Rules for the Later Modality
later-mono later-weak Löb .-∃ ∃-.
Q`P Q`P Q ∧ .P ` P τ is inhabited Q ` . ∃x : τ. P Q ` ∃x. . P
.Q ` .P Q ` .P Q`P Q ` ∃x : τ. . P Q ` . ∃x. P
Ht-beta
S ` {P } e [v/x] {u.Q} Ht-load
Ht-store
S ` {. ` 7→ −} ` ← w {v.v = () ∧ ` 7→ w}
Ht-Rec
Γ , g : Val | S ∧ ∀y. ∀v. {P } gv {u.Q} ` ∀y. ∀v. {P } e[g/f , v/x] {u.Q}
Γ | S ` ∀y. ∀v. {. P } (rec f x := e)v {u.Q}
Ht-Match
S ` {P } ei [u/xi ] {v.Q}
S ` {. P } match inji u with inj1 x1 ⇒ e1 | inj2 x2 ⇒ e2 end {v.Q}
Ht-let
S ` {P } e1 {x. . Q} S ` ∀v. {Q[v/x]} e2 [v/x] {u.R}
S ` {P } let x := e1 in e2 {u.R}
Ht-let-det
S ` {P } e1 {x. . x = v ∧ . Q} S ` {Q[v/x]} e2 [v/x] {u.R}
S ` {P } let x := e1 in e2 {u.R}
Ht-seq Ht-If
S ` {P } e1 {v. . Q} S ` {∃x. Q} e2 {u.R} {P ∗ b = true} e2 {u.Q} {P ∗ b = false} e3 {u.Q}
S ` {P } e1 ; e2 {u.R} {. P } if b then e2 else e3 {u.Q}
164
A.6 Rules for the Persistently Modality (Section 7)
persistently-sep persistently-mono
persistently-dup S ` P ∧ Q P `Q persistently-E
P a` P ∗ P S ` P ∗ Q P ` Q P ` P
Ht-persistently
persistently-Ht Q ∧ S ` {P } e {v. R}
{P } e {Φ} a` {P } e {Φ} S ` {P ∧ Q} e {v. R}
Derived rules for the Persistently Modality.
persistently-intro persistenly-sep-derived
P ` Q S ` (P ∧ Q)
P ` Q S ` (P ∗ Q)
Ht-rec-lob
Γ , f : Val | Q ∧ ∀y. ∀v. {. P } f v {Φ} ` ∀y. ∀v. {P } e[v/x] {Φ}
Γ | Q ` ∀y. ∀v. {. P } (rec f x := e)v {Φ}
Ht-inv-open
ι
e is an atomic expression S ∧ P ` {. P ∗ Q} e {v. . P ∗ R}E
ι
S ∧ P ` {Q} e {v.R}E]{ι}
Other rules related to invariants.
Ht-mask-weaken Ht-frame-atomic
S ` {P } e {v.Q}E1 E1 ⊆ E 2 e is an atomic expression S ` {P } e {v.Q}
S ` {P } e {v.Q}E2 S ` {P ∗ . R} e {v.Q ∗ R}
Ht-later-false Ht-inv-alloc-post
e is an atomic expression E infinite S ` {P2 } e {v.Q}E
n ιo
{.(False)} e {v.Q} S ` {(. P1 ) ∗ P2 } e v.Q ∧ ∃ι ∈ E. P1
E
165
A.8 Rules for Ghost State (Section 8)
Γ ` a : Mi |a|i defined Γ ` a : Mi γ ∈ GhostName Γ ` a : Mi
γ
Γ ` |a|i : Mi Γ ` a ∈ Vi : Prop Γ ` a : Mi : Prop
frame-preserving-update
a B ⇐⇒ ∀x ∈ M, a · x ∈ V ⇒ ∃b ∈ B, b · x ∈ V .
Persistently-core
Own-op Own-valid Γ ` a : Mi |a|i defined
γ γ γ γ γ γ
a : Mi ∗ b : Mi a` a · b : Mi a : Mi ` a ∈ Vi a : Mi ` |a|i : Mi
Ghost-alloc Ghost-update
a∈V a b
γ γ γ
| ∃γ. a
True ` V a | b
`V
Ht-csq-vs
S `P V P0 S ` P 0 e v. Q0 S ` ∀u. Q0 [u/v] V Q[u/v]
S persistent
S ` {P } e {v. Q}
Derived rules for the update modality.
upd-sep upd-bind
P1 ` V
| Q1 P2 ` V
| Q2 P2 ` V
| Q P1 ∗ Q ` V
| R
| (Q1 ∗ Q2 )
P1 ∗ P2 ` V P1 ∗ P2 ` V
| R
166