The Man Who Fell From The Wrong Side Of The Sky:2010年8月分

2010/8/6(Fri)

[NetBSD] Single UNIX is not dead

先日の W-ZERO3NetBSD BoFの 発表資料中に POSIX:2008 の改定にともない
Single UNIX Specifications は The Open Group Base Specifications になりました とか書いたんだけど
改めて SUS version 4 (including POSIX.1-2008) としてリリースされたよです。
まじややこしすぎ、SUS v3 の後に TOGBS Issue 4〜7が出たので 更に混乱を(ry

つーか POSIX はかつては

なのでPOSIX.1とか書くのですけど、POSIX:2001以降は章立て改まったので
POSIX.1-2008とは書かず、POSIX:2008が正しいはずなんだよなー
# IEEE Std 1003.1とは書くのですが ;-P

そろそろ婚活用に Single UNIXer Specifications(独身のUNIX使いのスペック表) 決めようz

SUSv3 は一括ダウンロードってhtml形式しかなかったんだけど、今回はPDFなので
印刷するのに便利ですね(Kindle? iPad? なにそれうまいの?)。

2010/8/17(Tue)

[C language][Ancient UNIX] before time_t

ヒウイッヒー(死語)というサイトで

なぜ localtime(3) には、ポインタを渡すのか?

ちうネタが流れてたので。

	struct tm *
	localtime(const time_t *clock);

よくある誤回答の例としては

time_tが構造体かもしれない + 構造体の値渡し回避


ですが、そもそも ISO-C では time_t は算術型 (Arithmetic) と決められとるので
time_t が構造体だったらを考慮してポインタ渡し〜ということはありえません。
もし過去にそういう実装が存在したとしたら、ちゃんとISO-Cもそれを考慮し
time_t は実装依存の opaque 型と定めるでしょう、それくらい互換性はケアされとり。

さて、どう頭捻っても「意味が無い」という答えしか出てきませんね。
考えてわからないならソース考古学のお時間です。Cのバヤイ
どんなに理不尽と思える仕様があっても、何らかの歴史的理由があることがほとんどでし。

んでわ Ancient UNIX のマニュアル読んでみましょ、まずは UNIX 1st Editionから。
すでに time(2)ctime(3)が存在します。
この最初期の、まだアセンブラで書かている UNIX のプラットフォームは 18bit の PDP-7 です。

 DESCRIPTION   time returns the time since 00:00:00, Jan. 1, 1971, measured in
               sixtieths of a second. The high order word is in the AC register and
               the low order is in the MQ.


18bit ぽっちじゃ3〜4日で尽きてしまうので AC - MQ という2つのレジスタを使った
36bit で epoch(00:00:00, Jan. 1, 1971) からの秒数を保持しているわけです。

そして UNIX は移植性を求めて、プログラミング幻語Cで書き直されることになります。
その作業があらかた終わった 4th Editionをみてみましょ。

この頃には ctime(3) に加えて、お題の localtime(3)も追加されています。

SYNOPSIS
	char *ctime(tvec)
	int tvec[2];
	...
	int *localtime(tvec)
	int tvec[2];

この頃のプラットフォームも PDP-11 のような 16bit なんかが主力ですので
1st Editionと同様に上位ビットと下位ビットを配列で渡す必要があるわけです。

これ K&R なので若者には見慣れない構文かもしれませんが、ISO-C だと

SYNOPSIS
	char *ctime(int tvec[2])
	...
	int *localtime(int tvec[2]);

ですし、仮引数では配列型とポインタ型は区別がにゃいので

	char *ctime(int *tvec)
	...
	int *localtime(int *tvec);

と書き直せます、おおいっきにクロマニヨンに進化した!

つまり ctime(3) や localtime(3) の引数がポインタ型を要求するのは
この時代の名残なのですよな、後の 7th Edition では

SYNOPSIS
	char *ctime(clock)
	long *clock;

	...

	#include <time.h>

	struct tm *localtime(clock)
	long *clock;

とtime.h と struct tm が導入され、現在のプロトタイプと異なるのは
まだ typedef long time_t が入ってないというだけの状態になります。

time_tが登場するのは 4.3BSD Tahoeあたりですかね。
単純に s/long/time_t/ されたまま現在に至ると、ちとSVR4の方は資料今手元にないので以下略。

2010/8/24(Tue)

[pcc] ほんやく

ヒマみてボチボチ訳していこうかと、原文は こちら
意味が通じればいいやレベルなので苦情とか撲滅運動な人は/dev/nullへどうぞ。

移植者のためのPCCガイド

アブストラクト
  この書は新たな対象にPCCを移植しようと考えている人向けです。
  移植作業に足る内部構造と、対象依存のサブルーチンについての解説を扱います。
  関連して読んで置いたほうがいいのは、オリジナルのPCCのドキュメントでしょう。

イントロダクション
  PCCは元はSteven C. Johnsonによって、今から30年以上も昔に書かれました。
  内部は多くの変更が加えられましたが、それでもなお基本構造はまだ変わらず元のままです。
  このコンパイラは概念的に2つのパートに分割することができます; 言語に特定する処理を
  扱うフロントエンド部分と、最適化とアセンブリコードの生成を行うバックエンド部です。

  この分割構造によって、他のフロントエンドを追加し易くなっています。
  その実例としてこのパッケージにはf77のフロントエンドも含めてあります。

パス
  コンパイラは先ほど述べた通り、異なる2つのパスから成り立ちますので
  この2つをそれぞれ別のプログラムとその中間ストアとして作り上げることは可能でしょう。
  まー普通はそこまでやりません、必要になった人がやればいいんじゃないでしょうか。
  でもこの2つを分割することを念頭におけば、よりクリーンな内部構造になりますし
  後から異なる言語フロントエンドをぶちこむのもより簡単になります。

  この全てのシンボルテーブル情報の(パース)結果と、(中間ストア)データ生成は全て
  pass1で終了し、pass2では最適化とアセンブリコードの生成のみを行います。
  pass2は関数の式木全体を保存します、式木はpass1から渡され最適化とレジスタの割り当てを
  して関数を完成させます。

  関数のコードはinterpass構造体のリンクリストで表現されます。
  このリンクリストは必ずプロローグ(interpass_prolog構造体)で始まり、同様に
  エピローグで終わりますが、これはフロントエンドによって詰め込まれます。
  この構造体の中身の詳細については、mip/manifest.h を参照してください。

型情報
  PCCで型情報はTWORD(ふつーはint)に変換されます、これにはビットシフトが使われます。
  最下位5bitは基本型を表現します、いわゆるintとかfloatとかなんかです。
  まぁそれだけじゃなしにFTN、PTRそしてARYと論理和されもします、必要に応じてですが。
  例えば、(PTR|LONG) はlong型へのポインタです。いくつかのマクロがこの型情報を操作するため
  用意されています; INCREF(LONG) これは前の(PTR|LONG)と同じです。他のマクロについては
  詳細は mip/manifest.h を参照してください。


シンボルテーブル
  そうのうち書く

続きはまた後日。

というかllvmでいいんじ(ry

んで 例の問題patch更新、 こっちの作業を進めるためにまずはドキュメントちゃんと読みましょう週間開始。

2010/8/26(Thu)

[pcc] _Bool + INCR/DECR(後置インクリメント/デクリメント命令) 問題

@ そもそも

なぜ pcc は INCR/DECR を pass1 で扱っているのかよーわかりませ。
これはUNIX V7のソースに含まれる pcc の過去の歴史がすっぽすっぽ抜け落ちてるので
もうコード読んで恐山気分でイマジンノーヘブンするしかないのですよね…

ありそうな理由としては、pass2 は言語非依存なんで、インクリメント演算のない言語(f77とか)を考慮して
pass1 に移しから(なんというパラノイア)とか、そもそも pass2 で扱うのがめんどくさいので pass1 で消しこんでるとか。
# なんとなく後者の気がするなぁ…

そもそも最適化を考えた場合、x += 1 なんかは i386 なら addl $1,x を incl x にした方が効率いいのですよな。
なんで

  • INCR/DECR は pass2 で処理する為、pass1で何もしない
  • pass2ではインクリメント/デクリメント命令のない cpu は INCR/DECR を PLUSEQ/MINUSEQ に書換する
  • 〃 ある cpu は pass2 の最適化で PLUSEQ/MINUSEQ が 1 の場合、INCR/DECR に書換する

というのがいいような気がする。

ところが現状だと

  • pass1 は INCR/DECR は PLUSEQ/MINUSEQ に書換する
  • 更に PLUSEQ/MINUSEQ は PLUS/MINUS つまり x = x + 1 / x = x - 1 に書換する
  • pass2 での最適化のため PLUS/MINUS が1の場合、SONE ちう bit を立てておく
  • pass2 ではインクリメント/デクリメント命令のない cpu は SONE を無視する
  • 〃 ある cpu は SONE が立ってたらインクリメント/デクリメント命令で最適化を行う

という感じ、関連するtable.cのエントリは↓とか。

[ arch/i386/table.c ]
647 { PLUS,         INAREG|FOREFF,
648         SAREG|SNAME|SOREG,      TWORD|TPOINT,
649         SONE,   TANY,
650                 0,      RLEFT,
651                 "       incl AL\n", },

まぁpass1でやるかpass2でやるかの問題ですが。
これはソース相当読み込まないと答えだせなさそうだなー。

明日くらいに和訳続きと、別途に俺オリジナルでソース解説やりますね。