Cコンパイラ8ccの1コミット目を読んでみよう

# 8cc とは?

rui314 さんが作られたCコンパイラです。

次のような特徴があります。

* C言語で書かれている
* スクラッチから作られた。字句解析・構文解析も手書き。
* インクリメンタルな開発

詳しくはご本人の記事と rebuildfm第153回をご参照ください。

[Cコンパイラをスクラッチから開発してみた（日記）](https://qiita.com/ruiu/items/4d471216b71ab48d8b74)

[Rebuild: 153: Connecting The Dots (rui314)](https://rebuild.fm/153/)


# 1コミット目

https://github.com/rui314/8cc/commit/3764b2071b9601067b81976d80175a0851d0f209

git cloneしてcheckoutするとこんな感じになります。

```console
/tmp $ git clone https://github.com/rui314/8cc
/tmp $ cd 8cc
/tmp/8cc $ git checkout 3764b2071b9601067b81976d80175a0851d0f209                                                                                                                                                             
/tmp/8cc $ ls
8cc.c  Makefile  driver.c  test.sh*
```

# 動かし方

8ccはアセンブリを出力しますが、これはLinux上でのみ動きます。
MacやWindowsを使ってる人はLinux環境を用意する必要があります。

私の方でDocker Imageを用意したのでよかったらお使いください。

```sh
docker pull dqneo/ubuntu-build-essential
```

## dockerコンテナを起動して中に入る

```console
/tmp/8cc $ docker run  --rm -it -w /mnt -v $PWD:/mnt dqneo/ubuntu-build-essential bash
```

## コンパイル 
```console
root@0b67a3589f2a:/mnt# make
cc    -c -o 8cc.o 8cc.c
cc   8cc.o   -o 8cc
```

## 使い方
標準入力から数値を渡すと、それをC言語のソースコードとみなしてアセンブリに変換してくれます。

```console
root@0b67a3589f2a:/mnt# echo 7 | ./8cc
  .text
  .global mymain
mymain:
  mov $7, %eax
  ret
```

このアセンブリコードをファイルとして保存します。

```console
root@0b67a3589f2a:/mnt# echo 7 | ./8cc > tmp.s
```

次に、アセンブリファイルと `driver.c` を一緒にコンパイルします。

```console
root@0b67a3589f2a:/mnt# gcc -o tmp.out driver.c tmp.s
```
そうすると `tmp.out` という実行可能ファイルが作られます。

これを実行すると、最初に標準入力から `8cc`に渡した数値が画面に表示されます。

```console
root@0b67a3589f2a:/mnt# ./tmp.out
7
```

ここまでできたら成功です！

以上の手順は、 `test.sh` を実行することで自動でテストすることができます。

```console
root@0b67a3589f2a:/mnt# ./test.sh
All tests passed
```

## ソースコード解説

### 8cc.c

コンパイラ本体です。

```c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  int val;
  if (scanf("%d", &val) == EOF) {
    perror("scanf");
    exit(1);
  }
  printf("\t.text\n\t"
         ".global mymain\n"
         "mymain:\n\t"
         "mov $%d, %%eax\n\t"
         "ret\n", val);
  return 0;
}
```

標準入力から数値を１つ受け取って、それをアセンブリに埋め込みつつ出力します。

吐き出されたアセンブリはこのようになります。 (7はダミー値)

```
    text
    .global mymain
mymain:
    mov 7, %eax
    ret
```

これは下記のようなC言語と同じ働きをします。

```c
int mymain() {
    return 7;
}
```

`8cc.c` は、このように数値をreturnするだけの関数(=プログラム部品)を出力します。

### driver.c

```c
#include <stdio.h>

extern int mymain(void);

int main(int argc, char **argv) {
  int val = mymain();
  printf("%d\n", val);
  return 0;
}
```

コンパイラ本体`8cc.c` にはまだ関数呼び出しの機能がないため、画面に文字を表示するということができません。
よって、動作確認するためには別の仕組みが必要になります。
`driver.c` によりmain関数という外側をつけてあげることでこれが可能になります。

この `driver.c` は後のコミットで消滅する (https://github.com/rui314/8cc/commit/dcc0795288ad8c65f0e677a214c9991251c3640d) ので、よくわからなければ気にしなくて大丈夫です。

興味がある人のために、もう少し詳しく説明してみます。
上の手順だと裏側が見えにくいので、ステップを細分化してみましょう。


```console
root@0b67a3589f2a:/mnt# echo 7 | ./8cc > tmp.s
root@0b67a3589f2a:/mnt# gcc -S -o driver.s driver.c
root@0b67a3589f2a:/mnt# as -o tmp.o tmp.s
root@0b67a3589f2a:/mnt# as -o driver.o driver.s
root@0b67a3589f2a:/mnt# gcc -o tmp.out tmp.o driver.o
root@0b67a3589f2a:/mnt# ./tmp.out
7
```

`driver.c` はいったんアセンブリ `driver.s` に変換されます。
アセンブリ `driver.s` と `tmp.s` はアセンブラ(as)によってオブジェクトファイル(`driver.o`, `tmp.o`)に変換されます。

最後に、オブジェクトファイルがリンカによってひとつの実行ファイルに結合されます。

`gcc` を使っているとリンカの動きが見えにくいですね。
そこで `gcc -v` とすると内部の動きを見ることができます。

```console
root@0b67a3589f2a:/mnt# gcc -v -o tmp.out tmp.o driver.o
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-16ubuntu3' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --with-as=/usr/bin/x86_64-linux-gnu-as --with-ld=/usr/bin/x86_64-linux-gnu-ld --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'tmp.out' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/7/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper -plugin-opt=-fresolution=/tmp/cc6xlhn6.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o tmp.out /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. tmp.o driver.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-o' 'tmp.out' '-mtune=generic' '-march=x86-64'
```

最後の方に現れる `/usr/lib/gcc/x86_64-linux-gnu/7/collect2` がリンカです。

```console
root@0b67a3589f2a:/mnt# /usr/lib/gcc/x86_64-linux-gnu/7/collect2 --version
collect2 version 7.3.0
/usr/bin/x86_64-linux-gnu-ld --version
GNU ld (GNU Binutils for Ubuntu) 2.30
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.
```

余談ですがruiさんはこれとは別のリンカ(lld)の作者でもあります。

## これでCコンパイラと呼べるのか？

C言語の仕様を極限まで小さくしたCっぽい言語のコンパイラであると言えるとは思います。

そして発想を変えると、ここから出発してC以外の言語のコンパイラに成長させることもできます。
実際に私はここから出発してGo言語コンパイラを作っていますが、今のところうまく動いています。