Compare commits

5 Commits
Author SHA1 Message Date
msyds 9d5140c0e2 return, pushcall
build / build (push) Failing after 13m21s
2026-08-28 11:39:37 -06:00
msyds 5ccb3f3e1a register & label newtypes 2026-08-28 11:39:37 -06:00
msyds 9cb169f9b8 new instrs, tail-call 2026-08-28 11:39:37 -06:00
msyds c0a44c89b4 wip: stack frames 2026-08-28 11:39:37 -06:00
msyds 49292d5d01 wip: call/cc primitives 2026-08-28 11:39:37 -06:00
10 changed files with 506 additions and 177 deletions
+88
View File
@@ -0,0 +1,88 @@
* example
#+begin_src scheme
(letrec ((fac (λ (n)
(if (zero? n)
1
(* n (fac (- n 1)))))))
(fac 3))
#+end_src
#+begin_src scheme
(λ (ktail0)
(letrec ((fac
(λ (n ktail1)
(zero?
n
(κ (x0)
(if x0
(continue ktail1 1)
(- n 1
(κ (x1)
(fac x1
(κ (x2)
(* n x2 ktail1)))))))))))
(fac 3)))
#+end_src
#+begin_example
n ktail1
| |
| | x0
| | |
| | ^
| |
| | x1
| | |
| | ^
| |
| | x2
| | |
^ ^ ^
#+end_example
#+begin_src scheme
(define $fac-c0
(pop! %x0 0) ; [ x0 $fac-c0 n $fac ktail1 ]
(if %x0 ; [ $fac-c0 n $fac ktail1 ]
;; every variable but `ktail1' is dead so we pop them all.
;; this probably means that `if' should take two continuations
;; rather than two blocks.
(then (pop! %_) ; [ $fac-c0 n $fac ktail1 ]
(pop! %_) ; [ n $fac ktail1 ]
(pop! %_) ; [ $fac ktail1 ]
(push! 1) ; [ ktail1 ]
(return 1)) ; [ 1 ktail1 ]
(else (load! %n 1) ; [ $fac-c0 n $fac ktail1 ]
(prim %x1 (- %n 1)) ; [ $fac-c0 n $fac ktail1 ]
(push! $fac-c1) ; [ $fac-c0 n $fac ktail1 ]
(push! $fac) ; [ $fac-c1 $fac-c0 n $fac ktail1 ]
(push! %x1) ; [ $fac $fac-c1 $fac-c0 n $fac ktail1 ]
(call 1) ; [ x1 $fac $fac-c1 $fac-c0 n $fac ktail1 ]
)))
(define $fac-c1
(pop! %x2) ; [ x2 $fac-c1 $fac-c0 n $fac ktail1 ]
(load! %n 3) ; [ $fac-c1 $fac-c0 n $fac ktail1 ]
(prim %x3 (* %n %x2))
(push! %x3) ; [ $fac-c1 $fac-c0 n $fac ktail1 ]
(return 1) ; [ x3 $fac-c1 $fac-c0 n $fac ktail1 ]
)
(define $fac
(load! %ktail1 2) ; [ n $fac ktail1 ]
(load! %n 0) ; [ n $fac ktail1 ]
(push! $fac-c0) ; [ n $fac ktail1 ]
(push! $zero?) ; [ $fac-c0 n $fac ktail1 ]
(push! %n) ; [ $zero? $fac-c0 n $fac ktail1 ]
(call 1) ; [ n $zero? $fac-c0 n $fac ktail1 ]
)
(define $start
(push! $fac) ; [ $start ktail0 ]
(push! 3) ; [ $fac $start ktail0 ]
(tail-call 1) ; [ 3 $fac $start ktail0 ]
;; ↑ `tail-call' knows how to dispose of the caller's stack frame.
)
#+end_src
+68
View File
@@ -0,0 +1,68 @@
* rationale?
previously, the VM's stack was used for storing local variables across blocks; a Scheme procedure was split into several low-level routines (one for the procedure itself and one for each continuation), and the stack was used as a communication channel for these separate routines. in contrast, registers were local to each routine. this aligns with Wasm's model of functions pretty well, with Wasm /locals/ acting as the VM's /registers/, and a global mutable stack serving as fallback.
this worked quite well until it became time to implement ~call/cc~.
we are considering making the following alterations to the VM:
- explicitly segment the stack into frames.
- passing procedures and return addresses on the stack.
* scratchpad
** Scheme source
#+begin_src scheme
(* 2 (call/cc
(λ (cc)
(begin (cc 6)
3))))
#+end_src
** CPS
#+begin_src scheme
(λ (ktail0)
(letrec ((with-cc
(λ (cc ktail1)
(letrec ((k0 (κ (_)
(continue ktail1 3))))
(cc 6 k0)))))
(prim (call/cc with-cc)
(κ (x1)
(prim (* 2 x1)
(κ (x2) (continue ktail0 x2)))))))
#+end_src
** stack VM
#+begin_src scheme
;; (call n) expects `n' values on the stack as arguments. then the
;; procedure is expected at index `n', and the return continuation
;; should be at `n+1'.
(define $k0
(pop! %_) ; [ _ ret ]
(push! 3) ; [ ret ]
(tail-call 1) ; [ 3 ret ]
)
(define $with-cc
(pop! %cc) ; [ cc ret ]
(push! $k0) ; [ ret ]
(push! %cc) ; [ $k0 ret ]
(push! 6) ; [ cc $k0 ret ]
;; call a procedure with one argument.
(call 1) ; [ 6 cc $k0 ret ]
)
(define $main
(pop! %ktail0) ; [ ret ]
(prim %x1 (call/cc $with-cc)) ; []
(prim %x2 (* 2 %x1)) ; []
(push! %ktail0) ; []
(push! %x2) ; [ ret ]
(tail-call 1) ; [ %x2 ret ]
)
#+end_src
+12 -12
View File
@@ -47,18 +47,18 @@ convert (Scm.ExpLit l) k = k . one . ValImm $ case l of
_ -> _ _ -> _
-- special case: call/cc is desugared during cps-conversion... -- special case: call/cc is desugared during cps-conversion...
convert (Scm.ExpPrim (PrimCallCC withcc)) k = do -- convert (Scm.ExpPrim (PrimCallCC withcc)) k = do
convert1 withcc \withcc' -> do -- convert1 withcc \withcc' -> do
cc <- gensym' @Name "cc" -- cc <- gensym' @Name "cc"
r <- gensym' "r" -- r <- gensym' "r"
m <- k . one $ ValVar r -- m <- k . one $ ValVar r
ccish <- gensym' @Name "cc-ish" -- ccish <- gensym' @Name "cc-ish"
x <- gensym' @Name "x" -- x <- gensym' @Name "x"
pure [cps| -- pure [cps|
(letrec ((#{cc} (κ (#{r}) #{m}))) -- (letrec ((#{cc} (κ (#{r}) #{m})))
(letrec ((#{ccish} (λ (#{x} _) (continue #{cc} #{x})))) -- (letrec ((#{ccish} (λ (#{x} _) (continue #{cc} #{x}))))
(#{withcc'} #{ccish} #{cc}))) -- (#{withcc'} #{ccish} #{cc})))
|] -- |]
-- ...while all other prims are left as-is for later stages to -- ...while all other prims are left as-is for later stages to
-- handle.. -- handle..
+38 -46
View File
@@ -48,12 +48,12 @@ stackify
=> Env -> Exp -> Eff es BlockBuilder => Env -> Exp -> Eff es BlockBuilder
stackify g (ExpLetRec [(f, AbsKappa kap)] e) = do stackify g (ExpLetRec [(f, AbsKappa kap)] e) = do
stackifyKappa g f kap \g' kap' -> do stackifyKappa g (MkLabel f) kap \g' kap' -> do
emitRoutine kap' emitRoutine kap'
stackify g' e stackify g' e
stackify g (ExpLetRec [(f, AbsLambda lam)] e) = do stackify g (ExpLetRec [(f, AbsLambda lam)] e) = do
stackifyLambda g f lam \g' lam' -> do stackifyLambda g (MkLabel f) lam \g' lam' -> do
emitRoutine lam' emitRoutine lam'
stackify g' e stackify g' e
@@ -63,69 +63,61 @@ stackify g (ExpIf c t f) = do
f' <- buildBlock <$> stackify g f f' <- buildBlock <$> stackify g f
pure . Tail $ Stk.If c' t' f' pure . Tail $ Stk.If c' t' f'
stackify g (ExpApply f xs ktail) = pure $ -- stackify g (ExpApply f xs ktail) = pure $
Code [ Stk.Push (Stk.ValReg l) | l <- ls ] $ -- Code [ Stk.Push (Stk.ValReg l) | l <- ls ] $
Tail (Stk.TailCall (stackifyVal g f) (k : (stackifyVal g <$> xs))) -- Tail (Stk.TailCall (stackifyVal g f) (k : (stackifyVal g <$> xs)))
where -- where
k = var g ktail -- k = var g ktail
ls = fold $ (k ^? #ValImm . #ImmLabel) -- ls = fold $ (k ^? #ValImm . #ImmLabel)
>>= \klbl -> g ^. #liveness . at klbl -- >>= \klbl -> g ^. #liveness . at klbl
stackify g e@(ExpContinue k xs) = do -- stackify g e@(ExpContinue k xs) = do
pure $ -- pure $
Code [ Stk.Push (Stk.ValReg l) | l <- ls ] $ -- Code [ Stk.Push (Stk.ValReg l) | l <- ls ] $
Tail (Stk.TailCall k' (stackifyVal g <$> xs)) -- Tail (Stk.TailCall k' (stackifyVal g <$> xs))
where -- where
k' = stackifyVal g k -- k' = stackifyVal g k
ls = fold $ (k' ^? #ValImm . #ImmLabel) -- ls = fold $ (k' ^? #ValImm . #ImmLabel)
>>= \klbl -> g ^. #liveness . at klbl -- >>= \klbl -> g ^. #liveness . at klbl
-- stackify g (ExpPrim (PrimCallCC withcc) cc) = do
-- cc_l <- gensym' "cc"
-- rcc_l <- gensym' "reified-cc"
-- stackifyKappa g cc_l cc \g' rt -> do
-- emitRoutine rt
-- pure $
-- Code [ Stk.Prim rcc_l $ PrimReifyCC (Stk.ValLabel cc_l) ] $
-- Tail (Stk.TailCall (stackifyVal g' withcc) [Stk.ValLabel rcc_l])
stackify g (ExpPrim p (MkKappa [x] e)) = do stackify g (ExpPrim p (MkKappa [x] e)) = do
e' <- stackify (g & #bound . at x ?~ Stk.ValReg x) e e' <- stackify (g & #bound . at x ?~ Stk.ValReg (MkReg x)) e
pure $ pure $
Code [ Stk.Prim x (stackifyVal g <$> p) ] e' Code [ Stk.Prim (MkReg x) (stackifyVal g <$> p) ] e'
stackify _ e = error [i|unimplemented exp: #{e}|] stackify _ e = error [i|unimplemented exp: #{e}|]
-- affine -- affine
_ValName :: Traversal' Val Name _ValName :: Traversal' Val Name
_ValName = failing #ValVar (#ValImm . #ImmLabel) _ValName = failing #ValVar (#ValImm . #ImmLabel . #MkLabel)
stackifyKappa stackifyKappa
:: (Stackify :> es, GenSym :> es) :: (Stackify :> es, GenSym :> es)
=> Env -> Name -> Kappa => Env -> Label -> Kappa
-> (Env -> Stk.Routine -> Eff es r) -> (Env -> Stk.Routine -> Eff es r)
-> Eff es r -> Eff es r
stackifyKappa g name kap@(MkKappa xs m) w = do stackifyKappa g name kap@(MkKappa xs m) w = _
let vs = (name, Stk.ValLabel name) : (bindReg <$> xs) -- stackifyKappa g name kap@(MkKappa xs m) w = do
let ls = live g kap -- let vs = (name, Stk.ValLabel name) : (bindReg <$> xs)
m' <- stackify (g & #bound <>~ H.fromList (vs ++ (bindReg <$> ls))) m -- let ls = live g kap
let g' = g & #bound . at name ?~ Stk.ValLabel name -- m' <- stackify (g & #bound <>~ H.fromList (vs ++ (bindReg <$> ls))) m
& #liveness . at name ?~ live g kap -- let g' = g & #bound . at name ?~ Stk.ValLabel name
let rt = Stk.MkRoutine name xs . buildBlock $ -- & #liveness . at name ?~ live g kap
-- pop in the opposite order we push -- let rt = Stk.MkRoutine name xs . buildBlock $
Code [Stk.Pop x | x <- reverse ls] m' -- -- pop in the opposite order we push
w g' rt -- Code [Stk.Pop x | x <- reverse ls] m'
-- w g' rt
stackifyLambda stackifyLambda
:: (Stackify :> es, GenSym :> es) :: (Stackify :> es, GenSym :> es)
=> Env -> Name -> Lambda => Env -> Label -> Lambda
-> (Env -> Stk.Routine -> Eff es r) -> (Env -> Stk.Routine -> Eff es r)
-> Eff es r -> Eff es r
stackifyLambda g name (MkLambda xs k m) w = do stackifyLambda g name (MkLambda xs k m) w = do
let vs = [ (x, Stk.ValReg x) | x <- k:xs ] let vs = [ (x, Stk.ValReg (MkReg x)) | x <- k:xs ]
m' <- stackify (g & #bound <>~ H.fromList vs) m m' <- stackify (g & #bound <>~ H.fromList vs) m
let g' = g & #bound . at name ?~ Stk.ValLabel name let g' = g & #bound . at (name ^. wrappedIso) ?~ Stk.ValLabel name
w g' $ Stk.MkRoutine name (k:xs) (buildBlock m') w g' $ Stk.MkRoutine name (buildBlock m')
stackifyVal :: Env -> Val -> Stk.Val stackifyVal :: Env -> Val -> Stk.Val
stackifyVal g = \case stackifyVal g = \case
@@ -136,10 +128,10 @@ stackifyVal g = \case
var :: Env -> Name -> Stk.Val var :: Env -> Name -> Stk.Val
var g v = case g ^. #bound . at v of var g v = case g ^. #bound . at v of
Just x -> x Just x -> x
Nothing -> Stk.ValLabel v Nothing -> Stk.ValLabel (MkLabel v)
bindReg :: Name -> (Name, Stk.Val) bindReg :: Name -> (Name, Stk.Val)
bindReg x = (x, Stk.ValReg x) bindReg x = (x, Stk.ValReg (MkReg x))
+37 -13
View File
@@ -18,6 +18,8 @@ module Gyehoek.CPS.Syntax
, Imm(..) , Imm(..)
, Obj(..) , Obj(..)
, Hob(..) , Hob(..)
, Label(..)
, Reg(..)
, pattern Halt , pattern Halt
, pattern Halt1 , pattern Halt1
, _MkKappa , _MkKappa
@@ -36,7 +38,7 @@ module Gyehoek.CPS.Syntax
, Abs(..) , Abs(..)
, Free(..) , Free(..)
, pattern ValLabel , pattern ValLabel
, labelName -- don't like that this is part of the api , pattern ObjLabel
) )
where where
@@ -52,6 +54,8 @@ import Gyehoek.Prelude hiding (op)
import Gyehoek.Sexp (Datum) import Gyehoek.Sexp (Datum)
import Gyehoek.Sexp (G, (:-)(..)) import Gyehoek.Sexp (G, (:-)(..))
import qualified Data.InvertibleGrammar.Base as IG import qualified Data.InvertibleGrammar.Base as IG
import Gyehoek.GenSym (Gen)
import Data.String (IsString)
-- Data types -- Data types
@@ -60,13 +64,23 @@ data Val
| ValVar Name | ValVar Name
deriving (Show, Generic, Data, Eq) deriving (Show, Generic, Data, Eq)
pattern ValLabel :: Name -> Val pattern ValLabel :: Label -> Val
pattern ValLabel x = ValImm (ImmLabel x) pattern ValLabel x = ValImm (ImmLabel x)
newtype Label = MkLabel { inner :: Name }
deriving stock (Generic, Data)
deriving newtype (Show, Eq, Gen, IsString, Hashable)
deriving anyclass (NFData, Wrapped)
newtype Reg = MkReg { inner :: Name }
deriving stock (Generic, Data)
deriving newtype (Show, Eq, Gen, IsString, Hashable)
deriving anyclass (NFData, Wrapped)
data Imm data Imm
= ImmInt Int = ImmInt Int
| ImmBool Bool | ImmBool Bool
| ImmLabel Name | ImmLabel Label
| ImmUndefined | ImmUndefined
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -77,9 +91,11 @@ data Obj
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
pattern ObjLabel l = ObjImm (ImmLabel l)
-- | a heap object. -- | a heap object.
data Hob data Hob
= HobClosure { label :: Name, env :: List Obj } = HobClosure { label :: Label, env :: List Obj }
| HobPair Obj Obj | HobPair Obj Obj
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -169,17 +185,25 @@ instance S.DatumIso Imm where
datumIso = S.match datumIso = S.match
$ S.With (. S.int) $ S.With (. S.int)
$ S.With (. S.datumIso) $ S.With (. S.datumIso)
$ S.With (. labelName) $ S.With (. S.datumIso)
$ S.With (. S.unreadable (const "#<undefined>")) $ S.With (. S.unreadable (const "#<undefined>"))
$ S.End $ S.End
labelName :: S.DatumGrammar Name instance S.DatumIso Label where
labelName = S.coproduct datumIso = S.with \g -> S.coproduct
[ S.decorate S.SynConstant >>> S.datumIso @Name >>> S.prismIso [ S.decorate S.SynConstant >>> S.datumIso @Name >>> S.prismIso
(S.expected "label") (S.expected "label")
(prefixed @Name "$") (prefixed @Name "$")
, S.list $ S.el (S.sym "$") >>> S.el (S.datumIso @Name) , S.list $ S.el (S.sym "$") >>> S.el (S.datumIso @Name)
] ]
>>> g
instance S.DatumIso Reg where
datumIso = S.with \g ->
S.decorate S.SynVariable >>> S.datumIso @Name >>> S.prismIso
(S.expected "register")
(prefixed @Name "%")
>>> g
instance S.DatumIso Hob where instance S.DatumIso Hob where
datumIso = S.match datumIso = S.match
@@ -189,7 +213,7 @@ instance S.DatumIso Hob where
where where
conspair = S.dottedList (S.el S.datumIso) S.datumIso conspair = S.dottedList (S.el S.datumIso) S.datumIso
-- closures can be printed, but not parsed. -- closures can be printed, but not parsed.
closure :: G (Datum :- t) (List Obj :- Name :- t) closure :: G (Datum :- t) (List Obj :- Label :- t)
closure = IG.Flip $ IG.PartialIso closure = IG.Flip $ IG.PartialIso
(\(env:-code:-t) -> S.Unreadable "#<procedure>" :- t) (\(env:-code:-t) -> S.Unreadable "#<procedure>" :- t)
(const . Left $ mempty) (const . Left $ mempty)
+2
View File
@@ -19,6 +19,7 @@ module Gyehoek.Prelude
, (>>>) , (>>>)
, (>=>) , (>=>)
, (<=<) , (<=<)
, wrappedIso
) where ) where
import Control.Lens hiding (List, (:<)) import Control.Lens hiding (List, (:<))
@@ -40,4 +41,5 @@ import Data.List.NonEmpty (NonEmpty((:|)))
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import Control.Category ((>>>)) import Control.Category ((>>>))
import Control.Monad import Control.Monad
import Data.Generics.Wrapped (Wrapped(..))
+4 -2
View File
@@ -84,6 +84,7 @@ data Prim e
| PrimEnvRef e Int | PrimEnvRef e Int
| PrimEnvCode e | PrimEnvCode e
| PrimCallCC e | PrimCallCC e
| PrimCaptureCC
| PrimValues (List e) | PrimValues (List e)
| PrimCallWithValues e e | PrimCallWithValues e e
deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq) deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
@@ -164,17 +165,18 @@ primDatumIso namefn a = S.match
$ S.With (. ht1 "integer?") $ S.With (. ht1 "integer?")
$ S.With (. ht1 "write") $ S.With (. ht1 "write")
$ S.With (. ht1 "zero?") $ S.With (. ht1 "zero?")
$ S.With (. nullop "newline") $ S.With (. ht0 "newline")
$ S.With (. ht1' "make-closure") $ S.With (. ht1' "make-closure")
$ S.With (. S.headTagged2 (namefn "env-ref") a S.int) $ S.With (. S.headTagged2 (namefn "env-ref") a S.int)
$ S.With (. ht1 "env-code") $ S.With (. ht1 "env-code")
$ S.With (. ht1 "call/cc") $ S.With (. ht1 "call/cc")
$ S.With (. ht0 "capture/cc")
$ S.With (. ht0' "values") $ S.With (. ht0' "values")
$ S.With (. ht2 "call-with-values") $ S.With (. ht2 "call-with-values")
$ S.End $ S.End
where where
idn = S.el . S.sym . namefn idn = S.el . S.sym . namefn
nullop s = S.list $ idn s ht0 s = S.list $ idn s
ht1 s = S.headTagged1 (namefn s) a ht1 s = S.headTagged1 (namefn s) a
ht2 s = S.headTagged2 (namefn s) a a ht2 s = S.headTagged2 (namefn s) a a
ht1' s = S.headTagged1' (namefn s) a a ht1' s = S.headTagged1' (namefn s) a a
+27 -20
View File
@@ -14,8 +14,11 @@ module Gyehoek.Stack.Syntax
, Imm(..) , Imm(..)
, Hob(..) , Hob(..)
, Prim(..) , Prim(..)
, Name , Name(..)
, Reg(..)
, Label(..)
, pattern ValLabel , pattern ValLabel
, pattern ObjLabel
, stkP , stkP
) where ) where
@@ -24,13 +27,13 @@ import qualified Gyehoek.Sexp as S
import Gyehoek.Scheme.Syntax (Name(..), Lit(..), Prim(..)) import Gyehoek.Scheme.Syntax (Name(..), Lit(..), Prim(..))
import GHC.Exts (IsList(..)) import GHC.Exts (IsList(..))
import Data.List (intersperse) import Data.List (intersperse)
import Gyehoek.CPS.Syntax (Imm(..), Obj(..), Hob(..), labelName) import Gyehoek.CPS.Syntax (Imm(..), Obj(..), Hob(..), pattern ObjLabel, Reg, Label)
import Gyehoek.Prelude import Gyehoek.Prelude
import Gyehoek.Sexp ((:-)((:-))) import Gyehoek.Sexp ((:-)((:-)))
newtype Program = MkProgram newtype Program = MkProgram
{ routines :: HashMap Name Routine { routines :: HashMap Label Routine
} }
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving newtype (Semigroup, Monoid) deriving newtype (Semigroup, Monoid)
@@ -44,8 +47,7 @@ instance IsList Program where
toList = toListOf $ #routines . each toList = toListOf $ #routines . each
data Routine = MkRoutine data Routine = MkRoutine
{ label :: Name { label :: Label
, params :: List Name
, start :: Block , start :: Block
} }
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
@@ -59,25 +61,31 @@ data Block = MkBlock
deriving anyclass (NFData) deriving anyclass (NFData)
data Tail data Tail
= TailCall Val (List Val) -- | call the procedure at stack index `n` supplied with `n`
-- arguments on top of the stack, then return by calling the
-- continuation at stack index `n+1`.
= TailCall Int
| Call Int
| If Val Block Block | If Val Block Block
| Return Int
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving anyclass (NFData) deriving anyclass (NFData)
data Instr data Instr
= Pop Name = Pop Reg
| Push Val | Push Val
| Prim Name (Prim Val) | Load Reg
| Prim Reg (Prim Val)
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving anyclass (NFData) deriving anyclass (NFData)
data Val data Val
= ValReg Name = ValReg Reg
| ValImm Imm | ValImm Imm
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
pattern ValLabel :: Name -> Val pattern ValLabel :: Label -> Val
pattern ValLabel x = ValImm (ImmLabel x) pattern ValLabel x = ValImm (ImmLabel x)
@@ -87,9 +95,10 @@ pure []
instance S.DatumIso Instr where instance S.DatumIso Instr where
datumIso = S.match datumIso = S.match
$ S.With (S.headTagged1 "pop!" regName >>>) $ S.With (S.headTagged1 "pop!" S.datumIso >>>)
$ S.With (S.headTagged1 "push!" S.datumIso >>>) $ S.With (S.headTagged1 "push!" S.datumIso >>>)
$ S.With (S.headTagged2 "prim" regName S.datumIso >>>) $ S.With (S.headTagged1 "load!" S.datumIso >>>)
$ S.With (S.headTagged2 "prim" S.datumIso S.datumIso >>>)
$ S.End $ S.End
where where
@@ -103,10 +112,13 @@ instance S.DataIso Block where
instance S.DatumIso Tail where instance S.DatumIso Tail where
datumIso = S.match datumIso = S.match
$ S.With (S.headTagged1' "tail-call" S.datumIso S.datumIso >>>) $ S.With (S.headTagged1 "tail-call" S.datumIso >>>)
$ S.With (S.headTagged1 "call" S.datumIso >>>)
$ S.With (if_ >>>) $ S.With (if_ >>>)
$ S.With (S.headTagged1 "return" S.datumIso >>>)
$ S.End $ S.End
where where
-- if_ = S.ifLike "if" (S.datumIso @Val) S.datumIso S.datumIso
if_ = S.ifLike "if" (S.datumIso @Val) (branch "then") (branch "else") if_ = S.ifLike "if" (S.datumIso @Val) (branch "then") (branch "else")
branch :: Text -> S.DatumGrammar Block branch :: Text -> S.DatumGrammar Block
branch s = branch s =
@@ -116,7 +128,7 @@ instance S.DatumIso Tail where
instance S.DatumIso Val where instance S.DatumIso Val where
datumIso = S.match datumIso = S.match
$ S.With (regName >>>) $ S.With (S.datumIso >>>)
$ S.With (S.datumIso >>>) $ S.With (S.datumIso >>>)
$ S.End $ S.End
@@ -124,16 +136,11 @@ instance S.DatumIso Routine where
datumIso = S.with \rout -> datumIso = S.with \rout ->
S.listWithIndentation (S.NSpecial 1) S.listWithIndentation (S.NSpecial 1)
( S.el (S.decorate S.SynBuiltin >>> S.sym "define") ( S.el (S.decorate S.SynBuiltin >>> S.sym "define")
>>> S.el (S.list $ S.el labelName >>> S.rest regName) >>> S.el (S.datumIso @Label)
>>> S.restData (S.dataIso @Block) >>> S.restData (S.dataIso @Block)
) )
>>> rout >>> rout
regName :: S.DatumGrammar Name
regName = S.decorate S.SynVariable >>> S.datumIso @Name >>> S.prismIso
(S.expected "register")
(prefixed @Name "%")
instance S.DataIso Program where instance S.DataIso Program where
dataIso = S.dataIso @(List Routine) >>> S.iso fromList toList dataIso = S.dataIso @(List Routine) >>> S.iso fromList toList
+156 -28
View File
@@ -12,7 +12,7 @@ module Gyehoek.Stack.VM
import Gyehoek.Stack.Syntax import Gyehoek.Stack.Syntax
import Control.Lens import Control.Lens
import qualified Data.HashMap.Strict as H import qualified Data.HashMap.Strict as H
import Data.List (unfoldr, intersperse) import Data.List (unfoldr, intersperse, compareLength)
import Gyehoek.Prelude import Gyehoek.Prelude
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import Lucid import Lucid
@@ -30,25 +30,55 @@ import Control.DeepSeq (deepseq, ($!!))
import Data.String (fromString) import Data.String (fromString)
-- | inessential information maintained only to aide in debugging. -- | non-essential information maintained only to aide in debugging.
data DebugVM = MkDebugVM data DebugVM = MkDebugVM
{ currentRoutine :: Name { currentRoutine :: Label
} }
deriving (Show, Generic) deriving (Show, Generic)
newtype Frame = MkFrame { locals :: List Obj }
deriving (Show, Generic)
returnAddress :: Traversal' Frame Label
returnAddress = #locals . _last . #ObjImm . #ImmLabel
newtype Stack = MkStack { frames :: NonEmpty Frame }
deriving (Show, Generic)
data VM = MkVM data VM = MkVM
{ stack :: List Obj { stack :: Stack
, code :: List Instr , code :: List Instr
, tail :: Tail , tail :: Tail
, registers :: HashMap Name Obj , registers :: HashMap Reg Obj
, stdout :: Text , stdout :: Text
, result :: Maybe (List Obj) , result :: Maybe (List Obj)
, debug :: DebugVM , debug :: DebugVM
} }
deriving (Show, Generic) deriving (Show, Generic)
instance Cons Frame Frame Obj Obj where
_Cons = prism'
(\(x,MkFrame xs) -> MkFrame (x:xs))
\case
MkFrame (x:xs) -> Just (x, MkFrame xs)
MkFrame [] -> Nothing
pushes :: Foldable f => f Obj -> Frame -> Frame
pushes = flip $ foldr cons
_NonEmpty :: Iso (NonEmpty a) (NonEmpty b) (a, List a) (b, List b)
_NonEmpty = iso
(\(x:|xs) -> (x,xs))
(\(x,xs) -> x:|xs)
pushFrame :: Frame -> Stack -> Stack
pushFrame f (MkStack xs) = MkStack $ NE.cons f xs
activeFrame :: Lens' VM Frame
activeFrame = #stack . #frames . _NonEmpty . _1
data Env = MkEnv data Env = MkEnv
{ labels :: HashMap Name Routine { labels :: HashMap Label Routine
} }
deriving (Show, Generic) deriving (Show, Generic)
@@ -62,8 +92,8 @@ vmerror = throwError . VMError
stepI :: Jalmot :> es => Env -> VM -> Instr -> Eff es VM stepI :: Jalmot :> es => Env -> VM -> Instr -> Eff es VM
stepI e vm (Push v) = traverseOf #stack push vm stepI e vm (Push v) = traverseOf activeFrame push vm
where push xs = (:) <$> evalVal e vm v <*> pure xs where push xs = cons <$> evalVal e vm v <*> pure xs
stepI e vm (Prim r p) = traverse (evalVal e vm) p >>= \case stepI e vm (Prim r p) = traverse (evalVal e vm) p >>= \case
PrimZeroP x -> case x of PrimZeroP x -> case x of
@@ -99,27 +129,84 @@ stepI e vm (Prim r p) = traverse (evalVal e vm) p >>= \case
ret $ ObjImm (ImmInt (op x y)) ret $ ObjImm (ImmInt (op x y))
arith_binop _ x y = vmerror [i|bad arith: #{x}, #{y}|] arith_binop _ x y = vmerror [i|bad arith: #{x}, #{y}|]
stepI e vm (Pop r) = case vm ^. #stack of stepI e vm (Pop r) = case vm ^? activeFrame . _Cons of
[] -> vmerror "empty stack" Nothing -> vmerror "empty stack"
(x:xs) -> pure $ vm & #registers . at r ?~ x Just (x,xs) -> pure $ vm & #registers . at r ?~ x
& #stack .~ xs & activeFrame .~ xs
stepI e vm ins = vmerror [i|unimplemented instruction: #{ins}|] stepI e vm ins = vmerror [i|unimplemented instruction: #{ins}|]
stepT :: Jalmot :> es => Env -> VM -> Tail -> Eff es VM stepT :: Jalmot :> es => Env -> VM -> Tail -> Eff es VM
stepT g vm (TailCall f xs) = do stepT g vm tc@(Call nargs) =
xs' <- traverse (evalVal g vm) xs case parseCall nargs (vm ^. activeFrame) of
evalToLabel g vm f >>= \case Nothing -> vmerror "bla"
"halt" -> pure $ vm & #result ?~ xs' Just (args,f,ret,frm) ->
l -> do case g ^. #labels . at ret of
rt <- case g ^. #labels . at l of Nothing -> vmerror [i|undefined label #{ret}|]
Nothing -> vmerror [i|undefined label: #{l}|] Just rt -> do
Just x -> pure x let newFrame = MkFrame $ args ++ [ObjLabel f,ObjLabel ret]
pure $ vm & #code .~ rt.start.code pure $ vm
& #code .~ rt.start.code
& #tail .~ rt.start.tail & #tail .~ rt.start.tail
& #registers .~ H.fromList (rt.params `zip` xs') & activeFrame .~ frm
& #debug . #currentRoutine .~ rt.label & #stack %~ pushFrame newFrame
& #registers .~ mempty
stepT g vm tc@(Return nret) =
case splitAtExact nret (vm ^. activeFrame . #locals) of
Nothing -> vmerror [i|bad stack at #{tc}|]
Just (xs,_) ->
case vm ^? activeFrame . returnAddress of
Nothing -> vmerror [i|bad stack #{tc}|]
Just "halt" -> pure $ vm & #result ?~ xs
Just ra ->
case g ^. #labels . at ra of
Nothing -> vmerror [i|undefined label #{ra}|]
Just rt -> vm
& traverseOf (#stack . #frames) \st -> case NE.uncons st of
(_, Nothing) -> vmerror "explode"
(f, Just fs) -> pure $ fs & _NonEmpty . _1 %~ pushes xs
stepT g vm tc@(TailCall nargs) =
case parseTailCall nargs (vm ^. activeFrame) of
Nothing -> vmerror [i|bad stack at #{tc}|]
Just (args,"halt",_) -> pure $ vm & #result ?~ args
Just (args,f,ra) -> do
rt <- case g ^. #labels . at f of
Nothing -> vmerror [i|undefined label #{f}|]
Just x -> pure x
let newFrame = MkFrame $ args ++ [ObjLabel f, ObjLabel ra]
pure $ vm
& #code .~ rt.start.code
& #tail .~ rt.start.tail
-- replace the active frame; don't push a new one.
& activeFrame .~ newFrame
-- it is not essential we clear the registers, but it'll
-- make bugs more obvious.
& #registers .~ mempty
-- stepT g vm tc@(TailCall nargs) =
-- case setupCall nargs (vm ^. stack) of
-- Nothing -> vmerror [i|bad stack at #{tc}|]
-- Just (xs,f,rest) ->
-- case f of
-- "halt" -> pure $ vm & #result ?~ xs
-- l -> do
-- rt <- case g ^. #labels . at l of
-- Nothing -> vmerror [i|undefined label: #{l}|]
-- Just x -> pure x
-- let ra = vm ^. #activeFrame . #returnAddress
-- let newFrame = MkFrame $ xs ++ [ObjLabel f, ra]
-- pure $ vm
-- & #code .~ rt.start.code
-- & #tail .~ rt.start.tail
-- & stack .~ rest
-- & #frames %~ NE.cons newFrame
-- -- it is not essential we clear the registers, but it'll
-- -- make bugs more obvious.
-- & #registers .~ mempty
-- & #debug . #currentRoutine .~ rt.label
stepT g vm (If c t f) = do stepT g vm (If c t f) = do
branch <- evalVal g vm c <&> \case branch <- evalVal g vm c <&> \case
@@ -127,7 +214,7 @@ stepT g vm (If c t f) = do
_ -> t _ -> t
pure $ vm & #code .~ branch.code & #tail .~ branch.tail pure $ vm & #code .~ branch.code & #tail .~ branch.tail
evalToLabel :: Jalmot :> es => Env -> VM -> Val -> Eff es Name evalToLabel :: Jalmot :> es => Env -> VM -> Val -> Eff es Label
evalToLabel e vm v = evalToLabel e vm v =
evalVal e vm v >>= \case evalVal e vm v >>= \case
ObjImm (ImmLabel x) -> pure x ObjImm (ImmLabel x) -> pure x
@@ -140,11 +227,40 @@ evalVal e vm = \case
Just x -> pure x Just x -> pure x
Nothing -> vmerror [i|undefined register: #{r}|] Nothing -> vmerror [i|undefined register: #{r}|]
splitAtExact :: Int -> List a -> Maybe (List a, List a)
splitAtExact n xs = case compareLength xs n of
(EQ;GT) -> Just $ splitAt n xs
LT -> Nothing
takeExact :: Int -> List a -> Maybe (List a)
takeExact n xs = case compareLength xs n of
(EQ;GT) -> Just $ take n xs
LT -> Nothing
parseCall :: Int -> Frame -> Maybe (List Obj, Label, Label, Frame)
parseCall nargs frm = do
(xs,ys) <- splitAtExact (nargs+2) (frm ^. #locals)
let (xs',[f,ret]) = splitAt nargs xs
f' <- f ^? #ObjImm . #ImmLabel
ret' <- ret ^? #ObjImm . #ImmLabel
pure (xs',f',ret',MkFrame ys)
parseTailCall :: Int -> Frame -> Maybe (List Obj, Label, Label)
parseTailCall nargs frm = do
(xs,_) <- splitAtExact (nargs+1) (frm ^. #locals)
let (xs',f) = xs ^?! _Snoc
f' <- f ^? #ObjImm . #ImmLabel
pure (xs',f',frm ^?! returnAddress)
initialVM :: VM initialVM :: VM
initialVM = MkVM initialVM = MkVM
{ stack = [] { stack = MkStack . NE.singleton . MkFrame $
[ ObjLabel "start"
, ObjLabel "<nowhere at all>"
, ObjLabel "halt"
]
, tail = TailCall 0
, code = [] , code = []
, tail = TailCall (ValLabel "start") [ValLabel "halt"]
, registers = mempty , registers = mempty
, stdout = "" , stdout = ""
, result = Nothing , result = Nothing
@@ -282,16 +398,28 @@ ppVM vm = do
details_ do details_ do
summary_ do summary_ do
var_ [class_ "loc"] . toHtml $ vm ^. #debug . #currentRoutine var_ [class_ "loc"] . toHtml $ vm ^. #debug . #currentRoutine
. re (_Unwrapped' . prefixed "$") . re (prefixed "$" . _Unwrapped' . _Unwrapped')
pre_ do pre_ do
code_ . toHtml . pShowNoColor $ vm code_ . toHtml . pShowNoColor $ vm
td_ do td_ do
code_ curi code_ curi
td_ do td_ do
let xs = code_ . ppDatum <$> (vm ^. #stack) let xs = _
sequence_ $ intersperse " | " xs sequence_ $ intersperse " | " xs
where where
curi = vm ^?! failing (#code . _head . to ppDatum) (#tail . to ppDatum) curi = vm ^?! failing (#code . _head . to ppDatum) (#tail . to ppDatum)
ppDatum :: S.DatumIso a => a -> Html () ppDatum :: S.DatumIso a => a -> Html ()
ppDatum = htmlDatum . runJalmotUnsafe . S.toDatum S.datumIso ppDatum = htmlDatum . runJalmotUnsafe . S.toDatum S.datumIso
blah = [stkP|
(define $id
(return 1))
(define $c
(return 1))
(define $start
(push! $c)
(push! $id)
(push! 123)
(call 1))
|]
+74 -56
View File
@@ -13,64 +13,82 @@ evalsTo :: List Obj -> Program -> Assertion
evalsTo rs p = runJalmotUnsafe (Sut.eval p) @?= rs evalsTo rs p = runJalmotUnsafe (Sut.eval p) @?= rs
test_root = testGroup "stack machine" test_root = testGroup "stack machine"
[ testCase "lit int" do [ testCase "immediate halt" do
evalsTo [] [stkP|
(define $start
(return 0))
|]
, testCase "lit int" do
evalsTo [ObjImm (ImmInt 3)] [stkP| evalsTo [ObjImm (ImmInt 3)] [stkP|
(define ($start %ktail) (define $start
(tail-call %ktail 3)) (push! 3)
(return 1))
|] |]
, testCase "return constant" do , testCase "non-tail identity function" do
evalsTo [ObjImm (ImmInt 123)] [stkP| evalsTo [ObjImm (ImmInt 123)] [stkP|
(define ($start %ktail) (define $id
(tail-call $silly %ktail)) (return 1))
(define ($silly %ktail) (define $c
(tail-call %ktail 123)) (return 1))
(define $start
(push! $c)
(push! $id)
(push! 123)
(call 1))
|] |]
, testCase "identity continuation" do -- , testCase "return constant" do
evalsTo [ObjImm (ImmInt 45)] [stkP| -- evalsTo [ObjImm (ImmInt 123)] [stkP|
(define ($start %ktail) -- (define ($start %ktail)
(push! %ktail) -- (tail-call $silly %ktail))
(tail-call $id 45)) -- (define ($silly %ktail)
(define ($id %x) -- (tail-call %ktail 123))
(pop! %ktail) -- |]
(tail-call %ktail %x)) -- , testCase "identity continuation" do
|] -- evalsTo [ObjImm (ImmInt 45)] [stkP|
, testCase "identity function" do -- (define ($start %ktail)
evalsTo [ObjImm (ImmInt 45)] [stkP| -- (push! %ktail)
(define ($start %ktail) -- (tail-call $id 45))
(tail-call $id 45 %ktail)) -- (define ($id %x)
(define ($id %x %ktail) -- (pop! %ktail)
(tail-call %ktail %x)) -- (tail-call %ktail %x))
|] -- |]
, testCase "square" do -- , testCase "identity function" do
evalsTo [ObjImm (ImmInt 16)] [stkP| -- evalsTo [ObjImm (ImmInt 45)] [stkP|
(define ($start %ktail) -- (define ($start %ktail)
(tail-call $square 4 %ktail)) -- (tail-call $id 45 %ktail))
(define ($square %x %ktail) -- (define ($id %x %ktail)
(prim %x2 (* %x %x)) -- (tail-call %ktail %x))
(tail-call %ktail %x2)) -- |]
|] -- , testCase "square" do
, testCase "factorial" do -- evalsTo [ObjImm (ImmInt 16)] [stkP|
let hsfac (n :: Int) = foldr (*) (1) [1..n] -- (define ($start %ktail)
let fac (n :: Int) = [stkP| -- (tail-call $square 4 %ktail))
(define ($fac %n %ktail) -- (define ($square %x %ktail)
(prim %x0 (zero? %n)) -- (prim %x2 (* %x %x))
(if %x0 -- (tail-call %ktail %x2))
(then (tail-call %ktail 1)) -- |]
(else (push! %n) -- , testCase "factorial" do
(push! %ktail) -- let hsfac (n :: Int) = foldr (*) (1) [1..n]
(prim %x1 (- %n 1)) -- let fac (n :: Int) = [stkP|
(tail-call $fac %x1 $fac-k0)))) -- (define ($fac %n %ktail)
(define ($fac-k0 %x2) -- (prim %x0 (zero? %n))
(pop! %ktail) -- (if %x0
(pop! %n) -- (then (tail-call %ktail 1))
(prim %x3 (* %x2 %n)) -- (else (push! %n)
(tail-call %ktail %x3)) -- (push! %ktail)
(define ($start %ktail) -- (prim %x1 (- %n 1))
(tail-call $fac #{n} %ktail)) -- (tail-call $fac %x1 $fac-k0))))
|] -- (define ($fac-k0 %x2)
evalsTo [ObjImm (ImmInt 1)] $ fac 0 -- (pop! %ktail)
evalsTo [ObjImm (ImmInt 1)] $ fac 1 -- (pop! %n)
evalsTo [ObjImm (ImmInt 720)] $ fac 6 -- (prim %x3 (* %x2 %n))
-- 20 is the greatest `n` for which n! ≤ maxBount @Int -- (tail-call %ktail %x3))
evalsTo [ObjImm (ImmInt 2432902008176640000)] $ fac 20 -- (define ($start %ktail)
-- (tail-call $fac #{n} %ktail))
-- |]
-- evalsTo [ObjImm (ImmInt 1)] $ fac 0
-- evalsTo [ObjImm (ImmInt 1)] $ fac 1
-- evalsTo [ObjImm (ImmInt 720)] $ fac 6
-- -- 20 is the greatest `n` for which n! ≤ maxBount @Int
-- evalsTo [ObjImm (ImmInt 2432902008176640000)] $ fac 20
] ]