vmgen のために gforth をインストール
gforth 0.7.3 をどっかからもってきて configure して make install します。ubuntu (Linux 64bit) では特別なオプションは必要ありませんでした。cygwin 64bit だとそもそもコンパイルできませんでした。
vmgen は何をするツールか?
vmgen は VM を作るための C のひな形を作ってくれるツールです。書式に従って .vmg というファイルをつくり、vmgen でコンパイルします。とにかく試したいという人は、gforth の vmgen-ex あるいは vmgen-ex2 というディレクトリで make すれば簡単な(Modula2 風の)インタプリタができあがります(m4 と flex と bisonが必要)。
その vmgen-ex にある mini.vmg というのが vmgen の入力になるファイルです。m4 で自動生成されます。もう m4 というだけで敷居が上がってしまいます。またサンプルの割には複雑で、gcc と gcc 以外への対応、各CPUへの対応、のぞき穴を使った最適化、などが含まれています。
vmgen の資料を読む
もともと gforth にドキュメントが ps としてついていますが(textinfo をコンパイルしたもの)、ついでなので pdf 化してみました。
vmgen.pdf の6章を読めばいいと思います。gforth.pdf はおまけです。vmgenを使うにあたってFORTH を知る必要はありません。
とにかくサンプル
とにかくサンプルを試したい人は
hvm.tar.gz
からダウンロードして make して hvm を動かしてみてください。(gforth なくても大丈夫なように i ファイルも入れておいた、、、つもり、、未確認)
ReadMe.txt をみれば概略もわかるでしょう。
レジスタマシン
vmgen では主にスタックマシンを生成することが出来ます。んが、レジスタマシンも作れるとドキュメントには書いてあります。そこで、トライしたというのが今回の趣旨で、やっとここにきてこの記事の趣旨が示されました。
先のサンプルの hvm を例にとって進めていきましょう。
hvm.vmg
hvm.vmg が vmgen に食わせる入力ファイルです。
\ stack definitions:
\E stack data-stack sp Cell
\E s" Operand" single inst-stream type-prefix operand
add ( operand -- )
regs[operand.r2] = regs[operand.r1] + regs[operand.r0];
halt ( operand -- )
{
uint32_t rv;
rv = regs[operand.r0];
printf("halt %d\n", rv);
return rv;
}
\ で始まるのはコメントで \ E からは FORTH のプログラムが書けます。んが、FORTH を知る必要はありません。典型的に使われる文法のみをしっていればよいのです。
\E stack data-stack sp Cell
この記述によりスタックが使えるようになります。vmgen が自動生成するソースでは 宣言した Cellという構造体と sp という変数が使われるようになります。そのため、Cell という構造を C と sp を用意する必要があります。
hvm-engine.h のなかで Cell は次のように定義されています。
typedef void *Label;
typedef union Cell {
long i;
union Cell *target;
Label inst;
//char *a;
} Cell, Inst;
\E s" Operand" single inst-stream type-prefix operand
Operand はここ(hvm.vmg)で勝手に考えた構造体です。次に示す VM の記述の中で operand というキーワードを使うとこの構造体を使った C のソースが自動生成されます。
operand は inst-stream に関連すると定義しているので、インストラクション側から読みだされるコードが生成されます。
おおくの VM ではスタック操作が必要になると思いますが、その場合は
\E s" Cell" single data-stack type-prefix i
のように宣言すれば i というプレフィックスを持った記述はスタック操作をする C のコードへと変換されます。
operand は hvm-engine.h で次のように定義されています。
typedef union Operand {
long i;
struct {
unsigned char r0;
unsigned char r1;
unsigned char r2;
};
} Operand;
VM のインストラクション
(再掲)
add ( operand -- )
regs[operand.r2] = regs[operand.r1] + regs[operand.r0];
このようにインストラクション名(この場合は add) の後に ( なんとか -- なんとか)
のように書きます。--
で区切られた左側がインストラクションが実行さる前のスタックの様子、右側がその後のスタックの様子、、、を指すのが FORTH の掟のようですが、この場合は operand を inst-stream を選んでいるのでスタック操作はされません。
add の次の行からは C のソースです。空行は書けないので必要であれば {} でくくります。
C の実装
vmgen で hvm.vmg をコンパイルすると複数の .i ファイルができます。これを C にインクルードしてコンパイルすればあら不思議 VM の完成だ!!
##hvm-vm.i
vm 本体。レジスタベースの VM のサンプルになっています。使用されているマクロを用意する必要があります。全部理解するのは大変なので、わからないままにだいたい vmgen-ex からコピーして実装してあります。各 CPU に適したマクロを用意するのが望ましいみたいです。gcc の && を使ったコーディングに依存しています。汎用的に switch/case を使うようにもできます。(vmgen-ex 参照)
TOS のキャッシュを持っていますが、そもそもスタックを使っていません。各自拡張してみてください。スタックを使うのは vmgen の資料を見れば、そう難しくはないでしょう(たぶん)。
gforth 本体ではもっと複雑なことをしているらしくarch/ の下に各CPU対応がなされています。arm はディレクトリだけあってなんか使われていない雰囲気です。
hvm-gen.i
コード生成用の関数です。今回は main から直接使っています。足りない関数は自分で用意します。support.c 参照。main でファイルを呼んで malloc したインストラクション用の配列にコードを積み上げていけばインタプリタが出来上がります。vmgen-ex を参照の事
##hvm-labels.i
インストラクションの集まりです。INST_ADDR という C のマクロを用意することでラベルの集まりを作ることが出来ます。INST_ADDR は便利なようにどんなマクロを使っても構いません。今回は gcc の && を大いに利用しています。
hvm-disasm.i
逆アセンブル用のコードです。今回は使っていません。使い方は gforth のvmgen-ex か vmgen-ex2 を見てください
hvm-peephole.i
のぞきあな。コンパイラ的なことはしていません。未使用。
hvm-profile.i
プロファイルもしてません。
みんなで VM づくりにチャレンジしよう!!
がんばれば週末で VM が作れます。よのなかをオレオレ VM であふれかえらせましょう!!