Mercurial > cpdt > repo
comparison src/StackMachine.v @ 18:82618ad75252
Typed target denotation
author | Adam Chlipala <adamc@hcoop.net> |
---|---|
date | Wed, 03 Sep 2008 15:39:41 -0400 |
parents | 2c5a2a221b85 |
children | 5db67b182ef8 |
comparison
equal
deleted
inserted
replaced
17:2c5a2a221b85 | 18:82618ad75252 |
---|---|
94 (** We declare explicitly that this is a recursive definition, using the keyword [Fixpoint]. The rest should be old hat for functional programmers. *) | 94 (** We declare explicitly that this is a recursive definition, using the keyword [Fixpoint]. The rest should be old hat for functional programmers. *) |
95 | 95 |
96 (** It is convenient to be able to test definitions before starting to prove things about them. We can verify that our semantics is sensible by evaluating some sample uses. *) | 96 (** It is convenient to be able to test definitions before starting to prove things about them. We can verify that our semantics is sensible by evaluating some sample uses. *) |
97 | 97 |
98 Eval simpl in expDenote (Const 42). | 98 Eval simpl in expDenote (Const 42). |
99 (** [[ | |
100 = 42 : nat | |
101 ]] *) | |
99 Eval simpl in expDenote (Binop Plus (Const 2) (Const 2)). | 102 Eval simpl in expDenote (Binop Plus (Const 2) (Const 2)). |
103 (** [[ | |
104 = 4 : nat | |
105 ]] *) | |
100 Eval simpl in expDenote (Binop Times (Binop Plus (Const 2) (Const 2)) (Const 7)). | 106 Eval simpl in expDenote (Binop Times (Binop Plus (Const 2) (Const 2)) (Const 7)). |
101 | 107 (** [[ |
108 = 28 : nat | |
109 ]] *) | |
102 | 110 |
103 (** ** Target language *) | 111 (** ** Target language *) |
104 | 112 |
105 (** We will compile our source programs onto a simple stack machine, whose syntax is: *) | 113 (** We will compile our source programs onto a simple stack machine, whose syntax is: *) |
106 | 114 |
152 | 160 |
153 | 161 |
154 (** Before we set about proving that this compiler is correct, we can try a few test runs, using our sample programs from earlier. *) | 162 (** Before we set about proving that this compiler is correct, we can try a few test runs, using our sample programs from earlier. *) |
155 | 163 |
156 Eval simpl in compile (Const 42). | 164 Eval simpl in compile (Const 42). |
165 (** [[ | |
166 = IConst 42 :: nil : prog | |
167 ]] *) | |
157 Eval simpl in compile (Binop Plus (Const 2) (Const 2)). | 168 Eval simpl in compile (Binop Plus (Const 2) (Const 2)). |
169 (** [[ | |
170 = IConst 2 :: IConst 2 :: IBinop Plus :: nil : prog | |
171 ]] *) | |
158 Eval simpl in compile (Binop Times (Binop Plus (Const 2) (Const 2)) (Const 7)). | 172 Eval simpl in compile (Binop Times (Binop Plus (Const 2) (Const 2)) (Const 7)). |
173 (** [[ | |
174 = IConst 7 :: IConst 2 :: IConst 2 :: IBinop Plus :: IBinop Times :: nil : prog | |
175 ]] *) | |
159 | 176 |
160 (** We can also run our compiled programs and chedk that they give the right results. *) | 177 (** We can also run our compiled programs and chedk that they give the right results. *) |
161 | 178 |
162 Eval simpl in progDenote (compile (Const 42)) nil. | 179 Eval simpl in progDenote (compile (Const 42)) nil. |
180 (** [[ | |
181 = Some (42 :: nil) : option stack | |
182 ]] *) | |
163 Eval simpl in progDenote (compile (Binop Plus (Const 2) (Const 2))) nil. | 183 Eval simpl in progDenote (compile (Binop Plus (Const 2) (Const 2))) nil. |
184 (** [[ | |
185 = Some (4 :: nil) : option stack | |
186 ]] *) | |
164 Eval simpl in progDenote (compile (Binop Times (Binop Plus (Const 2) (Const 2)) (Const 7))) nil. | 187 Eval simpl in progDenote (compile (Binop Times (Binop Plus (Const 2) (Const 2)) (Const 7))) nil. |
188 (** [[ | |
189 = Some (28 :: nil) : option stack | |
190 ]] *) | |
165 | 191 |
166 | 192 |
167 (** ** Translation correctness *) | 193 (** ** Translation correctness *) |
168 | 194 |
169 (** We are ready to prove that our compiler is implemented correctly. We can use a new vernacular command [Theorem] to start a correctness proof, in terms of the semantics we defined earlier: *) | 195 (** We are ready to prove that our compiler is implemented correctly. We can use a new vernacular command [Theorem] to start a correctness proof, in terms of the semantics we defined earlier: *) |
582 end. | 608 end. |
583 | 609 |
584 (** We can evaluate a few example programs to convince ourselves that this semantics is correct. *) | 610 (** We can evaluate a few example programs to convince ourselves that this semantics is correct. *) |
585 | 611 |
586 Eval simpl in texpDenote (TNConst 42). | 612 Eval simpl in texpDenote (TNConst 42). |
613 (** [[ | |
614 = 42 : typeDenote Nat | |
615 ]] *) | |
587 Eval simpl in texpDenote (TBConst true). | 616 Eval simpl in texpDenote (TBConst true). |
617 (** [[ | |
618 = true : typeDenote Bool | |
619 ]] *) | |
588 Eval simpl in texpDenote (TBinop TTimes (TBinop TPlus (TNConst 2) (TNConst 2)) (TNConst 7)). | 620 Eval simpl in texpDenote (TBinop TTimes (TBinop TPlus (TNConst 2) (TNConst 2)) (TNConst 7)). |
621 (** [[ | |
622 = 28 : typeDenote Nat | |
623 ]] *) | |
589 Eval simpl in texpDenote (TBinop (TEq Nat) (TBinop TPlus (TNConst 2) (TNConst 2)) (TNConst 7)). | 624 Eval simpl in texpDenote (TBinop (TEq Nat) (TBinop TPlus (TNConst 2) (TNConst 2)) (TNConst 7)). |
625 (** [[ | |
626 = false : typeDenote Bool | |
627 ]] *) | |
590 Eval simpl in texpDenote (TBinop TLt (TBinop TPlus (TNConst 2) (TNConst 2)) (TNConst 7)). | 628 Eval simpl in texpDenote (TBinop TLt (TBinop TPlus (TNConst 2) (TNConst 2)) (TNConst 7)). |
629 (** [[ | |
630 = true : typeDenote Bool | |
631 ]] *) | |
591 | 632 |
592 | 633 |
593 (** ** Target language *) | 634 (** ** Target language *) |
594 | 635 |
636 (** Now we want to define a suitable stack machine target for compilation. In the example of the untyped language, stack machine programs could encounter stack underflows and "get stuck." This was unfortunate, since we had to deal with this complication even though we proved that our compiler never produced underflowing programs. We could have used dependent types to force all stack machine programs to be underflow-free. | |
637 | |
638 For our new languages, besides underflow, we also have the problem of stack slots with naturals instead of bools or vice versa. This time, we will use indexed typed families to avoid the need to reason about potential failures. | |
639 | |
640 We start by defining stack types, which classify sets of possible stacks. *) | |
641 | |
595 Definition tstack := list type. | 642 Definition tstack := list type. |
643 | |
644 (** Any stack classified by a [tstack] must have exactly as many elements, and each stack element must have the type found in the same position of the stack type. | |
645 | |
646 We can define instructions in terms of stack types, where every instruction's type tells us what initial stack type it expects and what final stack type it will produce. *) | |
596 | 647 |
597 Inductive tinstr : tstack -> tstack -> Set := | 648 Inductive tinstr : tstack -> tstack -> Set := |
598 | TINConst : forall s, nat -> tinstr s (Nat :: s) | 649 | TINConst : forall s, nat -> tinstr s (Nat :: s) |
599 | TIBConst : forall s, bool -> tinstr s (Bool :: s) | 650 | TIBConst : forall s, bool -> tinstr s (Bool :: s) |
600 | TIBinop : forall arg1 arg2 res s, | 651 | TIBinop : forall arg1 arg2 res s, |
601 tbinop arg1 arg2 res | 652 tbinop arg1 arg2 res |
602 -> tinstr (arg1 :: arg2 :: s) (res :: s). | 653 -> tinstr (arg1 :: arg2 :: s) (res :: s). |
603 | 654 |
655 (** Stack machine programs must be a similar inductive family, since, if we again used the [list] type family, we would not be able to guarantee that intermediate stack types match within a program. *) | |
656 | |
604 Inductive tprog : tstack -> tstack -> Set := | 657 Inductive tprog : tstack -> tstack -> Set := |
605 | TNil : forall s, tprog s s | 658 | TNil : forall s, tprog s s |
606 | TCons : forall s1 s2 s3, | 659 | TCons : forall s1 s2 s3, |
607 tinstr s1 s2 | 660 tinstr s1 s2 |
608 -> tprog s2 s3 | 661 -> tprog s2 s3 |
609 -> tprog s1 s3. | 662 -> tprog s1 s3. |
610 | 663 |
664 (** Now, to define the semantics of our new target language, we need a representation for stacks at runtime. We will again take advantage of type information to define types of value stacks that, by construction, contain the right number and types of elements. *) | |
665 | |
611 Fixpoint vstack (ts : tstack) : Set := | 666 Fixpoint vstack (ts : tstack) : Set := |
612 match ts with | 667 match ts with |
613 | nil => unit | 668 | nil => unit |
614 | t :: ts' => typeDenote t * vstack ts' | 669 | t :: ts' => typeDenote t * vstack ts' |
615 end%type. | 670 end%type. |
671 | |
672 (** This is another [Set]-valued function. This time it is recursive, which is perfectly valid, since [Set] is not treated specially in determining which functions may be written. We say that the value stack of an empty stack type is any value of type [unit], which has just a single value, [tt]. A nonempty stack type leads to a value stack that is a pair, whose first element has the proper type and whose second element follows the representation for the remainder of the stack type. | |
673 | |
674 This idea of programming with types can take a while to internalize, but it enables a very simple definition of instruction denotation. We have the same kind of type annotations for the dependent [match], but everything else is like what you might expect from a Lisp-like version of ML that ignored type information. Nonetheless, the fact that [tinstrDenote] passes the type-checker guarantees that our stack machine programs can never go wrong. *) | |
616 | 675 |
617 Definition tinstrDenote ts ts' (i : tinstr ts ts') : vstack ts -> vstack ts' := | 676 Definition tinstrDenote ts ts' (i : tinstr ts ts') : vstack ts -> vstack ts' := |
618 match i in (tinstr ts ts') return (vstack ts -> vstack ts') with | 677 match i in (tinstr ts ts') return (vstack ts -> vstack ts') with |
619 | TINConst _ n => fun s => (n, s) | 678 | TINConst _ n => fun s => (n, s) |
620 | TIBConst _ b => fun s => (b, s) | 679 | TIBConst _ b => fun s => (b, s) |
622 match s with | 681 match s with |
623 (arg1, (arg2, s')) => ((tbinopDenote b) arg1 arg2, s') | 682 (arg1, (arg2, s')) => ((tbinopDenote b) arg1 arg2, s') |
624 end | 683 end |
625 end. | 684 end. |
626 | 685 |
686 (** Why do we choose to use an anonymous function to bind the initial stack in every case of the [match]? Consider this well-intentioned but invalid alternative version: | |
687 | |
688 [[ | |
689 Definition tinstrDenote ts ts' (i : tinstr ts ts') (s : vstack ts) : vstack ts' := | |
690 match i in (tinstr ts ts') return (vstack ts') with | |
691 | TINConst _ n => (n, s) | |
692 | TIBConst _ b => (b, s) | |
693 | TIBinop _ _ _ _ b => | |
694 match s with | |
695 (arg1, (arg2, s')) => ((tbinopDenote b) arg1 arg2, s') | |
696 end | |
697 end. | |
698 | |
699 The Coq type-checker complains that: | |
700 | |
701 [[ | |
702 The term "(n, s)" has type "(nat * vstack ts)%type" | |
703 while it is expected to have type "vstack (Nat :: t)" | |
704 ]] | |
705 | |
706 Recall from our earlier discussion of [match] annotations that we write the annotations to express to the type-checker the relationship between the type indices of the case object and the result type of the [match]. Coq chooses to assign to the wildcard [_] after [TINConst] the name [t], and the type error is telling us that the type checker cannot prove that [t] is the same as [ts]. By moving [s] out of the [match], we lose the ability to express, with [in] and [return] clauses, the relationship between the shared index [ts] of [s] and [i]. | |
707 | |
708 There %\textit{%#<i>#are#</i>#%}% reasonably general ways of getting around this problem without pushing binders inside [match]es. However, the alternatives are significantly more involved, and the technique we use here is almost certainly the best choice, whenever it applies. | |
709 | |
710 *) | |
711 | |
712 (** We finish the semantics with a straightforward definition of program denotation. *) | |
713 | |
627 Fixpoint tprogDenote ts ts' (p : tprog ts ts') {struct p} : vstack ts -> vstack ts' := | 714 Fixpoint tprogDenote ts ts' (p : tprog ts ts') {struct p} : vstack ts -> vstack ts' := |
628 match p in (tprog ts ts') return (vstack ts -> vstack ts') with | 715 match p in (tprog ts ts') return (vstack ts -> vstack ts') with |
629 | TNil _ => fun s => s | 716 | TNil _ => fun s => s |
630 | TCons _ _ _ i p' => fun s => tprogDenote p' (tinstrDenote i s) | 717 | TCons _ _ _ i p' => fun s => tprogDenote p' (tinstrDenote i s) |
631 end. | 718 end. |