How do we get a stack trace in a lazy functional language? - Haskell
How do we get a stack trace in a lazy functional language? - Haskell
How do we get a stack trace in a lazy functional language? - Haskell
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
Why can’t I <strong>get</strong> a <strong>stack</strong> <strong>trace</strong>?<br />
Simon Marlow
Motivation
Background<br />
• A <strong>stack</strong> <strong>trace</strong> (or lexical call context) conta<strong>in</strong>s<br />
a lot of <strong>in</strong>formation, often enough to<br />
diagnose a bug.<br />
• In an imperative <strong>language</strong>, where every<br />
function call pushes a <strong>stack</strong> frame, the<br />
execution <strong>stack</strong> conta<strong>in</strong>s enough <strong>in</strong>formation<br />
to reconstruct the lexical call context.<br />
• The same isn’t true <strong>in</strong> <strong>Haskell</strong>, for various<br />
reasons...
1. Tail Call Optimisation<br />
– TCO means that important <strong>in</strong>formation about the<br />
call cha<strong>in</strong> is not reta<strong>in</strong>ed on the <strong>stack</strong><br />
– But TCO is essential, <strong>we</strong> can’t just turn it off<br />
ma<strong>in</strong> = <strong>do</strong><br />
[x] Int<br />
f x = g (x-1)<br />
g :: Int -> Int<br />
g x = 100 `div` x
2. Lazy evaluation<br />
– Lazy evaluation results <strong>in</strong> an execution <strong>stack</strong> that<br />
looks noth<strong>in</strong>g like the lexical call <strong>stack</strong>.<br />
– When a computation is suspended (a thunk) <strong>we</strong><br />
should capture the call <strong>stack</strong> and store it with the<br />
thunk.<br />
ma<strong>in</strong> = <strong>do</strong><br />
[x] Int<br />
g x = 100 `div` x
3. Transformation and optimisation<br />
– <strong>we</strong> <strong>do</strong> not want the transformations <strong>do</strong>ne by<br />
GHC’s optimiser to lose <strong>in</strong>formation or mangle<br />
the call <strong>stack</strong>.<br />
– <strong>we</strong>’ve already established that strictness analysis<br />
should not distort the <strong>stack</strong>.<br />
– But even <strong>in</strong>l<strong>in</strong><strong>in</strong>g a function will lose <strong>in</strong>formation<br />
if <strong>we</strong> aren’t careful.
4. Even if <strong>we</strong> fix 1—3, high-level abstractions<br />
like monads result <strong>in</strong> strange <strong>stack</strong>s<br />
– examples com<strong>in</strong>g...<br />
• We need a framework for th<strong>in</strong>k<strong>in</strong>g about the<br />
issues.
A construct for push<strong>in</strong>g on the <strong>stack</strong><br />
push L E<br />
• “push label L on the <strong>stack</strong> while evaluat<strong>in</strong>g E”<br />
• this is a construct of the source <strong>language</strong> and the<br />
<strong>in</strong>termediate <strong>language</strong> (Core)<br />
• Compiler can add these automatically, or the user can add<br />
them<br />
• Th<strong>in</strong>k {-# SCC .. #-} <strong>in</strong> GHC<br />
• We <strong>get</strong> to choose how detailed <strong>we</strong> want to be:<br />
– exported functions only<br />
– top-level functions only<br />
– all functions (good for profil<strong>in</strong>g)<br />
– call sites (good for debugg<strong>in</strong>g)<br />
– all sub-expressions (f<strong>in</strong>e-gra<strong>in</strong>ed debugg<strong>in</strong>g or profil<strong>in</strong>g)
• Def<strong>in</strong>e <strong>stack</strong>s:<br />
type Stack = [Label]<br />
push :: Label -> Stack -> Stack<br />
call :: Stack -> Stack -> Stack
• Def<strong>in</strong>e <strong>stack</strong>s:<br />
type Stack = [Label]<br />
push :: Label -> Stack -> Stack<br />
call :: Stack -> Stack -> Stack<br />
<strong>stack</strong> at the call site
• Def<strong>in</strong>e <strong>stack</strong>s:<br />
type Stack = [Label]<br />
push :: Label -> Stack -> Stack<br />
call :: Stack -> Stack -> Stack<br />
<strong>stack</strong> at the call site<br />
<strong>stack</strong> of the<br />
function
• Def<strong>in</strong>e <strong>stack</strong>s:<br />
type Stack = [Label]<br />
push :: Label -> Stack -> Stack<br />
call :: Stack -> Stack -> Stack<br />
<strong>stack</strong> at the call site<br />
<strong>stack</strong> of the<br />
function<br />
<strong>stack</strong> for the call
Executable semantics<br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i)<br />
eval stk (ELam x e) = return (stk, ELam x e)<br />
eval stk (EPush l e) = eval (push l stk) e<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EApp f x) = <strong>do</strong><br />
(lam_stk, ELam y e)
Executable semantics<br />
current <strong>stack</strong><br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i)<br />
eval stk (ELam x e) = return (stk, ELam x e)<br />
eval stk (EPush l e) = eval (push l stk) e<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EApp f x) = <strong>do</strong><br />
(lam_stk, ELam y e)
Executable semantics<br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i) E is a State monad<br />
eval stk (ELam x e) = return (stk, ELam x conta<strong>in</strong><strong>in</strong>g e) the Heap:<br />
a mapp<strong>in</strong>g from Var<br />
eval stk (EPush l e) = eval (push l stk) e to (Stack,Expr)<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EApp f x) = <strong>do</strong><br />
(lam_stk, ELam y e)
Executable semantics<br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i)<br />
eval stk (ELam x e) = return (stk, ELam x e)<br />
Values are<br />
straightforward<br />
eval stk (EPush l e) = eval (push l stk) e<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EApp f x) = <strong>do</strong><br />
(lam_stk, ELam y e)
Executable semantics<br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i)<br />
eval stk (ELam x e) = return (stk, ELam x e)<br />
eval stk (EPush l e) = eval (push l stk) e<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EApp f x) = <strong>do</strong><br />
(lam_stk, ELam y e)
Executable semantics<br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i)<br />
eval stk (ELam x e) = return (stk, ELam x e)<br />
eval stk (EPush l e) = eval (push l stk) e<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
suspend the<br />
eval stk (EApp f x) = <strong>do</strong><br />
computation e1 on the<br />
(lam_stk, ELam y e)
Executable semantics<br />
eval :: Stack -> Expr -> E (Stack,Expr)<br />
eval stk (EInt i) = return (stk, EInt i)<br />
eval stk (ELam x e) = return (stk, ELam x e)<br />
eval stk (EPush l e) = eval (push l stk) e<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EApp f x) = <strong>do</strong><br />
(lam_stk, ELam y e)
Executable semantics (variables)<br />
eval stk (EVar x) = <strong>do</strong><br />
r <strong>do</strong><br />
deleteHeap x<br />
(stkv, v)
Given this semantics, def<strong>in</strong>e push & call<br />
• The problem now is to f<strong>in</strong>d suitable def<strong>in</strong>itions<br />
of push and call that<br />
– Behave like a call <strong>stack</strong><br />
– Have nice properties:<br />
• transformation-friendly<br />
• predictable/robust<br />
• implementable
Lazy evaluation is dealt with<br />
• Lazy evaluation is dealt<br />
with by<br />
– captur<strong>in</strong>g the current<br />
<strong>stack</strong> when <strong>we</strong> suspend a<br />
computation as a thunk<br />
<strong>in</strong> the heap<br />
– temporarily restor<strong>in</strong>g the<br />
<strong>stack</strong> when the thunk is<br />
evaluated<br />
• Noth<strong>in</strong>g controversial at<br />
all – <strong>we</strong> just need a<br />
mechanism for captur<strong>in</strong>g<br />
and restor<strong>in</strong>g the <strong>stack</strong>.<br />
eval stk (ELet (x,e1) e2) = <strong>do</strong><br />
<strong>in</strong>sertHeap x (stk,e1)<br />
eval stk e2<br />
eval stk (EVar x) = <strong>do</strong><br />
r
Tail calls are dealt with<br />
• The semantics says noth<strong>in</strong>g about tail calls –<br />
push always pushes on the <strong>stack</strong>.<br />
• Even if the underly<strong>in</strong>g execution model is<br />
<strong>do</strong><strong>in</strong>g TCO, the call <strong>stack</strong> simulation must not.
Examples<br />
f = λ x. push “f” x+x<br />
ma<strong>in</strong> = λ x. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> f y<br />
• The heap is <strong>in</strong>itialised with the top-level b<strong>in</strong>d<strong>in</strong>gs (give<br />
each the <strong>stack</strong> )<br />
• When <strong>we</strong> <strong>get</strong> to (f y), current <strong>stack</strong> is <br />
• f is already evaluated<br />
• call = <br />
• eval (push f y+y)<br />
• eval (y+y)<br />
• at the +, the current <strong>stack</strong> is <br />
Let’s assume, for now,<br />
call Sapp Slam = Sapp
Use the call-site <strong>stack</strong>?<br />
call sapp slam = sapp<br />
• Previous example suggests this might be a<br />
good choice?<br />
• After all, this gives exactly the call <strong>stack</strong> you<br />
would <strong>get</strong> <strong>in</strong> a strict <strong>language</strong>
But <strong>we</strong> have to be careful<br />
• If <strong>in</strong>stead of this:<br />
f = λx. push “f” x+x<br />
ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> f y<br />
• We wrote this:<br />
f = push “f” (λx . x+x)<br />
ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> f y<br />
• Now it <strong>do</strong>esn’t work so <strong>we</strong>ll: the “f” label is lost.<br />
• In this semantics, the scope of push <strong>do</strong>es not<br />
extend <strong>in</strong>to lambdas
Just label all the lambdas?<br />
• Idea: make the compiler label all the lambdas<br />
automatically<br />
• e.g. the compiler <strong>in</strong>serts a push <strong>in</strong>side any<br />
lambda:<br />
f = push “f” (λx . push “f1” x+x)<br />
ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> f y<br />
• Now <strong>we</strong> <strong>get</strong> a useful <strong>stack</strong> aga<strong>in</strong>:
Some properties<br />
• Add<strong>in</strong>g an extra b<strong>in</strong>d<strong>in</strong>g <strong>do</strong>esn’t change the<br />
<strong>stack</strong><br />
f = push “f” (λ x . push “f1” x+x)<br />
g = push “g” f<br />
ma<strong>in</strong> = λ x. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> g y<br />
• In this semantics ‘push L x == x’<br />
• arguably useful: the <strong>stack</strong> is robust with<br />
respect to this transformation (by the<br />
compiler or user)
But...<br />
• eta-expansion changes the <strong>stack</strong><br />
f = push “f” (λ x . push “f1” x+x)<br />
g = λ x . push “g” f x<br />
ma<strong>in</strong> = λ x. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> g y<br />
• Now the <strong>stack</strong> at the + will be
Concrete example<br />
• When <strong>we</strong> tried this for real, <strong>we</strong> found that <strong>in</strong><br />
functions like<br />
h = f . g<br />
• h <strong>do</strong>es not appear on the <strong>stack</strong>, although <strong>in</strong><br />
h x = (f . g) x<br />
• now it <strong>do</strong>es. This is surpris<strong>in</strong>g and<br />
undesirable.
Worse...<br />
• Let’s make a state monad:<br />
newtype M s a = M { unM :: s -> (s,a) }<br />
<strong>in</strong>stance Monad (M s) where<br />
(M m) >>= k = M $ λ s -> case m s of<br />
(s',a) -> unM (k a) s'<br />
return a = M $ λ s -> (s,a)<br />
errorM :: Str<strong>in</strong>g -> M s a<br />
errorM s = M $ λ _ -> error s<br />
runM :: M s a -> s -> a<br />
runM (M m) s = case m s of (_,a) -> a<br />
Suppose <strong>we</strong> want the<br />
<strong>stack</strong> when error is<br />
called, for debugg<strong>in</strong>g
Us<strong>in</strong>g a monad<br />
• Simple example:<br />
ma<strong>in</strong> = pr<strong>in</strong>t (runM (bar ["a","b"]) "state")<br />
bar :: [Str<strong>in</strong>g] -> M s [Str<strong>in</strong>g]<br />
bar xs = mapM foo xs<br />
foo :: Str<strong>in</strong>g -> M s Str<strong>in</strong>g<br />
foo x = errorM x
Us<strong>in</strong>g a monad<br />
• Simple example:<br />
ma<strong>in</strong> = pr<strong>in</strong>t (runM (bar ["a","b"]) "state")<br />
bar :: [Str<strong>in</strong>g] -> M s [Str<strong>in</strong>g]<br />
bar xs = mapM foo xs<br />
foo :: Str<strong>in</strong>g -> M s Str<strong>in</strong>g<br />
foo x = errorM x<br />
• We are look<strong>in</strong>g for a <strong>stack</strong> like<br />
Us<strong>in</strong>g a monad<br />
• Simple example:<br />
ma<strong>in</strong> = pr<strong>in</strong>t (runM (bar ["a","b"]) "state")<br />
bar :: [Str<strong>in</strong>g] -> M s [Str<strong>in</strong>g]<br />
bar xs = mapM foo xs<br />
foo :: Str<strong>in</strong>g -> M s Str<strong>in</strong>g<br />
foo x = errorM x<br />
• We are look<strong>in</strong>g for a <strong>stack</strong> like<br />
<br />
• Stack <strong>we</strong> <strong>get</strong>:
Why?<br />
• Take a typical monadic function:<br />
f = <strong>do</strong> p; q<br />
• Desurag<strong>in</strong>g gives<br />
f = p >> q<br />
• Add<strong>in</strong>g push:<br />
f = push “f” (p >> q)<br />
• Expand<strong>in</strong>g out (>>):<br />
f = push “f” (λ s -> case p s of (a,s’) -> b s’)<br />
• recall that push L (λ x . e) = λ x. e
The IO monad<br />
• In GHC the IO monad is def<strong>in</strong>ed like the state<br />
monad given earlier.<br />
• We found that with this <strong>stack</strong> semantics, <strong>we</strong><br />
<strong>get</strong> no useful <strong>stack</strong>s for IO monad code at all.<br />
• When profil<strong>in</strong>g, all the costs <strong>we</strong>re attributed<br />
to ma<strong>in</strong>.
call S app S lam = S app ?<br />
• We recovered the non-<strong>lazy</strong> non-TCO call <strong>stack</strong>,<br />
which is the <strong>stack</strong> you would <strong>get</strong> <strong>in</strong> a strict<br />
<strong>functional</strong> <strong>language</strong>.<br />
• But it isn’t good enough.<br />
– at least when used with monads or other highlevel<br />
<strong>functional</strong> abstractions
Can <strong>we</strong> f<strong>in</strong>d a better semantics?<br />
• call S app S lam = ?<br />
• non-starter: call S app S lam = S lam<br />
– ignores the call<strong>in</strong>g context<br />
– gives a purely lexical <strong>stack</strong>, not a call <strong>stack</strong><br />
– (possibly useful for flat profil<strong>in</strong>g though)<br />
• Clearly <strong>we</strong> want to take <strong>in</strong>to account both S app<br />
and S lam somehow.
The def<strong>in</strong>itions I want to use<br />
call Sapp Slam = Sapp ++ Slam’<br />
where (Spre, Sapp’, Slam’) = commonPrefix Sapp Slam<br />
push l s | l `elem` s = dropWhile (/= l) s<br />
| otherwise = l : s<br />
• Behaves nicely with <strong>in</strong>l<strong>in</strong><strong>in</strong>g:<br />
– “common prefix” is <strong>in</strong>tended to capture the call<br />
<strong>stack</strong> up to the po<strong>in</strong>t where the function was<br />
def<strong>in</strong>ed<br />
• useful for profil<strong>in</strong>g/debugg<strong>in</strong>g: the top-of<strong>stack</strong><br />
label is always correct, <strong>we</strong> just truncate<br />
the <strong>stack</strong> on recursion.
Status<br />
• GHC 7.4.1 has a new implementation of<br />
profil<strong>in</strong>g us<strong>in</strong>g push<br />
• +RTS –xc pr<strong>in</strong>ts the call <strong>stack</strong> when an<br />
exception is raised<br />
• Programmatic access to the call <strong>stack</strong>:
Status<br />
• GHC 7.4.1 has a new implementation of<br />
profil<strong>in</strong>g us<strong>in</strong>g push<br />
• +RTS –xc pr<strong>in</strong>ts the call <strong>stack</strong> when an<br />
exception is raised<br />
• Programmatic access to the call <strong>stack</strong>:<br />
-- | like '<strong>trace</strong>', but additionally pr<strong>in</strong>ts a call<br />
-- <strong>stack</strong> if one is available.<br />
<strong>trace</strong>Stack :: Str<strong>in</strong>g -> a -> a<br />
-- | like ‘error’, but <strong>in</strong>cludes a call <strong>stack</strong><br />
errorWithStackTrace :: Str<strong>in</strong>g –> a
Demo
Programmatic access to <strong>stack</strong> <strong>trace</strong><br />
• The GHC.Stack module provides runtime<br />
access to the <strong>stack</strong> <strong>trace</strong><br />
• On top of which is built this:<br />
• e.g. now when GHC panics it emits a <strong>stack</strong><br />
<strong>trace</strong> (if it was compiled with profil<strong>in</strong>g)
Programmatic access to <strong>stack</strong> <strong>trace</strong><br />
• The GHC.Stack module provides runtime<br />
access to the <strong>stack</strong> <strong>trace</strong><br />
• On top of which is built this:<br />
-- | like '<strong>trace</strong>', but additionally pr<strong>in</strong>ts a call <strong>stack</strong> if one is<br />
-- available.<br />
<strong>trace</strong>Stack :: Str<strong>in</strong>g -> a -> a<br />
• e.g. now when GHC panics it emits a <strong>stack</strong><br />
<strong>trace</strong> (if it was compiled with profil<strong>in</strong>g)
Properties<br />
• This semantics has some nice properties.<br />
push L x<br />
=> x<br />
push L (λx . e) => λx . e<br />
push L (C x1 .. xn) => C x1 .. xn<br />
let x = λy . e <strong>in</strong> push L e'<br />
=> push L (let x = λy . e <strong>in</strong> e')<br />
push L (let x = e <strong>in</strong> e')<br />
=> let x = push L e <strong>in</strong> push L e
Properties<br />
• This semantics has some nice properties.<br />
push L x<br />
=> x<br />
push L (λx . e) => λx . e<br />
push L (C x1 .. xn) => C x1 .. xn<br />
s<strong>in</strong>ce the <strong>stack</strong><br />
attached to a<br />
lambda is<br />
irrelevant (except<br />
for heap profil<strong>in</strong>g)<br />
let x = λy . e <strong>in</strong> push L e'<br />
=> push L (let x = λy . e <strong>in</strong> e')<br />
push L (let x = e <strong>in</strong> e')<br />
=> let x = push L e <strong>in</strong> push L e
Properties<br />
• This semantics has some nice properties.<br />
push L x<br />
=> x<br />
push L (λx . e) => λx . e<br />
push L (C x1 .. xn) => C x1 .. xn<br />
O(1) change to cost<br />
attribution, no<br />
change to profile<br />
shape<br />
let x = λy . e <strong>in</strong> push L e'<br />
=> push L (let x = λy . e <strong>in</strong> e')<br />
push L (let x = e <strong>in</strong> e')<br />
=> let x = push L e <strong>in</strong> push L e
Properties<br />
• This semantics has some nice properties.<br />
push L x<br />
=> x<br />
push L (λx . e) => λx . e<br />
push L (C x1 .. xn) => C x1 .. xn<br />
let x = λy . e <strong>in</strong> push L e'<br />
=> push L (let x = λy . e <strong>in</strong> e')<br />
push L (let x = e <strong>in</strong> e')<br />
=> let x = push L e <strong>in</strong> push L e<br />
Note if e is a<br />
value, the push<br />
L will disappear
Inl<strong>in</strong><strong>in</strong>g<br />
• We expect to be able to substitute a function’s<br />
def<strong>in</strong>ition for its name without affect<strong>in</strong>g the<br />
<strong>stack</strong>. e.g.<br />
f = λx . push “f1” x+x<br />
ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong> f y<br />
• should be the same as<br />
ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />
let y = 1 <strong>in</strong><br />
(λx . push “f1” x+x) y<br />
• and <strong>in</strong>deed it is <strong>in</strong> this semantics.<br />
– (<strong>in</strong>l<strong>in</strong><strong>in</strong>g functions is crucial for optimisation <strong>in</strong> GHC)
Th<strong>in</strong>k about what properties <strong>we</strong> want<br />
• Push <strong>in</strong>side lambda:<br />
push L (λ x. e) == λ x. push L e<br />
– (recall that the previous semantics allo<strong>we</strong>d<br />
dropp<strong>in</strong>g the push here)<br />
– This will give us a push that scopes over the <strong>in</strong>side<br />
of lambdas, not just outside.<br />
• which will <strong>in</strong> turn give us that <strong>stack</strong>s are robust to etaexpansion/contraction
What <strong>do</strong>es it take to make this true?<br />
• Consider<br />
let f = push “f” λ x . e<br />
<strong>in</strong> ... f ...<br />
let f = λ x . push “f” e<br />
<strong>in</strong> ... f ...<br />
• If <strong>we</strong> work through the details, <strong>we</strong> f<strong>in</strong>d that<br />
<strong>we</strong> need<br />
call S (push L S f ) == push L (call S S f )<br />
• Not difficult: e.g.<br />
type Stack = [Label]<br />
push = (:)<br />
call = foldr push<br />
like flip (++), but<br />
useful to def<strong>in</strong>e it<br />
this way
Recursion?<br />
• We <strong>do</strong> want f<strong>in</strong>ite <strong>stack</strong>s<br />
– the mutator is us<strong>in</strong>g tail recursion<br />
• Simplest approach: push is a no-op if the label<br />
is already on the <strong>stack</strong> somewhere:<br />
push l s | l `elem` s = s<br />
| otherwise = l : s<br />
• still satisfies the push-<strong>in</strong>side-lambda property<br />
• but: not so good for profil<strong>in</strong>g or debugg<strong>in</strong>g<br />
– the label on top of the <strong>stack</strong> is not necessarily<br />
where the program counter is
Inl<strong>in</strong><strong>in</strong>g of functions<br />
• (remember, allow<strong>in</strong>g <strong>in</strong>l<strong>in</strong><strong>in</strong>g is crucial)<br />
• Consider<br />
let g = λ x.e <strong>in</strong><br />
let f = push “f” g <strong>in</strong><br />
f y<br />
let f = push “f” λ x.e <strong>in</strong><br />
f y<br />
• Work through the details, and <strong>we</strong> need that<br />
call (push L S) S == push L S<br />
• <strong>in</strong>terest<strong>in</strong>g: call<strong>in</strong>g a function whose <strong>stack</strong> is a<br />
prefix of the current <strong>stack</strong> should not change<br />
the <strong>stack</strong>.
Break out the proof tools
Break out the proof tools<br />
• QuickCheck.
Break out the proof tools<br />
• QuickCheck.<br />
prop_append2 = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />
forAll Ma<strong>in</strong>.labels $ \x -><br />
call (s `push` x) s == s `push` x<br />
*** Failed! Falsifiable (after 8 tests and 2 shr<strong>in</strong>ks):<br />
(E :> "e") :> "b"<br />
"e"
Break out the proof tools<br />
• QuickCheck.<br />
prop_append2 = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />
forAll Ma<strong>in</strong>.labels $ \x -><br />
call (s `push` x) s == s `push` x<br />
*** Failed! Falsifiable (after 8 tests and 2 shr<strong>in</strong>ks):<br />
(E :> "e") :> "b"<br />
"e"<br />
• but this corresponds to<br />
someth<strong>in</strong>g very<br />
strange:
Break out the proof tools<br />
• QuickCheck.<br />
prop_append2 = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />
forAll Ma<strong>in</strong>.labels $ \x -><br />
call (s `push` x) s == s `push` x<br />
*** Failed! Falsifiable (after 8 tests and 2 shr<strong>in</strong>ks):<br />
(E :> "e") :> "b"<br />
"e"<br />
• but this corresponds to<br />
someth<strong>in</strong>g very<br />
strange:<br />
push “f”<br />
...<br />
let g = λ x.e <strong>in</strong><br />
let f = push “f” g <strong>in</strong><br />
f y
A more restricted property<br />
• This is a limited form of the real property <strong>we</strong><br />
need for <strong>in</strong>l<strong>in</strong><strong>in</strong>g<br />
• The push-<strong>in</strong>side-lambda property behaves<br />
similarly: <strong>we</strong> need to restrict the use of duplicate<br />
labels to make it go through.
A more restricted property<br />
prop_<strong>stack</strong>2a = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />
forAll Ma<strong>in</strong>.labels $ \x -><br />
x `elem<strong>stack</strong>` s ||<br />
call (s `push` x) s == s `push` x<br />
*Ma<strong>in</strong>> quickCheck prop_<strong>stack</strong>2a<br />
+++ OK, passed 100 tests.<br />
• This is a limited form of the real property <strong>we</strong><br />
need for <strong>in</strong>l<strong>in</strong><strong>in</strong>g<br />
• The push-<strong>in</strong>side-lambda property behaves<br />
similarly: <strong>we</strong> need to restrict the use of duplicate<br />
labels to make it go through.