Slide 1

Slide 1 text

コンパイラ作りの 魅力を語る @DQNEO phperkaigi 2019.3.30

Slide 2

Slide 2 text

自己紹介 @DQNEO どきゅねお アメリカ版メルカリを開発しています ● PHPer ● C初心者 ● Go中級者

Slide 3

Slide 3 text

目次 ● デモ ● コンパイラとは何か?何をしているのか? ● なんで作ろうと思ったのか? ● どうやって作ったのか?

Slide 4

Slide 4 text

趣味: コンパイラ作り Cコンパイラ https://github.com/DQNEO/8cc.go 8ccをGoに移植したもの Goコンパイラ https://github.com/DQNEO/minigo Goで書いたGoコンパイラ

Slide 5

Slide 5 text

デモ: Cコンパイラ Fizz Buzz

Slide 6

Slide 6 text

デモ: Goコンパイラ Fizz Buzz

Slide 7

Slide 7 text

PHPerとCコンパイラ

Slide 8

Slide 8 text

コンパイラとは何か

Slide 9

Slide 9 text

コンパイラとは何か 広義: 言語Xのコードを言語Yに変換するソフトウェア 狭義: 言語Xのコードを低レベル言語(アセンブリ/機械語 /etc)に変換するプログラム

Slide 10

Slide 10 text

コンパイラは 何をしているのか

Slide 11

Slide 11 text

Cコンパイラ (8cc) int sum(int a, int b) { return a + b; } sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret C言語 GNU assembler

Slide 12

Slide 12 text

コンパイラを作ってわかったこと アセンブリの知識が必要

Slide 13

Slide 13 text

コンパイラを作ってわかったこと ● (多くの)コンパイラはアセンブリを吐く ● アセンブリを理解し読み書きするスキル必要 = CPUのはたらきを理解すること

Slide 14

Slide 14 text

sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret アセンブリ言語 ≒ マシン語 ● 各行がCPUへの「命令」 ● どの行も、ハードウェア に対して副作用を生じる (レジスタ(後述)またはメ モリ上のデータを変更す る)

Slide 15

Slide 15 text

sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret GNU Assembler CPUへの命令

Slide 16

Slide 16 text

sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret GNU assembler メモリ 読み書き

Slide 17

Slide 17 text

sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret GNU assembler レジスタ 読み書き

Slide 18

Slide 18 text

コンパイラを作ってわかったこと “レジスタ”が主役 ※私の主観です

Slide 19

Slide 19 text

CPU内にある超高速な一時記憶装置 CPU ← レジスタ “レジスタ”が主役 出典: http://ftp.procmail.org/~sskulrat/Courses/2006F-170/lectures/chap14/part1.html

Slide 20

Slide 20 text

Cコンパイラ (8cc) int sum(int a, int b) { return a + b; } sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret C GNU Assembler 関数 → 関数 変数 → メモリ何番目

Slide 21

Slide 21 text

コンパイラを作ってわかったこと ● C言語上の関数はアセンブリ上でも関数 ● アセンブリには変数がない。 ○ ハードウェア(メモリ/レジスタ)に「書く、読む」 ● 「書き込む」「読み取る」という動作が基本

Slide 22

Slide 22 text

値とは何か

Slide 23

Slide 23 text

高級言語視点 値: 式を評価すると得られる もの 1;

Slide 24

Slide 24 text

mov $1, %rax 低級言語視点

Slide 25

Slide 25 text

値とは何か 1; mov $1, %rax C GNU assembler

Slide 26

Slide 26 text

値とは何か 式を評価して値を得る ↓ ある手続きを実行した際に、ハード ウェアのある場所に値を書き込む

Slide 27

Slide 27 text

値とは副作用である ※私個人の考えです

Slide 28

Slide 28 text

値とは何か 8ccの場合、一連の処理の最後に必 ずraxに値を書き込む約束になってい る。 raxが保持しているデータ、 これが「値」の実体

Slide 29

Slide 29 text

式を評価して値を得る x + y; mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax 演算の結果をraxに書き込む

Slide 30

Slide 30 text

隠れた副作用 実はrcxに副作用が残ったまま。 コンパイラ側で、このrcx値は再利用しないよう に気をつける。 これにより、副作用が1個(式の結果が1値)のよ うに見せかけている。 x + y; mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax

Slide 31

Slide 31 text

関数の戻り値 int sum(int x, int y) { return x + y; } sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret raxに値を書き込ん で関数を抜ける。 これが戻り値。 受け取る側は、rax から値を読み出す。 int sum(int x, int y) { return x + y; }

Slide 32

Slide 32 text

関数の戻り値 int sum(int x, int y) { return x + y; } sum: push %rbp mov %rsp, %rbp push %rdi push %rsi mov -8(%rbp), %rax mov -16(%rbp), %rcx add %rcx, %rax leave ret int sum(int x, int y) { return x + y; } 「純粋な関数」 も、ハードウェア レベルでは副作 用がある

Slide 33

Slide 33 text

コンパイラを作ってわかったこと ● if文とfor文は仕組みがほぼ同じ ○ 条件判定とジャンプ ● ローカル変数とグローバル変数は扱いが全然 違う ● 「スタック」とローカル変数の関係

Slide 34

Slide 34 text

コンパイラ作りで得たもの1 ● アセンブラの読み書き能力 ● 字句解析・構文解析の知識 ● 対象言語(C,Go)の仕様に詳しくなった

Slide 35

Slide 35 text

コンパイラ作りで得たもの2 ● 原典にあたる習慣 ○ 言語仕様書 ○ Intel CPUマニュアル ○ gdb, gasマニュアル

Slide 36

Slide 36 text

Intel® 64 and IA-32 Architectures Software Developer’s Manual

Slide 37

Slide 37 text

コンパイラ作りで得たもの3 ● Segmentation Fault に負けない心 ○ gdb/gccなどのコンパイラツール系スキル

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

簡単なC/Goのコードな ら 脳内コンパイルできる

Slide 40

Slide 40 text

何でコンパイラを作ろうと思っ たのか?

Slide 41

Slide 41 text

もともとは普通の PHPアプリケーション 開発者だった

Slide 42

Slide 42 text

コンパイラ知識ゼロからのスタート ● 字句解析・構文解析の解説を読んでも理解不能 ● C,Goもそんなに詳しくない 「プログラムが動く原理を知りたい」

Slide 43

Slide 43 text

Rebuild.fm153回 https://rebuild.fm/153/ Rui Ueyama さんが Cコンパイラ(8cc)を作った話 きっかけ めちゃくちゃ面白い!!

Slide 44

Slide 44 text

● C言語製Cコンパイラ ● スクラッチから開発 ● インクリメンタル開発 8cc https://github.com/rui314/8cc

Slide 45

Slide 45 text

さっそく git clone

Slide 46

Slide 46 text

8cc 1コミット目 #include #include 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; } #include extern int mymain(void); int main(int argc, char **argv) { int val = mymain(); printf("%d\n", val); return 0; } https://github.com/rui314/8cc/commit/3764b2071b9601067b81976d80175a0851d0f209

Slide 47

Slide 47 text

なんとなくわかる! しかし、ただ読んでるだけでは身につ かなそう Goに移植してみよう!

Slide 48

Slide 48 text

1コミット移植してみた https://github.com/DQNEO/8cc.go/commit/b711967431 07e50e0ac945383abc5b2c3bf2e302 動いた! 8cc.go (Goへの移植)

Slide 49

Slide 49 text

8ccのコミット履歴を1個ずつ ● テストの追加分を見る ● そのテストをパスするC実装を自力でやってみる ● 無理なら正解をチラ見する ● 自力でCで再現する ● 無理なら写経 ● Goに移植する 延々と繰り返す 8cc.go (Goへの移植)

Slide 50

Slide 50 text

● 5ヶ月半毎日継続 ● 95コミット移植完了 ● 基本的な文法はだいたい動く 8cc.go (Goへの移植)

Slide 51

Slide 51 text

移植で見えてきた問題点 ● 8ccの字句読み取りの設計が複雑 ● 9ccはきれいに設計できてる ● 自分でもゼロから設計してみたい!

Slide 52

Slide 52 text

Goコンパイラ開発 ● 試しにGoでGoのtokenizerを書いてみた ● あっさり動いた!感動! → その勢いでGoコンパイラ開発に突入

Slide 53

Slide 53 text

minigo (Goコンパイラ) ● 1日目:足し算が動いた! ● 2日目:引き算・掛け算・関数呼び出しが動いた! ● 5日目: helloworld.go が動いた!

Slide 54

Slide 54 text

minigo (Goコンパイラ) ● 1ヶ月後:FizzBuzzが動いた!

Slide 55

Slide 55 text

● 5ヶ月後:自分自身のコンパイルに成功! (時間があればデモ) minigo (Goコンパイラ)

Slide 56

Slide 56 text

野望 ● 夏〜秋ごろにセルフホスト達成予定 ● 来年のアメリカのGopherConに応募するぞ

Slide 57

Slide 57 text

コンパイラはいいぞ

Slide 58

Slide 58 text

ご清聴 ありがとうございました