Lecture 4: Staging Interpreters Tim Sheard Oregon Graduate Institute CSE 510 Section FSC Winter 2004 Languages and Calculation The calculator language datatype Exp = Var of string | Add of Exp*Exp | Mult of Exp*Exp | Const of int | Local of string*Exp*Exp; The calculator program (un-staged) fun calc term bindings = case term of Var s => lookup s bindings | Add(x,y) => (calc x bindings) + (calc y bindings) | Mult(x,y) => (calc x bindings) * (calc y bindings) | Const n => n | Local(s,x,y) => calc y ((s,calc x bindings)::bindings); Cse583 Winterl 2002 2 Staged solution 1 Just add annoations fun calc2 term bindings = case term of Var s => lookup s bindings | Add(x,y) => < ~(calc2 x bindings) + ~(calc2 y bindings) > | Mult(x,y) => < ~(calc2 x bindings) * ~(calc2 y bindings) > | Const n => lift n | Local(s,x,y) => calc2 y ((s,calc2 x bindings)::bindings); Note how the local “let” is inlined. Cse583 Winterl 2002 3 Results 1 val term = Local("x",Add(Const 3, Const 4), Local("y",Mult(Const 6,Var "x"), Add(Var "y",Const 12))); val ans2 = calc2 term []; -| ans2; val it = <6 %* 3 %+ 4 %+ 12> : <int> Cse583 Winterl 2002 Note how the let structure has disappeared 4 Staged solution 2 fun calc3 term bindings = case term of Var s => lookup s bindings | Add(x,y) => < ~(calc3 x bindings) + ~(calc3 y bindings) > | Mult(x,y) => < ~(calc3 x bindings) * ~(calc3 y bindings) > | Const n => lift n | Local(s,x,y) => <let val w = ~(calc3 x bindings) in ~(calc3 y ((s,<w>)::bindings)) end>; val ans3 val ans3 <let val val in b %+ = calc3 term []; = a = 3 %+ 4 b = 6 %* a 12 end> : <int> Cse583 Winterl 2002 5 Why Stage Interpreters? Interpreters are the classic example for using staging. Interpreter takes a program, its data, and returns the result of applying the program to its data. In transformer style interp: program -> <data> -> <result> In generator style interp: program -> <data -> result> Avoids the overhead of manipulating the data that represents the program each time it is applied. Dramatic speedups are possible. Abstract way of building a compiler. Cse583 Winterl 2002 6 Interpreter Characteristics Recursive descent over the syntax of the language. Ideally , the meaning of an “expression” in a language should depend only on the meaning of its constituent “sub-expressions”. Structure of the “syntax” is what guides the interpreter. One “case” for each kind of expression or statement The use of an environment abstract datatype encodes the meaning of variables encodes the “scope” of binding constructs The use of a “value” or semantic meaning type as the return type of the interpreter. Cse583 Winterl 2002 7 The Language datatype Exp = Constant of int | Variable of string | Minus of (Exp * Exp) | Greater of (Exp * Exp) | Times of (Exp * Exp) ; datatype Com = Assign of (string * Exp) | Seq of (Com * Com) | Cond of (Exp * Com * Com) | While of (Exp * Com) | Dec of (string * Exp * Com) Cse583 Winterl 2002 (* 5 (* x (* x - 5 (* x > 1 (* x * 4 (* (* (* (* (* x := 1 { x := 1; if x then while x>0 Dec x = 1 y := x := do x in x *) *) *) *) *) *) 2 } *) 1 else y := 0 *) := x - 1 *) := x - 1 *) 8 Environment ADT Lookup: string Set: string -> Ext: string -> Remove: env -> type env -> env -> int int -> env -> env int -> env -> env env = (string * int) list ; fun lookup x [] = error ("variable not found: "^x) | lookup x ((y,v)::zs) = if x=y then v else lookup x zs; fun set name v [] = error ("name not found: "^name) | set name v ((z as (y,_))::zs) = if name=y then (y,v)::zs else z::(set name v zs); fun ext nm v zs = (nm,v)::zs; fun remove (z::zs) = zs; Cse583 Winterl 2002 9 Simple unstaged interpeters Eval0 :: Exp -> env -> int Value = env -> int The meaning of an exp is a value fun eval0 exp env = case exp of Constant n => n | Variable s => lookup s env | Minus(x,y) => let val a = eval0 x env val b = eval0 y env in a - b end | Greater(x,y) => let val a = eval0 x env val b = eval0 y env in if a '>' b then 1 else 0 end | Times(x,y) => let val a = eval0 x env val b = eval0 y env in a * b end; Cse583 Winterl 2002 10 Interp0 : Com -> env -> env The meaning of a Com is an fun interpret0 stmt env = env transformer. case stmt of Assign(name,e) => let val v = eval0 e env in set name v env end | Seq(s1,s2) => let val env1 = interpret0 s1 env val env2 = interpret0 s2 env1 in env2 end | If(e,s1,s2) => let val x = eval0 e env in if x=1 then interpret0 s1 env else interpret0 s2 env end | While(e,body) => let val v = eval0 e env in if v=0 then env else interpret0 (While(e,body))(interpret0 body env) end | Declare(nm,e,stmt) => let val v = eval0 e env val env1 = ext nm v env in remove(interpret0 stmt env1) end; Cse583 Winterl 2002 11 Getting ready to stage What is the structure of the source language? Exp and Com What is the structure of the target language? MetaML with “let” and operations on environments and arithmetic What are the staging issues? What is completely known at compile-time Exp, Com, part of the environment (the names but not the values) How do I connect the structure of the source and target languages. Cse583 Winterl 2002 12 Staging (Binding time) improvements type env = (string * int) list; type location = int; type index = string list; Note the string and the “spine” of the list are known, but the int’s are not. Separate env into two parts. An index (the string and its position in the spine), and a stack (the int’s) Cse583 Winterl 2002 type stack = int list; eval1 : Exp -> index -> stack -> int interp1: Com -> index -> stack -> stack 13 Recoding up environments type location = int; type index = string list; type stack = int list; pos :: string -> index -> location get : location -> stack -> value put: location -> value -> stack -> stack fun | | | fun | | | get get get get put put put put 1 0 n n 1 0 n n (x::xs) = x _ = error "No value at index 0." (x::xs) = get (n-1) xs [] = error "Stack is empty"; v (x::xs) = (v::xs) v _ = error "No value at index 0." v (x::xs) = x :: (put (n-1) v xs) v [] = error "Stack is empty"; Cse583 Winterl 2002 14 eval1 fun eval1 exp index stack = case exp of Constant n => n | Variable s => get (pos s index) stack | Minus(x,y) => let val a = eval1 x index val b = eval1 y index in a - b end | Greater(x,y) => let val a = eval1 x index val b = eval1 y index in if a '>' b then 1 else | Times(x,y) => let val a = eval1 x index val b = eval1 y index in a * b end; Cse583 Winterl 2002 stack stack stack stack 0 end stack stack 15 interp1 fun interp1 stmt index stack = case stmt of Assign(name,e) => let val v = eval1 e index stack val loc = pos name index in put loc v stack end | Seq(s1,s2) => let val stack1 = interp1 s1 index stack val stack2 = interp1 s2 index stack1 in stack2 end | If(e,s1,s2) => let val x = eval1 e index stack in if x=1 then interp1 s1 index stack else interp1 s2 index stack end Cse583 Winterl 2002 16 Interp1 (cont.) fun interp1 stmt index stack = case stmt of . . . | While(e,body) => let val v = eval1 e index stack in if v=0 then stack else interp1 (While(e,body)) index (interp1 body index stack) end | Declare(nm,e,stmt) => let val v = eval1 e index stack val stack1 = v :: stack in tl (interp1 stmt (nm::index) stack1) end; Cse583 Winterl 2002 17 Adding staging annotations fun eval2 exp index stack = case exp of Constant n => lift n | Variable s => <get ~(lift (pos s index)) ~stack> | Minus(x,y) => <let val a = ~(eval2 x index stack) val b = ~(eval2 y index stack) in a - b end> | Greater(x,y) => <let val a = ~(eval2 x index stack) val b = ~(eval2 y index stack) in if a '>' b then 1 else 0 end> | Times(x,y) => <let val a = ~(eval2 x index stack) val b = ~(eval2 y index stack) in a * b end>; Cse583 Winterl 2002 18 interp2 fun interp2 stmt index stack = case stmt of Assign(name,e) => <let val v = ~(eval2 e index stack) in put ~(lift (pos name index)) v ~stack end> | Seq(s1,s2) => <let val stack1 = ~(interp2 s1 index stack) val stack2 = ~(interp2 s2 index <stack1>) in stack2 end> | If(e,s1,s2) => <let val x = ~(eval2 e index stack) in if x=1 then ~(interp2 s1 index stack) else ~(interp2 s2 index stack) end> Cse583 Winterl 2002 19 Interp2 (cont) fun interp2 stmt index stack = case stmt of . . . | While(e,body) => <let val v = ~(eval2 e index stack) in if v=0 then ~stack else ~(interp2 (While(e,body)) index (interp2 body index stack)) end> | Declare(nm,e,stmt) => <let val v = ~(eval2 e index stack) val stack1 = v :: ~stack in tl ~(interp2 stmt (nm::index) <stack1>) end>; Cse583 Winterl 2002 20 Using the staged code val s0 = Declare("x",Constant 150, Declare("y",Constant 200, Seq(Assign("x",Minus(Variable "x",Constant 1)), Assign("y",Minus(Variable "y",Constant 1))))); val ans2 = <fn stack => ~(interp2 s0 [] <stack>)>; Cse583 Winterl 2002 21 Results -| val ans2 = <(fn a => let val b = 150 val c = b :: a in %tl (let val d = 200 val e = d :: c in %tl (let val f = %get 1 e val g = 1 val h = f %- g val i = %put 1 h e val j = %get 0 i val k = 1 val l = j %- k val m = %put 0 l i in m end) end) end)> : <int list -> int list> Cse583 Winterl 2002 22 Beware val s1 = Declare("x",Constant 150, Declare("y",Constant 200, While(Greater(Variable "x",Constant Seq(Assign("x",Minus(Variable Assign("y",Minus(Variable 1)))))); val ans3 = <fn stack => ~(interp2 s1 [] Cse583 Winterl 2002 0), "x",Constant 1)), "y",Constant <stack>)>; fun interp2 stmt index stack = case stmt of . . . | While(e,body) => <let val v = ~(eval2 e index stack) in if v=0 then ~stack else ~(interp2 (While(e,body)) index (interp2 body index stack)) end> 23 Compare | While(e,body) => <let fun loop stk0 = let val v = ~(eval2 e index <stk0>) in if v=0 then stk0 else let val stk1 = ~(interp2 body index <stk0>) in loop stk1 end end in loop ~stack end> | While(e,body) => <let val v = ~(eval2 e index stack) in if v=0 then ~stack else ~(interp2 (While(e,body)) index (interp2 body index stack)) end> Cse583 Winterl 2002 24 Finally, results! -| val ans3 = <(fn a => %tl (%tl (let fun b c = let val d = %get 1 c val e = if d %'>' 0 then 1 else 0 in if e %= 0 then c else let val f = %get 1 c val g = f %- 1 val h = %put 1 g c val i = %get 0 h val j = i %- 1 val k = %put 0 j h in b k end end in b (200 :: 150 :: a) end)))> : <int list -> int list> Cse583 Winterl 2002 25 Generate and optimize vs Generate optimal code -| dotprod' 3 [0,1,2]; val it = <(fn a => (0 %* %nth 0 a) %+ (1 %* %nth 1 a) %+ (2 %* %nth 2 a) %+ 0)> Rules 1*x = x 0*x = 0 x+0 = x Cse583 Winterl 2002 <fn a => 0 %+ (%nth 1 a) %+ (2 %* %nth 2 a)> 26 Writing an optimizer is not easy! (* rule 1: x+0 = x *) (* rule 2: 0*x = 0 *) (* rule 3: 1*x = x *) fun opt <fn | opt <fn opt | opt <fn | opt <fn | opt <fn | opt <fn opt | opt <fn opt | opt <fn opt | opt <fn opt | opt x = x => ~(g x => ~(g <fn y => x => 0 + x => 0 * x => ~(e x => ~(e <fn y => x => 1 * <fn y => x => ~(e <fn y => x => ~(e <fn y => x; Cse583 Winterl 2002 <x>) + 0> = opt <fn y => ~(g <y>)> <x>) + 0 + ~(h <x>)> = ~(g <y>) + ~(h <y>)> ~(g <x>)> = opt <fn y => ~(g <y>)> ~(g <x>) + ~(h <x>)> = opt <fn y => ~(h <y>)> <x>) + 0 * ~(g <x>)> = opt <fn y => ~(e <y>)> <x>) + 0 * ~(g <x>) + ~(h <x>)> = ~(e <y>) + ~(h <y>)> ~(g <x>) + ~(h <x>)> = ~(g <y>) + ~(h <y>)> <x>) + 1 * ~(g <x>)> = ~(e <y>) + ~(g <y>)> <x>) + 1 * ~(g <x>) + ~(h <x>)> = ~(e <y>) + ~(g <y>) + ~(h <y>)> 27 Optimal Generation vs Post Generation Optimizing Complexity from several sources. Walking deep over the tree Complexity of pattern matching against code (vs matching against values) Dealing with binding occurrences Optimal generation can be directed by values at generation time. Cse583 Winterl 2002 28 Better Approach fun | | | | | dpopt n [] ys = <0> dpopt n [0] ys = <0> dpopt n [1] ys = <nth ~(lift n) ~ys> dpopt n [x] ys = < ~(lift x) * (nth ~(lift n) ~ys) > dpopt n (0::xs) ys = <~(dpopt (n+1) xs ys)> dpopt n (1::xs) ys = <(nth ~(lift n) ~ys) + ~(dpopt (n+1) xs ys)> | dpopt n (x::xs) ys = <(~(lift x) * (nth ~(lift n) ~ys)) + ~(dpopt (n+1) xs ys)>; fun gen n xs = <fn ys => ~(dpopt n xs <ys>)>; val ans0 = gen 5 [2,0,1,0,4]; <(fn a => 2 %* %nth 5 a %+ %nth 7 a %+ 4 %* %nth 9 a)> Cse583 Winterl 2002 29 Conclusion Interpreters need binding time improvements Split partially static data structures into two parts. E.g. Env = index + stack Split recursion so that it depends only on structure of the data being interpreted Use transformer style for easy construction Let lifting makes code look nice. Cse583 Winterl 2002 30 Things to think about Code not very modular What if we wanted to add “print” or some other feature. Cse583 Winterl 2002 31