はじめてのOSコードリーディング個人メモ【PDP-11/40 アセンブリ編】
                                            PDP-11/40におけるアセンブリ構文
PDP11のアセンブリは、その他のアセンブリと似ているところもあるが、初見だとなかなか読めず苦戦するのでまとめます。
なお、これからまとめる内容は、Lions本と呼ばれる有名なOSの教科書を参考にしています。
(リンク以外のオレンジの字は私の独り言です。読み進めたら解決できるかも、な部分。)
なお、この記事の対象者は主に下記の本を読んでいる人向けです。
おさらい
PSWと汎用レジスタについて
PDP11のニーモニック(命令の種類) について見ていく前に、PSWと汎用レジスタの構成についてまとめます。
これを理解していないと、ニーモニックの意味が理解できません。

算術演算 (ビット演算)
| 演算 | 例 | 
& (AND) | 
1100 & 1001 = 1000 | 
| (OR) | 
1100 | 1001 = 1101 | 
! / ~ (NOT) | 
~(1100) = 0011 | 
^ (XOR) | 
1100 ^ 1001 = 0101 | 
>> (右シフト) | 
1100 >> 1 = 0110 | 
<< (左シフト) | 
0011 << 2 = 1100 | 
上記演算した結果、オーバーフローしたら PSWの Vフラグが立つといったことが起こります。
用語整理
| 用語 | 意味 | 
| ニーモニック | 元々オペコードと呼ばれる数字の羅列を命令としてCPUは扱っているが、それを人間にわかりやすく短い文字列としたもの。(例: mov) | 
| オペランド | 被演算子。mov A, B のAとBのこと。1 + 2 の 1と2 もオペランドと言える。 | 
| アドレッシングモード | レジスタへの参照の仕方。(r0)や4(r1)などのような表記がそれ。 | 
アドレッシングモード一覧
アドレッシングモードとは、レジスタを参照したときにそれをどう扱うかを決定するモードです。
rNは汎用レジスタで、実際にはr1やr2のような表記になります。
基本
| 表記 | 説明 | 
rN | 
rNを参照して、それをオペランドとする。 | 
(rN) | 
rNを参照して、それをアドレスとしてオペランドを取得する。 | 
(rN)+ | 
rNを参照して、それをアドレスとしてオペランドを取得する。その後rNの内容をインクリメントする。 | 
*(rN)+ | 
rNを参照して、それをオペランドへのポインタのアドレスとする。その後rNの内容を 2 だけインクリメントする。 | 
-(rN) | 
実行前にrNの内容をデクリメントし、その値をアドレスとしてオペランドを取得する。 | 
*-(rN) | 
実行前にrNの内容を 2 だけデクリメントし、それをオペランドへのポインタのアドレスとする。 | 
X(rN) | 
rN + Xがオペランドのアドレスとする。Xは任意の数値が入る (例: 4(r3))。 | 
*X(rN) | 
rN + Xがオペランドへのポインタのアドレスとする。 | 
r6とr7はそれぞれ、sp (スタックポインタ) と pc (プログラムカウンタ) と表記される場合もあります。
またspのアドレッシングモードは一部異なります。
spのアドレッシングモード
| 表記例 | 説明 | 
(sp) | 
スタックのトップをオペランドとする。 | 
(sp)+ | 
スタックのトップをオペランドとする。実行後にそれをポップする。 | 
*(sp)+ | 
スタックのトップをオペランドのアドレスとする。実行後にそれをポップする。 | 
-(sp) | 
値をスタックにプッシュする。 | 
X(sp) | 
スタックのトップからX番目をオペランドとする。 | 
*X(sp) | 
スタックのトップからX番目をオペランドのアドレスとする。 | 
その他
| 表記例 | 説明 | 
$n | 
nをそのまま使う。 | 
*$n | 
nをアドレスとして使う。 | 
ニーモニック一覧
ここからは、[src] / [dest]は任意のオペランド (値やレジスタ) が入ると思ってください。
[label]には飛ぶ先のラベル (例: 1f)などが入ります。
ちなみに、nfはForwardで次のnラベルに飛ぶという意味で、nbはBackwardなので、前のnラベルに飛ぶという意味です。
演算後に状態フラグをいじるもの
先に、状態フラグ N, Z, V, Cを書き換えるニーモニックをまとめます。
これのあとに条件分岐のニーモニックが基本続きます。
| ニーモニック | 意味 | 
bit [src], [dest] | 
[src]と[dest]でAND演算をする。その結果をもとに状態フラグを書き換える。 | 
cmp [src], [dest] | 
[src]と[dest]を比較し状態フラグを書き換える。例えば[src]が[dest]より小さい場合、Nに1が立つ (= [src] - [dest])。等価であれば Zに1が立つ。 | 
tst [src] | 
[src]をもとに状態フラグを書き換える。 | 
条件分岐
下記は後に出てくるjmpとは違い、飛べる範囲が -128 ~ 127と決まっているようです。
| ニーモニック | 意味 | 
beq [label] | 
結果が0 (Z=1)のときに[label]へ飛ぶ。(Branch if equalsの略)  | 
bec [label] | 
C=0ならば[label]へ飛ぶ。 | 
bne [label] | 
結果が非0 (Z=0)のときに[label]へ飛ぶ。(Branch if not equalsの略)  | 
bge [label] | 
比較結果が大きいもしくは等しい(e.g. N=0 && V=0) ときに[label]へ飛ぶ。(Branch if greather than or equal toの略)  | 
ble [label] | 
比較結果が小さいもしくは等しい(e.g. N != V) ときに[label]へ飛ぶ。(Branch if less than or equal toの略)  | 
bhi [label] | 
結果が高い (e.g. C=0 && Z=0)のとき[label]へ飛ぶ。(Branch if higherの略: “高い” という表現がわかりにくい)  | 
bhis [label] | 
結果が高い、もしきは等価 (e.g. C=0)のとき[label]へ飛ぶ。(Branch if higherの略: “高い” という表現がわかりにくい)  | 
blo [label] | 
結果が低い (e.g. C=1)のとき[label]へ飛ぶ。(Branch if lowerの略)  | 
br [label] | 
無条件に[label]へ飛ぶ。 | 
sob rN, [label] | 
rNをデクリメントし、その結果が非0であれば、[label]へ飛ぶ。このとき[label]は2bのような戻る動作とする。 | 
ジャンプ
| ニーモニック | 意味 | 
jmp [dest] | 
[dest]に無条件で飛ぶ。 | 
jsr [src], [dest] | 
任意のレジスタ ([src]) をスタックのトップにおいて、サブルーチン ([dest])に飛ぶ。このとき [src]にサブルーチンから戻ったときの次の命令のアドレスが格納される。通常、 pcを渡すことが多く、rts pcで元の位置に戻る。(例 /sys/conf/m40.s#L492: jsr pc,copsu ) | 
その他
| ニーモニック | 意味 | 
adc [dest] | 
Cビットの中身を[dest]に加算する。 | 
add [src], [dest] | 
[src]を[dest]に加算する。 | 
ash $n, [src] | 
[src]の値を$nだけ左シフトする。(例: ash $2, r2 … r2の値を2bit左シフトする) | 
ashc $n, [src] | 
[src][src+1]を$nだけ左シフトする。(連鎖算術シフト) | 
asl [dest] | 
[dest]を1つ左シフトする。 | 
asr [dest] | 
[dest]を1つ右シフトする。 | 
bic [src], [dest] | 
[src]の1が立っている桁に対応した[dest]の桁を0にクリアする。 | 
bis [src], [dest] | 
[src]と[dest]をXOR演算し、結果を[dest]に格納する。 | 
clc | 
Cフラグをクリアする。 | 
clr [dest] | 
[dest]を0にする。 | 
dec [dest] | 
[dect]をデクリメントする。 | 
div [src], rN | 
rNとr(N+1) (このときNは偶数) に格納された32bitの2の補数表現された整数を[src]で除算する。rNに商を、r(N+1)に余りを格納する。 | 
inc [dest] | 
[dect]をインクリメントする。 | 
mfpi [dest] | 
“以前の” アドレス空間で指定されたワードを現在のスタックにプッシュする。 (Move From Previous Instruction space の略) (「”以前の” アドレス空間で指定されたワード」がよくわからない。以前のモードの仮想アドレス空間を指している?) (このあたりは “割り込み” の解説で解決するかも)  | 
mtpi [dest] | 
“以前の” アドレス空間で指定されたワードに、現在のスタックからポップした値を格納する。 (Move To Previous Instruction space の略)  | 
mov [src], [dest] | 
[src]の値を[dest]に格納する。 | 
mul [src], rN | 
[src]とrNの積を取る。Nが偶数ならば結果はrNとr(N+1)に格納される。 | 
reset | 
UnibusのINIT行を10ミリ秒に設定する。全てのデバイスコントローラを再初期化するということ。 | 
ror [dest] | 
[dest]の全てのビットを1つ右回転させる。ここで言う”回転”とは、最上位ビットと最下位ビットが連結して、数珠状になっているとみて、ビットをシフトすることを指す。 ただし、0ビット目はCフラグに、以前のCフラグの値が15ビット目に入る。  | 
rts [dest] | 
サブルーチンから戻る。rNからpcを読み込み、スタックからrNを読み込む。 | 
rtt | 
割り込み または トラップから戻る。pcとpsをスタックから読み込む。 | 
sbc [dest] | 
[dest]からキャリービットを引く。 | 
sub [src], [dest] | 
[dest]から[src]を引く。 | 
swab [dest] | 
[dest]の上位ビットと下位ビットをスワップ する。 | 
wait | 
ハードウェア割り込みが発生するまで、プロセッサをアイドル状態にし、Unibusを解放する。 | 
例: fork.s
以上を踏まえて試しに、PDP-11/40のアセンブリを読んでみます。
.globl	_fork, cerror, _par_uid  / _forkの公開と、他のグローバル変数、関数の使用を宣言
_fork:
	mov	r5,-(sp)	/ r5の値をスタックに積む
	mov	sp,r5	/ スタックポインタをr5に格納
	sys	fork	/ カーネルのfork()をコール
		br 1f	/ 前方の1:に飛ぶ
	bec	2f	/ もしC=0ならば前方の2:に飛ぶ
	jmp	cerror	/ 飛べなかった場合は、cerror (/source/s4/cerror.s)に飛ぶ
1:
	mov	r0,_par_uid	/ r0の値を _par_uidに格納
	clr	r0	/ r0 をクリア
2:
	mov	(sp)+,r5	/ スタックをPOPし、その値をr5に格納
	rts	pc	/ プログラムカウンタを返す
.bss
_par_uid: .=.+2
7,8行目に2種類の条件分岐が存在し、「brあるならbecに到達しなくない?」と一瞬思ったのですが、fork()内で親プロセスはpcを1命令分進めているので、親プロセスはbecに到達するということですね (書籍にも書いてありました)。
また、spと(sp)、(sp)-と+(sp)の違いをしっかり理解していることが大事ですね。
ちなみに書籍にも書いてある通り、fork()で何かしらエラーがあればCフラグに1が立つようになっているので、becを使用した分岐になっています。
