Linux の産室・MINIX。その MINIX 自身は、何がコンパイルしたのか
gcc とは違う思想 ── EM という中間コードを真ん中に挟む。この一手が、移植とブートストラップを一変させます。
第2回で、Linux は MINIX という産室で生まれたと見ました。では ── その MINIX 自身は、何がコンパイルしたのか。答えは ACK(Amsterdam Compiler Kit)。Tanenbaum らが作った、gcc とはまた別の系統のコンパイラです。今回はさかのぼりを少し休めて、ACK が体現する「中間コードを挟む」という大発明を味わいます。これは移植を劇的に楽にし、しかも第6回の BCPL の O-code、さらには現代の LLVM IR や WebAssembly まで貫く、コンパイラ史の背骨のひとつです。
MINIX は1987年、Tanenbaum が教科書『Operating Systems: Design and Implementation』の付録として作った、教育用のUnix風OSです。カーネル・メモリ管理・ファイルシステムで約12,000行。学生が丸ごと読める大きさに抑え、設計はマイクロカーネル(第1回の論争の主役)。第1回の言葉でいえば、再実装の木の一枚 ── AT&T のコードは1行も使わず、Unixらしさだけを白紙から書いたものでした。
その MINIX を実際にコンパイルしていたのが ACK。Tanenbaum と Ceriel Jacobs が作った、1980年代初頭としては先進的なretargetable(機種を差し替えられる)コンパイラ群です。ACK の特徴は、扱える言語が一つではないこと ── C・Pascal・Modula-2・Occam・BASIC のフロントエンドを持っていました。
フロントエンド(言語ごと)→ EM(中間バイトコード)→ 汎用オプティマイザ → バックエンド(機種ごと)→ 機械語。
肝は真ん中の EM。どの言語のフロントエンドも、いったん EM という共通の中間コードに落とす。あとはそれを機種ごとのバックエンドが機械語へ翻訳する。gcc の内部中間表現と似ていますが、EM は「ハードウェアでも実装できる実在の抽象機械の言語」として設計された点が個性です。
中間コードのありがたみは、数を数えるといっぺんに分かります。M 個の言語を N 種類の機種で動かしたい。素朴にやると、言語と機種の全組み合わせぶんのコンパイラが要る ── M×N 個。ところが真ん中に共通の中間コードを置けば、必要なのはM 個のフロントエンド + N 個のバックエンド = M+N 個で済む。下の図で、M と N を増やして、線の数がどれだけ違うか見てください。
中間コードは、第0回の「1回だけ着火」の言語版です。言語の知識はフロントエンドに1回だけ、機種の知識はバックエンドに1回だけ書けばいい。両者は EM を介してだけ会話するので、互いを知らなくてよい。関心事を真ん中で切り離すことで、掛け算だった手間が足し算になる。
M+N のありがたみが最大化するのは、新しい機種に移すときです。中間コードが無ければ、新機種のために M 個のコンパイラを全部書き直す。中間コードがあれば ── バックエンド(あるいは EM を動かす小さな核)を1つ書くだけで、既存の全言語がその機種で動きだす。下の図で、新機種を1台足してみてください。
まっさらな新機種を立ち上げるとき(第3回の宿題)、素朴には「コンパイラをどう持ち込むか」で詰まります。中間コードなら、EM を動かす小さな核を1つ移植すれば、EM で配られたコンパイラ資産がまるごと動く。大きな塔を、小さな土台1つで支える。第0回「輪は外から一度だけ持ち上げる」の、持ち上げ方をうんと軽くする工夫です。
「真ん中に共通の中間コードを置く」という発想は、ACK の専売ではありません。むしろコンパイラ史を貫く大河です。次回以降でさかのぼる BCPL の O-code(第6回)は、この考えのずっと古い先祖。そして現代の LLVM IR(clang の中間表現)、JVM バイトコード、WebAssembly は、みな同じ血を引く子孫です。ACK の EM は、その大河の途中にある、美しく分かりやすい一里塚でした。
clang に切り替えました。第1回で予告した「再実装の木が、こずえで血統の木から葉を借りる」実例です。ACK 自身も2003年に BSD ライセンスでオープンソース化され、いまも読める古典になっています。
(1) ACK 自身もプログラムであり、動かすにはコンパイラが要ります ── 中間コードは鶏と卵を消しません。移植や再利用を劇的に楽にするだけで、「最初の種」はやはりどこかから持ち込む必要がある(第3回の宿題は生きたまま)。(2) 「M+N」は概念上の数え方で、実際には汎用オプティマイザや実行時ライブラリなど別の部品もあります。線の本数は理想化した目安です。
(3) MINIX と ACK の関係も、バージョンや配布物で細部が異なります(初期は限定的なCコンパイラ、後年は他のコンパイラも移植・利用可能に)。ここでは「純正の中核ツールチェーンが ACK だった」という骨格を語っています。(4) 「EM はハードウェアで実装できる」は設計思想の主張で、実際に広く実装されたという意味ではありません。
Linux の産室・MINIX(1987, Tanenbaum, 約12,000行, マイクロカーネル, 再実装の木の一枚)をコンパイルしていたのは ACK。C・Pascal・Modula-2… を扱う retargetable なコンパイラで、肝は真ん中の中間コード EM。フロントエンド(言語ごと)→ EM → バックエンド(機種ごと)と三段に切り離すことで、素朴なら M×N 個要るコンパイラが M+N 個で済む。
真価は移植で出る。新機種には EM の核を1つ足すだけで全言語が動く ── 大きな塔を小さな土台で支える、ブートストラップの軽量化だ。ただし中間コードは鶏と卵を楽にするだけで消しはしない(種はやはり持ち込む)。この「真ん中に中間コードを置く」発想は、第6回の O-code を先祖に、LLVM IR や WebAssembly まで続く大河だった。── さて寄り道はここまで。次回はいよいよ、着火装置 cc の祖先、最初の C コンパイラの誕生へ降ります。
印刷 / PDF 化:⌘+P(Windows は Ctrl+P)。画面では図1のスライダーで M×N と M+N の差を、図2のボタンで新機種への移植を試せます。「答えを見る」で解答が開きます。