「低レイヤを知りたい人のためのCコンパイラ作成入門」の「ステップ12: 制御構文を足す」ではまる

はじめに

今、

低レイヤを知りたい人のためのCコンパイラ作成入門

を見ながらCコンパイラを作っている。C言語は一通り文法を学んでちょっとしたプログラムが書けるくらいで、ポインタへのポインタが出てくるとちょっと「うっ」となるレベル。

セルフホストまで行けるかかなり怪しいが、少なくともこのオンラインブックに書かれているステップはほぼ写経になろうともやり遂げたいと思う。

なお、この問題は自分があまりに愚直にオンラインブックの実装を辿っていただけだから発生したのかもしれない。 9ccやchibiccのソースを参考にしたり、自分でちゃんと実装を考えてた人は当たらないかも。

(ちなみに私はfor文は「for(初期化部; 条件部; 更新部) 本体」という構成だと思っているので「初期化部」などの用語が出てきたらあそこだと思ってほしい)

問題

で、今ステップ12まで来てif、for、whileあたりを実装していてできたと思っていた。 簡単なハッピーパスのテストは通ったので満足していた。

しかし、色々試しながら出てくるアセンブリを見ていたところpushとpopの数が合ってない。

例えば、この時点で以下のような無限ループするfor文

for(;;) 1;

を作ったコンパイラに通すと出てくるアセンブリ

.intel_syntax noprefix
.globl main
main:
  push rbp
  mov rbp, rsp
  sub rsp, 208
.Lbegin0:
  push 1
  jmp .Lbegin0
.Lend0:
  pop rax
  mov rsp, rbp
  pop rbp
  ret

こうなる。push 1が無限に呼ばれるので最終的にsegmentation faultになる。 無限ループしたいが無限ループにならずに落ちる。

解決

これは式(文)がスタックに結果を積むことを忘れていた(考慮してなかった)ために起こっているので、 forの本体のコード生成直後にpop raxを追加してやれば良い。

なお、自分は.Lend0:のあとにpush raxを追加するようにしておき、for文の評価結果とした。 これは、無限ループにならないfor文なら条件部の評価結果で0が入っているはず。

これをしておかないとコード全体のpushとpopの数が合わなくなる。

この状態で上のfor無限ループをコンパイルすると生成されるアセンブリはこうなった。

.intel_syntax noprefix
.globl main
main:
  push rbp
  mov rbp, rsp
  sub rsp, 208
.Lbegin0:
  push 1
  pop rax
  jmp .Lbegin0
.Lend0:
  push rax
  pop rax
  mov rsp, rbp
  pop rbp
  ret

おわりに

この対処が正しいのかもわからないし、本当ならそろそろ生成するアセンブリを工夫しないといけないのかもしれない。

なお、この問題はwhileでも起こる。while(1) 1;のようなコードを書けばわかる。

for文の初期化部や更新部でも起こる。for(;; i = i+1) 1;のようなコードをコンパイルすると同じくsegmentation faultになった。