2.4 符号付き数と符号なし数
1の補数と2の補数の話とか
情報処理試験とかで学べるレベルの内容なので飛ばし読み
2.5 コンピュータ内での命令の表現
MIPSのアセンブリ言語では32のレジスタがあり、例えば以下のような割り当てになっている$t0-$t7は、レジスタ8-15 $s0-$s7は、レジスタ16-23 |
MIPSアセンブリと機械語の割り当てについて
例えば
add $t0, $s1, $s2 |
は、以下の機械語に対応付けられる(10進表記)
0,17,18,8,0,32 |
MIPSの機械語は全て32bitの構成になっている。
add命令の場合、32bitの機械語は6つのフィールドに分かれている。
0,32がadd命令を意味し、17,18,8は$s1,$s2,$t0のレジスタ番号、5個目の0はadd命令では使用しないフィールドとなっている。
各フィールドはそれぞれ6,5,5,5,5,6bitの計32bitで構成されているので、2進で書くと以下のコードになる
(下記の例は、分かりやすいように各フィールド間にスペースを入れている)
000000 10001 10010 01000 00000 10000 |
このようなフィールド分けの事を、命令形式と呼ぶ
add命令で見た上記6つのフィールドには、以下の名前が付いている
命令によって必要なパラメータは異なるので全て使われるとは限らない。未使用の場合は0をセットする。
op rs rt rd shamt funct op:オペコード。命令の内容 rs:第1ソースオペランドのレジスタ。1つ目の引数。 rt:第2ソースオペランドのレジスタ。2つ目の引数。 rd:デスティネーションオペランドのレジスタ。結果値の保存先。 shamt:シフト量(shift amount) funct:操作命令のバリエーションをあらわす。 機能コードとも呼ぶ(funcion code) |
だだし、全ての命令が上記のフォーマットを取るわけではない。
例えば、前述したlw(load word:メモリからレジスタへのロード命令)なんかだと、メモリのアドレスを5bitや6bitで表しきれない。命令長を可変にすればよいんだけど、CPUハードの設計が難しくなる。
なので、MIPSでは命令の種類によって命令フォーマットを変えている。
lwの場合は、R形式(レジスタ用)、もしくはRフォーマットと呼ばれ、以下の形になる
op(6bit) rs(5bit) rt(5bit) constatntまたはaddress(16bit) |
このほかにもI形式(あるいはIフォーマット)というものもあり、これは即値・データ転送命令用で使用する
詳しくは、こちら:MIPS Instruction Coding
リンク先を見ると分かるけど、R形式の場合、opは常に0でfunctで命令の内容を指定している。
I形式のopは、000000, 00001x, 0100xx以外が指定され、ここで命令の内容が決まる。
命令形式が複数あると、当然ハードウェア設計が複雑になる。
だけど、各形式をなるべく同じにしておくとHW側の複雑さは押さえられる。上記の例だと、最初の3フィールドを同じにしている。
フォーマットは、各opコードごとに決まるので、解析側からすると、まず最初の6bitを見て後続の処理を振り分けるイメージ。これは、ファイルフォーマットや、通信プロトコルとかでも良く使われる手法なのでピンとくる。
ここで例題。以下の機械語がどうなるかを考える。
a[10] = h + a[4] |
まずアセンブリにすると、以下の命令になる
lw $t0, 14($t1) add $t0, $t0, $s1 sw $t0, 40($t1) |
これを、10進の機械語に置き換える。
lwは25,swは43のオペコードを持つので…
op:25, rs:9, rt:8, addr:14 op:0, rs:17, rt:8, rd:8, shamt:0, funct:32 op:43, rs:9, rt:8, addr:40 |
となる。ちなみに再掲すると、8番のレジスタが$t0で, 9が$t1, 17が$s1。
lw命令はデータのロード先をrtで指定している。
さらに2進に変換すると機械語になる。
(これはすぐに分かるし、書くのが面倒なので省略)
memo:本節の自己診断にある,問題の選択肢がおかしい?
s0,s1,s2ではなくt0,t1,t2が正しいはず。
2.6 論理演算
論理演算の命令説明srl : 論理右シフト(shift right logical) sll : 論理左シフト(shift left logical) and : 論理積(2つのレジスタ値の) andi : 論理積(即値との) |
他に、or, ori, norなど
常識レベルなので飛ばし読み。
2.7 条件判定用の命令
どうでも良いけど、タイトル横に有るBurks, Goldstine and von Neumannの文章訳がおかしい?
“…マシンでの取り扱い上は0を正とみなす”は、”真とみなす。”では
分岐処理
分岐(branch)系の説明(I形式の命令)
まずはbeq。これはbranch if equalの略。
beq reg1, reg2, L1 |
これは、以下のコードと等価
if ( reg1 == reg2 ) { goto L1; } |
条件式を == では無く != にしたい場合は、bne(not equal)命令になる。
例題1:
以下のコードをアセンブルせよ (i=$s3, j=$s4, f=$s0, g=$s1, h=$s2とする)
if ( i == j ) { f = g + h; } else { f = g - h; } |
回答例
beq $s3, $s4, Else add $s0, $s1, $s2 Else: j Exit sub $s0, $s1, $s2 Exit: |
ここでいきなりラベルが登場。
ラベルが機械語でどう実装されるかは未だ説明されてないけど、意味は分かる。
例題2:
以下のコードをアセンブルせよ(i=$s3, k=$s5, save=$s6 )
while ( save[ i ] == k ) { i += 1; } |
回答例
Loop: sll $t1, $s3, 2 # wk1 = i << 2; saveの要素は4byteなので,i*4を求める為に2bitシフト add $t1, $t1, $s6 # wk1 = wk1 + save; lw $t0, 0($t1) # wk0 = &(char*)wk1; beq $t0, $s5, Exit # if ( wk0 != k ) goto Exit; addi $s3, $s3, 1 # i++; j Loop Exit: |
2,3行目は、気持ち的に”lw $t0, $t1($s6)”と書きたいところだけど、オフセットにレジスタ値は使えないので一旦addする必要がある。で、addするとロードすべきアドレスが決まるのでlw命令自体のオフセットは0でよい。
末尾(or先頭)のみに分岐命令があるコードのカタマリを基本ブロック(basic block)と呼ぶ
コンパイル作業の最初の方に行われる1段階として、基本ブロックへの分解がある。
大小比較
slt(set less than)命令では、2つのレジスタの大小比較が出来る。
slt $t0, $s3, $s4 |
は、以下のコードと等価になる。
if ( $s3 < $4 ) { $t0 = 1; } else { $t0 = 0; } |
定数と比較したい場合はsltiを使用する(iはimmediate)
slti $t0, $s3, 10 |
レジスタ値をunsignedとみなしたい場合は、sltu/sltiuを使用する。
これは、最上位ビットが立っているときの振る舞いがu無しと異なる。
定数0と比較したい場合は$zeroレジスタを使用する。らしい。
-> これだけ読むと$zeroの存在意義が分からない... slti $t0, $s3, 0でよいのでは? パフォーマンス上の問題とかあるのだろうか? -> と思ったら、すぐ下で$zeroの活用場所があった... また、調べてみると$zeroは結果値が不要なときのゴミ捨て場としての役割もあるらしい。 unixの"/dev/null"的な役割 |
signedの値を、あえてunsigned版(slt/slti)で比較するワザがある。
負の数は、2の補数で表現すると最上位ビットに1が立つので、見かけ上大きな数字になる。
なので、unsignedで比較しておけば、配列のオフセットが負では無いかの境界チェックも同時に行える。
具体的には、以下の感じになる。
sltu $t0, $s1, $s2 # $s1はindex, $s2にはarrayのサイズが入っているとする beq $t0, $zero, IndexOutOfBounds # 配列境界エラー処理へジャンプさせる |
case/switch文
switch分はbeq系の羅列でも実装できるけど、ジャンプテーブル(jump table/jump address table)という仕組みが用意されている
jr命令(jump register)では、指定したレジスタに書かれている番地にプログラムカウンタを変更させる事が出来る。また、MIPSのハード上は、パフォーマンス向上のために遅延分岐の機能が実装されているが、ソフト側はその事を意識しなくて良い。

コンピュータの構成と設計(上)
関連記事
コメントを残す