R4000命令解説
Last-modified: 2020-08-10 (月) 21:57:42
概要
- ここではPSPに使われている機械語のR4000の命令達を紹介しますぞ。
- 全部頭に入れる必要はありません。忘れたらこのページに戻ってくればいいのですからな!
- マニアック命令は執筆中です。
レジスタ
汎用レジスタ
- R4000は32個の汎用レジスタを持つ。
- 実はこれ以外に32個の浮動小数点演算用のレジスタもある。
- 注意点として、s0~s7以外のレジスタはサブルーチンを呼ぶと内容が破壊されます。
zeroレジスタ
- 読み込んだ値は常にゼロ、どんな値を書き込んでもゼロのまま固定。
- ゼロが欲しい時に便利。
- 実はmove r1,r2は addu r1,r2,zeroを省略した表記だったりする。
- さらに言えばli r1,immはaddiu r1,zero,imm
atレジスタ
- アセンブラが使用するレジスタ。人間が勝手に触ってはいけない(ルールになっている)。
- ゆうなまではslt系の結果を格納するときにのみ使用されるが、ぶっちゃけそれ以外の用途で使ってもいい(チート作成ですごく重宝する)。
v0,v1レジスタ
- サブルーチン(jal)からの戻り値として使用される(ゆうなまではv0しか使われていない模様)。
- 上記の特別な使い方のほかに、計算用としても頻繁に使われる。
a0~a3レジスタ
- サブルーチンへの引数として使われる。全部使うとは限らない。
- これらもよく計算用として使われる。
t0~t7レジスタ
- 引数のためにa0~a3を使ってもまだ足りない場合、使われる。
- これらも計算に使ったりする。
s0~s7レジスタ
- 数あるレジスタの中で唯一サブルーチンをまたいでも内容が変化しないレジスタ。
- 変化しない、というよりは変化させたらちゃんと復元することが義務付けられている。
t8,t9レジスタ
- なかなかレア。一応計算用として使える。
- ゆうなまではポリモーフィズムの仮想関数へのジャンプにt9レジスタを使用している。
k0,k1レジスタ
gpレジスタ
spレジスタ
- スタックを構成する重要なレジスタ。スタックが分からない場合は検索しよう。
- サブルーチン内で一時変数を確保するためによくaddiuされてる。
fp/s8レジスタ
- フレームポインタとして使用するパターンと、s系レジスタとして使用するパターンの二種類あるレジスタ。
- ゆうなまでは多分後者。
raレジスタ
- サブルーチンからの復帰先のアドレスが勝手に格納され、復帰としてよくjr raが最後に出てくる。
- 二重三重にサブルーチンを呼ぶと上書きされてしまうのでたいていスタックに避難させている。
特殊レジスタ
命令一覧(アルファベット順)
命令詳細
四則演算
- uが後ろに付くものはunsigned(符号なし)演算を表す。
加算命令(add系)
- 足し算ができる。
- addi,addiuともにimmの部分は符号拡張されるため、こいつらは引き算も実現可能。
- addiは符号付き演算としてオーバーフローすると例外が発生し結果が更新されないというクセのある挙動なので普通はaddiuの方を使う。
減算命令(sub系)
- 引き算ができる。
- こいつは即値を使用したバージョンがないのでaddiuを使う。
- subもaddiと同様のクセがあるのでsubuを使った方がいい。
乗算命令(mult系)
- 掛け算。
- 4Byte×4Byteの結果は8ByteになるのでHIとLOの二つの特殊レジスタに格納する。
- 8ByteのうちHIには上位4Byte、LOは下位4Byteが書き込まれる。
- 命令順の注意事項があるのでmfhi/mfloの項目を参照。
除算命令(div系)
- 割り算。
- 割り算した結果の商はLOレジスタに、余りはHIレジスタに格納される。
- 命令順の注意事項があるのでmfhi/mfloの項目を参照。
代入演算
move命令
- moveという名前だが、実質コピー。
- 本当はmoveという命令は存在せず、addu r1,r2,zeroを省略表記した形。
movez/moven命令
- 上のmove命令に条件が付いたもの。
- movezならr3が0、movenならr3が非0のときのみ代入を行い、条件を満たさなければ代入はおこらない。
- 実はちょっと新しい命令なので資料によっては載ってない。
li命令
- レジスタの値をセットする命令。
- 本当はliという命令は存在せず、addiu r1,zero,immを省略表記した形。
lui命令
- レジスタの上位2Byteをセットする命令。
- MIPSには一度に4Byte分のデータをセットする命令はなく、上位2Byteと下位2Byteで分割してセットする。
- luiは下位2Byteをリセットしてしまうため後述のori命令より先に実行する必要がある。
ori命令
- 本当はビット演算命令(OR)。
- でもluiの後で下位2Byteをセットするのに使い勝手が良いのでよく使われる。
- addiuだと符号拡張がなされるので代わりにaddiuを使う場合は注意するべし(ゆうなまではoriとaddiuどちらも使われる)。
a0←0x08B88574をする場合、
1.oriバージョン
lui a0,0x08B8
ori a0,a0,0x8574
2.addiuバージョン
lui a0,0x08B9
addiu a0,a0,0x8574
mfhi/mflo命令
- 乗算や除算の結果を汎用レジスタに格納する命令。
- 以下のようにmult系やdiv系の手前2命令以内で実行すると、値がバグるので注意。
op1
op2
op3
mult/div
op4
op2,op3にあるmfhi/mfloはバグる。
mthi/mtlo命令
- HIレジスタやLOレジスタに値を格納してしまおうという謎命令。
- 基本使うな。
- チートコードを極力小さくしたいときに使えるかもしれない。
符号拡張命令(seb,seh)
- 基本moveだと思ってて良い。
- コピーする際にsebなら1Byteを符号拡張して4Byteに、sehなら2Byteを符号拡張して4Byteに。
ビット演算
論理積演算(and系)
論理和演算(or系,nor,xor系)
- OR演算、NOR演算、XOR演算ができる。
- oriのみ代入でも使われる。
ビットシフト演算(sll系,srl系,sra系,nop)
- sll系は左にビットシフト、sra,srl系は右にビットシフト。
- sraとsrlの違いだが、sraは符号ビットを考慮した右シフトを行うのに対し(Arthmetic)、srlは符号ビットを無視して右シフトを行う(Logical)。
- nopは何も起こらない(No OPeration)。そのため遅延スロットを埋めるために良く使われる。
- nopも実はsll zero,zero,0の省略表記。
- さらにnopは機械語が0x00000000なのでメモリ上のどこでも見られる。
ロード・ストア演算
- baseはレジスタである。
- offsetは符号拡張されるためbaseより前のアドレスにもアクセスできる。
- bは1Byte(Byte)、hは2Byte(Half word)、wは4Byte(Word)。
ロード命令(lb系,lh系,lw)
- メモリから値を読み込みレジスタに格納する。
- uがないと読み込んだ値が符号拡張されてレジスタに格納され、u付きだと読み込んだそのままがレジスタに入る。
- lh系はアドレスが2の倍数、lwはアドレスが4の倍数でないと例外が発生してバグる。
ストア命令(sb,sh,sw)
- レジスタの値をメモリに書き込む。
- ロード同様sh系はアドレスが2の倍数、swはアドレスが4の倍数でないと例外が発生してバグる。
特殊ロード命令(lwl,lwr)
特殊ストア命令(swl,swr)
- lwl,lwrのストア版。
- セットで使わないと正しく読み込まれないので注意。
比較演算
- uが付くと符号なし整数として比較を行い、u無しだと符号あり整数として比較する。
- 比較したとき、左にある方が右以上の場合は0が格納され、右の方が大きい時は1が格納される。
- 条件分岐命令(特にbeq系,bne系)の条件としてよく使用される。
- slti,sltiuはどちらもimmを符号拡張する。
条件分岐命令
- どの命令も条件を満たすことで別の場所に処理を移すことができる。
- どの命令も遅延スロットを持つが少し特殊(後述)。
- いわゆるif文。
- 基本的に、分岐するしないにかかわらず、直後の遅延スロットが実行されるが、後ろにlが付く(beqlなど)と「条件を満たしたときのみ」遅延スロットが実行される(満たさなければ無視される)。
beqとbeqlを使って具体例を示す。
op1
op2
beq(l)
op3
op4
上の状態で
1.条件を満たす場合:beqならop1,op2,op3,分岐先、beqlならop1,op2,op3,分岐先
2.条件を満たさない場合:beqならop1,op2,op3,op4、beqlならop1,op2,op4
という風に実行する。
- これら全ての条件分岐命令は近く(+-0x20000Byte)までしかジャンプできないため、サブルーチン内での小さな分岐でのみ使用される。
- b,blはif-else文としてよく用いられる。
- b(l)はbeq(l) zero,zero,addressの省略表記。
ジャンプ命令
j命令
- 好きな場所にジャンプする。
- bで始まる条件分岐命令とは違い任意の場所に跳べる。
jal命令
- サブルーチンを呼び出す。
- jとは違い、raレジスタに遅延スロットの次の命令のアドレスを格納するので戻ってこれる。
jalr命令
- 同様にサブルーチンを呼び出す。
- raレジスタに遅延スロットの次の命令のアドレスを格納するので戻ってこれる。
- jalrのおかげで関数ポインタを実現できる(特にポリモーフィズムの仮想関数など)。
jr命令
- レジスタのアドレスにジャンプする。
- ほとんどの場合、jr raとしてサブルーチンの最後で見かける。
- ra以外のレジスタを使っている場合、大体はswitch文の実現。