diff options
Diffstat (limited to 'services/nuldoc/content/posts/2025-05-05')
| -rw-r--r-- | services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.md (renamed from services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.dj) | 57 |
1 files changed, 9 insertions, 48 deletions
diff --git a/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.dj b/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.md index 51046f2..cb50ecd 100644 --- a/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.dj +++ b/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.md @@ -11,8 +11,7 @@ tags = [ date = "2025-05-05" remark = "公開" --- -{#intro} -# はじめに +# はじめに {#intro} C コンパイラと言えば、世界三大自作したいソフトウェアの一角である。 というわけで [『低レイヤを知りたい人のためのCコンパイラ作成入門』](https://www.sigbus.info/compilerbook) (以下 compilerbook) 片手に作ることにした。 @@ -21,8 +20,7 @@ C コンパイラと言えば、世界三大自作したいソフトウェアの [P4Dcc のリポジトリはこちら](https://github.com/nsfisis/P4Dcc) -{#regulation} -# レギュレーション +# レギュレーション {#regulation} * 実装するのは C 言語からアセンブリ言語への変換部分のみ。アセンブラやリンカは GCC をそのまま用いる * compilerbook を読みながら実装してよい @@ -30,114 +28,80 @@ C コンパイラと言えば、世界三大自作したいソフトウェアの * GCC の出力は見てもよい。それ以外のコンパイラの出力 (特に 9cc などの compilerbook 準拠のコンパイラ) は見ない * ソースコードの生成やデバッグに AI を使わない。ツールの使用方法を調べる目的 (GCC に渡すフラグなど) には使ってよい -{#design} -# 設計 +# 設計 {#design} ゴールデンウィークの4日間で終わらせたいので、実装する言語機能は最低限に絞ることが必要になる。 今回は次のような設計とした (compilerbook の設計を踏襲しているものは除く)。 * 宣言の文法を単純にパースできるものに絞る - * `typedef` をサポートしない - * 構造体には必ず `struct` キーワードを書く - * 配列型をサポートしない - * 常にヒープに確保してポインタ経由で扱う - * 以上の制限により、型に関する情報が必ず変数名の前に来る - * 無くてもなんとかなる構文糖を実装しない。ソースを書くときに頑張る - * インクリメント・デクリメント演算子 (1足したり引いたりする) * 複合代入演算子 (左辺と右辺で 2回書く) - * なお、`+=` と `-=` はセルフホスト達成後に実装された - * `while` (`for` で置き換える) - * なお、`while` はセルフホスト達成後に実装された - * `switch` (`if` で置き換える) * ほか多数 - * プリプロセッサのほとんどを実装しない - * 数値または識別子へ置換する単純な `#define` のみサポートする * 特に、`#include` をサポートしないのは重要な設計判断。すべて 1ファイルでおこなう - * グローバル変数を用いない - * `stdin`、`stdout`、`stderr` を含む * これは compilerbook とは大きく設計が変わった部分 * これにより、トップレベルに来るのは関数か構造体の定義/宣言のみとなった - * 変数のシャドウイングを実装しない - * 変数は常に関数スコープ * グローバル変数もないので、スコープチェーンの実装が不要になる -{#language-features} -## 言語機能 +## 言語機能 {#language-features} 最終的にサポートされた機能は以下のとおり。 * 文 - * `if` / `else` * `for` * `break` * `continue` * `return` * `while` (実装はセルフホスト達成後) - * 式 - * 二項演算 - * `+` / `-` / `*` / `/` / `%` * `==` / `!=` * `<` / `<=` / `>` / `>=` * `&&` / `||` - * 代入 - * `=` * `+=` / `-=` (実装はセルフホスト達成後) - * 単項演算: `-` / `!` / `*` / `&` / `sizeof` * 関数呼び出し: `f(a, b)` * 配列アクセス: `a[b]` * メンバ呼び出し: `a.b` / `a->b` * 整数リテラル * 文字列リテラル - * 型 - * `char` * `int` * `long` * `void` * `struct` * それらのポインタ - * 宣言・定義 - * 関数 * 構造体 - * プリプロセッサ - * 引数なし `#define` -{#development} -# 開発 +# 開発 {#development} 時系列順に開発の様子を辿っていく。 -{#day1} -## 1日目 (2025-05-03) +## 1日目 (2025-05-03) {#day1} compilerbook では整数一つのパース・コード生成から始めるが、今回は以下のようなソースをパースしてコード生成するところからスタートすることにした。 @@ -189,8 +153,7 @@ int main() { } ``` -{#day2} -## 2日目 (2025-05-03) +## 2日目 (2025-05-03) {#day2} この時点で、不足している機能はおおよそ2つ。ポインタと構造体である。 @@ -226,8 +189,7 @@ int main() { これらの作業をおこなうことで、処理系自身のソースコード `main.c` をパースしてバイナリを出力することができるようになった。 いわゆる第2世代のコンパイラである。この現時点ではまだ第2世代コンパイラは何もできない (何を与えてもクラッシュする)。 -{#day3} -## 3日目 (2025-05-03) +## 3日目 (2025-05-03) {#day3} さて、第2世代コンパイラが手に入ったので、ここからは地獄のデバッグ作業が始まる。多段になっているために問題が起きている箇所の特定が難しい。 @@ -278,8 +240,7 @@ $ diff -u <(hexdump -C p4dcc2) <(hexdump -C p4dcc3) これにてセルフホスト達成である。 -{#outro} -# おわりに +# おわりに {#outro} 最終的な実装は1900行ほど、所要時間は20時間弱となった。 |
