From a65bb9609284d273f0aa232dbaf69597c87f5a12 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 15 Jun 2025 13:12:46 +0900 Subject: feat(blog/nuldoc): merge consecutive text nodes --- .../make-tiny-self-hosted-c-compiler/index.html | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'vhosts/blog/public/posts/2025-05-05/make-tiny-self-hosted-c-compiler') diff --git a/vhosts/blog/public/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html b/vhosts/blog/public/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html index 4b7b2a68..299d3f94 100644 --- a/vhosts/blog/public/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html +++ b/vhosts/blog/public/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html @@ -60,7 +60,7 @@

はじめに

- C コンパイラと言えば、世界三大自作したいソフトウェアの一角である。 というわけで 『低レイヤを知りたい人のためのCコンパイラ作成入門』 (以下 compilerbook) 片手に作ることにした。 + C コンパイラと言えば、世界三大自作したいソフトウェアの一角である。というわけで 『低レイヤを知りたい人のためのCコンパイラ作成入門』 (以下 compilerbook) 片手に作ることにした。

実装する機能を適切に絞ってやればゴールデンウィークの間 (2025-05-03 から 2025-05-06) にセルフホストまで持っていけるのではないか?という仮説を立て、ISO 8601 の表記で 4日間を表す “P4D” を冠して P4Dcc と名付けた。 @@ -92,7 +92,7 @@

設計

- ゴールデンウィークの4日間で終わらせたいので、実装する言語機能は最低限に絞ることが必要になる。 今回は次のような設計とした (compilerbook の設計を踏襲しているものは除く)。 + ゴールデンウィークの4日間で終わらせたいので、実装する言語機能は最低限に絞ることが必要になる。今回は次のような設計とした (compilerbook の設計を踏襲しているものは除く)。

  • @@ -333,7 +333,7 @@ compilerbook のようなインクリメンタルな進め方を取らずに、最初から普通の言語処理系のような構成にしたのには理由がある。

    - それは、どのくらいの言語機能があればコンパイラを作るのに十分かをこの時点で見積もるためである。 開発を開始する前にも必要な言語機能にはあたりを付けていたが、実際にプロトタイプを作ってみて、これだけの機能セットがあれば足りるだろうという正確な TODO リストを作りたかった。 実際、このとき作ったチェックリストはこのあともほとんど変わっていない (大きな変化点は、配列型をサポートしないと決めたことくらいか)。 + それは、どのくらいの言語機能があればコンパイラを作るのに十分かをこの時点で見積もるためである。開発を開始する前にも必要な言語機能にはあたりを付けていたが、実際にプロトタイプを作ってみて、これだけの機能セットがあれば足りるだろうという正確な TODO リストを作りたかった。実際、このとき作ったチェックリストはこのあともほとんど変わっていない (大きな変化点は、配列型をサポートしないと決めたことくらいか)。

    このあとは、おおむね compilerbook に従って以下のように機能追加を続けた。 @@ -455,13 +455,13 @@ &*sizeof あたりの実装が終わるとかなり C 言語らしくなっていき楽しい。

    - このあたりから、セルフホストに向けて逆方向からのアプローチも並行しておこなっている。 セルフホストするためには処理系のソースコードで使っている言語機能をすべて実装する必要があるわけだが、これまでは処理系が扱える機能を拡充していくという方向だった。この逆、つまり処理系のソースコードで使っている機能を減らすことでもセルフホストに近付いていく。 + このあたりから、セルフホストに向けて逆方向からのアプローチも並行しておこなっている。セルフホストするためには処理系のソースコードで使っている言語機能をすべて実装する必要があるわけだが、これまでは処理系が扱える機能を拡充していくという方向だった。この逆、つまり処理系のソースコードで使っている機能を減らすことでもセルフホストに近付いていく。

    - 例えば、このコンパイラは typedef をサポートしていないが、開発中ずっと typedef を使わないというのは面倒だ。 そこで、セルフホストがある程度現実的になるまでは構造体を typedef しておいて、途中のどこかで typedef を手で脱糖する。 + 例えば、このコンパイラは typedef をサポートしていないが、開発中ずっと typedef を使わないというのは面倒だ。そこで、セルフホストがある程度現実的になるまでは構造体を typedef しておいて、途中のどこかで typedef を手で脱糖する。

    - これらの作業をおこなうことで、処理系自身のソースコード main.c をパースしてバイナリを出力することができるようになった。 いわゆる第2世代のコンパイラである。この現時点ではまだ第2世代コンパイラは何もできない (何を与えてもクラッシュする)。 + これらの作業をおこなうことで、処理系自身のソースコード main.c をパースしてバイナリを出力することができるようになった。いわゆる第2世代のコンパイラである。この現時点ではまだ第2世代コンパイラは何もできない (何を与えてもクラッシュする)。

@@ -470,7 +470,7 @@ さて、第2世代コンパイラが手に入ったので、ここからは地獄のデバッグ作業が始まる。多段になっているために問題が起きている箇所の特定が難しい。

- ……と考えていたのだが、実際のところデバッグは1時間ほどで終わってしまった。 修正したのは1点のみ。 なんのことはない、2日目終了時点でほとんど完成していたわけだ。 + ……と考えていたのだが、実際のところデバッグは1時間ほどで終わってしまった。修正したのは1点のみ。なんのことはない、2日目終了時点でほとんど完成していたわけだ。

記念すべき (?) 最後のバグはこちら。 @@ -485,13 +485,13 @@ }

- メモリアドレスから参照先の値を得る際、その型によってロードする命令の種類を変える必要があるのだが、その切替をポインタ型でおこなっていた。 正しくは、そのポインタ型が指す型を元にして切り替えなければならない。 + メモリアドレスから参照先の値を得る際、その型によってロードする命令の種類を変える必要があるのだが、その切替をポインタ型でおこなっていた。正しくは、そのポインタ型が指す型を元にして切り替えなければならない。

これを修正すると、第2世代コンパイラが第3世代コンパイラを出力できるようになり、その後も第N世代が第N+1世代を生成できるようになった。

- あとは、第2世代のコンパイラがそれ以降のコンパイラとバイナリレベルで一致するかどうかを確かめればよい。 実際に調べてみると、ほとんどの場所が一致したもののどの世代も 6バイトだけ異なることがわかった。 + あとは、第2世代のコンパイラがそれ以降のコンパイラとバイナリレベルで一致するかどうかを確かめればよい。実際に調べてみると、ほとんどの場所が一致したもののどの世代も 6バイトだけ異なることがわかった。

一体どこが異なるのか。hexdump の差分がこちら。 @@ -509,7 +509,7 @@ 00015e10 79 70 65 5f 6e 65 77 00 74 79 70 65 5f 6e 65 77 |ype_new.type_new|

- fatal_errorread_alltokenize type_new はいずれも main.c で定義された関数の名前である。 このことから考えると、これは GCC が埋め込んだシンボルテーブルである可能性が高い。 わずかに異なっている 6バイトは、ランダム生成された何かのように見える。 + fatal_errorread_alltokenize type_new はいずれも main.c で定義された関数の名前である。このことから考えると、これは GCC が埋め込んだシンボルテーブルである可能性が高い。わずかに異なっている 6バイトは、ランダム生成された何かのように見える。

そこで gcc-s (シンボルテーブルを削除するフラグ) を渡してみると、めでたく2世代目以降のコンパイラのバイナリが完全に一致するようになった。 @@ -525,10 +525,10 @@ 最終的な実装は1900行ほど、所要時間は20時間弱となった。

- 正直なところ、思ったより早く終わって拍子抜けしている。 これは compilerbook がうまく実装順を整理しているのと、アセンブリの細かい落とし穴を事前に解説して潰していることが大きいと思われる。 + 正直なところ、思ったより早く終わって拍子抜けしている。これは compilerbook がうまく実装順を整理しているのと、アセンブリの細かい落とし穴を事前に解説して潰していることが大きいと思われる。

- 当初の仮説どおり、サポートする機能を慎重に選ぶことにより短期間でセルフホストまで持っていくことができた。 案外簡単に作れてしまうので、まとまった休みに是非いかがだろうか。 + 当初の仮説どおり、サポートする機能を慎重に選ぶことにより短期間でセルフホストまで持っていくことができた。案外簡単に作れてしまうので、まとまった休みに是非いかがだろうか。

-- cgit v1.2.3-70-g09d2