Plan 9 ソースコードでの例外機構 実装編

前に取り上げた「Plan 9 ソースコードでの例外機構 使い方編」の続きです.こんどは実装について.


Plan 9のプロセスでは例外をサポートするために, それを表すProc構造体に特殊なメンバを導入しています.以下に挙げるnerrlabとerrlabがそれにあたります. これらはそれぞれ, 登録されたラベルの個数, 配列です.

/sys/src/9/port/portdat.h : 長いので抜粋

struct Proc
{
...
	int	nerrlab;
	Label	errlab[NERR];
...
}

ここでいうラベルとはエラー時に戻るべき, waserror()関数が呼び出された場所を表します.具体的には以下のようなLabel構造体を用いてこれを保持しています.それぞれスタックポインタとリターンアドレスを格納するためのメンバがあります.

/sys/src/9/pc/dat.h

typedef struct Label Label;

struct Label
{
	ulong	sp;
	ulong	pc;
};

ここまでくればなんとなく, やっていることが分かるかと思います. waserror()呼び出し時にそのときのスタックポインタやリターンアドレスを登録しておき, error()を呼び出したらその値を読み出してレジスタorスタックに書き戻してやればerror()→waserror()へのジャンプが可能になります.

これはもちろん単発での例外を想定してProc構造体にいっこだけ保持するようにしてもできそうです.しかし, Plan 9では先の例で見たようなnexterror()関数を使った連続逆行を可能にしています.このために, errlabメンバでLabel構造体を配列として保持, 利用中の個数をnerrlabメンバで管理することで, ラベルのスタックのように使っています. なお, NERR=64と決まっています*1.

waserror()関数

waserror()関数から見ていきましょう. 実際にはこれは以下のようなマクロになっています. nerrlabメンバで個数を管理しつつ, errlabメンバにあたる配列中のLabel構造体を利用していきます

/sys/src/9/port/fns.h

#define waserror() (up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1]))
setlabel()関数

setlabel関数はアセンブリ言語で書かれており, 以下のような実装になっています.引数としては先ほどみたようなLabel構造体が渡されます. ここではこの時点でのSP(=スタックポインタアドレス)とリターンアドレスを取得し, Label構造体の所定の位置にセットしています. なお, "waserror()関数は通常0が返値"となっているためここでは0を返しています.

/sys/src/9/pc/l.s

TEXT setlabel(SB), $0
	MOVL	label+0(FP), AX
	MOVL	SP, 0(AX)			/* store sp */
	MOVL	0(SP), BX			/* store return pc */
	MOVL	BX, 4(AX)
	MOVL	$0, AX				/* return 0 */
	RET

error()関数

error()関数は例外を発生させる関数でした. この関数ではエラーメッセージを現在のプロセス(up)のerrstrメンバにコピーしてnexterror()関数を呼び出しています. spllo()関数はちなみにsti命令を呼ぶような関数. これと途中のsetlabelは何の為にしてるんでしょうね?

void
error(char *err)
{
	spllo();
	assert(up->nerrlab < NERR);

	kstrcpy(up->errstr, err, ERRMAX);

	setlabel(&up->errlab[NERR-1]);

	nexterror();
}

nexterror()関数

nexterror()関数では. 実際に直前のwaserror()関数が呼び出された場所へのジャンプを可能にしています.先ほどのwaserror()関数ではsetlabel()関数を呼んでいました. これに対してここでは逆操作であるgotolabel()関数を呼んでいます.このとき引数にはerrlabメンバに登録されているモノのうち, 最後尾のLabel構造体へのアドレスを引数として渡しています. これには戻るべきwaserror()関数の場所の情報が入っているのでした.

void
nexterror(void)
{
	gotolabel(&up->errlab[--up->nerrlab]);
}
gotolabel()関数

gotolabel()関数は, 与えられたLabel構造体からsp, pcメンバの値を取得し, それぞれスタックポインタのアドレスを表すSPとその最上部に位置するリターンアドレスにセットします. これにより, この関数は対応するsetlabel()関数が返った位置(waserror()関数)にもどる事になります. またsetlabel()関数では0を返していましたが, 例外で戻ってきたことを表すためにここでは1を返します.

TEXT gotolabel(SB), $0
	MOVL	label+0(FP), AX
	MOVL	0(AX), SP			/* restore sp */
	MOVL	4(AX), AX			/* put return pc on the stack */
	MOVL	AX, 0(SP)
	MOVL	$1, AX				/* return 1 */
	RET

poperror()関数

poperror()関数は直前のwaserror()関数によるエラー処理を取り消すものでした. 先ほどみたような形でLabel構造体を管理しているので, nerrlabをデクリメントするだけでできたりします.

#define poperror() up->nerrlab--

*1:が、waserror()関数ではどうにもこれをきちんと管理しているようには見えない・・・