はじめに
x86_64コンパイラを作ろうとしたのですが、もう複雑で複雑で挫折しました。
そこで、自分で理想のISAを持つ仮想マシンを作ろうとしました。
仮想マシン
こんな感じに実行の様子が見えます。
仕様はこちら。
Ruka VM
手軽な命令セットアーキテクチャを採用した軽量仮想マシン
システム構成
名前 | 説明 | サイズ | 読み込み | 書き込み |
---|---|---|---|---|
プログラム領域 | 実行されるバイトコードを格納 | 可変長 | 不可 | 不可 |
メモリ領域 | データやオブジェクトを格納 | 512バイト固定 | 可能(アドレス指定) | 可能(同左) |
スタック領域 | 関数などとのデータ受け渡し用 | 可変長 | 可能(ポップのみ) | 可能(プッシュのみ) |
関数スタック | 呼び出し元アドレスが積まれる | 可変長 | 不可 | 不可 |
レジスタ群 | 演算時の一時的なデータ格納用 | 64ビット固定 | 可能(mov命令) | 可能(同左) |
※ 全ての値は64ビット浮動小数点数(f64)です。
ISA
ニーモニック | 意味 |
---|---|
MOV レジスタ, 値 | データ転送 |
ADD レジスタ, 値 | 足し算 |
MUL レジスタ, 値 | 掛け算 |
NEG レジスタ | 符号反転 |
INV レジスタ | 逆数 |
EQL レジスタ, 値 | 等価比較 |
LES レジスタ, 値 | 大小比較 |
NOR レジスタ, 値 | 論理ORして否定 |
JMP 条件, アドレス | 条件つきジャンプ |
CAL アドレス | 関数呼び出し |
RET | 呼び出し元に戻る |
LDA レジスタ, アドレス | データ読み込み |
STA アドレス, 値 | データ書き込み |
PSH 値 | スタックに追加 |
POP レジスタ | スタックから取り出し |
NOP | 何もしない |
HLT | プログラム終了 |
レジスタ
名前 | 意味 | 役割 |
---|---|---|
PC | Program Counter | 次の命令のアドレス |
AR | Accumulator Register | 演算結果の格納 |
DR | Data Register | データの格納 |
CR | Conditional Register | 条件の格納 |
BA | Base Address | メモリの基底アドレス |
SP | Stack Pointer | スタックに長さ |
コンパイラ群
コンパイラを作りました。現在対応してるのはBASICとForthです。他の言語もこれから実装できたらなと思います。
BASIC
Let bit = (1 + 2) * 3 - 7
Let max_1byte = pow(bit, 8) - 1
Exit Program
Sub pow(x, y)
Let n = 1
Let i = 0
While i < y
Let i = i + 1
Let n = n * x
End While
Return n
End Sub
コンパイルで生成されたアセンブリ
line_0:
mov ar, 7
psh ar
mov ar, 1
psh ar
mov ar, 2
mov dr, ar
pop ar
add ar, dr
psh ar
mov ar, 3
mov dr, ar
pop ar
mul ar, dr
mov dr, ar
pop ar
neg ar
add ar, dr
sta 0, ar ; bit
line_1:
mov ar, 1
psh ar
lda ar, 0
psh ar
mov ar, 8
psh ar
cal subroutine_pow
pop ar
mov dr, ar
pop ar
neg ar
add ar, dr
sta 1, ar ; max_1byte
line_2:
hlt
line_4:
subroutine_pow:
pop ar
sta 2, ar ; y
pop ar
sta 3, ar ; x
line_5:
mov ar, 1
sta 4, ar ; n
line_6:
mov ar, 0
sta 5, ar ; i
line_7:
while_start_0:
lda ar, 5
psh ar
lda ar, 2
mov dr, ar
pop ar
les ar, dr
mov cr, ar
nor cr, cr
jmp cr, while_end_0
line_8:
lda ar, 5
psh ar
mov ar, 1
mov dr, ar
pop ar
add ar, dr
sta 5, ar ; i
line_9:
lda ar, 4
psh ar
lda ar, 3
mov dr, ar
pop ar
mul ar, dr
sta 4, ar ; n
line_10:
jmp 1, while_start_0
while_end_0:
line_11:
lda ar, 4
psh ar
ret
line_12:
ret
Forth
思ったよりパーサーの実装が難しかったです。
dup : 2 * ;
half : 2 / ;
main :
5 dup dup half
10 =
? 1 2
¥ 3 4
# + half
8 ! ;
コンパイルで生成されたアセンブリ
cal word_main
hlt
word_dup:
psh 2
pop dr
pop ar
mul ar, dr
psh ar
ret
word_half:
psh 2
pop dr
pop ar
inv dr
mul ar, dr
psh ar
ret
word_main:
psh 5
cal word_dup
cal word_dup
cal word_half
psh 10
pop dr
pop ar
eql ar, dr
psh ar
pop cr
jmp cr, then_0
jmp 1, else_0
then_0:
psh 1
psh 2
jmp 1, end_0
else_0:
psh 3
psh 4
end_0:
pop dr
pop ar
add ar, dr
psh ar
cal word_half
psh 8
pop ba
pop ar
sta ba, ar
ret
日本語プログラミング言語Mind風の文法
倍 とは 2 を 掛 ける こと。
半分 とは 2 で 割 る こと。
始まり とは
5 の 倍 の 倍 の 半分 が
10 と 等 しい
ならば 1 と 2 を
でなければ 3 と 4 を
つぎに
足 しあわせて 半分 にして
メモリの 8 番地に 書 きこむ こと。
感想
ちょうど子供の日なので良いおもちゃになったと思います。