8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作仮想マシンの自作アセンブラに向けた自作コンパイラで遊んだ話

Last updated at Posted at 2025-05-05

はじめに

x86_64コンパイラを作ろうとしたのですが、もう複雑で複雑で挫折しました。
そこで、自分で理想のISAを持つ仮想マシンを作ろうとしました。

仮想マシン

こんな感じに実行の様子が見えます。

image.png

仕様はこちら。

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(base, exponent)
    Let number = 1
    Let index = 0
    Let flag = true
    While flag
        If index = exponent
            Let flag = false
        Else
            Let index = index + 1
            Let number = number * base
        End If
    End While
    Return number
End Sub
コンパイルで生成されたアセンブリ
	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
	psh ar
	mov ar, 7
	mov dr, ar
	pop ar
	neg dr
	add ar, dr
	sta 0, ar	; bit
	lda ar, 0	; bit
	psh ar
	mov ar, 8
	psh ar
	cal subroutine_pow
	pop ar
	psh ar
	mov ar, 1
	mov dr, ar
	pop ar
	neg dr
	add ar, dr
	sta 1, ar	; max_1byte
	hlt
subroutine_pow:
	pop ar
	sta 2, ar	; exponent
	pop ar
	sta 3, ar	; base
	mov ar, 1
	sta 4, ar	; number
	mov ar, 0
	sta 5, ar	; index
	mov ar, 1
	sta 6, ar	; flag
while_start_1:
	lda ar, 6	; flag
	mov cr, ar
	nor cr, cr
	jmp cr, while_end_1
	lda ar, 5	; index
	psh ar
	lda ar, 2	; exponent
	mov dr, ar
	pop ar
	eql ar, dr
	mov cr, ar
	jmp cr, if_then_0
	jmp 1, if_else_0
if_then_0:
	mov ar, 0
	sta 6, ar	; flag
	jmp 1, if_end_0
if_else_0:
	lda ar, 5	; index
	psh ar
	mov ar, 1
	mov dr, ar
	pop ar
	add ar, dr
	sta 5, ar	; index
	lda ar, 4	; number
	psh ar
	lda ar, 3	; base
	mov dr, ar
	pop ar
	mul ar, dr
	sta 4, ar	; number
if_end_0:
	jmp 1, while_start_1
while_end_1:
	lda ar, 4	; number
	psh ar
	ret

Forth

思ったよりパーサーの実装が難しかったです。(まだ不完全です)

twice: 2 * ;
half: 2 / ;

main:
    5 twice twice half
    10 =
        ? 1 2
        ¥ 3 4
    # + half
    8 ! ;
コンパイルで生成されたアセンブリ
	cal word_main
	hlt
word_twice:
	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_twice
	cal word_twice
	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

コロンの使われ方が本来のForthとは違うのではないか?

はい、違いますね。これは意図した独自の拡張です。
: twice 2 * ;よりtwice: 2 * ;の方が、単語名と本体の分離が明確になるので。

日本語プログラミング言語Mind風の文法

倍 とは 2 を 掛 ける こと。
半分 とは 2 で 割 る こと。

始まり とは
    5 の 倍 の 倍 の 半分 が
    10 と 等 しい
        ならば 1 と 2 を
        でなければ 3 と 4 を
    つぎに
    足 しあわせて 半分 にして
    メモリの 8 番地に 書 きこむ こと。

感想

ちょうど子供の日なので良いおもちゃになったと思います。

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?