comparison src/Coinductive.v @ 211:d06726f49bc6

Ported Coinductive
author Adam Chlipala <adamc@hcoop.net>
date Mon, 09 Nov 2009 14:48:46 -0500
parents f05514cc6c0d
children c4b1c0de7af9
comparison
equal deleted inserted replaced
210:b149a07b9b5b 211:d06726f49bc6
23 Laziness is easy to implement in Haskell, where all the definitions in a program may be thought of as mutually recursive. In such an unconstrained setting, it is easy to implement an infinite loop when you really meant to build an infinite list, where any finite prefix of the list should be forceable in finite time. Haskell programmers learn how to avoid such slip-ups. In Coq, such a laissez-faire policy is not good enough. 23 Laziness is easy to implement in Haskell, where all the definitions in a program may be thought of as mutually recursive. In such an unconstrained setting, it is easy to implement an infinite loop when you really meant to build an infinite list, where any finite prefix of the list should be forceable in finite time. Haskell programmers learn how to avoid such slip-ups. In Coq, such a laissez-faire policy is not good enough.
24 24
25 We spent some time in the last chapter discussing the Curry-Howard isomorphism, where proofs are identified with functional programs. In such a setting, infinite loops, intended or otherwise, are disastrous. If Coq allowed the full breadth of definitions that Haskell did, we could code up an infinite loop and use it to prove any proposition vacuously. That is, the addition of general recursion would make CIC %\textit{%#<i>#inconsistent#</i>#%}%. For an arbitrary proposition [P], we could write: 25 We spent some time in the last chapter discussing the Curry-Howard isomorphism, where proofs are identified with functional programs. In such a setting, infinite loops, intended or otherwise, are disastrous. If Coq allowed the full breadth of definitions that Haskell did, we could code up an infinite loop and use it to prove any proposition vacuously. That is, the addition of general recursion would make CIC %\textit{%#<i>#inconsistent#</i>#%}%. For an arbitrary proposition [P], we could write:
26 26
27 [[ 27 [[
28
29 Fixpoint bad (u : unit) : P := bad u. 28 Fixpoint bad (u : unit) : P := bad u.
30 29
31 ]] 30 ]]
32 31
33 This would leave us with [bad tt] as a proof of [P]. 32 This would leave us with [bad tt] as a proof of [P].
34 33
35 There are also algorithmic considerations that make universal termination very desirable. We have seen how tactics like [reflexivity] compare terms up to equivalence under computational rules. Calls to recursive, pattern-matching functions are simplified automatically, with no need for explicit proof steps. It would be very hard to hold onto that kind of benefit if it became possible to write non-terminating programs; we would be running smack into the halting problem. 34 There are also algorithmic considerations that make universal termination very desirable. We have seen how tactics like [reflexivity] compare terms up to equivalence under computational rules. Calls to recursive, pattern-matching functions are simplified automatically, with no need for explicit proof steps. It would be very hard to hold onto that kind of benefit if it became possible to write non-terminating programs; we would be running smack into the halting problem.
36 35
37 One solution is to use types to contain the possibility of non-termination. For instance, we can create a "non-termination monad," inside which we must write all of our general-recursive programs. In later chapters, we will spend some time on this idea and its applications. For now, we will just say that it is a heavyweight solution, and so we would like to avoid it whenever possible. 36 One solution is to use types to contain the possibility of non-termination. For instance, we can create a "non-termination monad," inside which we must write all of our general-recursive programs. This is a heavyweight solution, and so we would like to avoid it whenever possible.
38 37
39 Luckily, Coq has special support for a class of lazy data structures that happens to contain most examples found in Haskell. That mechanism, %\textit{%#<i>#co-inductive types#</i>#%}%, is the subject of this chapter. *) 38 Luckily, Coq has special support for a class of lazy data structures that happens to contain most examples found in Haskell. That mechanism, %\textit{%#<i>#co-inductive types#</i>#%}%, is the subject of this chapter. *)
40 39
41 40
42 (** * Computing with Infinite Data *) 41 (** * Computing with Infinite Data *)
63 CoFixpoint trues : stream bool := Cons true falses 62 CoFixpoint trues : stream bool := Cons true falses
64 with falses : stream bool := Cons false trues. 63 with falses : stream bool := Cons false trues.
65 64
66 (** Co-inductive values are fair game as arguments to recursive functions, and we can use that fact to write a function to take a finite approximation of a stream. *) 65 (** Co-inductive values are fair game as arguments to recursive functions, and we can use that fact to write a function to take a finite approximation of a stream. *)
67 66
68 Fixpoint approx A (s : stream A) (n : nat) {struct n} : list A := 67 Fixpoint approx A (s : stream A) (n : nat) : list A :=
69 match n with 68 match n with
70 | O => nil 69 | O => nil
71 | S n' => 70 | S n' =>
72 match s with 71 match s with
73 | Cons h t => h :: approx t n' 72 | Cons h t => h :: approx t n'
74 end 73 end
75 end. 74 end.
76 75
77 Eval simpl in approx zeroes 10. 76 Eval simpl in approx zeroes 10.
78 (** [[ 77 (** %\vspace{-.15in}% [[
79
80 = 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: nil 78 = 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: 0 :: nil
81 : list nat 79 : list nat
82 ]] *) 80 ]] *)
81
83 Eval simpl in approx trues 10. 82 Eval simpl in approx trues 10.
84 (** [[ 83 (** %\vspace{-.15in}% [[
85
86 = true 84 = true
87 :: false 85 :: false
88 :: true 86 :: true
89 :: false 87 :: false
90 :: true :: false :: true :: false :: true :: false :: nil 88 :: true :: false :: true :: false :: true :: false :: nil
91 : list bool 89 : list bool
92 ]] *) 90
93 91 ]]
94 (** So far, it looks like co-inductive types might be a magic bullet, allowing us to import all of the Haskeller's usual tricks. However, there are important restrictions that are dual to the restrictions on the use of inductive types. Fixpoints %\textit{%#<i>#consume#</i>#%}% values of inductive types, with restrictions on which %\textit{%#<i>#arguments#</i>#%}% may be passed in recursive calls. Dually, co-fixpoints %\textit{%#<i>#produce#</i>#%}% values of co-inductive types, with restrictions on what may be done with the %\textit{%#<i>#results#</i>#%}% of co-recursive calls. 92
93 So far, it looks like co-inductive types might be a magic bullet, allowing us to import all of the Haskeller's usual tricks. However, there are important restrictions that are dual to the restrictions on the use of inductive types. Fixpoints %\textit{%#<i>#consume#</i>#%}% values of inductive types, with restrictions on which %\textit{%#<i>#arguments#</i>#%}% may be passed in recursive calls. Dually, co-fixpoints %\textit{%#<i>#produce#</i>#%}% values of co-inductive types, with restrictions on what may be done with the %\textit{%#<i>#results#</i>#%}% of co-recursive calls.
95 94
96 The restriction for co-inductive types shows up as the %\textit{%#<i>#guardedness condition#</i>#%}%, and it can be broken into two parts. First, consider this stream definition, which would be legal in Haskell. 95 The restriction for co-inductive types shows up as the %\textit{%#<i>#guardedness condition#</i>#%}%, and it can be broken into two parts. First, consider this stream definition, which would be legal in Haskell.
97 96
98 [[ 97 [[
99 CoFixpoint looper : stream nat := looper. 98 CoFixpoint looper : stream nat := looper.
100 99
101 ]]
102
103 [[
104 Error: 100 Error:
105 Recursive definition of looper is ill-formed. 101 Recursive definition of looper is ill-formed.
106 In environment 102 In environment
107 looper : stream nat 103 looper : stream nat
108 104
109 unguarded recursive call in "looper" 105 unguarded recursive call in "looper"
106
110 ]] 107 ]]
111 108
112 *) 109 The rule we have run afoul of here is that %\textit{%#<i>#every co-recursive call must be guarded by a constructor#</i>#%}%; that is, every co-recursive call must be a direct argument to a constructor of the co-inductive type we are generating. It is a good thing that this rule is enforced. If the definition of [looper] were accepted, our [approx] function would run forever when passed [looper], and we would have fallen into inconsistency.
113
114
115 (** The rule we have run afoul of here is that %\textit{%#<i>#every co-recursive call must be guarded by a constructor#</i>#%}%; that is, every co-recursive call must be a direct argument to a constructor of the co-inductive type we are generating. It is a good thing that this rule is enforced. If the definition of [looper] were accepted, our [approx] function would run forever when passed [looper], and we would have fallen into inconsistency.
116 110
117 The second rule of guardedness is easiest to see by first introducing a more complicated, but legal, co-fixpoint. *) 111 The second rule of guardedness is easiest to see by first introducing a more complicated, but legal, co-fixpoint. *)
118 112
119 Section map. 113 Section map.
120 Variables A B : Set. 114 Variables A B : Set.
126 end. 120 end.
127 End map. 121 End map.
128 122
129 (** This code is a literal copy of that for the list [map] function, with the [Nil] case removed and [Fixpoint] changed to [CoFixpoint]. Many other standard functions on lazy data structures can be implemented just as easily. Some, like [filter], cannot be implemented. Since the predicate passed to [filter] may reject every element of the stream, we cannot satisfy even the first guardedness condition. 123 (** This code is a literal copy of that for the list [map] function, with the [Nil] case removed and [Fixpoint] changed to [CoFixpoint]. Many other standard functions on lazy data structures can be implemented just as easily. Some, like [filter], cannot be implemented. Since the predicate passed to [filter] may reject every element of the stream, we cannot satisfy even the first guardedness condition.
130 124
131 The second condition is subtler. To illustrate it, we start off with another co-recursive function definition that %\textit{%#<i>#is#</i>#%}% legal. The function [interleaves] takes two streams and produces a new stream that alternates between their elements. *) 125 The second condition is subtler. To illustrate it, we start off with another co-recursive function definition that %\textit{%#<i>#is#</i>#%}% legal. The function [interleave] takes two streams and produces a new stream that alternates between their elements. *)
132 126
133 Section interleave. 127 Section interleave.
134 Variable A : Set. 128 Variable A : Set.
135 129
136 CoFixpoint interleave (s1 s2 : stream A) : stream A := 130 CoFixpoint interleave (s1 s2 : stream A) : stream A :=
142 (** Now say we want to write a weird stuttering version of [map] that repeats elements in a particular way, based on interleaving. *) 136 (** Now say we want to write a weird stuttering version of [map] that repeats elements in a particular way, based on interleaving. *)
143 137
144 Section map'. 138 Section map'.
145 Variables A B : Set. 139 Variables A B : Set.
146 Variable f : A -> B. 140 Variable f : A -> B.
147
148 (* begin thide *) 141 (* begin thide *)
149 (** [[ 142 (** [[
150
151 CoFixpoint map' (s : stream A) : stream B := 143 CoFixpoint map' (s : stream A) : stream B :=
152 match s with 144 match s with
153 | Cons h t => interleave (Cons (f h) (map' s)) (Cons (f h) (map' s)) 145 | Cons h t => interleave (Cons (f h) (map' s)) (Cons (f h) (map' s))
154 end. 146 end.
155 147
156 ]] 148 ]]
157 *) 149
158 150 We get another error message about an unguarded recursive call. This is because we are violating the second guardedness condition, which says that, not only must co-recursive calls be arguments to constructors, there must also %\textit{%#<i>#not be anything but [match]es and calls to constructors of the same co-inductive type#</i>#%}% wrapped around these immediate uses of co-recursive calls. The actual implemented rule for guardedness is a little more lenient than what we have just stated, but you can count on the illegality of any exception that would enhance the expressive power of co-recursion.
159 (** We get another error message about an unguarded recursive call. This is because we are violating the second guardedness condition, which says that, not only must co-recursive calls be arguments to constructors, there must also %\textit{%#<i>#not be anything but [match]es and calls to constructors of the same co-inductive type#</i>#%}% wrapped around these immediate uses of co-recursive calls. The actual implemented rule for guardedness is a little more lenient than what we have just stated, but you can count on the illegality of any exception that would enhance the expressive power of co-recursion.
160 151
161 Why enforce a rule like this? Imagine that, instead of [interleave], we had called some other, less well-behaved function on streams. Perhaps this other function might be defined mutually with [map']. It might deconstruct its first argument, retrieving [map' s] from within [Cons (f h) (map' s)]. Next it might try a [match] on this retrieved value, which amounts to deconstructing [map' s]. To figure out how this [match] turns out, we need to know the top-level structure of [map' s], but this is exactly what we started out trying to determine! We run into a loop in the evaluation process, and we have reached a witness of inconsistency if we are evaluating [approx (map' s) 1] for any [s]. *) 152 Why enforce a rule like this? Imagine that, instead of [interleave], we had called some other, less well-behaved function on streams. Perhaps this other function might be defined mutually with [map']. It might deconstruct its first argument, retrieving [map' s] from within [Cons (f h) (map' s)]. Next it might try a [match] on this retrieved value, which amounts to deconstructing [map' s]. To figure out how this [match] turns out, we need to know the top-level structure of [map' s], but this is exactly what we started out trying to determine! We run into a loop in the evaluation process, and we have reached a witness of inconsistency if we are evaluating [approx (map' s) 1] for any [s]. *)
162 (* end thide *) 153 (* end thide *)
154
163 End map'. 155 End map'.
164 156
165 157
166 (** * Infinite Proofs *) 158 (** * Infinite Proofs *)
167 159
174 166
175 Theorem ones_eq : ones = ones'. 167 Theorem ones_eq : ones = ones'.
176 168
177 (** However, faced with the initial subgoal, it is not at all clear how this theorem can be proved. In fact, it is unprovable. The [eq] predicate that we use is fundamentally limited to equalities that can be demonstrated by finite, syntactic arguments. To prove this equivalence, we will need to introduce a new relation. *) 169 (** However, faced with the initial subgoal, it is not at all clear how this theorem can be proved. In fact, it is unprovable. The [eq] predicate that we use is fundamentally limited to equalities that can be demonstrated by finite, syntactic arguments. To prove this equivalence, we will need to introduce a new relation. *)
178 (* begin thide *) 170 (* begin thide *)
171
179 Abort. 172 Abort.
180 173
181 (** Co-inductive datatypes make sense by analogy from Haskell. What we need now is a %\textit{%#<i>#co-inductive proposition#</i>#%}%. That is, we want to define a proposition whose proofs may be infinite, subject to the guardedness condition. The idea of infinite proofs does not show up in usual mathematics, but it can be very useful (unsurprisingly) for reasoning about infinite data structures. Besides examples from Haskell, infinite data and proofs will also turn out to be useful for modelling inherently infinite mathematical objects, like program executions. 174 (** Co-inductive datatypes make sense by analogy from Haskell. What we need now is a %\textit{%#<i>#co-inductive proposition#</i>#%}%. That is, we want to define a proposition whose proofs may be infinite, subject to the guardedness condition. The idea of infinite proofs does not show up in usual mathematics, but it can be very useful (unsurprisingly) for reasoning about infinite data structures. Besides examples from Haskell, infinite data and proofs will also turn out to be useful for modelling inherently infinite mathematical objects, like program executions.
182 175
183 We are ready for our first co-inductive predicate. *) 176 We are ready for our first co-inductive predicate. *)
195 188
196 We can try restating the theorem with [stream_eq]. *) 189 We can try restating the theorem with [stream_eq]. *)
197 190
198 Theorem ones_eq : stream_eq ones ones'. 191 Theorem ones_eq : stream_eq ones ones'.
199 (** Coq does not support tactical co-inductive proofs as well as it supports tactical inductive proofs. The usual starting point is the [cofix] tactic, which asks to structure this proof as a co-fixpoint. *) 192 (** Coq does not support tactical co-inductive proofs as well as it supports tactical inductive proofs. The usual starting point is the [cofix] tactic, which asks to structure this proof as a co-fixpoint. *)
193
200 cofix. 194 cofix.
201 (** [[ 195 (** [[
202
203 ones_eq : stream_eq ones ones' 196 ones_eq : stream_eq ones ones'
204 ============================ 197 ============================
205 stream_eq ones ones' 198 stream_eq ones ones'
206 ]] *) 199
207 200 ]]
208 (** It looks like this proof might be easier than we expected! *) 201
202 It looks like this proof might be easier than we expected! *)
209 203
210 assumption. 204 assumption.
211 (** [[ 205 (** [[
212
213 Proof completed. 206 Proof completed.
214 207
215 ]] *) 208 ]]
216 209
217 (** Unfortunately, we are due for some disappointment in our victory lap. *) 210 Unfortunately, we are due for some disappointment in our victory lap.
218 211
219 (** [[ 212 [[
220 Qed. 213 Qed.
221 214
222 Error: 215 Error:
223 Recursive definition of ones_eq is ill-formed. 216 Recursive definition of ones_eq is ill-formed.
224 217
225 In environment 218 In environment
226 ones_eq : stream_eq ones ones' 219 ones_eq : stream_eq ones ones'
227 220
228 unguarded recursive call in "ones_eq" 221 unguarded recursive call in "ones_eq"
229 222
230 ]] *) 223 ]]
231 224
232 (** Via the Curry-Howard correspondence, the same guardedness condition applies to our co-inductive proofs as to our co-inductive data structures. We should be grateful that this proof is rejected, because, if it were not, the same proof structure could be used to prove any co-inductive theorem vacuously, by direct appeal to itself! 225 Via the Curry-Howard correspondence, the same guardedness condition applies to our co-inductive proofs as to our co-inductive data structures. We should be grateful that this proof is rejected, because, if it were not, the same proof structure could be used to prove any co-inductive theorem vacuously, by direct appeal to itself!
233 226
234 Thinking about how Coq would generate a proof term from the proof script above, we see that the problem is that we are violating the first part of the guardedness condition. During our proofs, Coq can help us check whether we have yet gone wrong in this way. We can run the command [Guarded] in any context to see if it is possible to finish the proof in a way that will yield a properly guarded proof term. 227 Thinking about how Coq would generate a proof term from the proof script above, we see that the problem is that we are violating the first part of the guardedness condition. During our proofs, Coq can help us check whether we have yet gone wrong in this way. We can run the command [Guarded] in any context to see if it is possible to finish the proof in a way that will yield a properly guarded proof term.
235 228
236 [[ 229 [[
237 Guarded. 230 Guarded.
238 231
239 ]] 232 ]]
243 We need to start the co-induction by applying one of [stream_eq]'s constructors. To do that, we need to know that both arguments to the predicate are [Cons]es. Informally, this is trivial, but [simpl] is not able to help us. *) 236 We need to start the co-induction by applying one of [stream_eq]'s constructors. To do that, we need to know that both arguments to the predicate are [Cons]es. Informally, this is trivial, but [simpl] is not able to help us. *)
244 237
245 Undo. 238 Undo.
246 simpl. 239 simpl.
247 (** [[ 240 (** [[
248
249 ones_eq : stream_eq ones ones' 241 ones_eq : stream_eq ones ones'
250 ============================ 242 ============================
251 stream_eq ones ones' 243 stream_eq ones ones'
252 ]] *) 244
253 245 ]]
254 (** It turns out that we are best served by proving an auxiliary lemma. *) 246
247 It turns out that we are best served by proving an auxiliary lemma. *)
248
255 Abort. 249 Abort.
256 250
257 (** First, we need to define a function that seems pointless on first glance. *) 251 (** First, we need to define a function that seems pointless on first glance. *)
258 252
259 Definition frob A (s : stream A) : stream A := 253 Definition frob A (s : stream A) : stream A :=
271 265
272 Theorem ones_eq : stream_eq ones ones'. 266 Theorem ones_eq : stream_eq ones ones'.
273 cofix. 267 cofix.
274 268
275 (** We can use the theorem to rewrite the two streams. *) 269 (** We can use the theorem to rewrite the two streams. *)
270
276 rewrite (frob_eq ones). 271 rewrite (frob_eq ones).
277 rewrite (frob_eq ones'). 272 rewrite (frob_eq ones').
278 (** [[ 273 (** [[
279
280 ones_eq : stream_eq ones ones' 274 ones_eq : stream_eq ones ones'
281 ============================ 275 ============================
282 stream_eq (frob ones) (frob ones') 276 stream_eq (frob ones) (frob ones')
283 ]] *) 277
284 278 ]]
285 (** Now [simpl] is able to reduce the streams. *) 279
280 Now [simpl] is able to reduce the streams. *)
286 281
287 simpl. 282 simpl.
288 (** [[ 283 (** [[
289
290 ones_eq : stream_eq ones ones' 284 ones_eq : stream_eq ones ones'
291 ============================ 285 ============================
292 stream_eq (Cons 1 ones) 286 stream_eq (Cons 1 ones)
293 (Cons 1 287 (Cons 1
294 ((cofix map (s : stream nat) : stream nat := 288 ((cofix map (s : stream nat) : stream nat :=
295 match s with 289 match s with
296 | Cons h t => Cons (S h) (map t) 290 | Cons h t => Cons (S h) (map t)
297 end) zeroes)) 291 end) zeroes))
298 ]] *) 292
299 293 ]]
300 (** Since we have exposed the [Cons] structure of each stream, we can apply the constructor of [stream_eq]. *) 294
295 Since we have exposed the [Cons] structure of each stream, we can apply the constructor of [stream_eq]. *)
301 296
302 constructor. 297 constructor.
303 (** [[ 298 (** [[
304
305 ones_eq : stream_eq ones ones' 299 ones_eq : stream_eq ones ones'
306 ============================ 300 ============================
307 stream_eq ones 301 stream_eq ones
308 ((cofix map (s : stream nat) : stream nat := 302 ((cofix map (s : stream nat) : stream nat :=
309 match s with 303 match s with
310 | Cons h t => Cons (S h) (map t) 304 | Cons h t => Cons (S h) (map t)
311 end) zeroes) 305 end) zeroes)
312 ]] *) 306
313 307 ]]
314 (** Now, modulo unfolding of the definition of [map], we have matched our assumption. *) 308
309 Now, modulo unfolding of the definition of [map], we have matched our assumption. *)
310
315 assumption. 311 assumption.
316 Qed. 312 Qed.
317 313
318 (** Why did this silly-looking trick help? The answer has to do with the constraints placed on Coq's evaluation rules by the need for termination. The [cofix]-related restriction that foiled our first attempt at using [simpl] is dual to a restriction for [fix]. In particular, an application of an anonymous [fix] only reduces when the top-level structure of the recursive argument is known. Otherwise, we would be unfolding the recursive definition ad infinitum. 314 (** Why did this silly-looking trick help? The answer has to do with the constraints placed on Coq's evaluation rules by the need for termination. The [cofix]-related restriction that foiled our first attempt at using [simpl] is dual to a restriction for [fix]. In particular, an application of an anonymous [fix] only reduces when the top-level structure of the recursive argument is known. Otherwise, we would be unfolding the recursive definition ad infinitum.
319 315
324 One common source of difficulty with co-inductive proofs is bad interaction with standard Coq automation machinery. If we try to prove [ones_eq'] with automation, like we have in previous inductive proofs, we get an invalid proof. *) 320 One common source of difficulty with co-inductive proofs is bad interaction with standard Coq automation machinery. If we try to prove [ones_eq'] with automation, like we have in previous inductive proofs, we get an invalid proof. *)
325 321
326 Theorem ones_eq' : stream_eq ones ones'. 322 Theorem ones_eq' : stream_eq ones ones'.
327 cofix; crush. 323 cofix; crush.
328 (** [[ 324 (** [[
329
330 Guarded. 325 Guarded.
331 326
332 ]] *) 327 ]] *)
333 Abort. 328 Abort.
334 (* end thide *) 329 (* end thide *)
335 330
336 (** The standard [auto] machinery sees that our goal matches an assumption and so applies that assumption, even though this violates guardedness. One usually starts a proof like this by [destruct]ing some parameter and running a custom tactic to figure out the first proof rule to apply for each case. Alternatively, there are tricks that can be played with "hiding" the co-inductive hypothesis. We will see examples of effective co-inductive proving in later chapters. *) 331 (** The standard [auto] machinery sees that our goal matches an assumption and so applies that assumption, even though this violates guardedness. One usually starts a proof like this by [destruct]ing some parameter and running a custom tactic to figure out the first proof rule to apply for each case. Alternatively, there are tricks that can be played with "hiding" the co-inductive hypothesis. *)
337 332
338 333
339 (** * Simple Modeling of Non-Terminating Programs *) 334 (** * Simple Modeling of Non-Terminating Programs *)
340 335
341 (** We close the chapter with a quick motivating example for more complex uses of co-inductive types. We will define a co-inductive semantics for a simple assembly language and use that semantics to prove that an optimization function is sound. We start by defining types of registers, program labels, and instructions. *) 336 (** We close the chapter with a quick motivating example for more complex uses of co-inductive types. We will define a co-inductive semantics for a simple assembly language and use that semantics to prove that assembly programs always run forever. This basic technique can be combined with typing judgments for more advanced languages, where some ill-typed programs can go wrong, but no well-typed programs go wrong.
342 337
343 Inductive reg : Set := R1 | R2. 338 We define suggestive synonyms for [nat], for cases where we use natural numbers as registers or program labels. That is, we consider our idealized machine to have infinitely many registers and infinitely many code addresses. *)
339
340 Definition reg := nat.
344 Definition label := nat. 341 Definition label := nat.
345 342
346 Inductive instrs : Set := 343 (** Our instructions are loading of a constant into a register, copying from one register to another, unconditional jump, and conditional jump based on whether the value in a register is not zero. *)
347 | Const : reg -> nat -> instrs -> instrs 344
348 | Add : reg -> reg -> reg -> instrs -> instrs 345 Inductive instr : Set :=
349 | Halt : reg -> instrs 346 | Imm : reg -> nat -> instr
350 | Jeq : reg -> reg -> label -> instrs -> instrs. 347 | Copy : reg -> reg -> instr
351 348 | Jmp : label -> instr
352 (** [Const] stores a constant in a register; [Add] stores in the first register the sum of the values in the second two; [Halt] ends the program, returning the value of its register argument; and [Jeq] jumps to a label if the values in two registers are equal. Each instruction but [Halt] takes an [instrs], which can be read as "list of instructions," as the normal continuation of control flow. 349 | Jnz : reg -> label -> instr.
353 350
354 We can define a program as a list of lists of instructions, where labels will be interpreted as indexing into such a list. *) 351 (** We define a type [regs] of maps from registers to values. To define a function [set] for setting a register's value in a map, we import the [Arith] module from Coq's standard library, and we use its function [eq_nat_dec] for comparing natural numbers. *)
355 352
356 Definition program := list instrs. 353 Definition regs := reg -> nat.
357 354 Require Import Arith.
358 (** We define a polymorphic map type for register keys, with its associated operations. *) 355 Definition set (rs : regs) (r : reg) (v : nat) : regs :=
359 Section regmap. 356 fun r' => if eq_nat_dec r r' then v else rs r'.
360 Variable A : Set. 357
361 358 (** An inductive [exec] judgment captures the effect of an instruction on the program counter and register bank. *)
362 Record regmap : Set := Regmap { 359
363 VR1 : A; 360 Inductive exec : label -> regs -> instr -> label -> regs -> Prop :=
364 VR2 : A 361 | E_Imm : forall pc rs r n, exec pc rs (Imm r n) (S pc) (set rs r n)
365 }. 362 | E_Copy : forall pc rs r1 r2, exec pc rs (Copy r1 r2) (S pc) (set rs r1 (rs r2))
366 363 | E_Jmp : forall pc rs pc', exec pc rs (Jmp pc') pc' rs
367 Definition empty (v : A) : regmap := Regmap v v. 364 | E_JnzF : forall pc rs r pc', rs r = 0 -> exec pc rs (Jnz r pc') (S pc) rs
368 Definition get (rm : regmap) (r : reg) : A := 365 | E_JnzT : forall pc rs r pc' n, rs r = S n -> exec pc rs (Jnz r pc') pc' rs.
369 match r with 366
370 | R1 => VR1 rm 367 (** We prove that [exec] represents a total function. In our proof script, we use a [match] tactic with a [context] pattern. This particular example finds an occurrence of a pattern [Jnz ?r _] anywhere in the current subgoal's conclusion. We use a Coq library tactic [case_eq] to do case analysis on whether the current value [rs r] of the register [r] is zero or not. [case_eq] differs from [destruct] in saving an equality relating the old variable to the new form we deduce for it. *)
371 | R2 => VR2 rm 368
372 end. 369 Lemma exec_total : forall pc rs i,
373 Definition set (rm : regmap) (r : reg) (v : A) : regmap := 370 exists pc', exists rs', exec pc rs i pc' rs'.
374 match r with 371 Hint Constructors exec.
375 | R1 => Regmap v (VR2 rm) 372
376 | R2 => Regmap (VR1 rm) v 373 destruct i; crush; eauto;
377 end. 374 match goal with
378 End regmap. 375 | [ |- context[Jnz ?r _] ] => case_eq (rs r)
379 376 end; eauto.
380 (** Now comes the interesting part. We define a co-inductive semantics for programs. The definition itself is not surprising. We could change [CoInductive] to [Inductive] and arrive at a valid semantics that only covers terminating program executions. Using [CoInductive] admits infinite derivations for infinite executions. An application [run rm is v] means that, when we run the instructions [is] starting with register map [rm], either execution terminates with result [v] or execution runs safely forever. (That is, the choice of [v] is immaterial for non-terminating executions.) *)
381
382 Section run.
383 Variable prog : program.
384
385 CoInductive run : regmap nat -> instrs -> nat -> Prop :=
386 | RConst : forall rm r n is v,
387 run (set rm r n) is v
388 -> run rm (Const r n is) v
389 | RAdd : forall rm r r1 r2 is v,
390 run (set rm r (get rm r1 + get rm r2)) is v
391 -> run rm (Add r r1 r2 is) v
392 | RHalt : forall rm r,
393 run rm (Halt r) (get rm r)
394 | RJeq_eq : forall rm r1 r2 l is is' v,
395 get rm r1 = get rm r2
396 -> nth_error prog l = Some is'
397 -> run rm is' v
398 -> run rm (Jeq r1 r2 l is) v
399 | RJeq_neq : forall rm r1 r2 l is v,
400 get rm r1 <> get rm r2
401 -> run rm is v
402 -> run rm (Jeq r1 r2 l is) v.
403 End run.
404
405 (** We can write a function which tracks known register values to attempt to constant fold a sequence of instructions. We track register values with a [regmap (option nat)], where a mapping to [None] indicates no information, and a mapping to [Some n] indicates that the corresponding register is known to have value [n]. *)
406
407 Fixpoint constFold (rm : regmap (option nat)) (is : instrs) {struct is} : instrs :=
408 match is with
409 | Const r n is => Const r n (constFold (set rm r (Some n)) is)
410 | Add r r1 r2 is =>
411 match get rm r1, get rm r2 with
412 | Some n1, Some n2 =>
413 Const r (n1 + n2) (constFold (set rm r (Some (n1 + n2))) is)
414 | _, _ => Add r r1 r2 (constFold (set rm r None) is)
415 end
416 | Halt _ => is
417 | Jeq r1 r2 l is => Jeq r1 r2 l (constFold rm is)
418 end.
419
420 (** We characterize when the two types of register maps we are using agree with each other. *)
421
422 Definition regmapCompat (rm : regmap nat) (rm' : regmap (option nat)) :=
423 forall r, match get rm' r with
424 | None => True
425 | Some v => get rm r = v
426 end.
427
428 (** We prove two lemmas about how register map modifications affect compatibility. A tactic [compat] abstracts the common structure of the two proofs. *)
429
430 (** remove printing * *)
431 Ltac compat := unfold regmapCompat in *; crush;
432 match goal with
433 | [ H : _ |- match get _ ?R with Some _ => _ | None => _ end ] => generalize (H R); destruct R; crush
434 end.
435
436 Lemma regmapCompat_set_None : forall rm rm' r n,
437 regmapCompat rm rm'
438 -> regmapCompat (set rm r n) (set rm' r None).
439 destruct r; compat.
440 Qed. 377 Qed.
441 378
442 Lemma regmapCompat_set_Some : forall rm rm' r n, 379 (** We are ready to define a co-inductive judgment capturing the idea that a program runs forever. We define the judgment in terms of a program [prog], represented as a function mapping each label to the instruction found there. *)
443 regmapCompat rm rm' 380
444 -> regmapCompat (set rm r n) (set rm' r (Some n)). 381 Section safe.
445 destruct r; compat. 382 Variable prog : label -> instr.
446 Qed. 383
447 384 CoInductive safe : label -> regs -> Prop :=
448 (** Finally, we can prove the main theorem. *) 385 | Step : forall pc r pc' r',
449 386 exec pc r (prog pc) pc' r'
450 Section constFold_ok. 387 -> safe pc' r'
451 Variable prog : program. 388 -> safe pc r.
452 389
453 Theorem constFold_ok : forall rm is v, 390 (** Now we can prove that any starting address and register bank lead to safe infinite execution. Recall that proofs of existentially-quantified formulas are all built with a single constructor of the inductive type [ex]. This means that we can use [destruct] to "open up" such proofs. In the proof below, we want to perform this opening up on an appropriate use of the [exec_total] lemma. This lemma's conclusion begins with two existential quantifiers, so we want to tell [destruct] that it should not stop at the first quantifier. We accomplish our goal by using an %\textit{%#<i>#intro pattern#</i>#%}% with [destruct]. Consult the Coq manual for the details of intro patterns; the specific pattern [[? [? ?]]] that we use here accomplishes our goal of destructing both quantifiers at once. *)
454 run prog rm is v 391
455 -> forall rm', regmapCompat rm rm' 392 Theorem always_safe : forall pc rs,
456 -> run prog rm (constFold rm' is) v. 393 safe pc rs.
457 Hint Resolve regmapCompat_set_None regmapCompat_set_Some. 394 cofix; intros;
458 Hint Constructors run. 395 destruct (exec_total pc rs (prog pc)) as [? [? ?]];
459 396 econstructor; eauto.
460 cofix;
461 destruct 1; crush; eauto;
462 repeat match goal with
463 | [ H : regmapCompat _ _
464 |- run _ _ (match get ?RM ?R with
465 | Some _ => _
466 | None => _
467 end) _ ] =>
468 generalize (H R); destruct (get RM R); crush
469 end.
470 Qed. 397 Qed.
471 End constFold_ok. 398 End safe.
472 399
473 (** If we print the proof term that was generated, we can verify that the proof is structured as a [cofix], with each co-recursive call properly guarded. *) 400 (** If we print the proof term that was generated, we can verify that the proof is structured as a [cofix], with each co-recursive call properly guarded. *)
474 401
475 Print constFold_ok. 402 Print always_safe.
476 403
477 404
478 (** * Exercises *) 405 (** * Exercises *)
479 406
480 (** %\begin{enumerate}%#<ol># 407 (** %\begin{enumerate}%#<ol>#