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.