Lecture 4 Staging Interpreters Tim Sheard Oregon Graduate

























![Generate and optimize vs Generate optimal code -| dotprod' 3 [0, 1, 2]; val Generate and optimize vs Generate optimal code -| dotprod' 3 [0, 1, 2]; val](https://slidetodoc.com/presentation_image/60a405313e304a9ba9e0adf498415874/image-26.jpg)


![Better Approach fun | | | dpopt n [] ys = <0> dpopt n Better Approach fun | | | dpopt n [] ys = <0> dpopt n](https://slidetodoc.com/presentation_image/60a405313e304a9ba9e0adf498415874/image-29.jpg)


- Slides: 31
Lecture 4: Staging Interpreters Tim Sheard Oregon Graduate Institute CSE 510 Section FSC Winter 2004 Algorithms CSE 532, Spring 2000
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; 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); Cse 583 Winterl 2002 2
Staged solution 1 Just add annoations fun calc 2 term bindings = case term of Var s => lookup s bindings | Add(x, y) => < ~(calc 2 x bindings) + ~(calc 2 y bindings) > | Mult(x, y) => < ~(calc 2 x bindings) * ~(calc 2 y bindings) > | Const n => lift n | Local(s, x, y) => calc 2 y ((s, calc 2 x bindings): : bindings); Note how the local “let” is inlined. Cse 583 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 ans 2 = calc 2 term []; -| ans 2; val it = <6 %* 3 %+ 4 %+ 12> : <int> Cse 583 Winterl 2002 Note how the let structure has disappeared 4
Staged solution 2 fun calc 3 term bindings = case term of Var s => lookup s bindings | Add(x, y) => < ~(calc 3 x bindings) + ~(calc 3 y bindings) > | Mult(x, y) => < ~(calc 3 x bindings) * ~(calc 3 y bindings) > | Const n => lift n | Local(s, x, y) => <let val w = ~(calc 3 x bindings) in ~(calc 3 y ((s, <w>): : bindings)) end>; val ans 3 <let val in b %+ = calc 3 term []; = a = 3 %+ 4 b = 6 %* a 12 end> : <int> Cse 583 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. n In transformer style w interp: program -> <data> -> <result> n In generator style w 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. Cse 583 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 n n 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. Cse 583 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) | While of (Exp * Com) | Dec of (string * Exp * Com) Cse 583 Winterl 2002 (* 5 (* 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 *) 8
Environment ADT Lookup: string Set: string -> Ext: string -> Remove: env -> type env -> int -> 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; Cse 583 Winterl 2002 9
Simple unstaged interpeters Eval 0 : : Exp -> env -> int Value = env -> int The meaning of an exp is a value fun eval 0 exp env = case exp of Constant n => n | Variable s => lookup s env | Minus(x, y) => let val a = eval 0 x env val b = eval 0 y env in a - b end | Greater(x, y) => let val a = eval 0 x env val b = eval 0 y env in if a '>' b then 1 else 0 end | Times(x, y) => let val a = eval 0 x env val b = eval 0 y env in a * b end; Cse 583 Winterl 2002 10
Interp 0 : Com -> env The meaning of a Com is an fun interpret 0 stmt env = env transformer. case stmt of Assign(name, e) => let val v = eval 0 e env in set name v end | Seq(s 1, s 2) => let val env 1 = interpret 0 s 1 env val env 2 = interpret 0 s 2 env 1 in env 2 end | If(e, s 1, s 2) => let val x = eval 0 e env in if x=1 then interpret 0 s 1 env else interpret 0 s 2 env end | While(e, body) => let val v = eval 0 e env in if v=0 then env else interpret 0 (While(e, body))(interpret 0 body env) end | Declare(nm, e, stmt) => let val v = eval 0 e env val env 1 = ext nm v env in remove(interpret 0 stmt env 1) end; Cse 583 Winterl 2002 11
Getting ready to stage What is the structure of the source language? n Exp and Com What is the structure of the target language? n Meta. ML with “let” and operations on environments and arithmetic What are the staging issues? n What is completely known at compile-time w Exp, Com, part of the environment (the names but not the values) How do I connect the structure of the source and target languages. Cse 583 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) Cse 583 Winterl 2002 type stack = int list; eval 1 : Exp -> index -> stack -> interp 1: Com -> index -> 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 fun | | | get get put put 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"; Cse 583 Winterl 2002 14
eval 1 fun eval 1 exp index stack = case exp of Constant n => n | Variable s => get (pos s index) stack | Minus(x, y) => let val a = eval 1 x index val b = eval 1 y index in a - b end | Greater(x, y) => let val a = eval 1 x index val b = eval 1 y index in if a '>' b then 1 else | Times(x, y) => let val a = eval 1 x index val b = eval 1 y index in a * b end; Cse 583 Winterl 2002 stack 0 end stack 15
interp 1 fun interp 1 stmt index stack = case stmt of Assign(name, e) => let val v = eval 1 e index stack val loc = pos name index in put loc v stack end | Seq(s 1, s 2) => let val stack 1 = interp 1 s 1 index stack val stack 2 = interp 1 s 2 index stack 1 in stack 2 end | If(e, s 1, s 2) => let val x = eval 1 e index stack in if x=1 then interp 1 s 1 index stack else interp 1 s 2 index stack end Cse 583 Winterl 2002 16
Interp 1 (cont. ) fun interp 1 stmt index stack = case stmt of. . . | While(e, body) => let val v = eval 1 e index stack in if v=0 then stack else interp 1 (While(e, body)) index (interp 1 body index stack) end | Declare(nm, e, stmt) => let val v = eval 1 e index stack val stack 1 = v : : stack in tl (interp 1 stmt (nm: : index) stack 1) end; Cse 583 Winterl 2002 17
Adding staging annotations fun eval 2 exp index stack = case exp of Constant n => lift n | Variable s => <get ~(lift (pos s index)) ~stack> | Minus(x, y) => <let val a = ~(eval 2 x index stack) val b = ~(eval 2 y index stack) in a - b end> | Greater(x, y) => <let val a = ~(eval 2 x index stack) val b = ~(eval 2 y index stack) in if a '>' b then 1 else 0 end> | Times(x, y) => <let val a = ~(eval 2 x index stack) val b = ~(eval 2 y index stack) in a * b end>; Cse 583 Winterl 2002 18
interp 2 fun interp 2 stmt index stack = case stmt of Assign(name, e) => <let val v = ~(eval 2 e index stack) in put ~(lift (pos name index)) v ~stack end> | Seq(s 1, s 2) => <let val stack 1 = ~(interp 2 s 1 index stack) val stack 2 = ~(interp 2 s 2 index <stack 1>) in stack 2 end> | If(e, s 1, s 2) => <let val x = ~(eval 2 e index stack) in if x=1 then ~(interp 2 s 1 index stack) else ~(interp 2 s 2 index stack) end> Cse 583 Winterl 2002 19
Interp 2 (cont) fun interp 2 stmt index stack = case stmt of. . . | While(e, body) => <let val v = ~(eval 2 e index stack) in if v=0 then ~stack else ~(interp 2 (While(e, body)) index (interp 2 body index stack)) end> | Declare(nm, e, stmt) => <let val v = ~(eval 2 e index stack) val stack 1 = v : : ~stack in tl ~(interp 2 stmt (nm: : index) <stack 1>) end>; Cse 583 Winterl 2002 20
Using the staged code val s 0 = Declare("x", Constant 150, Declare("y", Constant 200, Seq(Assign("x", Minus(Variable "x", Constant 1)), Assign("y", Minus(Variable "y", Constant 1))))); val ans 2 = <fn stack => ~(interp 2 s 0 [] <stack>)>; Cse 583 Winterl 2002 21
Results -| val ans 2 = <(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)> : <int list -> int list> Cse 583 Winterl 2002 22
Beware val s 1 = Declare("x", Constant 150, Declare("y", Constant 200, While(Greater(Variable "x", Constant Seq(Assign("x", Minus(Variable Assign("y", Minus(Variable 1)))))); val ans 3 = <fn stack => ~(interp 2 s 1 [] Cse 583 Winterl 2002 0), "x", Constant 1)), "y", Constant <stack>)>; fun interp 2 stmt index stack = case stmt of. . . | While(e, body) => <let val v = ~(eval 2 e index stack) in if v=0 then ~stack else ~(interp 2 (While(e, body)) index (interp 2 body index stack)) end> 23
Compare | While(e, body) => <let fun loop stk 0 = let val v = ~(eval 2 e index <stk 0>) in if v=0 then stk 0 else let val stk 1 = ~(interp 2 body index <stk 0>) in loop stk 1 end in loop ~stack end> | While(e, body) => <let val v = ~(eval 2 e index stack) in if v=0 then ~stack else ~(interp 2 (While(e, body)) index (interp 2 body index stack)) end> Cse 583 Winterl 2002 24
Finally, results! -| val ans 3 = <(fn a => %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 in b (200 : : 150 : : a) end)))> : <int list -> int list> Cse 583 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 n n n 1*x = x 0*x = 0 x+0 = x Cse 583 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 | opt x => ~(g <fn y => x => 0 + x => 0 * x => ~(e <fn y => x => 1 * <fn y => x => ~(e <fn y => x; Cse 583 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. n n n 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. Cse 583 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 ans 0 = gen 5 [2, 0, 1, 0, 4]; <(fn a => 2 %* %nth 5 a %+ %nth 7 a %+ 4 %* %nth 9 a)> Cse 583 Winterl 2002 29
Conclusion Interpreters need binding time improvements n n 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. Cse 583 Winterl 2002 30
Things to think about Code not very modular What if we wanted to add “print” or some other feature. Cse 583 Winterl 2002 31