-- | 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 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>= 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