世界を動かす技術を、日本語で。

SectorC: 512バイトのCコンパイラ

概要

  • SectorC は、512バイトのブートセクタに収まる 超小型Cコンパイラ
  • x86-16アセンブリ で書かれ、実用的なCのサブセットをサポート。
  • トークナイザやパーサ の極限までの最適化が特徴。
  • 独自の工夫 (ハッシュや“Barely C”など)で限界に挑戦。
  • 実際の x86ハードウェア制御例 も多数収録。

SectorC:512バイトに収まるCコンパイラ

  • SectorC は、 x86-16アセンブリ で記述されたCコンパイラ。
  • 512バイトのブートセクタ に全機能を実装。
  • 実用的な C言語サブセット (グローバル変数、関数、if/while文、各種演算子、ポインタ、インライン機械語、コメント等)をサポート。
  • 例として、 サイン波アニメーション を描画するCプログラムも実行可能。
    • VGAやPCスピーカー等、 x86-16 IBM PCのハードウェア制御 にも対応。

トークナイズと最小化への挑戦

  • Cの トークナイザ/レキサ は通常巨大で、512バイト制限では不可能に思える規模。
  • キーワード・識別子・演算子・数値 の判別、数値変換(atoi)等が必須。
  • 通常の実装では 数百バイト超 が必要となり、とても収まらない。
  • それでも作者は「 負ける勝負こそ面白い」精神で開発を継続。

Big Insight #1:スペース区切り“Barely C”言語

  • Forth のようなスペース区切り言語に着想を得て、 “Barely C” を考案。
  • すべてのトークンをスペースで区切ることで、トークナイザを大幅に単純化。
  • 例:int(main)(){while(!done){ のような “メガトークン” を生成。
  • 通常のCコンパイラでも解釈可能なため、互換性を維持。

Big Insight #2:atoi()によるハッシュ化

  • atoi() を利用し、識別子やキーワードの ハッシュ値 として活用。
  • 16ビット整数値に変換し、64K配列を変数アクセスに利用。
  • 整数リテラル、キーワード、識別子 すべてをハッシュ一元化で管理。

Barely Cの実装と最適化

  • 最初のBarely C実装 は468バイトで完成。
  • シンボルテーブルなし、64Kセグメントをハッシュ値でアクセス。
  • OTCC風 のコード生成を採用し、最小限の命令で値をスタックやレジスタに移動。

Byte-Threaded Codeの試行

  • Forth風の“バイトスレッドコード” も試行。
    • アドレスを1バイトで指定、2バイト境界にガジェットを配置。
  • オーバーヘッド が大きく、512バイト制限下ではメリットが薄いと判明。
  • アイデア自体は記録として残し、他者の参考用に公開。

ストレートな最小化とC言語機能の拡張

  • ストレート実装 に戻し、468→303バイトまで徹底最適化。
  • 207バイト分の余剰 を新機能追加に活用。
  • 最適化手法
    • “fall-through”によるjmp/call削減
    • tail-call最適化
    • call-fusionの利用
    • stosw/lodswの多用
    • 小型命令選択、ジャンプオフセット短縮
  • if/while文の多重ネスト、豊富な演算子、関数定義/再帰呼び出し、インラインasm、コメント対応等、 本格的なC言語 へ進化。

文法仕様(グラマー)

  • プログラム=変数宣言または関数宣言の連続
    • 変数宣言:int 識別子 ;
    • 関数宣言:void 関数名 { ステートメント群 }
    • ステートメント:if/while/asm/関数呼び出し/代入式
    • 代入式:*(int*)によるデリファレンス、各種演算、グルーピング式対応
    • //コメント、/コメント/両対応
  • 実装自体より 文法記述の方が大きい という極小設計。

インライン機械語とI/O

  • C言語はI/O非依存 のため、 asm拡張 で直接x86-16機械語を挿入可能。
  • これにより、 VGA制御やPCスピーカー出力 など、低レベル操作も可能。

エラーハンドリング

  • エラー処理は“自己責任”。プログラマの完全性を信じる設計。
  • バイト節約のため、エラー検出は省略。
  • 別途、 lintツール も提供(セクタ内には収まらない)。

ランタイムと実行方法

  • SectorCは独自ランタイム (rt/lib.c, rt/_start.c)を持つ。
    • ライブラリルーチンやエントリポイントをCで実装。
  • ランタイム+ユーザープログラムを連結して フルソースを生成・実行

代表的なサンプル

  • hello.c :0xB8000へのメモリ書き込みで画面に挨拶表示。
  • sinwave.c :VGAモード0x13でサイン波アニメーション描画。
  • twinkle.c :PCスピーカーで「きらきら星」を再生(音量注意)。

結論・学び

  • 「不可能に見えることも、やってみれば意外とできる」 という教訓。
  • ソフトウェアの肥大化への警鐘、 極小・効率重視設計 の価値。
  • 楽しさ・挑戦心 こそが最大のモチベーション。

このように、SectorCは 極小サイズで本格的なC言語機能 を実現した、技術的にも遊び心にも溢れたプロジェクト。 限界への挑戦最適化の工夫 が随所に詰まっており、低レイヤーやコンパイラ設計に興味のあるエンジニアにとって大いに刺激となる内容。

Hackerたちの意見

美しいけど、タイトルに2023をすぐに追加した方がいいよ。あの時話題になったね。https://news.ycombinator.com/item?id=36064971

トークンのハッシュ化や擬似シンボルテーブルを作る方法は、本当にエレガントなアイデアだね。

私も同じことを思う。すごくいいプロジェクトだし、トークンのハッシュ化のトリックも良いね。PS. 21バイト残ってるよ(21 * 0x00 - 0x01e0から0x01fdまで)。そこに何か詰め込めるかもね ;)

もしかしたら私が作者かも…楽しんで!これを作るのは本当に楽しかったよ!

すごくいいね。今、ミニマリストなCコンパイラを書いてるんだけど、目標はブートセクターに収めることじゃなくて、もっと余裕のある8ビットシステムをターゲットにしてるんだ。Cの基本がどれだけシンプルかを示す素晴らしいデモだと思う。これが、私や他の多くの人がCを魅力的に感じる理由の一つだと思う。CはBから進化したもので、BはFortranのダウングレードだったらしいよ、ケン・トンプソンを信じるならね。

もし、whileやforをシンプルなgotoルーチンに置き換えたら、どれくらい縮むんだろう?(結局、アセンブリにはjmpしかないし、他の派手なジャンプ命令はないと思うけど)。それと、PS、「自分で冒険を選べ」だよ。:-) ミニマリズムが大好き。

このコンパイラそのものや、ほぼCの本質的なアイデアの面白い使い方は、ブートストラップチェーンにあるかもしれないね。つまり、特定のプラットフォーム用の小さなバイナリから始めて、それを逆アセンブルして確認しながら、徐々にもっと複雑なツールやインタプリタ、コンパイラを作っていくことで、最終的にはGCCのバージョンのようなものを得て、完全なOSディストリビューションを構築できるようになるんだ。例: https://github.com/cosinusoidally/mishmashvm/ と https://github.com/cosinusoidally/tcc_bootstrap_alt/

かなりストレートフォワードでミニマリストなレクサーを書いたんだけど、150行以上のCコードが必要だった。 "<150" にする予定だったの?

ナイーブな実装は150行以上のCコード(300-450バイト)だったって言ってるね、つまり大きすぎるってこと。

ああ、最近作ったX86-16ブートセクター用のCコンパイラがあるみたいだね。ブートセクターゲームを書くのって、プログラミングが本当に楽しかった頃の懐かしい魔法があるよね。自分のスキルを見せつけることができたし。AIの時代が来て、こういうプロジェクトがすごく評価されなくなったのは残念だな。

それに比べて、クラウドが2週間で10万行書いたCコンパイラは2万ドルだったらしいよ(昨日HNに投稿されてたと思う)。

面白い比較だけど、あっちはLinuxカーネルをコンパイルできて、いろんなアーキテクチャ用のコードを生成できるのに対して、こっちは有効なCのほんの一部しかコンパイルできないっていう大きな違いがあるよね。素晴らしいプロジェクトだけど、実際のCコンパイラでコンパイルできるプログラムもこのコンパイラでコンパイルできるけど、その逆はできないっていう、Cのサブセット用のコンパイラって感じ。

なんでCのサブセットなのに「Cコンパイラ」って呼ばれるの?

構造体のサポートがないから、これを「Cコンパイラ」って呼ぶのはちょっとミニマリスティックすぎると思う。

オプションで含められるライブラリにブートストラップするんだよ、当たり前じゃん。

10KBでゲームを作ったAllegro SizeHackを思い出すな〜。でも、あの時はCとAllegroライブラリを使ってたけどね!

もしこの実装が1980年代にあったら、C標準には異なるトークンが同じ16ビットの値にハッシュされると未定義動作になるっていうルールがあっただろうね。2000年代の最適化コンパイラは、そんなトークンを単に最適化して何もしないようにしちゃうだろうな。 ;)

めっちゃリアル!笑