new instrs, tail-call

This commit is contained in:
2026-08-28 11:39:37 -06:00
parent c0a44c89b4
commit 9cb169f9b8
4 changed files with 96 additions and 79 deletions
+3
View File
@@ -36,6 +36,7 @@ module Gyehoek.CPS.Syntax
, Abs(..) , Abs(..)
, Free(..) , Free(..)
, pattern ValLabel , pattern ValLabel
, pattern ObjLabel
, labelName -- don't like that this is part of the api , labelName -- don't like that this is part of the api
) )
where where
@@ -77,6 +78,8 @@ 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 :: Name, env :: List Obj }
+8 -3
View File
@@ -16,6 +16,7 @@ module Gyehoek.Stack.Syntax
, Prim(..) , Prim(..)
, Name , Name
, pattern ValLabel , pattern ValLabel
, pattern ObjLabel
, stkP , stkP
) where ) where
@@ -24,7 +25,7 @@ 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(..), labelName, pattern ObjLabel)
import Gyehoek.Prelude import Gyehoek.Prelude
import Gyehoek.Sexp ((:-)((:-))) import Gyehoek.Sexp ((:-)((:-)))
@@ -62,14 +63,16 @@ data Tail
-- arguments on top of the stack, then return by calling the -- arguments on top of the stack, then return by calling the
-- continuation at stack index `n+1`. -- continuation at stack index `n+1`.
= TailCall Int = TailCall Int
| Call Int
| If Val Block Block | If Val Block Block
| Return | 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 Name
| Push Val | Push Val
| Load Name
| Prim Name (Prim Val) | Prim Name (Prim Val)
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -92,6 +95,7 @@ instance S.DatumIso Instr where
datumIso = S.match datumIso = S.match
$ S.With (S.headTagged1 "pop!" regName >>>) $ S.With (S.headTagged1 "pop!" regName >>>)
$ S.With (S.headTagged1 "push!" S.datumIso >>>) $ S.With (S.headTagged1 "push!" S.datumIso >>>)
$ S.With (S.headTagged1 "load!" regName >>>)
$ S.With (S.headTagged2 "prim" regName S.datumIso >>>) $ S.With (S.headTagged2 "prim" regName S.datumIso >>>)
$ S.End $ S.End
where where
@@ -107,8 +111,9 @@ 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.With (S.headTagged1 "tail-call" S.datumIso >>>)
$ S.With (S.headTagged1 "call" S.datumIso >>>)
$ S.With (if_ >>>) $ S.With (if_ >>>)
$ S.With (S.headTagged0 "return" >>>) $ 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) S.datumIso S.datumIso
+83 -74
View File
@@ -30,24 +30,23 @@ 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 :: Name
} }
deriving (Show, Generic) deriving (Show, Generic)
data Frame = MkFrame newtype Frame = MkFrame { locals :: List Obj }
{ returnAddress :: Name
, procedure :: Name
, locals :: List Obj
}
deriving (Show, Generic) deriving (Show, Generic)
newtype Stack = MkStack (NonEmpty Frame) returnAddress :: Traversal' Frame Name
returnAddress = #locals . _last . #ObjImm . #ImmLabel
newtype Stack = MkStack { frames :: NonEmpty Frame }
deriving (Show, Generic) deriving (Show, Generic)
data VM = MkVM data VM = MkVM
{ frames :: NonEmpty Frame { stack :: Stack
, code :: List Instr , code :: List Instr
, tail :: Tail , tail :: Tail
, registers :: HashMap Name Obj , registers :: HashMap Name Obj
@@ -57,13 +56,23 @@ data VM = MkVM
} }
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
_NonEmpty :: Iso (NonEmpty a) (NonEmpty b) (a, List a) (b, List b) _NonEmpty :: Iso (NonEmpty a) (NonEmpty b) (a, List a) (b, List b)
_NonEmpty = iso _NonEmpty = iso
(\(x:|xs) -> (x,xs)) (\(x:|xs) -> (x,xs))
(\(x,xs) -> x:|xs) (\(x,xs) -> x:|xs)
stack :: Lens' VM (List Obj) pushFrame :: Frame -> Stack -> Stack
stack = #frames . _NonEmpty . _1 . #locals 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 Name Routine
@@ -80,8 +89,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
@@ -117,52 +126,59 @@ 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 Return = vmerror "aajak" stepT g vm (Return nret) =
case splitAtExact nret (vm ^. activeFrame . #locals) of
Nothing -> _
Just (_,_) -> _
stepT g vm tc@(TailCall nargs) = stepT g vm tc@(TailCall nargs) =
case setupCall nargs (vm ^. stack) of case parseTailCall nargs (vm ^. activeFrame) of
Nothing -> vmerror [i|bad stack at #{tc}|] Nothing -> vmerror [i|bad stack at #{tc}|]
Just (xs,f,rest) -> Just (args,"halt",_) -> pure $ vm & #result ?~ args
case f of Just (args,f,ra) -> do
"halt" -> pure $ vm & #result ?~ xs rt <- case g ^. #labels . at f of
l -> do Nothing -> vmerror [i|undefined label #{f}|]
rt <- case g ^. #labels . at l of Just x -> pure x
Nothing -> vmerror [i|undefined label: #{l}|] let newFrame = MkFrame $ args ++ [ObjLabel f, ObjLabel ra]
Just x -> pure x pure $ vm
let newFrame = MkFrame & #code .~ rt.start.code
{ returnAddress = & #tail .~ rt.start.tail
vm ^. #frames . _NonEmpty . _1 . #returnAddress -- replace the active frame; don't push a new one.
, procedure = f & activeFrame .~ newFrame
, locals = xs -- it is not essential we clear the registers, but it'll
} -- make bugs more obvious.
pure $ vm & #registers .~ mempty
& #code .~ rt.start.code
& #tail .~ rt.start.tail -- stepT g vm tc@(TailCall nargs) =
& stack .~ rest -- case setupCall nargs (vm ^. stack) of
& #frames %~ NE.cons newFrame -- Nothing -> vmerror [i|bad stack at #{tc}|]
-- it is not essential we clear the registers, but it'll -- Just (xs,f,rest) ->
-- make bugs more obvious. -- case f of
& #registers .~ mempty -- "halt" -> pure $ vm & #result ?~ xs
& #debug . #currentRoutine .~ rt.label -- l -> do
-- evalToLabel g vm f >>= \case -- rt <- case g ^. #labels . at l of
-- "halt" -> pure $ vm & #result ?~ xs' -- Nothing -> vmerror [i|undefined label: #{l}|]
-- l -> do -- Just x -> pure x
-- rt <- case g ^. #labels . at l of -- let ra = vm ^. #activeFrame . #returnAddress
-- Nothing -> vmerror [i|undefined label: #{l}|] -- let newFrame = MkFrame $ xs ++ [ObjLabel f, ra]
-- Just x -> pure x -- pure $ vm
-- pure $ vm & #code .~ rt.start.code -- & #code .~ rt.start.code
-- & #tail .~ rt.start.tail -- & #tail .~ rt.start.tail
-- & #registers .~ H.fromList (rt.params `zip` xs') -- & stack .~ rest
-- & #debug . #currentRoutine .~ rt.label -- & #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
@@ -188,32 +204,25 @@ splitAtExact n xs = case compareLength xs n of
(EQ;GT) -> Just $ splitAt n xs (EQ;GT) -> Just $ splitAt n xs
LT -> Nothing LT -> Nothing
setupCall :: Int -> List Obj -> Maybe (List Obj, Name, Name, List Obj) takeExact :: Int -> List a -> Maybe (List a)
setupCall n stk = do takeExact n xs = case compareLength xs n of
(xs,stk') <- splitAtExact (n+2) stk (EQ;GT) -> Just $ take n xs
case splitAtExact n xs of LT -> Nothing
Just (args, [p,ret]) -> case (p,ret) of
(ObjImm (ImmLabel p'),ObjImm (ImmLabel ret')) -> Just (args,p',ret',stk')
_ -> Nothing
_ -> error "unreachable"
setupTailCall :: Int -> List Obj -> Maybe (List Obj, Name, List Obj) parseTailCall :: Int -> Frame -> Maybe (List Obj, Name, Name)
setupTailCall n stk = do parseTailCall nargs frm = do
(xs,stk') <- splitAtExact (n+1) stk (xs,_) <- splitAtExact (nargs+1) (frm ^. #locals)
case xs ^? _Snoc of let (xs',f) = xs ^?! _Snoc
Just (args, [p,ret]) -> case (p,ret) of f' <- f ^? #ObjImm . #ImmLabel
(ObjImm (ImmLabel p'),ObjImm (ImmLabel ret')) -> Just (args,p',stk') pure (xs',f',frm ^?! returnAddress)
_ -> Nothing
_ -> error "unreachable"
initialVM :: VM initialVM :: VM
initialVM = MkVM initialVM = MkVM
{ frames = NE.singleton $ MkFrame { stack = MkStack . NE.singleton . MkFrame $
{ returnAddress = "halt" [ ObjLabel "start"
, procedure = "<nowhere at all>" , ObjLabel "<nowhere at all>"
, locals = [ ObjImm (ImmLabel "start") , ObjLabel "halt"
] ]
}
, tail = TailCall 0 , tail = TailCall 0
, code = [] , code = []
, registers = mempty , registers = mempty
@@ -359,7 +368,7 @@ ppVM vm = do
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)
@@ -369,5 +378,5 @@ ppDatum = htmlDatum . runJalmotUnsafe . S.toDatum S.datumIso
blah = [stkP| blah = [stkP|
(define $start (define $start
(return)) (return 0))
|] |]
+2 -2
View File
@@ -16,13 +16,13 @@ test_root = testGroup "stack machine"
[ testCase "immediate halt" do [ testCase "immediate halt" do
evalsTo [ObjImm (ImmInt 3)] [stkP| evalsTo [ObjImm (ImmInt 3)] [stkP|
(define $start (define $start
(tail-call 0)) (return 0))
|] |]
, testCase "lit int" do , testCase "lit int" do
evalsTo [ObjImm (ImmInt 3)] [stkP| evalsTo [ObjImm (ImmInt 3)] [stkP|
(define $start (define $start
(push! 3) (push! 3)
(tail-call 1)) (return 1))
|] |]
-- , testCase "return constant" do -- , testCase "return constant" do
-- evalsTo [ObjImm (ImmInt 123)] [stkP| -- evalsTo [ObjImm (ImmInt 123)] [stkP|