Files
kraken/doc/presentation.tex
2022-02-02 01:41:19 -05:00

571 lines
16 KiB
TeX

\documentclass{beamer}
%from https://www.overleaf.com/learn/latex/Beamer
%Information to be included in the title page:
\title{Efficient compilation of a functional Lisp based on Vau calculus}
\author{Nathan Braswell}
\institute{Georgia Tech}
\date{2022}
\begin{document}
\frame{\titlepage}
\begin{frame}
\frametitle{Combiners and Vau Introduction}
Motivation and examples
\begin{enumerate}
\item<1-> Vau/Combiners unify and make first class functions, macros, and built-in forms in a single simple system
\item<2-> They are also much simpler conceptually than macro systems, which often end up quite complex (Racket has positive and negative evaluation levels, etc)
\item<3-> Downside: naively executing a language using combiners instead of macros is exceedingly slow
\begin{enumerate}
\item<4-> The code of the fexpr (analogus to a macro invocation) is re-executed at runtime, every time it is encountered
\item<5-> Additionally, because it is unclear what code will be evaluated as a parameter to a function call and what code must be passed unevaluated to the combiner, little optimization can be done.
\end{enumerate}
\end{enumerate}
\end{frame}
\begin{frame}
\frametitle{Solution: Partial Eval}
\begin{enumerate}
\item<1-> Partially evaluate a purely functional version of this language in a nearly-single pass over the entire program
\item<2-> Environment chains consisting of both "real" environments with every contained symbol mapped to a value and "fake" environments that only have placeholder values.
\item<3-> Since the language is purely functional, we know that if a symbol evaluates to a value anywhere, it will always evaluate to that value at runtime, and we can perform inlining and continue partial evaluation.
\item<4-> If the resulting partially-evaluated program only contains static references to a subset of built in combiners and functions (combiners that evaluate their parameters exactly once), the program can be compiled just like it was a normal Scheme program
\end{enumerate}
\end{frame}
\begin{frame}[fragile]
\frametitle{Example time!}
\begin{enumerate}
\item<1-> We will wrap angle brackets $<>$ around values that are not representable in the syntax of the language - i.e. $+$ is a symbol that will be looked up in an environment, $<+>$ is the addition function.
\item<2-> We will use square brackets $[]$ to indiciate array values, and we will use a single quote to indicate symbol values $'$, for instance $'+$ is the symbol $+$ as a value.
\item<3-> Additionally, we will use curly braces ($\{\}$) to indicate the environment (mapping symbols to values). Elipses will be used to omit unnecessary information.
\item<4-> Finally, we will not show the static environment nested in combiners, but know that each combiner carries with it the environment it was created with, which becomes the upper environment when its body is executing (the immediate environment being populated with the parameters).
\end{enumerate}
\end{frame}
\begin{frame}[fragile]
\frametitle{A few more things..}
\begin{enumerate}
\item<1-> ; is the comment character for the language
\item<2-> We will sometimes make larger evaluation jumps for (some) brevity
\item<3-> wraplevel is how many times a combiner will evaluate its parameters before the body starts executing. 0 makes it work like a macro, 1 is like a function, etc
\item<4-> Wrap takes a combiner and returns the same combiner with an incremented wraplevel, unwrap does the reverese
\item<5-> Typing these examples by hand is too finicky, next time they'll be autogenerated with color by the prototype partial evaluator!
\end{enumerate}
\end{frame}
\begin{frame}[fragile]
\frametitle{Smallest Example}
\footnotesize
\begin{verbatim}
{ ...root environment...}
(wrap (vau (n) (* n 2)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<wrap> (vau (n) (* n 2)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<wrap> (<vau> (n) (* n 2)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<wrap> <comb wraplevel=0 (n) (* n 2)>)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
<comb wraplevel=1 (n) (* n 2)>
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\frametitle{Small Example}
\footnotesize
\begin{verbatim}
{ ...root environment...}
((wrap (vau (n) (* n 2))) (+ 2 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
((<wrap> (vau (n) (* n 2))) (+ 2 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
((<wrap> (<vau> (n) (* n 2))) (+ 2 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
((<wrap> <comb wraplevel=0 (n) (* n 2)>) (+ 2 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<comb wraplevel=1 (n) (* n 2)> (+ 2 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<comb wraplevel=1 (n) (* n 2)> (<+> 2 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<comb wraplevel=1 (n) (* n 2)> 4)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{n: 4 | upper: {...root environment...}}
(* n 2)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{n: 4 | upper: {...root environment...}}
(<*> n 2)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{n: 4 | upper: {...root environment...}}
(<*> 4 2)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
8
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\frametitle{Small Vau-specific Example (implementing quote)}
\footnotesize
\begin{verbatim}
{ ...root environment...}
((vau (x) x) hello)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{ ...root environment...}
((<vau> (x) x) hello)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{ ...root environment...}
(<comb wraplevel=0 (x) x> hello)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{ x: 'hello | upper: {...root env....}}
x
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
'hello
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\frametitle{Larger Example}
\footnotesize
\begin{verbatim}
{...root environment...}
((wrap (vau (let1)
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
; impl of let1
)) (vau de (s v b) (eval (array (array vau (array s) b) (eval v de))
de)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
((<wrap> (vau (let1)
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
; impl of let1
)) (vau de (s v b) (eval (array (array vau (array s) b) (eval v de))
de)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
((<wrap> (<vau> (let1)
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
; impl of let1
)) (vau de (s v b) (eval (array (array vau (array s) b) (eval v de))
de)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
((<wrap> <comb wraplevel=0 (let1)
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
; impl of let1
>) (vau de (s v b) (eval (array (array vau (array s) b) (eval v de))
de)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<comb wraplevel=1 (let1)
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
; impl of let1
> (vau de (s v b) (eval (array (array vau (array s) b) (eval v de))
de)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{...}
(<comb wraplevel=1 (let1)
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
; impl of let1
> <comb wraplevel=0 de (s v b) (eval (array (array vau (array s) b)
(eval v de)) de)>)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{let1: <comb wraplevel=0 de (s v b)
(eval (array (array vau (array s) b) (eval v de)) de)> | upper: {...}}
(let1 lambda (vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2)))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{let1: ... | upper: {...}}
(<comb wraplevel=0 de (s v b) (eval (array (array vau (array s) b)
(eval v de)) de)>
lambda
(vau se (p b1) (wrap (eval (array vau p b1) se)))
(lambda (n) (* n 2))
)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
Note the de in the environment being the environment the combiner was called in,
as well as the current environment's upper being not the environment with let, but
the root environment (the static environment from the function's creation).
\footnotesize
\begin{verbatim}
{s: 'lambda,
v: ['vau 'se ['p 'b1] ['wrap ['eval ['array 'vau 'p 'b1] 'se]]],
b: ['lambda ['n] ['* 'n 2]]
de: {let1: ... | upper: {root...}} | upper: {root...}}
(eval (array (array vau (array s) b) (eval v de)) de)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{s: 'lambda,
v: ['vau 'se ['p 'b1] ['wrap ['eval ['array 'vau 'p 'b1] 'se]]],
b: ['lambda ['n] ['* 'n 2]]
de: {let1: ... | upper: {root...}} | upper: {root...}}
(<eval> (array (array vau (array s) b) (eval v de)) de)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{s: 'lambda,
v: ['vau 'se ['p 'b1] ['wrap ['eval ['array 'vau 'p 'b1] 'se]]],
b: ['lambda ['n] ['* 'n 2]]
de: {let1: ... | upper: {root...}} | upper: {root...}}
(<eval> (<array> (array vau (array s) b) (eval v de)) de)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{s: 'lambda,
v: ['vau 'se ['p 'b1] ['wrap ['eval ['array 'vau 'p 'b1] 'se]]],
b: ['lambda ['n] ['* 'n 2]]
de: {let1: ... | upper: {root...}} | upper: {root...}}
(<eval> (<array> (<array> vau (array s) b) (eval v de)) de)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
Ok, evaluating all parameters of the array at the same time to be (slightly) consise.
Note the replacement of de with the de environment, and that (eval v de) was fully executed to it's value. We'll see what the execution inside of eval looks like in a minute.
\footnotesize
\begin{verbatim}
{s: 'lambda,
v: ['vau 'se ['p 'b1] ['wrap ['eval ['array 'vau 'p 'b1] 'se]]],
b: ['lambda ['n] ['* 'n 2]]
de: {let1: ... | upper: {root...}} | upper: {root...}}
(<eval> (<array> (<array> <vau> ['lambda] ['lambda ['n] ['* 'n 2]])
<comb wraplevel=0 se (p b1) (wrap (eval (array vau p b1)
se))>) {let1: ...
| upper: {root...}})
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
Again, let's take a few steps til we get the arrays as values
\footnotesize
\begin{verbatim}
(<eval> [ [ <vau> ['lambda] ['lambda ['n] ['* 'n 2]]]
<comb wraplevel=0 se (p b1) (wrap (eval (array vau p b1)
se))>] {let1: ...
| upper: {root...}})
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
Note that now that we are inside the eval call, our environment has swapped
to that specified in the call.
\footnotesize
\begin{verbatim}
{let1: ... | upper: {root...}}
( ( <vau> (lambda) (lambda (n) (* n 2)))
<comb wraplevel=0 se (p b1) (wrap (eval (array vau p b1) se))>)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{let1: ... | upper: {root...}}
( <comb wraplevel=0 (lambda) (lambda (n) (* n 2))>
<comb wraplevel=0 se (p b1) (wrap (eval (array vau p b1) se))>)
\end{verbatim}
Ok, finally the let1 has reduced to a function application
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{lambda: <comb wraplevel=0 se (p b1)
(wrap (eval (array vau p b1) se))>
| upper: {let1: ... }}
(lambda (n) (* n 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{lambda: ...}
(<comb wraplevel=0 se (p b1) (wrap (eval (array vau p b1) se))>
(n) (* n 2))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{p: ['n],
b1: ['* 'n 2],
se:{lambda: ...} | upper: {let1: ...}}
(wrap (eval (array vau p b1) se))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{p: ['n],
b1: ['* 'n 2],
se:{lambda: ...} | upper: {let1: ...}}
(<wrap> (eval (array vau p b1) se))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
We'll evaluate a few parameters at a time again
\footnotesize
\begin{verbatim}
{p: ['n],
b1: ['* 'n 2],
se:{lambda: ...} | upper: {let1: ...}}
(<wrap> (<eval> (<array> <vau> ['n] ['* 'n 2]) {lambda: ...}))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{p: ['n],
b1: ['* 'n 2],
se:{lambda: ...} | upper: {let1: ...}}
(<wrap> (<eval> [<vau> ['n] ['* 'n 2]] {lambda: ...}))
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
{p: ['n],
b1: ['* 'n 2],
se:{lambda: ...} | upper: {let1: ...}}
(<wrap> <comb wraplevel=0 (n) (* n 2)>)
\end{verbatim}
\end{frame}
\begin{frame}[fragile]
\footnotesize
\begin{verbatim}
<comb wraplevel=1 (n) (* n 2)>
\end{verbatim}
\end{frame}
\begin{frame}
\frametitle{Conclusion: slow}
\begin{enumerate}
\item<1-> Look at all of the steps it took to simply get a function value that multiplies by 2!
\item<2-> This would make our program much slower if this happened at runtime, for every function in the program.
\item<3-> What's the solution? Partial Evaluation!
\end{enumerate}
\end{frame}
\begin{frame}
\frametitle{Partial Eval: How it works}
\begin{enumerate}
\item<1-> Evaluate as much as possible ahead of time, at compile time.
\item<2-> If some call sites are indeterminate, they can still be compiled, but there will have to be a runtime check inserted that splits evaluation based on if the combiner evaluates its parameters or not, and eval and all builtins will have to be compiled into the resulting executable.
\item<3-> When compiling in the wraplevel=1 side of conditional, further partial evaluate the parameter value
\end{enumerate}
\end{frame}
\begin{frame}[fragile]
Partial evaluation could have done all the work from that last big example at compile time, leaving only the final value to be compiled:
\footnotesize
\begin{verbatim}
<comb wraplevel=1 (n) (* n 2)>
\end{verbatim}
Additionally, if this was a more complex function that used other functions, those functions would also generally be fully partially evaluated at compile time.
It's the full power of Vau/Combiners/Fexprs with the expected runtime performance of Scheme!
\end{frame}
\begin{frame}
\frametitle{Partial Eval: Current Status}
\begin{enumerate}
\item<1-> No longer *super* slow
\item<2-> Fixed most BigO algo problems (any naive traversal is exponential)
\item<3-> Otherwise, the implementation is slow (pure function, Chicken Scheme not built for it, mostly un-profiled and optimized, etc)
\item<4-> Compiles wraplevel=0 combs to a assert(false), but simple to implement.
\item<5-> Working through bugs - right now figuring out why some things don't partially evaluate as far as they should
\end{enumerate}
\end{frame}
\begin{frame}
\frametitle{Partial Eval: Future: Type System Hints}
Allow type systems to be built using Vaus, like the type-systems-as-macros paper (\url{https://www.ccs.neu.edu/home/stchang/pubs/ckg-popl2017.pdf}).
This type system could pass down type hints to the partial evaluator, enabling:
\begin{enumerate}
\item<2-> Compiletime: Drop optimizing compiled version if wraplevel=0
\item<3-> Compiletime: Drop emitting constant code for if wraplevel=1
\item<4-> Runtime: Eliminate branch on wrap level
\item<5-> Runtime: Eliminate other typechecks for builtin functions
\end{enumerate}
\end{frame}
\end{document}