Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コンパイラ作りの魅力を語る / Making compilers is fun

DQNEO
March 30, 2019

コンパイラ作りの魅力を語る / Making compilers is fun

PHPerKaigi 2019で、C/Goコンパイラを作る過程で学んだことについて話しました。

DQNEO

March 30, 2019
Tweet

More Decks by DQNEO

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. デモ: Cコンパイラ
    Fizz Buzz

    View Slide

  6. デモ: Goコンパイラ
    Fizz Buzz

    View Slide

  7. PHPerとCコンパイラ
    php-src
    (C)
    C compiler
    Assembly
    Binary
    /usr/bin/php
    スクリプト
    処理系

    View Slide

  8. コンパイラとは何か

    View Slide

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

    View Slide

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

    View Slide

  11. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. 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への命令

    View Slide

  16. 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
    メモリ
    読み書き

    View Slide

  17. 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
    レジスタ
    読み書き

    View Slide

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

    View Slide

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

    View Slide

  20. 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
    関数 → 関数
    変数 → メモリ何番目

    View Slide

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

    View Slide

  22. 値とは何か

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. 値とは何か
    式を評価して値を得る

    ある手続きを実行した際に、ハード
    ウェアのある場所に値を書き込む

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. 関数の戻り値
    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;
    }

    View Slide

  32. 関数の戻り値
    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;
    }
    「純粋な関数」
    も、ハードウェア
    レベルでは副作
    用がある

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. View Slide

  39. 簡単なC/Goのコードな

    脳内コンパイルできる

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. さっそく git clone

    View Slide

  46. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. コンパイラはいいぞ

    View Slide

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

    View Slide