Last Modified: <%LastModified%>

Q言語コンパイラ

yacc および lex を使用したコンパイラ作成の例として, 単純なプログラミング言語「Q言語」のコンパイラを作成した.

以下にその覚書を記す.


ソース言語 - Q言語

基本的には,C言語類似のシンタックスを持っていると考えてよい.ただし,

サンプルも参照されたい.


目的言語 - IA-32インテル(R)アーキテクチャのアセンブリ言語

目的言語は,IA-32インテル(R)アーキテクチャのアセンブリ言語である. gcc に付随しているアセンブラ (gas) で処理できるように,gas の intel 形式で出力する.そのため,出力されたコード (*.s) は通常の cc (gcc) を用いて実行ファイルが生成できる.

IA-32インテル(R)アーキテクチャの詳細については下記資料を参照されたい.

命令の一部を以下に簡単に示す.

.intel_syntax
インテル形式を指定する.
.macro マクロ名 引数...
マクロ命令の定義を始める.
.endm
マクロ命令の定義を終了する.
.comm 領域名, サイズ
共有領域 (大域変数) の名前とサイズを定義する.
.ascii 文字列, …
文字列リテラル(定数)を定義する.
.globl 名前
名前を大域的な名前として宣言する.
jmp ラベル
ラベルに分岐する (無条件分岐).
jnz/jz/jne/je/jge/jg/jle/jl ラベル
条件が満たされたとき,ラベルに分岐する (条件分岐).
call 手続き
手続きを呼び出す.手続きは, ラベル (ラベルそのものを飛び先とする〈直接〉) や [ラベル] (ラベルの場所のデータを番地として飛び先とする〈間接〉) や [レジスタ + 数値] (レジスタの内容に数値を足した結果の番地を飛び先とする〈インデックス〉) などがある.
ret
手続きから呼び出し元へ戻る.
mov 先, 元
元から先へデータを転送する.先, 元 の表現については後述.
lea 先, 元
元の実効アドレスを先に格納する.
push 元
元の内容をスタックにプッシュする.
pop 先
スタックからポップして先に転送する.
or/add/sub/imul 先, 元
先と元で演算(論理和/加算/減算/乗算)して,結果を先へ格納する.
cdq
eax の32bit長の数値を(符号を保って)64bit長に拡張し,ebx:eax に格納する.
idiv 元
ebx:eax と元で演算(除算)して, 結果を eax へ格納する.
cmp 先, 元
先と元を比較して,結果をフラグレジスタへ格納する.
inc 先
先の内容に1を足す.
dec 先
先の内容から1を引く.
neg 先
先の内容の正負を反転させる.

また,除算を他の演算と同じ形式で書けるように, 以下のようなマクロ命令 mdiv を定義して用いる.

mdiv eax, 元
eax と元で演算して,結果を eax へ格納する.

その定義は以下のとおりである.

  .macro mdiv  eax, SRC
         cdq
         idiv  \SRC
  .endm

なお,上の「先」「元」には以下のようなものがある.(組合せによっては許されないものもある)

レジスタ
%eax, %ebx, %ecx, %edx (以上汎用レジスタ), %ebp (ベースポインタ), %esp (スタックポインタ) など.(いずれも 32bit)〈レジスタ直接〉
数値
数値を直接用いる〈イミディエイト〉
ラベル
ラベルが表わす番地のメモリ上の領域〈直接〉
[%eax*4+ラベル]
ラベルが表わす番地に %eax の内容の4倍を加えた番地のメモリ上の領域 〈インデックスの一種〉
[%ebp+数値]
ベースレジスタ %ebp の内容に数値を加えた結果を番地として,その番地のメモリ上の領域 〈ベースレジスタ相対〉
[%ebp+%eax*4+数値]
ベースレジスタ %ebp の内容に %eax の内容の4倍を加え,更に数値を加えた結果を番地として, その番地のメモリ上の領域〈ベースレジスタ相対,インデックス〉

開発環境

コンパイラは RedHat Linux 上で bison (GNU版の yacc 類似品),flex, cc (gcc) を使用して作成したが,gcc (gas) のバージョンによってはうまく動かないかもしれない.


コンパイラの概略


各ファイルの概略

src/

sample/

Q言語で書かれたプログラムのサンプル.


三浦欽也 (miura@mail.kobe-c.ac.jp), 三浦研究室