-
Notifications
You must be signed in to change notification settings - Fork 14
/
later.v
206 lines (176 loc) · 6.65 KB
/
later.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
From iris.heap_lang Require Import lang proofmode notation.
(* ################################################################# *)
(** * Later *)
Section later.
Context `{!heapGS Σ}.
(* ================================================================= *)
(** ** Basics *)
(**
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.
*)
(**
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.
*)
Lemma later_mono (P Q : iProp Σ) : (Q ⊢ P) → (▷ Q ⊢ ▷ P).
Proof.
intros HQP.
iIntros "HQ".
iNext.
by iApply HQP.
Qed.
(**
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.
*)
Lemma later_mono' (P Q : iProp Σ) : (Q ⊢ P) → (▷ Q ⊢ ▷ P).
Proof.
intros HQP.
iIntros "HQ !>".
by iApply HQP.
Qed.
(**
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.
*)
Lemma later_weak (P : iProp Σ) : P ⊢ ▷ P.
Proof.
iIntros "HP".
iNext.
done.
Qed.
(**
The later modality distributes over [∧], [∨], [∗], and is preserved
by [∃] and [∀]. This means we can destruct these constructs
regardless of being prefaced by any laters.
*)
Lemma later_sep (P Q : iProp Σ) : ▷ (P ∗ Q) ⊣⊢ ▷ P ∗ ▷ Q.
Proof.
iSplit.
- iIntros "[HP HQ]".
iFrame.
- iIntros "[HP HQ] !>".
iFrame.
Qed.
(**
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.
*)
Lemma later_impl (P Q : iProp Σ) : P ∗ ▷ (P -∗ Q) -∗ ▷ Q.
Proof.
(* exercise *)
Admitted.
(* ================================================================= *)
(** ** 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.
*)
Lemma take_2_steps (P : iProp Σ) : ▷ ▷ P -∗ WP #1 + #2 + #3 {{_, P}}.
Proof.
iIntros "HP".
wp_pure.
wp_pure.
done.
Qed.
(**
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.
*)
Lemma later_points_to (l : loc) : ▷ (l ↦ #5) -∗ WP !#l + #1 {{v, ⌜v = #6⌝}}.
Proof.
iIntros "Hl".
wp_load.
by wp_pures.
Qed.
(**
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.
*)
(* ================================================================= *)
(** ** 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.
*)
Example count : val :=
rec: "count" "x" := "count" ("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.
*)
Lemma count_spec (x : Z) : ⊢ WP count #x {{_, False}}.
Proof.
(**
The tactic for Löb induction, [iLöb], 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.
*)
iLöb as "IH" forall (x).
(**
[iLöb] automatically introduces the universally quantified variables in
the goal, so we can proceed to execute the function.
*)
wp_rec.
wp_pure.
(**
Since we have taken steps, the [▷] in our induction hypothesis has
been stripped, allowing us to apply the hypothesis for the recursive
call.
*)
iApply "IH".
Qed.
End later.