発掘!現存する最古のCコンパイラの初期バージョンがマジですごい
引用元:https://news.ycombinator.com/item?id=43433030
Oracle Databaseの最初の公開バージョン(1979年のv2)はPDP-11向けにアセンブリで書かれてたんだって。その後、Oracleはv3(1983年)をCで書き換えて、いろんなプラットフォームで使えるようにしたんだね。当時のメインフレームにはCコンパイラがなかったから、COBOLとか別の言語でメインフレーム専用のデータベースを作る代わりに、メインフレーム用のCコンパイラも作ったらしいよ。
1980年にUNIXがSystem/370に移植されたけど、TSS上で動いてたんだって。TSSはあんまり知られてないみたいだけど。
System/370向けのUNIXシステムの実装設計は1979年にほとんど終わってて、コーディングは1980年に完了。最初の本番システムは1981年初頭にBell Laboratoriesの施設に導入されたらしい。
詳細は[https://web.archive.org/web/20240930232326/https://www.bell-…]で見れるよ。
へー、面白いね。84/85(たぶん85/86)の夏に、University of British Columbiaのメインフレーム(AmdahlのMTS)で、Scott Kristjansonが作ったSystem/360へのPCCの移植版を使ったよ。メールソフトを開発してたから、EBCDIC/ASCIIの問題に対処する必要があって、大変だった。
そのコンパイラがまだどこかに残ってないかな。
もしこのリストが正しいなら、z/OS 3.1はUNIX 95の認証を受けてるはずだよ。
[https://www.opengroup.org/openbrand/register/index2.html]
それにはCコンパイラも含まれてるはずだけど、君のやつはたぶんテープの中だね。Linuxも中国の会社のおかげでリストに載ってるよ。
>Oracle Databaseの最初の公開バージョン(1979年のv2)はPDP-11向けにアセンブリで書かれてた”
Oracle v2かv3を持ってる人いるのかな?abandonwareサイトで見た中で一番古いのはDOS用のOracle 5.1だよ。
>当時のメインフレームにはCコンパイラがなかった”
1975年のBell Labsのメモには、当時Cコンパイラが3つのマシン向けに存在したって書いてある[0]。PDP-11 UNIX、Honeywell 6000 GCOS、そしてOS/370(OS/VS2のことだと思う。15ページにTSOって書いてあるから、OS/VS1じゃないよね)
OracleがBell LabsのCコンパイラを知らなかったのは信じられるし、Bell Labsも共有しなかっただろうね。それに、最新バージョンのCに対応してたかもわからないし…。SASはLatticeにMVSとCMSへのCコンパイラの移植を依頼したのが1983/1984年頃で、OracleがOracleをIBMメインフレームに移植してた頃とほぼ同じ頃だと思う。
[0] [https://archive.org/details/ThePortableCLibrary_May75/page/n…]
当時のことを話したがる人はいないと思うけど、OracleとIngresの間にはかなりの確執があるんだ。この話は公になってない部分もあるし、弁護士なしでは話せない。
あんなに大きなものをアセンブリで書くなんて、1979年でもクレイジーだね。
Oracleは128KBのRAM(スワップなし)で動くように設計されてたんだよ。だから、何百万行じゃなくて、実際には数万行だったんだ。
当時、そんなに珍しいことだったのかな?UNIX自体もそうだったみたいだし(Cより前に作られて、後からCで書き直された)、70年代にはアセンブリで書かれたものが他にもあったと思うけど。Oracleが他のものよりずっと大きかったのかな。アセンブリは昔からローレベルなものに使われてた言語だと思ってたんだ。
私のお気に入りの関数はこちら: [https://github.com/mortdeus/legacy-cc/blob/936e12cfc756773cb…]
マジで昔のUnixツールって使いにくいよね。if (argc<4) {
error(“Arg count”);
exit(1);
}って感じだし。
SQLiteのエラーメッセージも同じくらい質素だよね。最近SQLite拡張を書いたんだけど、詳細で動的なエラーメッセージを作るのは難しくなかったから、作者の好みだったのかも。
ああ、インラインアセンブリがなかったからかな?それなら’nop’オペレーションで置き換えられる気がする。
それって何の意味があるの?
それはメモリを確保するための変な方法だよ。重要なのは、コンパイラのフェーズ両方でこれが行われていて、プログラムのリンク方法によって、予約された領域が両方のフェーズで同じアドレスを持つことが保証されてること。だから、ポインタを含む式木を2番目のフェーズに簡潔に渡せるんだ。見た目は良くないけど、ハードウェアの制限で奇妙な解決策を考えざるを得ないこともあるんだよ。
前に出てきた’ospace’を’waste’から参照する実際のコードはここにあるよ。https://github.com/mortdeus/legacy-cc/blob/936e12cfc756773cb…
ありがとうございます!それって今でも関係あるのかな?それとも、今日でも使い道があるのかな?
いや、固定アドレスが必要なら、リンカースクリプトを使うのが普通じゃない? またはこの場合は、ポインタを含まないようにデータをシリアライズするだけでいいと思う。
ASLRがあるから無理じゃない? 最近のマシンだと、こういうのは無効になってると思うよ。
最近はもっと良いツールがあるよ。例えば、GNU toolchainなら、リンカースクリプトを使って、position-independentじゃない静的な実行ファイルを作ればいい。または、self-relativeポインタを使うこともできる。foo_t *foo を使って p をそこに置く代わりに、ptrdiff_t foo を使って ((char *)p - (char *)&foo) をそこに置くんだ。
ospaceポインタのために静的にメモリを確保する、ちょっとわかりにくいやり方だね。 コード生成について仮定せずに、サイズをもっとうまく制御できる配列を使う方が良くない? よくわかんないけど、生成されたbytecodeを後からパッチできるようにするためかな?昔、友達がASMで同じようなパターンを使って、NOPをコードに追加して、リコンパイルせずにパッチできるようにしてたのを思い出した。 たぶんそれだね。 スタックを暖める?(正直わからん) ハードウェアの“halt and catch fire”命令のC言語版? FortranのCOMMONブロックのC言語版。 余談だけど、昨日Think C [2]とmacOS 6.0.8 (Mini vMac [1]でエミュレート)で遊んでたんだ。 1989年中頃にTHINK C 4.0 (と THINK Pascal) に同梱されていたTCL (THINK Class Library) を使えば、自分で書くコードはもっと少なくなるよ。 ありがとう。昨夜think C 3.Xを使ってたんだけど、5.0があるの知らなかった。今朝調べてわかったよ。5.0をまた触ってみて、電子書籍を探してみる。 お気に入りの関数だね。Windowsにも入ってるかもって人もいるくらいだし(笑) え、なんで?コンパイルされたバイナリのサイズを無駄にするため?それともソースコードのスペース?もしかして初期の従業員の評価指標を良くするためかな? waste関数のすぐ前に変数が宣言されてるんだよね。無駄にされてるスペースは、その直前の変数’ospace’のために静的に確保されたメモリだよ。 そのリポジトリには何も書いてないけど、推測だけどね。昔の機械はメモリへのアクセス方法が均一じゃなかったから、バイナリが特定の閾値を超えた場合にコンパイラがちゃんと動くかテストするためだったんじゃないかな。 ハードウェアのテストに関係してそう。メモリとかレジスタとか、Xバイトだけ必要なものがオーバーフローするとかさ。本当にランダムだし、書いた本人しか知らないだろうね(笑) 大胆な予想:”main”関数の場所を任意のバイト数だけオフセットさせる方法だったんじゃないかな。a.outバイナリ形式では、これはゼロでないエントリポイントになるんだ。 http://cm.bell-labs.co/who/dmr/primevalC.html 考えられる理由の1つは、静的なグローバル領域を割り当てること。メモリの読み取り専用保護がないと、その領域に書き込むことができたんだ。 他のコメントを見てね。 へー、“extern”と“auto”の使い方、今どきのC言語と全然違うんだね! >“extern”はグローバルsymbolをfunction scopeに持ってくるために使うみたいだね。 うん、だいたいそんな感じ。 BCPLにはC言語に無くて今も無いfeatureがたくさんあったよ。B言語のことだよね? どんなfeatureがあったか詳しく教えて! 全部言いたいこと言ってくれた。知らなかったこともあったわ。 BCPLにあってC言語に無かったfeatureって何? “auto”は昔はautomatic memory managementの意味だったんだよ。 modernなC compilerでも同じことができるよ。externとautoの意味は同じだし、intはdefault typeだよ。 C23では、autoはdefault typeを持たない。type deductionになる。 委員会での設計って、現場の人が欲しいものと違う結果になりがちだよね。 最近、old-style autoなんて使ってる人いないでしょ。 >最近old-style autoなんて使ってる人いないでしょ。 じゃあ、autoのbetter useって何? ギリシャ語プログラミングで、”self”の同義語。 確かに。でも、セキュリティを重視して欲しいと思ってる人は多いけど、政府機関が介入するまで、50年間文句言っても何も変わらなかったじゃん。 これもコンパイラが対処できたはずの問題なのに、してこなかったんだよね。結局、現場のユーザーが気にしなかったから。パフォーマンスを最優先するコンパイラにみんな群がって、パフォーマンスが落ちるものは全部拒否してたし。だから、ユーザーが安全性を求めてたとは思えないな。文句言うのは得意だけど。 GCCとClangはasan/ubsanをサポートしてるよ。パフォーマンスと引き換えに、メモリアクセスとか未定義動作に関する挙動を改善できる。asan/ubsanが使える環境なら、開発とテストは常に有効にしてる。デバッグ時間がマジで減るから。 だよね。Ubsanは本番環境でもオンにした方が良いかも。 民主主義みたいなもんで、選挙結果がみんなのニーズを反映するとは限らないし、特定のグループが優遇されたりするよね。 俺が言いたいのは、標準化委員会は政府じゃないってこと。 外から見ると確かにそう見えるかもね。 ほとんどの機能は以前からコンパイラに存在してたことに気づいたかな? autoキーワードの最初の追加は、委員会による設計に期待するものに沿ってるんじゃない?理論的な完全性以外に何の役にも立たないキーワードを含めるなんてさ。 俺が言ってるのはもっと一般的なことだよ。autoに関してじゃなくてね。 「委員会による設計」の興味深い別の意味は、「委員会で設計」ってことだね。 現場の人たちは、この委員会が設計したものにかなり満足してるみたいだよ。 そんなの関係ないよ。現場の人たちは自分たちの標準バージョンをアップデートしないんだから。 変数または関数をextern(al)として宣言すると、コンパイラはそれが「外部」、つまり別のソースファイルで定義されていると見なすだけだよ。コンパイラは名前付き変数/関数への参照を生成し、リンカーはすべてのオブジェクトファイルをリンクするときに変数の実際のアドレスを代入するんだ。 これらはすべてB言語から来てるんだ。 あー、sizeless配列ね。DMRさんのサイトにあるC言語の初期の歴史に関するドキュメントを見てみてよ。初期のポインタの構文はそうだったみたいよ。 プログラマーは謙虚であるべきだなって思うよね。結局、俺たちは巨人の肩に乗ってるようなもんで、ほとんどが抽象化されたものの上で動いてるんだから。コンピューターサイエンスの80年以上の歴史だよ。最近のイケてるやつらはメモリ安全性のこと言うけど、誰かが面倒見なきゃいけなかったんだよな。自分のコードでか、抽象化された機能で。 メモリ安全性はC言語より10年も前からあったんだよ。JOVIAL(1958)とか、ESPOL/NEWP(1961)とか、PL/I(1964)みたいな言語でね。ベル研究所の外でも、PL/S(1970)、PL.8(1970)、Mesa(1976)、Modula-2(1978)とかがあった。最近のイケてるやつらは、C言語の普及によってシステムプログラミングの安全性が失われたことを再発見してるんだよ。1980年代のイケてるやつらがメモリ安全性を気にしなかったからね。 「メモリ安全性のこと言ってるイケてるやつら」は、まさに巨人の肩に乗って、他の人がもっと高く立てるようにしてるんだよ。 話が飛躍してるけど、年寄りはテクノロジーを理解できないみたいに言う人がいるのが皮肉だなって思う。 >…年寄りはテクノロジーを理解できないみたいに言う人がいる これはテクノロジーに限らず、どの分野でも若い奴らがやることだよ。ティーンエイジャーが自分たちが初めてセックスしたと思ってるのと同じように、若い奴らは「現状はクソだ」って気づいて、それを解決しようとするんだよな。 1つの抽象レベルを理解してても、その上に構築された抽象レベルを理解してるとは限らない。その逆もまた然り。 あなたのコメントは謎かけみたいだね。25年間プログラミングしてるけど、知らないことだらけだって思ってるよ。 自分で読み返したら、分かりにくかったね。俺が言いたいのは、ほとんどの人が使ってる言語とか、OSの基盤技術とかは、今80代の人たちが設計・発明したもので、Linuxのコアチームの多くも50~60代だってこと。 C言語の良いところってシンプルさだと思ってたけど、実際はめっちゃ複雑で奥が深いよね。C言語みたいにローレベルで、ほんとにシンプルな言語ってないのかな?Zigをちょっと調べてみたけど、シンプルそうなんだけど、なんか引っかかるんだよね…もっとコメントを表示(1)
edit:
http://cm.bell-labs.co/who/dmr/primevalC.html に答えがあったよ。
>スペースの割り当てがすごい。プログラムの先頭を上書きしてスペースを節約してるんだ。初期化コードを潰してるってこと。コンパイラによってやり方が違うみたい。初期のコンパイラでは関数名で開始位置を見つけてて、後のコンパイラでは単に0としてる。最初のコンパイラはメモリアドレスがないマシンで書かれてて、プログラムの開始位置が0じゃなかったんだね。後のコンパイラはPDP-11でメモリアドレスがあったからできたんだ。prestruct-c/c10.cでそれがよくわかるよ。”
main()の前に配置するために関数にして、不要になったコードをバッファオーバーフローさせる必要があるってことかな。
昔は自己書き換えが多かったんだよ。昔のマシン語はリソースが限られてたから、コードを書き換えたり、コード領域を再利用したりしてたんだ。
昔のwindowを動かすのって、めっちゃコードが必要だったんだね…これはかなりモダンな方なんだけどね。ANSI CだけどAPIが分厚い。
macOS 6のUXは好きだったな。簡潔な見た目っていうか [3]。
[1] https://www.gryphel.com/c/minivmac/start.html
[2] https://archive.org/details/think_c_5
[3] https://miro.medium.com/v2/resize:fit:1024/format:webp/0*S57…
System 6.0.8は1991年4月だから、その頃にはTCLは確立されていて、THINK C 5のC/C++バージョンでは、THINK C 4の“CでのOOP” (関数ポインタを持つネストされた構造体) の代わりに、ちゃんとしたC++機能が使われていたんだ。
TCLは小さなプロジェクトで使ってたよ。主にObject Pascalの方が自然なTHINK Pascalでね。Toolboxを直接使っていたプログラムを移行するのを手伝ったりもした。もっと本格的なプログラムではMacAppを使ってた。MacAppは1985年にObject Pascal向けにリリースされ、1991年にC++向けにリリースされた。
waste() /* スペースを浪費する */
{
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
waste(waste(waste),waste(waste),waste(waste));
}
もちろん”スペースを無駄にするため”っていう答えはナシでお願い(笑)
今の機械でも、命令に含められるオフセットには制限があることが多いから、分岐やロード/ストアに大きなオフセットが必要な場合は、コンパイラは別の機械語命令を使わないといけないんだよね。それもこの関数がテストに役立つことかも。こっちの方が可能性高いかな。この関数のバイナリサイズを、様々なPDP-11の機械語命令で許容されるオフセット長と比較してみると面白いかもね。
>“2番目に目立たないけど、驚くべき特徴はスペースの割り当て:一時的なストレージが割り当てられ、プログラムの先頭を故意に上書きし、スペースを節約するために初期化コードを破壊します。2つのコンパイラは、これに対処する方法の詳細が異なります。初期のコンパイラでは、開始は関数に名前を付けることで見つけられます。後のコンパイラでは、開始は単に0と見なされます。これは、最初のコンパイラがメモリマッピングを備えたマシンが登場する前に書かれたことを示しています。したがって、プログラムのオリジンはロケーション0にはありませんでした。2番目のコンパイラの時点では、マッピングを提供するPDP-11がありました。(Unix Historyの論文を参照)。ファイルの1つ(prestruct-c/c10.c)では、その場しのぎが特に明らかです。”
グローバルなsymbolをfunction scopeに持ってきてるみたい。全部デフォルトで“int”扱いっぽいし。arrayの宣言でsize指定があったりなかったり… size指定なしのarrayはpointerとして使うってことかな?もっとコメントを表示(2)
>もっと良い考え方としては、externは“このsymbolはここで宣言・定義・確保されてないよ。どっか別の場所で宣言・定義・確保されてる”って意味かな。
>”型はこれで、codeがちゃんと参照できるようにして、linkerが後で参照と宣言・定義・確保されたstorageをマッチさせる”って感じ。
referenceは一般的な意味で使ってるよ
当時のC言語はほぼBCPLでsyntaxがちょっと違うくらいだったんだよね(char/stringのsupportは良かったけど)。structとかlongが入ってきてから、ガラッと変わったんだよ。
nested functionとか?あれは実装が大変な割に使い道が微妙だった。
labelが定数constantになっててcomputed GOTOが使えるのはC言語には無いね。manifest constantはRitchieの言語設計で一番謎。
multiple assignmentはsyntaxが楽なだけでしょ。valof-resultisも便利だけどsyntax sugarみたいなもんじゃない?
assemblyとか古い言語だとlocal variableを好き勝手に使えないから。storageを宣言してlifetimeを管理しないといけない。
C言語とかはlocalとかstack allocated valueを導入したんだよね。externはfileの外にstorageがあるって意味で、registerはcompilerにregisterにvalueを置いてほしいって意味。
autoはdefaultだったからあんまり使われなかった。C23でC++みたいにtype inferenceの意味になった。
昔のC言語のfunction declarationも変だったよね
WG14がC++を修正する手段になってるってWG14のmemberが嘆いてる。
deductionはゴミtype inferenceだと思って。
文句言うならコンパイラベンダーに言うのが筋だよ。特にClangはCとC++を合わせようとしてるから、何か要望があればバグトラッカーに報告してね。他のコンパイラも同様だよ。
>俺の知る限り、autoが冗長じゃないケースはなかったな。例えば、”https://stackoverflow.com/a/2192761”を見て。
だから、autoの再利用はまあ良いかなと思うけど、使い方がクソなのは変わらず嫌だわ。もっとコメントを表示(3)
新しい機能が追加されるには、それを推進する人が必要で(候補者)、仲間による投票(選挙)があって、政府のサイクル(ISO revision)が終わると、コンパイラのユーザーは新しい機能を喜ぶんだ。
実はK&R CサブセットのCコンパイラをホームコンピューターで使ってたことがあって、そこではautoが重要だったんだ。もちろん今はもうないけどね。1990年代初頭の話さ。
アイデアを提案する時に、通常のライフサイクルやフィードバックをスキップして、いきなり委員会に持ち込む人がいるんだ。
最近のCでは、extern宣言を関数内に入れることはできないんだ。基本的にそれは悪い習慣であり、コードの可読性を低下させるからね。もちろん、グローバルスコープ(例えば、ソースファイルの先頭)に配置することはまだできるけど、ヘッダーファイルに入れて、コードを.h定義ファイルと.c実装ファイルのペアのモジュールに編成する方が良いよ。
>https://www.nokia.com/bell-labs/about/dennis-m-ritchie/bintr…”,
>“この原則の結果として、すべての添字付き変数のすべての添字のすべての出現は、配列の上限と下限に対して実行時にチェックされました。生産実行時の効率を上げるために、これらのチェックをオフにするオプションを提供してほしいか、お客様に尋ねたところ、満場一致で反対されました。添字のエラーが頻繁に発生し、検出されないと悲惨なことになることはすでに知っていました。1980年になっても言語設計者やユーザーがこの教訓を学んでいないことに恐怖を感じます。まともなエンジニアリングの分野では、このような基本的な予防措置を講じないことは、とうの昔に法律違反になっていたでしょう。”
C.A.R Hoareの“The 1980 ACM Turing Award Lecture”から。どの言語のことだと思う?
俺は若い奴らの方が理解できてないと思うけどね。
同じ間違いが何度も繰り返されて、昔学んだ教訓は無視されるんだよ。
書くのは読むより簡単で、話すのは聞くより簡単で、古いものを拡張するより新しいものを構築する方が簡単なんだ。
それは強みにもなるけどね。人間の心は慣れによって固定観念にとらわれがちだから、新しい人が解決策を見つけ出すこともある。でも、過去の教訓を忘れて無駄な試みに終わることも多い。