Haskell で仮想マシンを書く fetch

仮想マシンなので命令はメモリから読もう。レジスタを2つ増やして、プログラムカウンタと計算結果を保存するレジスタを持つようにしてみる。命令は停止命令とACCレジスタをインクリメントする二つだけ用意しよう。

data Reg = PC | FP | ACC deriving (Eq, Ord, Show, Enum)
data Insn = INCR | STOP deriving (Eq, Ord, Show, Enum)

命令を PC から読み出す fetch と、それを decode する関数は、メモリアドレスとメモリの持つ値が両方とも同じ Int にしておくと簡単になる。

-- word = ptr
fetch :: (Ord x, Show x, MonadState (VM Reg x x) m) => m x
fetch = do
  pc <- loadReg PC
  load pc

decode :: Int -> Insn  
decode = toEnum

実行するのも簡単だ。fetch - decode と命令による分岐を書けば良い。とりあえずは ACC レジスタの値を返しておくことにする。ハンドアセンブルして命令列を作るほうが面倒なくらい。

exec ::  (MonadState (VM Reg Int Int) m) => m Int
exec = do
  op <- fetch
  case decode op of
    STOP -> do { loadReg ACC }
    INCR -> do { 
      acc <- loadReg ACC;
      storeReg ACC (acc + 1);
      pc <- loadReg PC;
      storeReg PC (pc + 1);
      exec }

適切なメモリとレジスタの状態を作って実行する。

*Main> evalStateT exec (makeVm [STOP])
0
*Main> evalStateT exec (makeVm [INCR, STOP])
1
*Main> evalStateT exec (makeVm [INCR, INCR, INCR, STOP])
3

これではハンドアセンブルがあまりに辛い。TMR-Issue6 (https://wiki.haskell.org/wikiupload/1/14/TMR-Issue6.pdf) を斜め読みすると、DSL としてアセンブラを書いている(ように見える)。同じことを目指してみよう。


たぶん続く。