packages feed

hs2bf-0.6: GMachine.hs

-- | GMachine
-- reference: Implementing Functional Languages: a tutorial
--
-- GC is executed every 256 allocation.
module GMachine where
import Control.Arrow
import Control.Monad
import Control.Monad.State
import Control.Monad.Identity
import Data.Ord
import Data.Char
import Data.List
import Data.Maybe
import qualified Data.Map as M
import qualified Data.Set as S

import Util as U hiding(Pack)
import qualified Util as U
import SRuntime
import SAM


data GFCompileFlag=GFCompileFlag
    {addrSpace :: Int -- ^ bytes
    }


-- | Compile 'GMCode's to SAM
--
-- See my blog (japanese) for overview of operational model.
--
-- Heap frame of size k with n-byte address:
--
-- * 1 B: size of this frame
--
-- * 1 B: GC tag
--
-- * k B: payload
--
-- * n B: id of this frame
-- 
-- * 1 B: size of this frame 
--
-- Heap frame of size k with n-byte address:
--
-- Is it a good idea to remove GC tag, and attach it only when GC is running?
--   (PRO:normally faster,CON:slower gcTransfer)
--
--
-- Heap payload:
--
-- You can return from anywhere on stack to origin, but not from heap.
compile :: M.Map String [GMCode] -> Process SAM
compile m
    |codeSpace>1          = error "GM->SAM: 255+ super combinator is not supported"
    |heapSpace>1          = error "GM->SAM: 2+ byte addresses are not supported"
    |M.notMember "main" m = error "GM->SAM: entry point not found"
    |otherwise            = return $ SAM (ss++hs) (library++dispatcher++procs)
    where
        t=M.fromList $ ("main",2):zip (filter (/="main") $ M.keys m) [3..]
        
        -- code generation
        library=genLibrary $ S.toList $ S.unions $ map (S.unions . map collectConArity) $ M.elems m
        procs=map (uncurry $ compileProc t) $ M.assocs m
        dispatcher=[exec $ M.assocs t]
                
        -- layout configuration
        codeSpace=ceiling $ log (fromIntegral $ M.size m+2)/log 256
        heapSpace=1
        ss=map (("S"++) . show) [0..heapSpace-1]
        hs=["Hp","Hs"]

collectConArity :: GMCode -> S.Set Int
collectConArity (Pack _ n)=S.singleton n
collectConArity (Case cs)=S.unions $ map (S.unions . map collectConArity . snd) cs
collectConArity _=S.empty



simplify :: M.Map String [GMCode] -> Process (M.Map String [GMCode])
simplify=return . M.map elimBase . elimReduce . removeLoneSC

removeLoneSC :: M.Map String [GMCode] -> M.Map String [GMCode]
removeLoneSC m=M.filterWithKey (\k _->S.member k col) m
    where col=rlscAux m S.empty (S.singleton "main")

rlscAux :: M.Map String [GMCode] -> S.Set String -> S.Set String -> S.Set String
rlscAux m col front
    |S.null front = col
    |otherwise    = rlscAux m col' (S.difference new col')
    where
        col'=S.union col front
        new=S.unions $ map (S.unions . map collectDepSC . find) $ S.toList front
        find x=M.findWithDefault (error $ "rlscAux:"++show x) x m


collectDepSC :: GMCode -> S.Set String
collectDepSC (PushSC x)=S.singleton x
collectDepSC (Case cs)=S.unions $ map (S.unions . map collectDepSC . snd) cs
collectDepSC _=S.empty


-- | Optmize away /base/ cases like following.
--
-- * Case with 1 clause
--
-- * Pop 0
--
-- * Slide 0 (in fact, successive 'Slide's form a 'Monoid')
elimBase :: [GMCode] -> [GMCode]
elimBase []=[]
elimBase (Slide 0:xs)=elimBase xs
elimBase (Slide n:Slide m:xs)=elimBase $ Slide (n+m):xs
elimBase (Case cs:xs)
    |length cs<=1 = elimBase $ (snd $ head cs)++xs
    |otherwise    = Case (map (second elimBase) cs):elimBase xs
elimBase (Pop 0:xs)=elimBase xs
elimBase (x:xs)=x:elimBase xs


-- | Separate ['GMCode'] at 'Reduce'.
elimReduce :: M.Map String [GMCode] -> M.Map String [GMCode]
elimReduce=M.fromList . concatMap f . M.assocs
    where f (n,xs)=aux n [] xs


aux :: String -> [GMCode] -> [GMCode] -> [(String,[GMCode])]
aux n cs []=[(n,reverse cs)]
aux n cs (Reduce _:xs)=(n,reverse cs++[PushSC n',Swap]):aux n' [] xs
    where n'=n++"_"
aux n cs (Case as:xs)
    |null rs   = aux n (Case as:cs) xs
    |otherwise = (n,reverse $ Case as'':cs):rs
    where
        as'=map (\(k,x)->(k,aux (n++"_d"++show k) [] $ x++xs)) as
        as''=map (second $ snd . head) as'
        rs=concatMap (tail . snd) as'
aux ns cs (x:xs)=aux ns (x:cs) xs




-- | Thin wrapper of 'compileCodeBlock'
compileProc :: M.Map String Int -> String -> [GMCode] -> SProc
compileProc m name cs=SProc ("!"++name) [] $ contWith m Origin cs []


data MPos
    =HeapA
    |StackA
    |StackT
    |Origin
    deriving(Show,Eq)

fPos :: GMCode -> MPos
fPos (PushByte _)=HeapA
fPos (PushSC _)=HeapA
fPos MkApp=HeapA
fPos (Pack _ _)=HeapA
fPos Swap=StackT
fPos (Push _)=StackA
fPos (Slide _)=StackT
fPos (PushArg _)=StackT
fPos (Case _)=StackT
fPos (UnPack _)=StackA
fPos (Update _)=StackT
fPos (Pop _)=StackT
fPos (GMachine.Alloc _)=fPos $ PushByte 0
fPos (Arith _)=StackT
fPos (UError _)=StackA -- any position will do, actually.
fPos x=error $ show x


-- requirement: HeapA
newFrame :: Int -> [Int] -> (Pointer -> [Stmt]) -> [Stmt]
newFrame tag xs post=
    [Comment $ unwords ["nf",show tag,show xs]
    ,SAM.Alloc "addr"
    ,Inline "#heapNewHp" ["addr"]
    ,Clear (Memory "Hp" $ size-2)
    ,Move (Register "addr") [Memory "Hp" $ size-2]
    ,Delete "addr"
    ,Val (Memory "Hp" 0) size
    ,Clear (Memory "Hp" 1),Val (Memory "Hp" 1) 0 -- GC tag
    ,Clear (Memory "Hp" 2),Val (Memory "Hp" 2) tag -- node tag
    ]++
    concatMap set (zip [3..] xs)++
    [Clear (Memory "Hp" $ size-1),Val (Memory "Hp" $ size-1) size
    ,Clear (Memory "Hp" size) -- next frame
    ]++
    post (Memory "Hp" $ size-2)
    where
        size=5+length xs
        set (ix,v)=[Clear (Memory "Hp" ix),Val (Memory "Hp" ix) v]

-- | Compile 'GMCode's from given 'MPos' to 'Stmt's, followed by 'Origin' returning code.
contWith :: M.Map String Int -> MPos -> [GMCode] -> [Stmt] -> [Stmt]
contWith m Origin [] ss=ss
contWith m HeapA  [] ss=ss++[Inline "#heap1Hp" []]
contWith m StackA [] ss=ss++[Inline "#stack1S0" []]
contWith m StackT [] ss=ss++[Inline "#stack1S0" []]
contWith m prev xs@(x:_) ss=ss++transition prev (fPos x)++[Comment (show x)]++compileCode m xs

-- TODO: come up with good abstraction
transition :: MPos -> MPos -> [Stmt]
transition x y
    |x==y                   = []
    |x==Origin && y==StackT = [Inline "#stackTopS0" []]
    |x==Origin              = []
    |x==StackA && y==StackT = [Inline "#stackTopS0" []]
    |x==StackA && y==Origin = [Inline "#stack1S0" []]
    |x==StackA && y==HeapA  = [Inline "#stack1S0" []]
    |x==StackT && y==StackA = []
    |x==StackT && y==Origin = [Inline "#stack1S0" []]
    |x==StackT && y==HeapA  = [Inline "#stack1S0" []]
    |x==HeapA  && y==StackT = [Inline "#heap1Hp" [],Inline "#stackTopS0" []]
    |x==HeapA               = [Inline "#heap1Hp" []]

-- | Compile a single 'GMCode' to a procedure. StackA|HeapA -> StackA|HeapA
compileCode :: M.Map String Int -> [GMCode] -> [Stmt]
compileCode m (PushByte x:is)= -- constTag x
    contWith m StackT is $ newFrame constTag [x] $ \pa->
    [SAM.Alloc "addr"
    ,Copy pa [Register "addr"]
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ,Move (Register "addr") [Memory "S0" 0]
    ,Delete "addr"
    ]
compileCode m (PushSC k:is)= -- scTag sc
    contWith m StackT is $ newFrame scTag [m M.! k] $ \pa->
    [SAM.Alloc "addr"
    ,Copy pa [Register "addr"]
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ,Move (Register "addr") [Memory "S0" 0]
    ,Delete "addr"
    ]
compileCode m (MkApp:is)= -- appTag ap0 ap1
    contWith m HeapA is $ newFrame appTag [0,0] $ \pa->
    [SAM.Alloc "addr"
    ,Copy pa [Register "addr"]
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ,SAM.Alloc "tr1"
    ,Move (Memory "S0" (-1)) [Register "tr1"]
    ,SAM.Alloc "tr2"
    ,Move (Memory "S0" (-2)) [Register "tr2"]
    ,Copy (Register "addr") [Memory "S0" (-2)]
    ,Locate (-2)
    ,Inline "#stack1S0" []
    ,Inline "#heapRefHp" ["addr"]
    ,Delete "addr"
    ,Move (Register "tr1") [Memory "Hp" 3]
    ,Delete "tr1"
    ,Move (Register "tr2") [Memory "Hp" 4]
    ,Delete "tr2"
    ]
compileCode m (Pack t 0:is)=
    contWith m StackT is $ newFrame structTag [t] $ \pa->
    [SAM.Alloc "addr"
    ,Copy pa [Register "addr"]
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ,Move (Register "addr") [Memory "S0" 0]
    ,Delete "addr"
    ]
compileCode m (Pack t n:is)= -- stTag t x1...xn
    contWith m HeapA is $ newFrame structTag (t:replicate n 0) $ \pa->
    [SAM.Alloc "addr"
    ,Copy pa [Register "addr"]
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ]++
    concatMap (\n->let r="tr"++show n in [SAM.Alloc r,Move (Memory "S0" $ negate n) [Register r]]) [1..n]++
    [Copy (Register "addr") [Memory "S0" $ negate n]
    ,Locate $ negate n
    ,Inline "#stack1S0" []
    ,Inline "#heapRefHp" ["addr"]
    ,Delete "addr"
    ]++
    concatMap (\n->let r="tr"++show n in [Move (Register r) [Memory "Hp" $ n+3],Delete r]) [1..n]
compileCode m (UnPack 0:is)=contWith m StackT is $
    [Inline "#stackNewS0" []
    ,Clear (Memory "S0" (-1))
    ,Locate (-2)
    ]
compileCode m (UnPack n:is)=contWith m StackA is $ -- the last item becomes top
    [Inline "#stackNewS0" []
    ,SAM.Alloc "saddr"
    ,Move (Memory "S0" (-1)) [Register "saddr"]
    ,Locate (-2)
    ,Inline "#stack1S0" []
    ,Inline "#heapRefHp" ["saddr"]
    ,Delete "saddr"
    ]++
    map (SAM.Alloc . ("tr"++) . show) [1..n]++
    map (\x->Copy (Memory "Hp" $ 3+x) [Register $ "tr"++show x]) [1..n]++
    [Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ]++
    map (\x->Move (Register $ "tr"++show x) [Memory "S0" $ x-1]) (reverse [1..n])++
    map (Delete . ("tr"++) . show) [1..n]
compileCode m (Swap:is)=contWith m StackT is $
    [SAM.Alloc "temp"
    ,Move (Memory "S0" 0) [Register "temp"]
    ,Move (Memory "S0" (-1)) [Memory "S0" 0]
    ,Move (Register "temp") [Memory "S0" (-1)]
    ,Delete "temp"
    ]
compileCode m (Push n:is)=contWith m StackT is $
    [Inline "#stackNewS0" []
    ,Copy (Memory "S0" $ negate $ n+1) [Memory "S0" 0]
    ]
compileCode m (Slide n:is)=if n<=0 then error "Slide 0" else contWith m StackT is $
    [Clear (Memory "S0" $ negate n)
    ,Move (Memory "S0" 0) [Memory "S0" $ negate n]
    ]++
    map (Clear . Memory "S0" . negate) [1..n-1]++
    [Locate $ negate n]
compileCode m (PushArg n:is)=contWith m StackT is $
    [SAM.Alloc "aaddr"
    ,Copy (Memory "S0" $ negate n) [Register "aaddr"]
    ,Inline "#stack1S0" []
    ,Inline "#heapRefHp" ["aaddr"]
    ,Delete "aaddr"
    ,SAM.Alloc "arg"
    ,Copy (Memory "Hp" 4) [Register "arg"]
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ,Move (Register "arg") [Memory "S0" 0]
    ,Delete "arg"
    ]
compileCode m (Case cs:is)=contWith m Origin is $
    [SAM.Alloc "saddr"
    ,Copy (Memory "S0" 0) [Register "saddr"]
    ,Inline "#stack1S0" []
    ,Inline "#heapRefHp" ["saddr"]
    ,Delete "saddr"
    ,SAM.Alloc "tag"
    ,Copy (Memory "Hp" 3) [Register "tag"]
    ,Dispatch "tag" $ map (second $ flip (contWith m HeapA) []) cs
    ,Delete "tag"
    ]
compileCode m (Update n:is)=contWith m HeapA is $
    [SAM.Alloc "to"
    ,Move (Memory "S0" 0) [Register "to"]
    ,Locate (-1)
    ,SAM.Alloc "from"
    ,Copy (Memory "S0" $ 1-n) [Register "from"]
    ,Inline "#stack1S0" []
    -- rewrite stack
    ,While (Memory "S0" 0)
        [Inline "#rewriteS0" ["from","to"]
        ,Locate 1
        ]
    ,Locate (-1)
    ,Inline "#stack1S0" []
    -- rewrite heap
    ,While (Memory "Hp" 0)
        [SAM.Alloc "ntag"
        ,Copy (Memory "Hp" 2) [Register "ntag"]
        ,Dispatch "ntag"
            [(appTag,
                [Locate 3
                ,Inline "#rewriteHp" ["from","to"]
                ,Locate 1
                ,Inline "#rewriteHp" ["from","to"]
                ,Locate 3
                ])
            ,(scTag,
                [Locate 6])
            ,(constTag,
                [Locate 6])
            ,(structTag,
                [SAM.Alloc "size"
                ,Copy (Memory "Hp" 0) [Register "size"]
                ,Val (Register "size") (-6)
                ,Locate 4
                ,While (Register "size")
                    [Inline "#rewriteHp" ["from","to"]
                    ,Locate 1
                    ,Val (Register "size") (-1)
                    ]
                ,Delete "size"
                ,Locate 2
                ])
            ]
        ,Delete "ntag"
        ]
    ,Delete "from"
    ,Delete "to"
    ]
compileCode m (Pop n:is)=contWith m StackT is $
    concat $ replicate n [Clear (Memory "S0" 0),Locate (-1)]
compileCode m (GMachine.Alloc n:is)=compileCode m $ replicate n (PushByte 0)++is

compileCode m (UError s:_)=Clear ptr:concatMap (\d->[Val ptr d,Output ptr]) ds
    where
        ds=head ns:zipWith (-) (tail ns) ns
        ns=map ord s
        ptr=Memory "S0" 0
compileCode m (Arith op:is)=contWith m StackT is $
    [SAM.Alloc "x"
    ,SAM.Alloc "y"
    ,Move (Memory "S0" 0) [Register "x"]
    ,Move (Memory "S0" (-1)) [Register "y"]
    ,Locate (-2)
    ,Inline "#stack1S0" []
    ,Inline "#heapRefHp" ["x"]
    ,Copy (Memory "Hp" 3) [Register "x"]
    ,Inline "#heap1Hp" []
    ,Inline "#heapRefHp" ["y"]
    ,Delete "y"
    ,SAM.Alloc "temp"
    ,Copy (Memory "Hp" 3) [Register "temp"]
    ]++
    f (Register "temp") (Register "x") op++
    [Delete "temp"
    ,SAM.Alloc "addr"
    ,Inline "#heapNewHp" ["addr"]
    ,Clear (Memory "Hp" 0) ,Val (Memory "Hp" 0) 6
    ,Clear (Memory "Hp" 1) ,Val (Memory "Hp" 1) 0
    ,Clear (Memory "Hp" 2) ,Val (Memory "Hp" 2) $ tag op
    ,Clear (Memory "Hp" 3) ,Move (Register "x") [Memory "Hp" 3] ,Delete "x"
    ,Clear (Memory "Hp" 4) ,Copy (Register "addr") [Memory "Hp" 4]
    ,Clear (Memory "Hp" 5) ,Val (Memory "Hp" 5) 6
    ,Clear (Memory "Hp" 6)
    ,Inline "#heap1Hp" []
    ,Inline "#stackNewS0" []
    ,Move (Register "addr") [Memory "S0" 0]
    ,Delete "addr"
    ]
    where
        tag CCmp=structTag
        tag _=constTag
        f from to AAdd=[While from [Val from (-1),Val to 1]]
        f from to ASub=[While from [Val from (-1),Val to (-1)]]
        f from to CCmp=
            [SAM.Alloc "t"
            ,Val (Register "t") 1
            ,While (Register "t")
                [SAM.Alloc "s"
                ,Copy from [Register "s"]
                ,Val (Register "t") 1
                ,While (Register "s")
                    [Clear (Register "s")
                    ,Val (Register "t") (-1)
                    ]
                ,Copy to [Register "s"]
                ,While (Register "s")
                    [Clear (Register "s")
                    ,Val (Register "t") (-1)
                    ]
                ,Val (Register "s") 1
                ,While (Register "t")
                    [Clear (Register "t")
                    ,Val (Register "s") (-1)
                    ]
                ,Move (Register "s") [Register "t"]
                ,Delete "s"
                ,Val from (-1)
                ,Val to (-1)
                ]
            ,Val from 1
            ,Val to 1
            ,While from [Clear from,Val (Register "t") 1]
            ,While to [Clear to,Val (Register "t") 2]
            ,Move (Register "t") [to] -- 0:EQ 1:from>to 2:to<from
            ,Delete "t"
            ]
          






-- | G-machine intstruction
--
-- Note1: 'MkApp' 'Pack' ordering: first pushed -> last packed
--
-- Note2: 'PushArg' counts from top=0
data GMCode
    =Slide Int -- ^ pop 1st...nth items
    |Update Int -- ^ replace all reference to the nth address to 0th address.
    |Pop Int -- ^ remove n items
    |Push Int
    |PushSC String
    |Alloc Int
    |Swap -- ^ used for implementing 'elimReduce'
    |Reduce RHint -- ^ reduce stack top to WHNF
    -- function
    |MkApp -- ^ function must be pushed after arguments. then use this.
    |PushArg Int
    -- data structure
    |Pack Int Int
    |Case [(Int,[GMCode])]
    |UnPack Int
    -- arithmetic
    |PushByte Int
    |Arith ArithOp
    -- error
    |UError String -- ^ output the given string with undefined consequence
    deriving(Show)

data ArithOp
    =AAdd
    |ASub
    |CCmp
    deriving(Show)

data RHint
    =RByte
    |RE
    |RAny
    deriving(Show)

pprint :: M.Map String [GMCode] -> String
pprint=compileSB . Group . intersperse EmptyLine . map (uncurry pprintGMF) . M.assocs

pprintGMF :: String -> [GMCode] -> SBlock
pprintGMF name cs=Group
    [Line $ U.Pack [Prim name,Prim ":"]
    ,Indent $ Group $ map pprintGMC cs
    ]

pprintGMC :: GMCode -> SBlock
pprintGMC (Case cs)=Group
    [Line $ Prim "Case"
    ,Indent $ Group $ map (f . first show) $ sortBy (comparing fst) cs
    ]
    where f (label,xs)=Group [Line $ Span [Prim label,Prim "->"],Indent $ Group $ map pprintGMC xs]
pprintGMC c=Line $ Prim $ show c


-- | G-machine state for use in 'interpretGM'
type GMS=State GMInternal
type GMST m a=StateT GMInternal m a

data GMInternal=GMInternal{stack::Stack,heap::Heap} deriving(Show)
data GMNode
    =App Address Address
    |Const Int
    |Struct Int [Address]
    |Combinator String
    deriving(Show)

type Stack=[Address]
type Heap=M.Map Address GMNode

newtype Address=Address Int deriving(Show,Eq,Ord)





interpret :: M.Map String [GMCode] -> IO ()
interpret fs=evalStateT (evalGM False fs []) (makeEmptySt "main")

interpretR :: M.Map String [GMCode] -> IO ()
interpretR fs=evalStateT (evalGM True fs []) (makeEmptySt "main")

makeEmptySt :: String -> GMInternal
makeEmptySt entry=runIdentity $ execStateT (alloc (Combinator entry) >>= push) $ GMInternal [] M.empty


-- | Interpret a single combinator and returns new combinator to be executed.
evalGM :: Bool -> M.Map String [GMCode] -> [GMCode] -> GMST IO ()
evalGM fl fs []=do
    st<-get
    liftIO $ putStrLn $ "GMi: aux:\n"++showState st
    
    node<-refStack 0 >>= refHeap
    
    case node of
        App a0 a1 -> push a0 >> evalGM fl fs []
        Combinator x -> evalGM fl fs (fs M.! x)
        _ -> do x<-isRootNode
                if x
                    then case node of
                        Struct 0 [f] -> do
                            pop
                            x<-liftIO (liftM ord getChar)
                            alloc (Const x) >>= push >> push f >> evalGM fl fs [MkApp]
                        Struct 1 [x,k] -> do
                            pop
                            refHeap x >>= (liftIO . putChar . unConst)
                            push k
                            evalGM fl fs []
                        Struct 2 [] -> pop >> return ()
                    else when fl $ do{[e,c]<-popn 2; Combinator x<-refHeap c; push e; evalGM fl fs (fs M.! x)}
    where unConst (Const x)=chr x

evalGM fl fs (Reduce _:xs)=evalGM fl fs [] >> evalGM fl fs xs
evalGM fl fs (Push n:xs)=
    refStack n >>= push >> evalGM fl fs xs
evalGM fl fs (PushArg n:xs)=do
    App _ arg<-refStack n >>= refHeap
    push arg
    evalGM fl fs xs
evalGM fl fs (MkApp:xs)=do
    [s0,s1]<-popn 2
    alloc (App s0 s1) >>= push
    evalGM fl fs xs
evalGM fl fs (Pack t n:xs)=do
    ss<-popn n
    alloc (Struct t ss) >>= push
    evalGM fl fs xs
evalGM fl fs (PushSC n:xs)=do
    alloc (Combinator n) >>= push
    evalGM fl fs xs
evalGM fl fs (Slide n:xs)=do
    x<-pop
    popn n
    push x
    evalGM fl fs xs
evalGM fl fs (PushByte x:xs)=alloc (Const x) >>= push >> evalGM fl fs xs
evalGM fl fs (Case cs:xs)=do
    Struct t _<-refStack 0 >>= refHeap
    maybe (error $ "GMi: Case:"++show t) (evalGM fl fs . (++xs)) $ lookup t cs
evalGM fl fs (UnPack n:xs)=do
    Struct _ cs<-pop >>= refHeap
    when (length cs/=n) (error $ "GMi: UnPack arity error")
    mapM_ push cs
    evalGM fl fs xs
evalGM fl fs (Swap:xs)=popn 2 >>= mapM_ push >> evalGM fl fs xs
evalGM fl fs (Pop n:xs)=popn n >> evalGM fl fs xs
evalGM fl fs (Update n:xs)=do
    t<-pop
    f<-refStack $ n-1
    modify $ \(GMInternal st hp)->GMInternal (map (fS f t) st) (M.map (fH f t) hp)
    evalGM fl fs xs
    where
        fS f t x|x==f      = t
                |otherwise = x
        fH f t (App x y)=App (fS f t x) (fS f t y)
        fH f t (Struct tag xs)=Struct tag $ map (fS f t) xs
        fH _ _ x=x
evalGM fl fs (GMachine.Alloc n:xs)=evalGM fl fs $ replicate n (PushByte 0)++xs
evalGM fl fs (Arith op:xs)=do
    Const x<-pop >>= refHeap
    Const y<-pop >>= refHeap
    case op of
        AAdd -> alloc (Const $ (x+y) `mod` 256) >>= push
        ASub -> alloc (Const $ (x-y) `mod` 256) >>= push
        CCmp -> alloc (Struct (if x==y then 0 else if x<y then 1 else 2) []) >>= push
    evalGM fl fs xs
evalGM _ _ x=error $ "evalGM: unsupported: "++show x




showState :: GMInternal -> String
showState g=unlines $
    unwords (map show st):map (\(k,v)->show k++":"++show v) (M.assocs hp)
    where GMInternal st hp=GMachine.gc g


-- | do not modify pointers
gc :: GMInternal -> GMInternal
gc (GMInternal st hp)=GMInternal st hp'
    where
        hp'=M.filterWithKey (\k _ ->S.member k ns) $ hp
        ns=S.unions $ map (collect hp) st


collect heap addr=S.insert addr $
    case heap M.! addr of
        App a0 a1 -> S.union (collect heap a0) (collect heap a1)
        Struct _ as -> S.unions $ map (collect heap) as
        _ -> S.empty


refHeap :: Monad m => Address -> GMST m GMNode
refHeap addr=liftM ((M.!addr) . heap) get

refStack :: Monad m => Int -> GMST m Address
refStack n=liftM ((!!n) . stack) get

isRootNode :: Monad m => GMST m Bool
isRootNode=do
    n<-liftM (length . stack) get
    return $ n==1


push :: Monad m => Address -> GMST m ()
push addr=do
    GMInternal st h<-get
    put $ GMInternal (addr:st) h

alloc :: Monad m => GMNode -> GMST m Address
alloc n=do
    GMInternal st h<-get
    let addr=if M.null h then Address 0 else let Address base=fst $ M.findMax h in Address (base+1)
    put $ GMInternal st $ M.insert addr n h
    return addr

pop :: Monad m => GMST m Address
pop=do
    GMInternal (s:ss) h<-get
    put $ GMInternal ss h
    return s

popn :: Monad m => Int -> GMST m [Address]
popn=flip replicateM pop