サイト「低レイヤを知りたい人のためのCコンパイラ作成入門」をやってみる 1回目

掲題通りです。コンパイラを作成することでコード解析とアセンブラ出力が学習できれば。

参照サイト

www.sigbus.info

環境

想定する実行環境が64bit LinuxということでWSLをインストールします。 Windows側では/User/hoge/ubuntuというフォルダを用意します。 WSL側で/home/hoge/.bashrcの末尾に cd /mnt/c/users/hoge/ubuntu と記載しておき、WSL起動時に開発ディレクトリにアクセスできるようにしています。

簡単な例

int main() {
  return 42;
}
$ gcc -o test1 test1.c
$ ./test1
$ echo $?
42

gccして終了コードを$?で見れることが分かる。

次に上記コードに対応するアセンブリプログラムを書いてみる

.intel_syntax noprefix
.global main
main:
        mov rax, 42
        ret

呼び出し規約で関数の戻り値はレジスタraxに入れる、とのこと。gccして$?を参照すると先ほどと同様終了コードをみることが出来た。

関数呼び出しのコードがアセンブラでどうなるか

関数呼び出しはただのジャンプと違い、呼び出された関数の終了後、元の場所に戻らなければいけない。 そこで元のアドレス「リターンアドレス」をスタックメモリに保存しておく。 なぜスタックなのかは、多段呼び出しに対応するため。

スタックはスタックの一番上のアドレスを記憶しておけばよい。この一番上のアドレスを記憶しているのがスタックポインタ。x86-64には

がサポートされている。ちなみにスタックにデータを積むことを「プッシュ」と言い、データを取り出すことを「ポップ」という(復習)。(キュー構造ではデータ追加がenqueue、データ取り出しがdequeue)

以下、関数呼び出しの実例。引数x, yを足し合わせる関数plusとそれを呼び出して戻り値を終了コードに乗せるmain関数をアセンブラで書いたもの。Cコードは割愛。

.intel_syntax noprefix #アセンブリ文法指定
.global plus, main #plusとmainがプログラム全体から見えることを指示

plus:
        add rsi, rdi
        mov rax, rsi
        ret

main:
        mov rdi, 3 #お約束
        mov rsi, 4 #第一引数はrdi、第二引数はrsiレジスタ
        call plus
        ret

関数を呼び出す命令callは以下のことを行う。

  • callの次の命令のアドレスをスタックにプッシュ(上述の「リターンアドレス」?)
  • callの引数アドレスにジャンプ

関数の戻り値はraxに入れておく決まりなので、plus関数のret命令の前にraxレジスタへの格納命令がある。

plus関数の最後にret命令がある。ret命令は以下のことを行う。

  • スタックからアドレスを1つポップ(取り出す)
  • 取り出したアドレスにジャンプ

mainの中でcall plusの次の命令のアドレスをスタックにプッシュしていたので、plus関数のret命令によりcall plusの次のアドレスに飛ぶことができる。

章の(個人的)まとめ

  • 関数戻り値はraxレジスタに格納
  • callとretは対。

次章

次は「電卓レベルの言語の作成」をやります。