MIPSのレジスタ - 2024年度 システムプログラミング
MIPSのレジスタ
はじめに
CPUは,メモリからデータを読み出し(ロード), 演算をした後にメモリに書き込む(ストア)ことで,一連の計算を行う装置です.しかし,メモリの応答速度は, CPUのそれに比べて圧倒的に低速なので,できるだけロードとストアをしない方が実行速度は高速になります. そのため,CPUはレジスタと呼ばれるメモリより高速に読み書きできる一時的なデータの保管場所を持っています. 備えるレジスタの数や用途は,CPU によって違いがあります.
ここでは,MIPS のレジスタとその用途の特徴的な部分について説明します. 教科書と合わせて読むと理解が深まるでしょう.
MIPS レジスタの種類
MIPS には,32ビットの数を扱えるレジスタが
32個あります.アセンブリ言語中では,
それぞれ $0
から $31
で表すこともできますが,
通常は,下のように名前を付けて区別します.
名前 | 番号 | 用途 |
---|---|---|
$zero | 0 | 常にゼロ(書込み不可) |
$at | 1 | アセンブラ(擬似命令)が使用 |
$v0 〜 $v1 | 2-3 | 戻り値 |
$a0 〜 $a3 | 4-7 | 引数 |
$t0 〜 $t7 | 8-15 | 一時変数用 |
$s0 〜 $s7 | 16-23 | 退避が必要な変数用 |
$t8 〜 $t9 | 24-25 | 一時変数用 |
$k0 〜 $k1 | 26-27 | OS用 |
$gp | 28 | グローバルポインタ |
$sp | 29 | スタックポインタ |
$fp | 30 | フレームポインタ |
$ra | 31 | 戻りアドレス記憶用 |
MIPSの演算命令は,全てこれらのレジスタを対象として行なわれます. あるアドレスのメモリの中身に1を加えるという操作は, 一旦メモリの中身をレジスタに読み出して,加算して戻すという作業が必要になります.
lw $t0, address # Load Word: address から $t0 にロード addi $t0, $t0, 1 # $t0 = $t0 + 1 sw $t0, address # Store Word: $t0 から address にストア
レジスタには, $zero
という一風変わった書込不可のレジスタや
jal
命令で暗黙的に代入される $ra
などがあります.
それ以外のレジスタは,CPUにとっては,大きな区別はありません.
では,なぜこのように名前を違えているのでしょうか.
レジスタ $v0〜$s7
($2〜$23
) は,
プログラムが手続き (C言語でいう関数)の集合でできていることと関係しています.
手続きの利点は,他の手続きを利用する際にその中身を意識しなくてよいです.
ただし,その利点のためには,手続き呼出しに使う引数の渡し方や戻り値の受け取り方,
手続き中で勝手に値を変更してもいいレジスタについて,厳密な取り決めが必要です.
このような取り決めを手続き呼出し規約 (calling convention) といって,
教科書では,2.8節,A.6節に詳細な説明があります.
$gp
は,メモリ中の大きなデータの塊を扱う際に,
その先頭を指しておくことで, $gp
からの相対的なアドレスを使ってメモリに効率よくアクセスするために主に使用します.
大抵,プログラム中で不変です.本講義では学習しません.
$sp
は,スタックポインタと呼ばれます.
スタックとは,レジスタだけでは保存しきれないデータ,例えば配列や関数内で動的に確保したデータの保存場所です.
そのスタック最上位アドレス(つまり,メモリをどこまで使ったか)
を示すレジスタです.本講義では,後に詳しく取り上げます.
MIPSに特徴的なレジスタ
$zero ($0)
プログラム中では,特定のレジスタやメモリを 0で初期化することが頻繁にあります. そこで,MIPS では,常に値が0であるレジスタを用意しています.
add $a0, $zero, $zero # $a0 = 0 の代り add $a0, $zero, $a1 # $a0 = $a1
$at ($1)
MIPS の命令表を見ると擬似命令と書かれた項目があります.
例えば, li
命令や move
命令は擬似命令です.
li $a0, 1 #-> ori $a0, $zero, 1 move $a0, $v0 #-> addu $a0, $zero, $v0
実は, li
や move
は,左側のように書くと,
右側のようにアセンブラが解釈して自動で変換してくれるようになっています.
左側の命令の方が人間とって直感的であるため,アセンブラ(実行時のCPUではなく)
が簡単な置き換えを行っているのです.
spim の Text Segments を見ると, 右側に自分で書いたプログラムが行番号付きで, 左側に実際にMIPSで実行されるコードが見られます. これによって擬似命令の展開の様子が確認できます.
擬似命令の中には 1つの命令から複数の命令に展開されるものがあります.
例えば, $a0
に label
を代入する la
命令では,
以下の置き換えが行われています.
la $a0, label # -> lui $at, label上位16bit # ori $a0, $at, label下位16bit
なぜなら,MIPS には 32bit の即値を代入する命令がないので,
上位16ビットと下位16ビットを分けて代入しているからです.
ここで, $at
に代入途中の値を一時保管していることに注意してください.
このように, $at
はアセンブラによって暗黙に一時利用されるレジスタなのです.
この置き換えを人間が意識する必要はありませんが, $at
レジスタを
直接使ってはいけないことになっています.
$ra ($31)
プログラムでは,同種の命令の繰り返しが頻繁に起こります. このようなまとまりをサブルーチン,手続き,あるいは関数と呼んで,メインルーチンから呼出します.
このとき,呼出す側は,そのアドレスを指定して呼出しますから,
MIPS でいうと, j
命令や jr
命令で飛び先を指定することになります.
呼出された側は,どのように元に復帰すればいいのでしょうか.
MIPS では,そのために $ra
レジスタを使用します.
(a) jal func で func を呼出し ($ra に次アドレスが代入) → (b) 手続きスタート : 処理(A) (c) 結果→規定のレジスタ (e) 次の命令から実行 ← (d) j $ra で戻る
jal
命令は,指定した label
にジャンプすると同時に
$ra
に戻って来るべきアドレスを代入してくれる便利な命令です.
このおかげで,どのアドレスから呼出されても手続きは戻り先を見失うことなく元に戻れます.
しかし,ここで注意してください.処理(A)の中で, $ra
を書換えてはいけません.
すなわち 処理(A)中で更に別の手続きを呼出す (つまりもう一度 jal
を使う)場合は,
何らかの方法で $ra
を保存しておかなくてはなりません.
この方法については,教科書2.8節 「コンピュータ・ハードウェア内での手続きのサポート」における
「入れ子になった手続き」で詳しく説明されています.