-
Notifications
You must be signed in to change notification settings - Fork 51
/
Imp.v
392 lines (316 loc) · 13.9 KB
/
Imp.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
(** * The Imp language *)
(** We now demonstrate how to use ITrees in the context of verified compilation.
To this end, we will consider a simple compiler from an imperative language
to a control-flow-graph language. The meaning of both languages will be
given in terms of ITrees, so that the proof of correctness is expressed by
proving a bisimulation between ITrees.
We shall emphasize two main satisfying aspects of the resulting
formalization.
- Despite the correctness being termination-sensitive, we do not write any
cofixpoints. All reasoning is purely equational, and the underlying
coinductive reasoning is hidden on the library side.
- We split the correctness in two steps. First, a linking theory of the CFG
language is proved correct. Then, this theory is leveraged to prove the
functional correctness of the compiler. The first piece is fairly generic,
and hence reusable.
*)
(** This tutorial is composed of the following files:
- Utils_tutorial.v : Utilities
- Fin.v : Finite types as a categorical embedding
- KTreeFin.v : Subcategory of ktrees over finite types
- Imp.v : Imp language, syntax and semantics
- Asm.v : Asm language, syntax and semantics
- AsmCombinators.v : Linking theory for Asm
- Imp2Asm.v : Compiler from Imp to Asm
- Imp2AsmCorrectness.v : Proof of correctness of the compiler
- AsmOptimizations.v : (Optional) optimization passes for the Asm language
The intended entry point for reading is Imp.v.
*)
(** We start by introducing a simplified variant of Software
Foundations' [Imp] language. The language's semantics is first expressed in
terms of [itree]s. The semantics of the program can then be obtained by
interpreting the events contained in the trees.
*)
(* begin hide *)
From Coq Require Import
Arith.PeanoNat
Lists.List
Strings.String
Morphisms
Setoid
RelationClasses.
From ExtLib Require Import
Data.String
Structures.Monad
Structures.Traversable
Data.List.
From ITree Require Import
ITree
ITreeFacts
Events.MapDefault
Events.StateFacts.
Import Monads.
Import MonadNotation.
Local Open Scope monad_scope.
Local Open Scope string_scope.
(* end hide *)
(* ========================================================================== *)
(** ** Syntax *)
(** Imp manipulates a countable set of variables represented as [string]s: *)
Definition var : Set := string.
(** For simplicity, the language manipulates [nat]s as values. *)
Definition value : Type := nat.
(** Expressions are made of variables, constant literals, and arithmetic operations. *)
Inductive expr : Type :=
| Var (_ : var)
| Lit (_ : value)
| Plus (_ _ : expr)
| Minus (_ _ : expr)
| Mult (_ _ : expr).
(** The statements are straightforward. The [While] statement is the only
potentially diverging one. *)
Inductive stmt : Type :=
| Assign (x : var) (e : expr) (* x = e *)
| Seq (a b : stmt) (* a ; b *)
| If (i : expr) (t e : stmt) (* if (i) then { t } else { e } *)
| While (t : expr) (b : stmt) (* while (t) { b } *)
| Skip (* ; *)
.
(* ========================================================================== *)
(** ** Notations *)
Module ImpNotations.
(** A few notations for convenience. *)
Definition Var_coerce: string -> expr := Var.
Definition Lit_coerce: nat -> expr := Lit.
Coercion Var_coerce: string >-> expr.
Coercion Lit_coerce: nat >-> expr.
Declare Scope expr_scope.
Bind Scope expr_scope with expr.
Infix "+" := Plus : expr_scope.
Infix "-" := Minus : expr_scope.
Infix "*" := Mult : expr_scope.
Declare Scope stmt_scope.
Bind Scope stmt_scope with stmt.
Notation "x '←' e" :=
(Assign x e) (at level 60, e at level 50): stmt_scope.
Notation "a ';;;' b" :=
(Seq a b)
(at level 100, right associativity,
format
"'[v' a ';;;' '/' '[' b ']' ']'"
): stmt_scope.
Notation "'IF' i 'THEN' t 'ELSE' e 'FI'" :=
(If i t e)
(at level 100,
right associativity,
format
"'[v ' 'IF' i '/' '[' 'THEN' t ']' '/' '[' 'ELSE' e ']' 'FI' ']'").
Notation "'WHILE' t 'DO' b" :=
(While t b)
(at level 100,
right associativity,
format
"'[v ' 'WHILE' t 'DO' '/' '[v' b ']' ']'").
End ImpNotations.
Import ImpNotations.
(* ========================================================================== *)
(** ** Semantics *)
(** _Imp_ produces effects by manipulating its variables. To account for this,
we define a type of _external interactions_ [ImpState] modeling reads and
writes to global variables.
A read, [GetVar], takes a variable as an argument and expects the
environment to answer with a value, hence defining an event of type
[ImpState value].
Similarly, [SetVar] is a write event parameterized by both a variable and a
value to be written, and defines an event of type [ImpState unit], no
informative answer being expected from the environment. *)
Variant ImpState : Type -> Type :=
| GetVar (x : var) : ImpState value
| SetVar (x : var) (v : value) : ImpState unit.
Section Denote.
(** We now proceed to denote _Imp_ expressions and statements.
We could simply fix in stone the universe of events to be considered,
taking as a semantic domain for _Imp_ [itree ImpState X]. That would be
sufficient to give meaning to _Imp_, but would prohibit us from relating this
meaning to [itree]s stemmed from other entities. Therefore, we
parameterize the denotation of _Imp_ by a larger universe of events [eff],
of which [ImpState] is assumed to be a subevent. *)
Context {eff : Type -> Type}.
Context {HasImpState : ImpState -< eff}.
(** _Imp_ expressions are denoted as [itree eff value], where the returned
value in the tree is the value computed by the expression.
In the [Var] case, the [trigger] operator smoothly lifts a single event to
an [itree] by performing the corresponding [Vis] event and returning the
environment's answer immediately.
A constant (literal) is simply returned.
Usual monadic notations are used in the other cases: we can [bind]
recursive computations in the case of operators as one would expect. *)
Fixpoint denote_expr (e : expr) : itree eff value :=
match e with
| Var v => trigger (GetVar v)
| Lit n => ret n
| Plus a b => l <- denote_expr a ;; r <- denote_expr b ;; ret (l + r)
| Minus a b => l <- denote_expr a ;; r <- denote_expr b ;; ret (l - r)
| Mult a b => l <- denote_expr a ;; r <- denote_expr b ;; ret (l * r)
end.
(** We turn to the denotation of statements. As opposed to expressions,
statements do not return any value: their semantic domain is therefore
[itree eff unit]. The most interesting construct is, naturally, [while].
To define its meaning, we make use of the [iter] combinator provided by
the [itree] library:
[iter : (A -> itree E (A + B)) -> A -> itree E B].
The combinator takes as argument the body of the loop, i.e. a function
that maps inputs of type [A], the accumulator, to an [itree] computing
either a new [A] that can be fed back to the loop, or a return value of
type [B]. The combinator builds the fixpoint of the body, hiding away the
[A] argument from the return type.
Compared to the [mrec] and [rec] combinators introduced in
[Introduction.v], [iter] is more restricted in that it naturally
represents tail recursive functions. It, however, enjoys a rich equational
theory: its addition grants the type of _continuation trees_ (named
[ktree]s in the library), a structure of _traced monoidal category_.
We use [loop] to first build a new combinator [while].
The accumulator degenerates to a single [unit] value indicating
whether we entered the body of the while loop or not. Since the
the operation does not return any value, the return type is also
taken to be [unit].
That is, the right tag [inr tt] says to exit the loop,
while the [inl tt] says to continue. *)
Definition while (step : itree eff (unit + unit)) : itree eff unit :=
iter (C := Kleisli (itree eff)) (fun _ => step) tt.
(** Casting values into [bool]: [0] corresponds to [false] and any nonzero
value corresponds to [true]. *)
Definition is_true (v : value) : bool := if (v =? 0)%nat then false else true.
(** The meaning of Imp statements is now easy to define. They are all
straightforward, except for [While], which uses our new [while] combinator
over the computation that evaluates the conditional, and then the body if
the former was true. *)
Fixpoint denote_imp (s : stmt) : itree eff unit :=
match s with
| Assign x e => v <- denote_expr e ;; trigger (SetVar x v)
| Seq a b => denote_imp a ;; denote_imp b
| If i t e =>
v <- denote_expr i ;;
if is_true v then denote_imp t else denote_imp e
| While t b =>
while (v <- denote_expr t ;;
if is_true v
then denote_imp b ;; ret (inl tt)
else ret (inr tt))
| Skip => ret tt
end.
End Denote.
(* ========================================================================== *)
(** ** EXAMPLE: Factorial *)
Section Example_Fact.
(** We briefly illustrate the language by writing the traditional factorial.
example. *)
Open Scope expr_scope.
Open Scope stmt_scope.
Variable input: var.
Variable output: var.
Definition fact (n:nat): stmt :=
input ← n;;;
output ← 1;;;
WHILE input
DO output ← output * input;;;
input ← input - 1.
(** We have given _a_ notion of denotation to [fact 6] via [denote_imp].
However, this is naturally not actually runnable yet, since it contains
uninterpreted [ImpState] events. We therefore now need to _handle_ the
events contained in the trees, i.e. give a concrete interpretation of the
environment. *)
End Example_Fact.
(* ========================================================================== *)
(** ** Interpretation *)
(* begin hide *)
From ITree Require Import
Events.MapDefault.
From ExtLib Require Import
Data.String
Core.RelDec
Structures.Maps
Data.Map.FMapAList.
(** We provide an _ITree event handler_ to interpret away [ImpState] events. We
use an _environment event_ to do so, modeling the environment as a
0-initialized map from strings to values. Recall from [Introduction.v] that
a _handler_ for the events [ImpState] is a function of type [forall R, ImpState R
-> M R] for some monad [M]. Here we take for our monad the special case of
[M = itree E] for some universe of events [E] required to contain the
environment events [mapE] provided by the library. It comes with an event
interpreter [interp_map] that yields a computation in the state monad. *)
Definition handle_ImpState {E: Type -> Type} `{mapE var 0 -< E}: ImpState ~> itree E :=
fun _ e =>
match e with
| GetVar x => lookup_def x
| SetVar x v => insert x v
end.
(** We now concretely implement this environment using ExtLib's finite maps. *)
Definition env := alist var value.
(** Finally, we can define an evaluator for our statements.
To do so, we first denote them, leading to an [itree ImpState unit].
We then [interp]ret [ImpState] into [mapE] using [handle_ImpState], leading to
an [itree (mapE var value) unit].
Finally, [interp_map] interprets the latter [itree] into the state monad,
resulting in an [itree] free of any event, but returning the final
_Imp_ env.
*)
(* SAZ: would rather write something like the following:
h : E ~> M A
h' : F[void1] ~> M A
forall eff, {pf:E -< eff == F[E]} (t : itree eff A)
interp pf h h' t : M A
*)
Definition interp_imp {E A} (t : itree (ImpState +' E) A) : stateT env (itree E) A :=
let t' := interp (bimap handle_ImpState (id_ E)) t in
interp_map t'.
Definition eval_imp (s: stmt) : itree void1 (env * unit) :=
interp_imp (denote_imp s) empty.
(** Equipped with this evaluator, we can now compute.
Naturally since Coq is total, we cannot do it directly inside of it.
We can either rely on extraction, or use some fuel.
*)
Compute (burn 200 (eval_imp (fact "input" "output" 6))).
(* ========================================================================== *)
Section InterpImpProperties.
(** We can lift the underlying equational theory on [itree]s to include new
equations for working with [interp_imp].
In particular, we have:
- [interp_imp] respects [≈]
- [interp_imp] commutes with [bind].
We could justify more equations than just the ones below. For instance,
_Imp_ programs also respect a coarser notation of equivalence for the
[env] state. We exploit this possibility to implement optimzations
at the _Asm_ level (see AsmOptimizations.v).
*)
Context {E': Type -> Type}.
Notation E := (ImpState +' E').
(** This interpreter is compatible with the equivalence-up-to-tau. *)
Global Instance eutt_interp_imp {R}:
Proper (@eutt E R R eq ==> eq ==> @eutt E' (prod (env) R) (prod _ R) eq)
interp_imp.
Proof.
repeat intro.
unfold interp_imp.
unfold interp_map.
rewrite H0. eapply eutt_interp_state_eq; auto.
rewrite H. reflexivity.
Qed.
(** [interp_imp] commutes with [bind]. *)
Lemma interp_imp_bind: forall {R S} (t: itree E R) (k: R -> itree E S) (g : env),
(interp_imp (ITree.bind t k) g)
≅ (ITree.bind (interp_imp t g) (fun '(g', x) => interp_imp (k x) g')).
Proof.
intros.
unfold interp_imp.
unfold interp_map.
repeat rewrite interp_bind.
repeat rewrite interp_state_bind.
apply eqit_bind; [ reflexivity | ].
red. intros.
destruct a as [g' x].
simpl.
reflexivity.
Qed.
End InterpImpProperties.
(** We now turn to our target language, in file [Asm].v *)