Haskell で仮想マシンを書く
モナドの練習として仮想マシンを書く。レジスタとメモリを、キーをレジスタまたはアドレスとし値を保持できる Map で表すことにしよう。
type Regs r x = Map.Map r x type Memory p x = Map.Map p x data VM r p x = VM (Regs r x) (Memory p x) deriving (Eq, Show)
この仮想マシンを状態モナドにして、メモリとレジスタを更新/参照する関数 store, load, storeReg, loadReg を用意し、runStateT や evalStateT で実行することを考える。
利用しているメモリアドレスの上限をレジスタで管理することにすると、単純化したメモリアロケーションを行う関数 alloc が書ける。
alloc r = do fp <- loadReg r modifyReg (Map.insert r (succ fp)) return $ succ fp
具体的にレジスタ FP をもった仮想マシン vm0 を考えることにする。
data Reg = FP deriving (Eq, Ord, Show, Enum) vm0 :: VM Reg Int Int vm0 = VM (Map.fromList $ zip (enumFrom FP) [0 .. ]) Map.empty
メモリをアロケートしてそこに値を書き込む処理 assign は以下のように書ける。
assign x = do p <- alloc FP store x p return p
これを使うと、以下のような手続き型言語のような処理が書ける。ここで var はアドレス値から値を取得する関数で、全体を計算の連鎖として書くために必要だった。
また plus は liftM2 (+) で定義されるモナドで、 m r -> m r -> m r の型を持つ。
test00 = do x <- assign 10 y <- assign 3 (plus (var x) (var y)) Main> run0 test00 (13,VM (fromList [(FP,2)]) (fromList [(1,10),(2,3)]))
続く。
以下、全コード。
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-} import qualified Data.Map as Map import Control.Monad.State import Data.Maybe type Regs r x = Map.Map r x type Memory p x = Map.Map p x data VM r p x = VM (Regs r x) (Memory p x) deriving (Eq, Show) -- Register の更新 modifyReg :: MonadState (VM r p x) m => (Regs r x -> Regs r x) -> m () modifyReg f = modify $ \(VM rs ms) -> VM (f rs) ms -- Memory の更新 modifyMem :: MonadState (VM r p x) m => (Memory p x -> Memory p x) -> m () modifyMem f = modify $ \(VM rs ms) -> VM rs (f ms) -- load from Register loadReg :: (Ord r, MonadState (VM r t x) m) => r -> m x loadReg r = do VM rs _ <- get return $ fromMaybe (error "un-initialized reg") (Map.lookup r rs) -- store to Register storeReg :: (Ord r, MonadState (VM r p x) m) => r -> x -> m () storeReg r x = modifyReg (Map.insert r x) -- load from memory load :: (Ord p, MonadState (VM r p x) m) => p -> m x load p = do VM _ ms <- get return $ fromMaybe (error "un-initialized memory") (Map.lookup p ms) -- store to memory store :: (Ord p, MonadState (VM r p x) m) => x -> p -> m () store x p = modifyMem (Map.insert p x) -- memory allocate use register r as free pointer. alloc :: (Enum x, Ord r, MonadState (VM r p x) m) => r -> m x alloc r = do fp <- loadReg r modifyReg (Map.insert r (succ fp)) return $ succ fp -- test machine with one register data Reg = FP deriving (Eq, Ord, Show, Enum) vm0 :: VM Reg Int Int vm0 = VM (Map.fromList $ zip (enumFrom FP) [0 .. ]) Map.empty -- 新規の領域に x を保存 assign x = do p <- alloc FP store x p return p assign' p = do x <- p assign x -- 変数 p を参照 var :: (Ord p, MonadState (VM r p x) m) => p -> m x var = load plus :: (Monad m, Num r) => m r -> m r -> m r plus = liftM2 (+) minus :: (Monad m, Num r) => m r -> m r -> m r minus = liftM2 (-) run0 f = runStateT f vm0 eval0 f = evalStateT f vm0 test00 :: Monad m => StateT (VM Reg Int Int) m Int test00 = do x <- assign 10 y <- assign 3 (plus (var x) (var y)) test01 :: Monad m => StateT (VM Reg Int Int) m Int test01 = do x <- assign 10 -- x = 10 y <- assign 3 -- y = 3 z <- assign' (plus (var x) (var x)) -- z = x + x (minus (var z) (var y)) -- return z - y