The Iris Tutorial in Lean

2. Basics of Iris🔗

2.1. Introduction🔗

In short, Iris is a higher-order concurrent separation logic framework. That is quite a mouthful, so let us break it down.

Firstly, the framework part means that Iris is not tied to any single programming language — it consists of a base logic and can be instantiated with any language one sees fit.

Secondly, a separation logic is a logic used to reason about programs by introducing a notion of resource ownership. The idea is that one must own a resource before one can interact with it. Ownership is generally exclusive but can be transferred. To support this notion, separation logic introduces a new connective called separating conjunction, written P ∗ Q. This asserts ownership of the resources described by propositions P and Q, and, in particular, P and Q describe separate resources. So what is a resource? In Iris, we may define our own notion of resources by creating a so-called resource algebra, which we discuss later. For languages with a heap, a canonical example of a resource is a heap fragment. Owning a resource then amounts to controlling a fragment of the heap, allowing one to read and update the associated locations.

Thirdly, a concurrent separation logic (CSL) extends on the above by adding rules supporting concurrent constructions, such as Fork. As ownership is exclusive, a program that spawns threads must decide how to separate and delegate its resources to its threads, so that they may perform their desired actions.

Finally, higher-order refers to the fact that predicates may depend on other predicates. Being a program logic means that programs are proved correct with respect to some specification — a description of the program's behavior and interaction with resources. As programs are usually composed of other programs, we would want our specifications to be generic so that they may be used in a myriad of contexts. Having support for higher-order predicates means that program specifications can be parametrized by arbitrary propositions. This allows one to write specifications for libraries independently of their clients — the clients will instantiate the propositions to specialize the specification to fit their needs.

In this chapter, we introduce basic separation logic in Iris.

2.2. Iris in Lean🔗

The type of propositions in Iris is IProp GF. All proofs in Iris are performed in a context with a GF : BundledGFunctors, used to specify available resources. The details of GF will come later when we introduce resource algebras. For now, just remember to work inside a variable {GF} block, which makes a fixed but arbitrary collection of resources available throughout the section.

open Iris Iris.BI namespace Basics variable {GF : BundledGFunctors}

Iris provides two kinds of propositional statements:

  • ⊢ P asks whether P holds with no assumptions;

  • P ⊢ Q asks whether Q holds assuming P.

In Lean, we work in the Iris Proof Mode (IPM/MoSeL). The practical implication is that we get a new context, called the spatial context, in addition to the usual Lean context, now called the non-spatial context. Hypotheses from both contexts can be used to prove the goal.

The regular Lean tactics can still be used when we work within the non-spatial context, but, in general, we shall use new tactics that work natively with the spatial context. These new tactics start with the letter i: instead of intro H we use iintro H, and instead of apply H we use iapply H. Note that identifiers for hypotheses in the spatial context are ordinary Lean identifiers.

To see this in action we will prove the statement P ⊢ P, for all P.

theorem asm (P : IProp GF) : P ⊢ P := GF:BundledGFunctorsP:IProp GF⊢ P ⊢ P GF:BundledGFunctorsP:IProp GF⊢ ∗H : P ⊢ P All goals completed! 🐙

The tactic iintro adds P to the spatial context with the identifier H. To finish the proof, one would normally use either exact or apply. So in Iris, we use either iexact or iapply.

2.2.1. Technical Details🔗

In Lean, the context and the goal form a sequent (writing ⊢ₓ for the Lean entailment to distinguish it from the Iris entailment ⊢):

H₁ : Φ₁, ..., Hₙ : Φₙ  ⊢ₓ  Ψ

This is equivalent to the proposition Φ₁ ∧ ... ∧ Φₙ ⊢ₓ Ψ.

The Iris Proof Mode mimics this in the sense that the spatial context and the goal form an Iris sequent:

H₁ : Φ₁, ..., Hₙ : Φₙ  ⊢  Ψ

However, as Iris is a separation logic, this is equivalent to the entailment Φ₁ ∗ ... ∗ Φₙ ⊢ Ψ.

Technically, since Iris is built on top of Lean, proving an Iris entailment in Lean corresponds to proving ⊢ₓ (P ⊢ Q). In other words, the spatial context is part of the Lean goal. This is the reason why the regular Lean tactics no longer suffice. The new tactics work with both the non-spatial and the spatial contexts.

Iris propositions include many of the usual logical connectives such as conjunction P ∧ Q. These notations are overloaded directly on the IProp GF type, so they can be used without any extra scope annotation.

2.3. Basic Separation Logic🔗

The core connective in separation logic is the separating conjunction, written P ∗ Q, for propositions P and Q. Separating conjunction differs from regular conjunction, particularly in its introduction rule:

       P₁ ⊢ Q₁     P₂ ⊢ Q₂
       ─────────────────────
         P₁ ∗ P₂ ⊢ Q₁ ∗ Q₂

That is, if we want to prove Q₁ ∗ Q₂, we must decide which of our owned resources we use to prove Q₁ and which we use to prove Q₂. To see this in action, let us prove that separating conjunction is commutative.

theorem sep_comm (P Q : IProp GF) : P ∗ Q ⊢ Q ∗ P := GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ P ∗ Q ⊢ Q ∗ P GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ Q ∗ P GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HQ : Q ⊢ QGF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ⊢ P GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HQ : Q ⊢ Q All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ⊢ P All goals completed! 🐙

To eliminate a separating conjunction we use the cases pattern ⟨HP, HQ⟩ in iintro — analogous to Lean's anonymous-constructor notation.

Unlike ∧, ∗ is not idempotent. Specifically, there are Iris propositions for which ¬(P ⊢ P ∗ P). Because of this, it is generally not possible to use isplit to introduce ∗. The isplit tactic would duplicate the spatial context and is therefore not available when the context is non-empty.

Instead, Iris introduces the tactics isplitl and isplitr. These allow you to specify how you want to separate your resources to prove each subgoal. The hypotheses listed in brackets — space-separated, e.g. [HP HQ] — are passed to the left subgoal (for isplitl), and the remaining to the right; conversely for isplitr.

Separating conjunction has an analogue to implication which, instead of introducing the antecedent to the assumptions with conjunction, introduces it with separating conjunction. This connective is written as P -∗ Q and pronounced "magic wand" or simply "wand". Separation is so widely used that P -∗ Q is treated specially; instead of writing P ⊢ Q, we can write P -∗ Q, with the ⊢ being implicit. That is, ⊢ P -∗ Q is notationally equivalent to P -∗ Q.

Writing a wand instead of entailment makes currying more natural. Here is the Iris version of modus ponens. It is provable using only iintro and iapply.

theorem modus_ponens (P Q : IProp GF) : P -∗ (P -∗ Q) -∗ Q := GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ⊢ P -∗ (P -∗ Q) -∗ Q GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ∗HPQ : P -∗ Q ⊢ Q GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ⊢ P All goals completed! 🐙

Just as with Lean tactics, Iris allows nesting of introduction patterns. In fact, like Lean, Iris supports patterns of the form ⟨H1, H2, H3⟩ as a shorthand for nested ⟨H1, ⟨H2, H3⟩⟩.

Note that ∗ is right-associative, so P ∗ Q ∗ R is parsed as P ∗ (Q ∗ R).

theorem sep_assoc_1 (P Q R : IProp GF) : P ∗ Q ∗ R ⊢ (P ∗ Q) ∗ R := GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ P ∗ Q ∗ R ⊢ (P ∗ Q) ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ∗HR : R ⊢ (P ∗ Q) ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ QGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HR : R ⊢ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ Q GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ PGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ P All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HR : R ⊢ R All goals completed! 🐙

Manually splitting a separation can become tedious. To alleviate this, we can use the iframe tactic. This tactic pairs up hypotheses with pieces of a separation sequence.

theorem sep_comm_v2 (P Q : IProp GF) : P ∗ Q ⊢ Q ∗ P := GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ P ∗ Q ⊢ Q ∗ P GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ Q ∗ P All goals completed! 🐙

Bi-entailment of Iris propositions is denoted P ⊣⊢ Q. It is an equivalence relation, and most connectives preserve it. Bi-entailment is defined as the conjunction of P -∗ Q and Q -∗ P, so it can be decomposed using the isplit tactic (which is permitted here because the spatial context is empty at the point of splitting).

For hypotheses with multiple curried wands, we use the proof-mode term syntax of iapply: the form iapply H $$ pat₁ … patₙ supplies arguments for the wand premises of H.

theorem wand_adj_1 (P Q R : IProp GF) : (P -∗ Q -∗ R) ∗ P ∗ Q ⊢ R := GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ (P -∗ Q -∗ R) ∗ P ∗ Q ⊢ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H : P -∗ Q -∗ R ∗HP : P ∗HQ : Q ⊢ R All goals completed! 🐙

Hypotheses that fit arguments exactly can be supplied directly without generating a trivial subgoal.

theorem wand_adj (P Q R : IProp GF) : (P -∗ Q -∗ R) ⊣⊢ (P ∗ Q -∗ R) := GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ (P -∗ Q -∗ R) ⊣⊢ (P ∗ Q -∗ R) GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ (P -∗ Q -∗ R) -∗ P ∗ Q -∗ RGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ (P ∗ Q -∗ R) -∗ P -∗ Q -∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ (P -∗ Q -∗ R) -∗ P ∗ Q -∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H : P -∗ Q -∗ R ∗HP : P ∗HQ : Q ⊢ R All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ (P ∗ Q -∗ R) -∗ P -∗ Q -∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H : P ∗ Q -∗ R ∗HP : P ∗HQ : Q ⊢ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ Q GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ PGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ P All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q All goals completed! 🐙

Disjunctions ∨ are treated just like disjunctions in Lean. The introduction pattern (HP | HQ) allows us to eliminate a disjunction, while the tactics ileft and iright let us introduce them.

theorem or_comm (P Q : IProp GF) : Q ∨ P ⊢ P ∨ Q := GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ Q ∨ P ⊢ P ∨ Q GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HQ : Q ⊢ P ∨ QGF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ⊢ P ∨ Q GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HQ : Q ⊢ P ∨ Q GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HQ : Q ⊢ Q; All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ⊢ P ∨ Q GF:BundledGFunctorsP:IProp GFQ:IProp GF⊢ ∗HP : P ⊢ P; All goals completed! 🐙

We can even prove the usual elimination rule for or-elimination written with separation. This version is, however, not very useful, as it does not allow the two cases to share resources.

theorem or_elim (P Q R : IProp GF) : (P -∗ R) -∗ (Q -∗ R) -∗ P ∨ Q -∗ R := GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ (P -∗ R) -∗ (Q -∗ R) -∗ P ∨ Q -∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H1 : P -∗ R ∗H2 : Q -∗ R ∗HP : P ⊢ RGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H1 : P -∗ R ∗H2 : Q -∗ R ∗HQ : Q ⊢ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H1 : P -∗ R ∗H2 : Q -∗ R ∗HP : P ⊢ R All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗H1 : P -∗ R ∗H2 : Q -∗ R ∗HQ : Q ⊢ R All goals completed! 🐙

Separating conjunction distributes over disjunction (for the same reason as ordinary conjunction).

theorem sep_or_distr (P Q R : IProp GF) : P ∗ (Q ∨ R) ⊣⊢ P ∗ Q ∨ P ∗ R := GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ P ∗ (Q ∨ R) ⊣⊢ P ∗ Q ∨ P ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ P ∗ (Q ∨ R) -∗ P ∗ Q ∨ P ∗ RGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ P ∗ Q ∨ P ∗ R -∗ P ∗ (Q ∨ R) GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ P ∗ (Q ∨ R) -∗ P ∗ Q ∨ P ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ Q ∨ P ∗ RGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HR : R ⊢ P ∗ Q ∨ P ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ Q ∨ P ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ Q; All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HR : R ⊢ P ∗ Q ∨ P ∗ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HR : R ⊢ P ∗ R; All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ⊢ P ∗ Q ∨ P ∗ R -∗ P ∗ (Q ∨ R) GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ (Q ∨ R)GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HR : R ⊢ P ∗ (Q ∨ R) GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HQ : Q ⊢ P ∗ (Q ∨ R) GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ PGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q ∨ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ P All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q ∨ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HQ : Q ⊢ Q; All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ∗HR : R ⊢ P ∗ (Q ∨ R) GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ PGF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HR : R ⊢ Q ∨ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HP : P ⊢ P All goals completed! 🐙 GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HR : R ⊢ Q ∨ R GF:BundledGFunctorsP:IProp GFQ:IProp GFR:IProp GF⊢ ∗HR : R ⊢ R; All goals completed! 🐙

Iris has existential and universal quantifiers over any Lean type. Existential quantifiers are proved using the iexists tactic. Elimination of existentials uses the pattern %x (with a % in front of the bound variable) to move it to the pure (Lean) context.

theorem sep_ex_distr {α : Type} (P : IProp GF) (Φ : α → IProp GF) : (P ∗ ∃ x, Φ x) ⊣⊢ ∃ x, P ∗ Φ x := GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GF⊢ (P ∗ ∃ x, Φ x) ⊣⊢ ∃ x, P ∗ Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GF⊢ ⊢ (P ∗ ∃ x, Φ x) -∗ ∃ x, P ∗ Φ xGF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GF⊢ ⊢ (∃ x, P ∗ Φ x) -∗ P ∗ ∃ x, Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GF⊢ ⊢ (P ∗ ∃ x, Φ x) -∗ ∃ x, P ∗ Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HP : P ∗HΦ : Φ x ⊢ ∃ x, P ∗ Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HP : P ∗HΦ : Φ x ⊢ P ∗ Φ x All goals completed! 🐙 GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GF⊢ ⊢ (∃ x, P ∗ Φ x) -∗ P ∗ ∃ x, Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HP : P ∗HΦ : Φ x ⊢ P ∗ ∃ x, Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HP : P ⊢ PGF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HΦ : Φ x ⊢ ∃ x, Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HP : P ⊢ P All goals completed! 🐙 GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HΦ : Φ x ⊢ ∃ x, Φ x GF:BundledGFunctorsα:TypeP:IProp GFΦ:α → IProp GFx:α⊢ ∗HΦ : Φ x ⊢ Φ x All goals completed! 🐙

Likewise, forall quantification works almost as in Lean. To introduce a universally quantified variable in the Iris context, you use the intro pattern %x. To specialise a hypothesis at a concrete value x, you write H $$ %x.

theorem sep_all_distr {α : Type} (P Q : α → IProp GF) : (∀ x, P x) ∗ (∀ x, Q x) -∗ (∀ x, P x ∗ Q x) := GF:BundledGFunctorsα:TypeP:α → IProp GFQ:α → IProp GF⊢ ⊢ ((∀ x, P x) ∗ ∀ x, Q x) -∗ ∀ x, P x ∗ Q x GF:BundledGFunctorsα:TypeP:α → IProp GFQ:α → IProp GFx:α⊢ ∗HP : ∀ x, P x ∗HQ : ∀ x, Q x ⊢ P x ∗ Q x GF:BundledGFunctorsα:TypeP:α → IProp GFQ:α → IProp GFx:α⊢ ∗HP : ∀ x, P x ⊢ P xGF:BundledGFunctorsα:TypeP:α → IProp GFQ:α → IProp GFx:α⊢ ∗HQ : ∀ x, Q x ⊢ Q x GF:BundledGFunctorsα:TypeP:α → IProp GFQ:α → IProp GFx:α⊢ ∗HP : ∀ x, P x ⊢ P x All goals completed! 🐙 GF:BundledGFunctorsα:TypeP:α → IProp GFQ:α → IProp GFx:α⊢ ∗HQ : ∀ x, Q x ⊢ Q x All goals completed! 🐙 end Basics