vmgen で VM つくる

CPS も大詰めだ。最後は VM をつくることになるわけだ。gcc までいくなら cgen というのがあって、特定の CPU の gcc をつくるのをサポートしてくれるようだ。まぁいまなら LLVM の方がいいのかもしれない。MeP や Epiphany の記述があって興味深い、、、がスキップ。

さて、vm といえば FORTH。gforth は vmgen で(どうやら miniforth を動かして vmgen プログラムを動かして、それをブートストラップに本格的な vm をつくるみたいだ)簡単に(はいかない)VM ができたりするらしい。

ということで、gforth の textinfo の vmgen.ps をpdfにして参照しながらすすめることにした。あと gforth のしたの vmgen-ex と vmgen-ex2 が簡単なサンプルになっている。

vmgen-ex をみながらまずは簡単な vmg を書いてみる。さすがにHatenaも vmg はたいおうしていないかな?

\ 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;
}

\ さすがに対応していない。なにもハイライトされない、、

気を取り直して add と halt だけの VM を作る。Cell と Operand は自分で union を作る。やっぱりこういうのは C が得意だな。C++ じゃだめだ。で、vmgen で i ファイルを生成して、あとは適当に C で補完。

スゲー省略するけどこんなかんじ。

ain(int argc, char **argv)
{
    vm_out = stderr;

    Cell ip[16];
    Cell stack[16];

    engine(0, 0, 0);

    vmcodep = (Inst *)ip;

{
    Operand operand;
    operand.r0 = 1;
    operand.r1 = 2;
    operand.r2 = 0;

    gen_add(&vmcodep, operand);
    operand.r0 = 0;
    gen_halt(&vmcodep, operand);
}

    engine(ip, &stack[15], 0);
    return 0;
}

gen_add と gen_halt を呼ぶと ip にコードが生成される。この調子で書いていけば、自前の Qemu も夢じゃないな。で engine で実行。

engine はこんな感じ。

int
engine(Cell *ip0, Cell *sp, char *fp)
{
    Cell *ip;
    Cell cfa;
    Cell spTOS;
    long regs[16];

    for( int i = 0 ; i < 16 ; i++ ) {
        regs[i] = i;
    }

    static Label labels[] = {
#include "hvm-labels.i"
    };

    vm_out = stderr;

    if ( ip0 == 0 ) {
        vm_prim = labels;
        return 0;
    }

    spTOS = sp[0];
    SET_IP(ip0);
    SUPER_END;

    NEXT;

#include "hvm-vm.i"
}

まぁとにかく大半は自動生成されるのだ。で実行してみる。

> ./hvm
halt 3

できました。あとは vm を増やしていくのと、Inst の羅列をつくるだけ。じつは作る方は common lisp で書いてしまったので、これを i ファイルと称して先の main に入れ込んでしまえばいい(はず)。

こんな感じ。

INST_ADDR(jump),
0x20000000,
&codes[__codes__MAIN__],
&codes[__codes__MAIN__],
&codes[__codes__EXIT__],
INST_ADDR(swap),
0x00000000,
INST_ADDR(jump),
0x20000000,
&codes[__codes__label0__],
INST_ADDR(eqi),
0x04020000,
&codes[__codes__label1__],
INST_ADDR(subi8),
0x04020102,
INST_ADDR(add),
0x00030409,
INST_ADDR(record-refi8),
0x04000008,
INST_ADDR(swap),
0x00090300,
INST_ADDR(move),
0x00030400,
INST_ADDR(jump),
0x00080000,
INST_ADDR(record-refi8),
0x04010009,
INST_ADDR(move),
0x00010000,
INST_ADDR(move),
0x00030100,
INST_ADDR(jump),
0x00090000,
INST_ADDR(record-offsi8),
0x04000100,
INST_ADDR(record-refi8),
0x04000009,
INST_ADDR(movei),
0x04000103,
INST_ADDR(movei),
0x04000004,
INST_ADDR(jump),
0x00090000,
INST_ADDR(heap),
0x04000209,
0x0000C000,
&codes[__codes__FIBO__],
&codes[__codes__FIBO-2__],
INST_ADDR(record-refi8),
0x04090008,
INST_ADDR(move),
0x00090000,
INST_ADDR(movei),
0x20000000,
&codes[__codes__EXIT__],
INST_ADDR(movei),
0x04000802,
INST_ADDR(jump),
0x00080000,

#define __codes_MAIN__ 0x00000003
#define __codes_EXIT__ 0x00000004
#define __codes__EXIT__ 0x00000005
#define __codes__MAIN__ 0x00000007
#define __codes__FIBO-2__ 0x0000000A
#define __codes__label1__ 0x00000019
#define __codes__FIBO__ 0x00000021
#define __codes__label0__ 0x0000002B

元のソースは mini scheme のフィボナッチだ。あやしいけどできるはず。ところどころ - があるな、、、まぁいいや手で修正しよう。