8. The Later Modality and Recursive Functions
8.1. Introduction
Iris is a step-indexed logic, meaning it has a built-in notion of
time. This can be expressed with the later modality ▷ P signifying
that P holds after one time step. With the reading of propositions
as describing owned resources, ▷ P asserts that we will own the
resources described by P after one time step.
The later modality is used quite extensively in Iris. We have already seen that it is used to define Hoare triples, but it has many more uses. For instance, it is a prime tool for reasoning about recursive programs. It can be used to write specifications that capture the minimum number of steps taken by a program. It is also an integral part of working with invariants, which we introduce in a later chapter.
section later_general
open Iris
variable (σ : BundledGFunctors)
8.2. Basics of later modality
The later modality is monotone, meaning that if we know P ⊢ Q, then
we can also conclude ▷ P ⊢ ▷ Q. In words, if we know that P
entails Q, then we also know that if we get P after one step, we
will also get Q after one step. This is captured by the inext
tactic, which introduces a later while stripping laters from our
hypotheses.
theorem later_mono (P Q : IProp σ) :
(Q ⊢ P) → (▷ Q ⊢ ▷ P) := σ:BundledGFunctorsP:IProp σQ:IProp σ⊢ (Q ⊢ P) → ▷ Q ⊢ ▷ P
σ:BundledGFunctorsP:IProp σQ:IProp σqp:Q ⊢ P⊢ ▷ Q ⊢ ▷ P
σ:BundledGFunctorsP:IProp σQ:IProp σqp:Q ⊢ P⊢
∗q : ▷ Q
⊢ ▷ P
σ:BundledGFunctorsP:IProp σQ:IProp σqp:Q ⊢ P⊢
∗q : Q
⊢ P
All goals completed! 🐙
The inext tactic is actually a specialisation of the more general
imodintro tactic, which works with all modalities. The imodintro
tactic can be invoked with the introduction pattern !>, making it
less verbose to handle the later modality.
theorem later_mono' (P Q : IProp σ) :
(Q ⊢ P) → (▷ Q ⊢ ▷ P) := σ:BundledGFunctorsP:IProp σQ:IProp σ⊢ (Q ⊢ P) → ▷ Q ⊢ ▷ P
σ:BundledGFunctorsP:IProp σQ:IProp σqp:Q ⊢ P⊢ ▷ Q ⊢ ▷ P
σ:BundledGFunctorsP:IProp σQ:IProp σqp:Q ⊢ P⊢
∗q : Q
⊢ P
All goals completed! 🐙
The later modality weakens propositions; owning resources now is
stronger than owning them later. In other words, P ⊢ ▷ P. This means
that we can always remove a later from the goal, regardless of whether
our hypotheses have a later.
theorem later_weak (P : IProp σ) : P ⊢ ▷ P := σ:BundledGFunctorsP:IProp σ⊢ P ⊢ ▷ P
σ:BundledGFunctorsP:IProp σ⊢
∗p : P
⊢ ▷ P
σ:BundledGFunctorsP:IProp σ⊢
∗p : P
⊢ P
All goals completed! 🐙
The later modality distributes over ∧, ∨, ∗, and is preserved
by ∃ and ∀. This means we can destruct these constructs
regardless of being prefaced by any laters.
theorem later_sep (P Q : IProp σ) :
▷ (P ∗ Q) ⊣⊢ ▷ P ∗ ▷ Q := σ:BundledGFunctorsP:IProp σQ:IProp σ⊢ ▷ (P ∗ Q) ⊣⊢ ▷ P ∗ ▷ Q
σ:BundledGFunctorsP:IProp σQ:IProp σ⊢
⊢ ▷ (P ∗ Q) -∗ ▷ P ∗ ▷ Qσ:BundledGFunctorsP:IProp σQ:IProp σ⊢
⊢ ▷ P ∗ ▷ Q -∗ ▷ (P ∗ Q)
σ:BundledGFunctorsP:IProp σQ:IProp σ⊢
⊢ ▷ (P ∗ Q) -∗ ▷ P ∗ ▷ Q σ:BundledGFunctorsP:IProp σQ:IProp σ⊢
∗p : ▷ P
∗q : ▷ Q
⊢ ▷ P ∗ ▷ Q
All goals completed! 🐙
σ:BundledGFunctorsP:IProp σQ:IProp σ⊢
⊢ ▷ P ∗ ▷ Q -∗ ▷ (P ∗ Q) σ:BundledGFunctorsP:IProp σQ:IProp σ⊢
∗p : P
∗q : Q
⊢ P ∗ Q
All goals completed! 🐙
As a consequence of monotonicity, weakening, and distribution over
∗, the inext tactic can simply ignore hypotheses in the context
that do not have a later on them.
theorem later_impl (P Q : IProp σ) :
P ∗ ▷ (P -∗ Q) -∗ ▷ Q := σ:BundledGFunctorsP:IProp σQ:IProp σ⊢ ⊢ P ∗ ▷ (P -∗ Q) -∗ ▷ Q
-- Exercise
All goals completed! 🐙
end later_general
8.3. Tying Later to Program Steps
A somewhat important clarification is that the later modality exists
independently of the specific language Iris is instantiated with; the
later modality is part of the Iris base logic. However, when
instantiating Iris with a language, the obvious choice is to tie a
single ▷ to a single program step. This is also the choice that has
been made for HeapLang – every time we use one of the wp_* tactics to
symbolically execute a single step, we let time tick one unit forward,
stripping away a single ▷ from our hypotheses.
To see this in action, let us look at a simple program: #1 + #2 * #3.
This program takes two steps to evaluate, so we can prove that if a
proposition holds after two steps, it will hold after the program has
terminated.
section later_specs
open Iris HeapLang Par
variable [HeapLangGS hlc GF]
theorem take_2_steps (P: IProp GF):
▷ ▷ P -∗ WP (hl(#1 + #2 * #3)) {{ _v, P }} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFP:IProp GF⊢ ⊢ ▷ ▷ P -∗ WP hl((#1 + (#2 * #3))) {{ _v, P }}
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFP:IProp GF⊢
∗P : ▷ ▷ P
⊢ WP hl((#1 + (#2 * #3))) {{ _v, P }}
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFP:IProp GF⊢
∗P : ▷ P
⊢ WP hl((#1 + #(2 * 3))) {{ _v, P }}; hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFP:IProp GF⊢
∗P : P
⊢ |={⊤}=> P
All goals completed! 🐙
The reason this works is that under the hood of WP, there is a later
for every step of the program. Thus, the wp_* tactics can use the
properties mentioned in the previous section to remove laters from the
context, similarly to inext.
Further, it turns out that in many cases, a ▷ on an assumption can
be safely ignored. For instance, in the example below, we only own the
points-to predicate later, yet we can still perform the load.
theorem later_points_to (l : Loc):
▷ (l ↦ hl_val(#5)) -∗
WP hl(!#l + #1) {{v, ⌜v = hl_val(#6)⌝}} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢ ⊢ ▷ l ↦ some hl_val(#5) -∗ WP hl((!#l + #1)) {{ v, ⌜v = hl_val(#6)⌝ }}
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢
∗x✝ : ▷ l ↦ some hl_val(#5)
⊢ WP hl((!#l + #1)) {{ v, ⌜v = hl_val(#6)⌝ }}
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢
∗x✝ : l ↦ some hl_val(#5)
⊢ WP hl((#5 + #1)) {{ v, ⌜v = hl_val(#6)⌝ }}
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFl:Loc⊢
∗x✝ : l ↦ some hl_val(#5)
⊢ |={⊤}=> ⌜hl_val(#(5 + 1)) = hl_val(#6)⌝
All goals completed! 🐙
The technical reason for this is that points-to predicates are
so-called timeless propositions, and the wp_* tactics are aware of
this fact. We study timeless propositions further in a separate
chapter.
8.4. Löb Induction
The later modality allows for a strong induction principle called Löb
induction. Essentially, Löb induction states that to prove a
proposition P, we are allowed to assume that P holds later, i.e.
▷ P. Formally, we have □ (▷ P -∗ P) -∗ P. Recall that ▷
represents a single step in the logic. Löb induction essentially
performs induction in the number of steps. Intuitively, Löb induction
states that if we can show that whenever P holds for strictly
smaller than n steps, we can prove that P holds for n steps,
then P holds for all steps.
We can use this principle to prove many properties of recursive programs. To see this in action, we will define a simple recursive function that increments a counter.
def count: Val := hl_val%
rec cnt x := cnt (x + #1)
This function never terminates for any input as it will keep calling
itself with larger and larger inputs. To show this, we pick the
postcondition False. We can now use Löb induction, along with
wp_rec, to prove this specification.
theorem count_spec (x : Int) :
⊢@{IProp GF} WP hl(&count #x) {{_v, False}} := hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFx:Int⊢ ⊢ WP hl(v(&count) #x) {{ _v, False }}
/- The tactic for Löb induction, `iloeb`, requires us to
specify the name of the induction hypothesis, which we
here call `IH`. Optionally, it can also universally
quantify over any of our variables before performing
induction. We here universally quantify over `x` as it
changes for every recursive call. -/
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFx:Int⊢
□IH : ▷ ∀ x, WP hl(v(&count) #x) {{ _v, False }}
⊢ WP hl(v(&count) #x) {{ _v, False }}
/- `iloeb` automatically introduces the universally
quantified variables in the goal, so we can proceed to
execute the function. -/
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFx:Int⊢
□IH : ∀ x, WP hl(v(&count) #x) {{ _v, False }}
⊢ WP hl(v(&count) (#x + #1)) {{ _v, False }}
hlc:HasLCGF:BundledGFunctorsinst✝:HeapLangGS hlc GFx:Int⊢
□IH : ∀ x, WP hl(v(&count) #x) {{ _v, False }}
⊢ WP hl(v(&count) #(x + 1)) {{ _v, False }}
/- Since we have taken steps, the `▷` in our induction
hypothesis has been stripped, allowing us to apply
the hypothesis for the recursive call. -/
All goals completed! 🐙
end later_specs