わかるUNIXの歴史第 6 回 / 中間コードという逃げ道

C を育てた土台の B。その B は、何から来たのか

BとBCPL ── 中間コードという逃げ道 C の親 B は、Martin Richards の BCPL の子。そして BCPL には、第4回の EM を数年先取りする発明 ── O-code という中間コードがありました。「機種が変わっても、真ん中まで作り直さなくていい」。移植とブートストラップを支えた、古くて強い逃げ道。

必要な道具:第4回(EM)・第5回(B→C) 種明かし:真ん中に“機種非依存の島”を置く

第5回で C は B から育ったと見て、最後に「では B は何から?」と残しました。答えは BCPL。1967年に Martin Richards が作った言語です。じつは Ken Thompson は最初 PDP-7 用に Fortran のコンパイラを書こうとし、メモリに収まらず、BCPL をぐっと簡略化して型なし(1語だけ)の B を作りました。そして BCPL には、今回の主役 ── O-code という中間コードがあった。第4回の EM で見た「真ん中を挟む」発想の、ずっと古い先祖です。この逃げ道こそ、鶏と卵をさかのぼる旅でくり返し現れる“移植の魔法”でした。

01系図の続き ── CPL → BCPL → B → C

第5回の系図を、もう一段さかのぼります。C の祖先は一本道です。

/* 言語の系図(続き) */ CPL ──▶ BCPL // Martin Richards, 1967, Cambridge。移植性のために設計 BCPL ──▶ B // Thompson, 1969。BCPLを簡略化、型なし(1語)、PDP-7 B ──▶ C // Ritchie(第5回)。型を足してCへ

B は「BCPL の意味を絞り込み、別の書き方に詰め込んだ」言語でした。最初期の PDP-7 版はスレッデッドコードにコンパイルされ、のちに Ritchie が TMG という道具(コンパイラを作るためのコンパイラ)を使って機械語を吐く B コンパイラを書いています。── 道具にもまた道具がある。輪は本当にどこまでも続きます。

02BCPL の発明 ── O-code という中間コード

BCPL がコンパイラ史に残る理由は、言語そのものよりコンパイラの作りにあります。Richards は BCPL のコンパイラを、はっきり二つ(実際は三段)に割りました ── 前半が原文を読んで O-code(機種非依存の中間コード)を吐き、後半がその O-code をその機種の機械語へ翻訳する。第4回の ACK/EM と、まったく同じ骨格です。

図1:O-code パイプライン。フロントエンド(BCPL→O-code)は機種非依存で共通。機種を変えても、差し替えるのはバックエンド(O-code→機械語)だけ ── コンパイラの約1/5
O-code がくれた具体的な数字

新しい機種に BCPL を移すとき、書き直すのはコンパイラの約 1/5(バックエンドだけ)。当時の見積もりで2〜5人月。残り 4/5(フロントエンド)は機種を知らないので、そのまま使い回せる。

結果、BCPL コンパイラはそれ自身が BCPL で書かれ、しかも移植が容易。だから当時「新しいシステムをブートストラップする定番の言語」になりました。Richards は2003年、この功績(「BCPL によるシステムソフトの移植性の先駆」)で表彰されています。まさに本シリーズのテーマそのものです。

03種明かし ── 逃げ道の正体は「機種非依存の島」

中間コードの正体は、ソース言語と機械語のあいだに、“機種を知らない島”を一つ置くことです。フロントエンドは島まで運べばよく、機械のことを知らなくていい。機械の知識はバックエンドに隔離される。だから機種が変わっても、島から向こう(バックエンド)だけ架け替えればいい。

今回の急所 ── 一族の名を並べる

この「機種非依存の島」は、名前を変えて何度も現れます ── O-code(BCPL, 1967) → Pascal の P-code → 第4回の EM(ACK) → JVM バイトコードLLVM IR(clang) → WebAssembly。半世紀を貫く同じ一つの発明です。ブートストラップの文脈では効き方が特に鮮やか ── 島まで作っておけば、新機種で架けるのは最後の短い橋(小さなバックエンド/インタプリタ)だけ

◇ ◇ ◇

04逃げ道で、機械から機械へ渡る

O-code が本当に威力を出すのは、コンパイラ自身を新しい機械へ運ぶときです。O-code は機種非依存なので、コンパイラを O-code の姿にしておけば、そのまま新しい機械へ運べる。あとは新機械で小さな受け皿(O-code を動かす後半)を用意するだけ。下の図で、その渡り方を順に見てください。

図2:O-code で機械から機械へ。BCPLコンパイラを O-code にして新マシンへ運び、小さな受け皿を1つ書けば、そこでコンパイラが動きだす ── 第8回「紙テープで運ぶ」の原理版
つなぐ声 ── これは第8回の“原理”です 「機種非依存の姿にして、別の機械へ運び、向こうで受け取る」── この筋書きは、第8回で見る初代 Unix の作り方とそっくりです。あちらは、別のマシンでアセンブルした結果を紙テープに穴で刻んで PDP-7 まで物理的に運びました。運ぶものが O-code か紙テープかの違いだけで、「よそで作って、こちらへ渡す」=クロス方式という骨格は同じ。中間コードは、その渡しを身軽にする工夫でした(番外編「クロスコンパイル」でも深掘り)。

05それでも、逃げ道の下には機械がある

O-code は移植を身軽にしますが、鶏と卵を消しはしません(第4回と同じ但し書き)。新機械には、結局「O-code を動かす最初の受け皿」を、機械語で用意しないといけない。その受け皿は誰が作る? ── ここからは中間コードの魔法が効かない領域です。B も BCPL も、いちばん下ではアセンブリ言語、そして初代 Unix そのものへ。次回からは、いよいよ言語の下・機械のすぐ上へ降ります。

正直な線 ── ていねいに

(1) 「約1/5」「2〜5人月」は当時の代表的な見積もりで、機種や時代で幅があります。骨格(フロントエンドは共通、バックエンドだけ差し替え)が本体です。(2) BCPL のコンパイラは「二つに割る」と言いましたが、実際は三段構成で、O-code はその中間段の名前です。さらに移植をうんと軽くするための、より単純な中間形(インタプリタで受ける方式)も用意されました。ここでは骨格を優先しています。

(3) B が「BCPL の簡略化」なのは事実ですが、Thompson 自身の好みや以前の言語(bon)の影響も混じります。系図は一本の細い糸ではなく、束です。(4) 中間コードは移植を楽にするだけで、最初の受け皿(機械語)と最初の機械は、やはり別に用意が要る ── 鶏と卵は下段へ送られるだけ、という点は第3・4回と同じです。

練習問題(今回の話だけで解けます)
  1. C の系図を CPL から C まで並べよ。
    答えを見る
    CPL → BCPL(Richards, 1967)→ B(Thompson, 1969, 型なし)→ C(Ritchie)。
  2. O-code を挟むと、新機種への移植で書き直すのはどこか。だいたいどれくらいか。
    答えを見る
    バックエンド(O-code→機械語)だけ。コンパイラの約1/5。フロントエンド(約4/5)は機種非依存なので使い回せる。
  3. 「機種非依存の島」を置くと、機械の知識はどこに集まるか。
    答えを見る
    バックエンド(島→機械語)に隔離される。フロントエンドは機械を知らずに済み、島(中間コード)までを担当する。
  4. O-code は「鶏と卵」を解決するか。第8回とどうつながるか。
    答えを見る
    解決しない。新機械には最初の受け皿(機械語)が別に要る。「よそで作って運ぶ」という骨格は、第8回で紙テープを別マシンから PDP-7 へ運ぶクロス方式と同じ。

まとめ真ん中に島を置けば、機械が変わっても橋だけ架け替える

C を育てた B は、Martin Richards の BCPL(1967)の子。Thompson が PDP-7 で Fortran コンパイラに挫折し、BCPL を型なしに簡略化したのが B だった(CPL→BCPL→B→C)。BCPL の真価はコンパイラの作りにあり、前半がO-code(機種非依存の中間コード)を吐き、後半がそれを機械語へ訳す。新機種にはバックエンド(約1/5・2〜5人月)だけ書き直せばよく、だから BCPL はブートストラップの定番言語になった。

中間コードの正体は「機種を知らない島」を真ん中に置き、機械の知識をバックエンドに隔離すること。O-code → P-code → EM → JVM → LLVM IR → WASM と、半世紀を貫く同じ発明だ。しかも O-code はコンパイラを別の機械へ運ぶ逃げ道になる ── これは第8回「紙テープで運ぶ」初代 Unix の原理版。ただし逃げ道の下では、新機械に最初の受け皿(機械語)を別に用意せねばならず、鶏と卵は下段へ送られる。── 次回、いよいよ言語の下、アセンブリで書かれた初代 Unix へ。

この文書は「わかるUNIXの歴史」シリーズ第6回です。史実:BCPL は Martin Richards が University of Cambridge で1967年に最初に実装した言語で、CPL に由来する。BCPL コンパイラはフロントエンドが機種非依存の中間コード O-code(OCODE)を生成し、バックエンドがそれを対象機種の機械語へ変換する構成(実際は三段)で、新機種対応に必要な書き換えはコンパイラの約1/5・当時の見積もりで2〜5人月とされた。BCPL コンパイラ自身が BCPL で書かれ移植容易だったため、システムのブートストラップに広く使われた。Richards は2003年に IEEE Computer Society の Computer Pioneer Award を「BCPL によるシステムソフトウェアの移植性の先駆」で受賞。B は Ken Thompson が1969年に Bell 研で作った BCPL の直系子孫で、当初 PDP-7 用 Fortran コンパイラの試みがメモリに収まらず、BCPL を簡略化した型なし(唯一のデータ型が計算機語)言語となった。最初期の PDP-7 実装はスレッデッドコードにコンパイルされ、Ritchie は TMG を用いて機械語を生成する B コンパイラを書いた。中間コードを介する設計は P-code・EM・JVM バイトコード・LLVM IR・WebAssembly に通じる。数値や段数、系図の細部には諸説・幅がある。 ── 印刷する場合はブラウザの「印刷」から「PDF に保存」を(印刷版では図の操作と解答は静止/非表示になります)。

印刷 / PDF 化:⌘+P(Windows は Ctrl+P)。画面では図1のボタンで対象機種を変え(差し替わるのはバックエンドだけ)、図2のスライダーで O-code による機械間の移植を追えます。「答えを見る」で解答が開きます。