仮想機械(VM)の作り方

こんにちは。趣味でVMを作っている者です(挨拶)。

VMとは何か?

VMとはVirtual Machine。仮想機械の事なんですが、

ja.wikipedia.org

コンピュータの動作を再現するソフトウェアである。すなわち、エミュレートされた仮想のコンピュータそのものも仮想機械という。

と言うことで、コンピュータ上で動くコンピュータ、がざっくりとした解釈でしょうか。よって、エミュレータは仮想機械だと思いますし、プログラミング界隈としてはJava VM辺りが有名かと思います。

コンピュータを作るなんて、なんか難しそうだ!と思うかも知れません。しかしコンピュータって、ようは計算が出来れば良い訳です。

Minecraft 上でコンピュータを作ってみた。みたいな動画がたまに出てきたりしますが、レッドストーン回路と言う0と1の計算が出来るものさえあれば、コンピュータが作れる訳です。なんだか簡単に思えてきましたよね?

「計算」とは何でしょうか。一番簡単なのは電卓ですね。入力に対して何らかの作業をして結果を出力すれば、それは「計算」だと思います。多分・・・。

というわけで早速作ってみましょう。手元にbashか何かがあれば簡単に出来ます。

$ echo 'echo $1 $2' > concat
$ chmod 755 concat
$ ./concat Hello world!
Hello world!

出来ました!え、単なるシェルスクリプトじゃないかって?でも一応「2つの文字列を繋げる」と言う作業をしていますよね。なんだか納得していない顔ですね…それではこう作り変えましょう。

# calc
if [ $2 = + ]; then
  expr $1 + $3
elif [ $2 = - ]; then
  expr $1 - $3
fi
$ ./calc 1 + 2
3
$ ./calc 1 - 2
-1

四則演算のうち加算と減算が行えるようになりました!え、これでも納得していないですか…?しょうがないなぁ…

バイトコードを実行してみる

この記事をわざわざ読みに来た人はきっと「もっと真面目に話をしてくれ」と思っているので、もう少し真面目にやってみたいと思います。

バイトコードの話をしてみましょうか。それっぽい単語が出てきた!と多くの人は思うかと思います。

バイトコードとは読んで時の如くです。例えばさっきのシェルスクリプトは、コンピュータからするとASCIIコードです。

e-words.jp

我々が見ている、例えば「1」と言う数字は、実はコンピュータ上では「0x31」、10進数で49と言う値になっています。なんだかそれっぽい話になってきました。

余りいきなり難しい物を作るのは大変なので、さっきの加減算を行うプログラムを書いてみます。

bytes=(`echo $1 | hexdump -e '99/1 "%02x "'`)
case ${bytes[0]} in
  "01") expr ${bytes[1]} + ${bytes[2]};;
"02") expr ${bytes[1]} - ${bytes[2]};;
esac
$ ./calc "\x01\x02\x03"
5
$ ./calc "\x02\x02\x03"
-1

出来ました。一応バイナリを入力して、その結果を処理する形になっています。

本来はこれを、プログラムの終了まで無限にループする…と言う形ですが、省略しています。

ともかくとして、これが一番シンプルな形で一応のコンピュータの動作を表している形になりました。

バイトコードの仕組み

基本的には、 case ${bytes[0]} の部分をどんどん増やしていけばいい訳です。条件分岐やループ等を実装していく形になります。

バイトコードについて説明しますと、上記プログラムでは3byteで一つの命令を表していて、

[op-code] [operand1] [operand2]

となっています。今回作ったものでは、op-codeは、0x01が足し算。0x02が減算となっています。オペランドとは演算子と呼ばれ、具体的な計算を行う対象の値等を指します。

今回作ったものは3byte固定ですが、1byte命令としたり更に長い命令を作ったりも出来ます(設計による)

実際のVMでは、まずスタックが作られるでしょうし(呼び出し元を記憶する等のために)、より複雑な物になっていきます。

今回は特定の言語に依存したものを作りたくなかったので、bashで実現可能なシンプルな物を作ってみましたが、実際にはC言語等で書いていく事になります。勿論、RubyJava等好きな言語で書いてみてもいいです(パフォーマンスは落ちますが)。