2013-05-23

デニス・リッチーによって書かれた最初のCコンパイラーがGitHubで公開

mortdeus/legacy-cc · GitHub

デニス・リッチー(1941-2011)によって書かれた発展途中の初期のCコンパイラーのソースコードがGitHubで公開されている。ソースコード自体は、まだデニス・リッチー存命の頃から公開されていたが、この機会に紹介してみる。

ライセンスは許諾的で、このライセンス文を同梱すること、並びに、ソースコードから生成されたバイナリはラインセンス文を出力することとなっている。

追記:ライセンスに関してふと思った。このライセンスは名義が企業で、著作権を盾に同意を求めている。日本では、映画以外の団体の著作権の保護期間は公開後50年である。UNIXと付属するソフトウェアのソースコードは当時から公開されていた。ということは、1973年に制作されたこのCコンパイラーのソースコードは、日本国内では、2024年に著作権が切れるのだろうか。

詳しい経緯は、以下のページに書かれている。

Primeval C: two very early compilers

数年前、Paul VixieとKeith Bosticが、VAXに接続されたDECテープドライブを発見したので、昔のDECテープを読み取ってくれることになった。当時でさえ、もはや考古学的な発掘作業であり、1970年代前半のDECテープに日の光を当てる機会であった。私は喜んで手伝い、いくつかのアーティファクトをここに提供する。残念ながら、現存するテープには、最初期のUNIX OSソースのような興味深いものは入っていなかったものの、展示のための化石は用意できた。

追記:今はBond大学にいるWarren Toomeyが、このうちのひとつのコンパイラー(last1120c、下記参照)を使って、PDP-11用のFirst/Second edition Unixエミュレーター上で、自分自身をコンパイルさせることに成功したそうだ。彼のFTPディレクトリを参照されたし。さらに、The PDP-11 Unix Preservation Societyのページで、ソースとエミュレーターについて解説している。

C歴史論文で書いているように、1972年から1973年にかけては、C言語が形成された年である。この時、型無しのBから、弱い型付けがされたCが生まれ、NB言語[訳注:B言語から派生したNew B言語のこと](ネアンデルタール人か?)の影響を受けて修正され、ソースは現存していないと思われていた。またまさにこの時、UNIXがCで書きなおされた。

これを眺めると、なんとも言えない感情が沸き上がってくる。まだ未発展であまりうまく書かれていない。これを展示するのは恥ずべきことだ。だが同時に、創造的な時代のふたつの瞬間を捉えており、歴史的興味のあるものかもしれない。

ここで公開するのはふたつのテープだ。一つ目は"last1120c"とラベル付けされているもので、二つ目は"prestruct-c"というものだ。昔の記憶から、私にはこれが何を意味するものか知っている。一つ目は、我々がPDP-11/20対応をやめるときに保存されたコンパイラーのコピーだ。PDP-11/20には乗算や除算の命令がなく、別の追加ユニットでそのような操作(それとシフト)を、メモリ場所にオペランドを配置することで行なっていた。このハードウェアを利用する話は、別の場所で語られている。

"prestruct-c"というのは、コンパイラー自身が構造体を使い始める直前に私が保存したコンパイラーのコピーだ。

このコンパイラーの年代を正確に割り出すのは難しい。ただし1972-1973の間であることは確かだ。テープイメージには日付ビットも存在するのだが、この時代、我々は起点時間を何度も変更していたので、年度がずれたようなエラーとなっているし、ファイルも幾度とない利用により、コピーされたか変更されたかしているからだ。

初期の方のコンパイラーは、構造体を知らなかった。構造体("struct")という文字列はどこにもでてこない。二つ目のテープは、構造体を今のような意味で実装したコンパイラーが入っている。宣言文法は、どうも{}ではなく()を使っているようだが、それでも . や -> を使って構造体や構造体のポインターのメンバーを指定する機能は、両方とも存在する。

どちらのコンパイラーも、現代の一般的な宣言文法や、それどころかK&R第一版のような、int **ipp; のような複合的な宣言子文法を扱えない。この当時のコンパイラーは、まだ複合的な型の構築(「関数へのポインターの配列」とかのような)という形を扱うほど進歩していなかったのだ。その機能は、5thか6thのUNIX(1975年あたりだったかな)で出現していて、それから数年後のCマニュアルに(Postscriptで)書かれている。

かわりに、ポインター宣言は int ip[] ; の形で書かれていた。この時代の化石は、現代のCでも生き残っている。この文法は、引数を宣言する際に使われている。ただし、* という文法は使われていないものの、受け付けるようだ(自分自身の言語で書かれている発展中のコンパイラーは、自分自身の最新の機能を使わないように注意を払うものである)

興味深いことに、初期の方のコンパイラーは、"long"キーワードをサポートするためのコメントアウトされた用意が残されている。後期の方のコンパイラーは、その場所が"struct"で占められている。longの実装は、数年後のようだ。

その小さなサイズもさることながら、最も衝撃的なのは、その原始的な構築方法だろう。多くの定数がそこら中に散らばっている。これは、当時プリプロセッサーが存在しなかったためである。

もうひとつ、気づきにくいが、驚くほど特徴的なのは、容量確保だ。一時ストレージは、意図的にプログラムの冒頭を上書きし、容量を節約するために、初期化コードをぶち壊している。ふたつのコンパイラーは、これを扱う方法について違いが観られる。初期の方は、関数に名前をつけることで、冒頭を発見している。後期の方では、冒頭は単に0としている。このことからみるに、初期のコンパイラーはメモリーマッピングが存在する以前のコンピューターで書かれたものであり、プログラムの場所は0からではないが、二つ目のコンパイラーの時代では、我々にはマッピング機能を提供するPDP-11があった。(Unix歴史論文を参照されたし)。ファイル(prestruct-c/c10.c)でも、やっつけ仕事なのは明らかだ。

ソースコードへのリンクは下に並べてある[訳注:原文を参照]。c0?.cという名前のファイルから始まる。これはソースをパースして、中間ファイルとしてのテキストで表現されたシンタックスツリーを出力する。c1?.cファイルは、コードジェネレーターで、ツリーを読み込んで、コードを生成する。フォーマットは単純なテキストだ(行を分割するためにNL文字が使われているが、私の使っているブラウザーは問題なく表示できるようだ)。

コード生成の方法は、命令プロトタイプのテーブルを使っている。パースツリーはテーブルの中のルート演算子と再帰的にマッチされる。型の制約とオペランドの複雑性が表現され、テーブルは最初にマッチするものを見つけるまで線形に探される。各制限指定の次に来るのは、展開していだ。小文字はリテラルであり、大文字はツリーのオペランドで置き換えられる。これはPDO-11コンパイラーへのツアーでより詳しく解説されている。(このリファレンスはtroffソースである。PostscriptやPDFの形式のものが、他の論文とともに、7th Edition Manualのホームページで公開されている)。しかし、このツアーは、このソースコードより数年後のものを解説していることに注意。

4つのテーブルで、式からレジスタへのコンパイル方法、副作用だけの目的でのコンパイル、条件コードの判定だけの目的でのコンパイル、スタックにプッシュする方式でのコンパイル(関数の実引数や一時オブジェクトに使われる)を指定している。これは"last1120c"の方しか残っていない。後期もよく似たものだったはずだ。

last1120cコンパイラーのソースは、各パスに補助的なテーブルがあり、ライブラリにないものとか、.s(アセンブラー言語)ファイルのエンコード方式などを指定している。

最後に、cvoptプログラムというものがある。これはnonce-language expression template tableをアセンブラーに変換するために使われている。多くの手作業での修正を行えば、おそらくは動くlast1120cコンパイラーを構築することが可能ではないか。「動く」というのは、「ソースをPDP-11アセンブラーに変換」という意味である。(ページの上部にある成功例を参照)

最後の方のコンパイラーの説明はかなり怪しいやっつけ翻訳だ。

No comments: