- x86-16アセンブリで書かれた SectorC は、x86マシンの ブートセクタ(512バイト) に収まる超小型Cコンパイラで、実際に動作するプログラムを書けるだけの C言語の部分集合 をサポートする
- グローバル変数、関数、if/while文、演算子、ポインタのデリファレンス、コメント、インラインアセンブリ などを含み、最小限の構成でも完全なプログラム実行が可能
- トークナイザを単純化するため、空白ベースのトークン化 と
atoi() ハッシュを利用した Barely C言語 を設計し、既存のC文法を保ちながらも極端なサイズ削減を実現
- コード最適化の過程で ジャンプ削減、呼び出し統合、8ビットオフセット活用、
stosw/lodsw 使用 など、さまざまなアセンブリレベルの圧縮技法を適用し、468バイトから303バイトへ縮小
- 結果として、512バイトの中に トークナイザ、パーサ、コード生成器、ランタイム をすべて含む完全なCコンパイラを実装し、ソフトウェア極小化の極端な実例 を示した
SectorC 概要
- SectorCは x86-16アセンブリ で書かれたCコンパイラで、512バイトのブートセクタ に完全に収まる
- 対応機能には グローバル変数、関数、制御文(if/while)、各種演算子、ポインタのデリファレンス、インラインアセンブリ、コメント などが含まれる
- 例として VGAモードでサイン波アニメーションを描画するコード が示されている
設計背景とアプローチ
- 既存のCトークナイザは512バイトでは不可能なほど大きいため、言語構造そのものを単純化 する必要があった
- Big Insight #1: Forth言語のように 空白で区切られたトークン構造 を導入し、「Barely C」という変形言語を設計
- 例:
int(main)(){while(!done){ のような構文を1つの「メガトークン」として処理
- 既存のCコンパイラでも依然として有効なCコードとして認識される
- Big Insight #2:
atoi() 関数を ハッシュ関数 のように使ってトークンを数値に変換
- 整数リテラル、キーワード、識別子をすべて
atoi() の結果値として扱う
- 識別子は64K配列のインデックスとしてアクセスする
Barely C の実装
- 最初の実装は 468バイト で、
atoi ベースのトークンを使う 再帰下降パーサ 構造
- シンボルテーブルなしで64Kセグメントへハッシュ値で直接アクセス
- コード生成はOTCCスタイルで
ax レジスタを結果の格納先として使う
- その後 バイトスレッドコード(byte-threaded code) の実験を通じてForth式構造も試したが、512バイト内ではかえって非効率だったため破棄された
コード極小化の技法
- 直線的な構造に戻して 468バイト → 303バイト に縮小
- ジャンプ削減(fall-through)、tail-call、呼び出し統合(call fusion)、
stosw/lodsw 活用、8ビットジャンプオフセット維持 などの技法を使用
- 207バイトの空き領域を確保し、追加機能を実装
完全なC機能への拡張
- 200バイト追加して 完全なC文法対応 を達成
- ネストした
if/while ブロック、さまざまな 二項演算子 (+, -, *, &, |, ^, <<, >>, ==, !=, <, >, <=, >=)
- 関数定義と再帰呼び出し、インラインアセンブリ (
asm)、1行/複数行コメント をサポート
- 演算子テーブル (
binary_oper_tbl) により各演算子を4バイトで定義し、14個の演算子を56バイトで処理
文法構造
- 全体の文法は
program、var_decl、func_decl、statement、expr などで構成される
// および /* */ コメントの両方をサポート
- 文法定義テキスト自体は704バイトで、実装そのものより大きい
インラインアセンブリとランタイム
asm 構文を通じて x86-16機械語を直接挿入 可能
- ランタイム(
rt/)はCで書かれた2つのファイルで構成
rt/lib.c: インラインアセンブリベースのライブラリルーチン
rt/_start.c: プログラムのエントリポイント _start()
サンプルプログラム
examples/hello.c: テキストを 0xB8000 メモリへ直接出力
examples/sinwave.c: VGAモード 0x13 でサイン波アニメーションを表示
examples/twinkle.c: PCスピーカーで「Twinkle Twinkle Little Star」を演奏(音の警告あり)
結論
- SectorCは 不可能に見える目標を実現した超小型Cコンパイラ であり、
ソフトウェア極小化と創造的な言語設計の極端な実例 を示している
- 記事の最後はユーモラスな「何を学んだか」という選択肢で締めくくられ、
不可能へ挑む姿勢とソフトウェア単純化の価値 を風刺的に強調している
1件のコメント
Hacker Newsのコメント
2000年代の最適化コンパイラなら、そういうトークンをそのままno-opに最適化してしまっていたかもしれない 😉
ブートセクタゲームを作るのは本当に郷愁を誘う魔法みたいな体験だ。プログラミングが本当に楽しかった時代を思い出す。
今のAI時代では、こういうプロジェクトがあまりにも過小評価されているのが残念。
自分のプロジェクトへのリンク
君のプロジェクトは、ブートセクタ向けコードを生成できるオプションがあるように見える。
どちらも面白いけれど、共通点は「boot sector」「C」「compiler」くらいしかない。
だから、もう自分は特別じゃないという気分になる。
私たちは抽象化を積み重ね続けていて、「Hello World」を1つ動かすだけでも200MBのnode_modulesが必要になる。
なのに、ある人は512バイトの中にCコンパイラを収めてしまう。
みんながブートセクタコードを書くべきだという話ではないけれど、こういうプロジェクトを読むのは本当に謙虚な気持ちにさせられる体験だ。教育的価値も大きい。
このプロジェクトは、Cの中核となる構造がどれほど単純かをよく示している。
CがB言語から、そしてBがFortranの縮小版から発展したという点も興味深い。
アセンブリには結局jmpしかないわけだし。
それと、「choose your own adventure」ではなく「chose your own adventure」と書くべきだね :)
私はミニマリズムが大好きだ。
非常に小さなプラットフォーム専用バイナリから始めて、そこから徐々により複雑なツールやコンパイラを作っていく、という形だ。
例としては、mishmashvm や tcc_bootstrap_alt のプロジェクトを参照できる。
当時の議論はこちらで行われていた。
SectorC: A C Compiler in 512 bytes - リンク - 2023年5月(コメント80件)くらいにまとめられる。
一方でこのプロジェクトが扱えるのはCの一部だけだ。
つまり、これは完全なCコンパイラというより、Cのサブセットを扱うコンパイラだ。
このコンパイラでコンパイルできるコードは本物のCコンパイラでもコンパイルできるが、その逆は成り立たない。
幸い、32ビットで衝突するほど大量のシンボルは使っていなかったと願う。
ちなみに、0x01e0〜0x01fdの間には21バイトの空き領域が残っている。まだ何か入れられるかもしれない ;)
ただ、私の記憶ではatoi()は無効な入力に対して0を返すよう定義されていたはずだ。
Linuxで実行する場合は、qemuの呼び出しをcoreaudioではなくalsaに変えればよい。
そのためのpull requestをGitHubに送った。作者が自分の冗長なシェルスクリプトのスタイルを許容してくれれば、マージされるかもしれない。
今こそそこにCコンパイラを追加してみる時かもしれない。
自分のOSプロジェクトへのリンク