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 のフィボナッチだ。あやしいけどできるはず。ところどころ - があるな、、、まぁいいや手で修正しよう。