aboutsummaryrefslogtreecommitdiffhomepage
path: root/services/nuldoc/content/posts/2025-05-05
diff options
context:
space:
mode:
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時間弱となった。