R4000命令解説

Last-modified: 2020-08-10 (月) 21:57:42

概要

  • ここではPSPに使われている機械語のR4000の命令達を紹介しますぞ。
  • 全部頭に入れる必要はありません。忘れたらこのページに戻ってくればいいのですからな!
  • マニアック命令は執筆中です。

レジスタ

汎用レジスタ

  • R4000は32個の汎用レジスタを持つ。
  • 実はこれ以外に32個の浮動小数点演算用のレジスタもある。
  • 注意点として、s0~s7以外のレジスタはサブルーチンを呼ぶと内容が破壊されます
    番号レジスタ名特徴
    0zero何をしようが常にゼロ
    1atアレンブラが使用
    2v0サブルーチンの戻り値
    3v1サブルーチンの戻り値2
    4~7a0~a3サブルーチンへの引数
    8~15t0~t7サブルーチンへの引数
    16~23s0~s7データ保持用レジスタ
    24,25t8,t9
    26,27k0,k1OS用レジスタ
    28gpグローバルポインタ
    29spスタックポインタ
    30fp/s8フレームポインタ/データ保持用レジスタ
    31ra復帰先アドレス記録用

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レジスタ

  • OS用。気にする必要はない。

gpレジスタ

  • まず見かけない。

spレジスタ

  • スタックを構成する重要なレジスタ。スタックが分からない場合は検索しよう。
  • サブルーチン内で一時変数を確保するためによくaddiuされてる。

fp/s8レジスタ

  • フレームポインタとして使用するパターンと、s系レジスタとして使用するパターンの二種類あるレジスタ。
  • ゆうなまでは多分後者。

raレジスタ

  • サブルーチンからの復帰先のアドレスが勝手に格納され、復帰としてよくjr raが最後に出てくる。
  • 二重三重にサブルーチンを呼ぶと上書きされてしまうのでたいていスタックに避難させている。

特殊レジスタ

  • 汎用レジスタ以外に特殊な奴らもいる。
    レジスタ名特徴
    pcプログラムカウンタ(現在実行している命令のアドレス)
    lomult命令では結果の下位4Byteが、div命令では商が格納される
    himult命令では結果の上位4Byteが、div命令では余りが格納される

命令一覧(アルファベット順)

オペコードオペランド効果
add(u)r1,r2,r3r1←r2+r3
addi(u)r1,r2,immr1←r2+imm
andr1,r2,r3r1←r2 AND r3
andir1,r2,immr1←r2 AND imm
b(l)addresspc←address
beq(l)r1,r2,addressr1==r2ならpc←address
bgez(l)r1,addressr1>=0ならpc←address
bgezal(l)r1,addressr1>=0ならra←pc+8,pc←address
bgtz(l)r1,addressr1>0ならpc←address
bitrev
blez(l)r1,addressr1<=0ならpc←address
bltz(l)r1,addressr1<0ならpc←address
bltzal(l)r1,addressr1<0ならra←pc+8,pc←address
bne(l)r1,r2,addressr1!=r2ならpc←address
clor1,r2r1←(r2のMSBから連続する1の数)
clzr1,r2r1←(r2のMSBから連続する0の数)
div(u)r1,r2lo←r1/r2,hi←r1%r2
extr1,r2,immA,immBr1←(r2のimmA~immB-1のビットを最下位詰め)
insr1,r2,immA,immBr1←(r1のimmAの場所をr2のimmB個のビットで置き換えたもの)
jaddresspc←address
jaladdressra←pc+8,pc←address
jalrr1ra←pc+8,pc←r1
jrr1pc←r1
lb(u)r1,imm(r2)r1←[r2+imm]
lh(u)r1,imm(r2)r1←[r2+imm]
lir1,immr1←imm
luir1,immr1←imm<<16
lwr1,imm(r2)r1←[r2+imm]
lwlr1,imm(r2)r1←[r2+imm]
lwrr1,imm(r2)r1←[r2+imm]
madd(u)
max
mfhir1r1←hi
mflor1r1←lo
min
mover1,r2r1←r2
movenr1,r2,r3r3!=0ならr1←r2
movezr1,r2,r3r3==0ならr1←r2
msub(u)
mthir1hi←r1
mtlor1lo←r1
mult(u)r1,r2lo←(r1*r2の下位4Byte),hi←(r1*r2の上位4Byte)
nop-なし
norr1,r2,r3r1←r2 NOR r3
orr1,r2,r3r1←r2 OR r3
orir1,r2,immr1←r2 OR imm
sbr1,imm(r2)[r2+imm]←r1
sc
sebr1,r2r1←r2
sehr1,r2r1←r2
shr1,imm(r2)[r2+imm]←r1
sllr1,r2,immr1←r2<<imm
sllvr1,r2,r3r1←r2<<r3
slt(u)r1,r2,r3r1←(r2>=r3で0、それ以外で1)
slti(u)r1,r2,immr1←(r2>=immで0、それ以外で1)
srar1,r2,immr1←r2>>imm
sravr1,r2,r3r1←r2>>r3
srlr1,r2,immr1←r2>>>imm
srlvr1,r2,r3r1←r2>>>r3
sub(u)r1,r2,r3r1←r2-r3
swr1,imm(r2)[r2+imm]←r1
swlr1,imm(r2)[r2+imm]←r1
swrr1,imm(r2)[r2+imm]←r1
sync
syscall
teq
teqi
tge(u)
tgei(u)
tlbp
tlbr
tlbwi
tlbwr
tlt(u)
tlti(u)
tne
tnei
wsbh
wsbw
xorr1,r2,r3r1←r2 XOR r3
xorir1,r2,immr1←r2 XOR imm
abs.s
add.s
bc1f(l)
bc1t(l)
c.eq
c.f
c.le
c.lt
c.nge
c.ngl
c.ngle
c.ngt
c.ole
c.olt
c.seq
c.sf
c.ueq
c.ule
c.ult
c.un
ceil.w.s
cfc1
ctc1
cvt.s.w
cvt.w.s
div.s
floor.w.s
lwc1
mfc1r1,f1r1←f1
mov.s
mtc1r1,f1f1←r1
mul.s
neg.s
round.w.s
sqrt.s
sub.s
swc1
trunc.w.s

命令詳細

四則演算

  • uが後ろに付くものはunsigned(符号なし)演算を表す。
    オペコードオペランド効果
    add(u)r1,r2,r3r1←r2+r3
    addi(u)r1,r2,immr1←r2+imm
    sub(u)r1,r2,r3r1←r2-r3
    mult(u)r1,r2lo←(r1*r2の下位4Byte),hi←(r1*r2の上位4Byte)
    div(u)r1,r2lo←r1/r2,hi←r1%r2

加算命令(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の項目を参照。

代入演算

オペコードオペランド効果
mover1,r2r1←r2
movezr1,r2,r3r3==0ならr1←r2
movenr1,r2,r3r3!=0ならr1←r2
lir1,immr1←imm
luir1,immr1←imm<<16
orir1,r2,immr1←r2 OR imm
mfhir1r1←hi
mflor1r1←lo
mthir1hi←r1
mtlor1lo←r1
sebr1,r2r1←r2
sehr1,r2r1←r2

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に。

ビット演算

オペコードオペランド効果
andr1,r2,r3r1←r2 AND r3
andir1,r2,immr1←r2 AND imm
orr1,r2,r3r1←r2 OR r3
orir1,r2,immr1←r2 OR imm
norr1,r2,r3r1←r2 NOR r3
xorr1,r2,r3r1←r2 XOR r3
xorir1,r2,immr1←r2 XOR imm
sllr1,r2,immr1←r2<<imm
sllvr1,r2,r3r1←r2<<r3
srar1,r2,immr1←r2>>imm
sravr1,r2,r3r1←r2>>r3
srlr1,r2,immr1←r2>>>imm
srlvr1,r2,r3r1←r2>>>r3
nop-なし

論理積演算(and系)

  • 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(u)r1,offset(base)r1←[base+offset]
    lh(u)r1,offset(base)r1←[base+offset]
    lwr1,offset(base)r1←[base+offset]
    sbr1,offset(base)[base+offset]←r1
    shr1,offset(base)[base+offset]←r1
    swr1,offset(base)[base+offset]←r1
    lwlr1,offset(base)r1←[base+offset]
    lwrr1,offset(base)r1←[base+offset]
    swlr1,offset(base)[base+offset]←r1
    swrr1,offset(base)[base+offset]←r1

ロード命令(lb系,lh系,lw)

  • メモリから値を読み込みレジスタに格納する。
  • uがないと読み込んだ値が符号拡張されてレジスタに格納され、u付きだと読み込んだそのままがレジスタに入る。
  • lh系はアドレスが2の倍数、lwはアドレスが4の倍数でないと例外が発生してバグる。

ストア命令(sb,sh,sw)

  • レジスタの値をメモリに書き込む。
  • ロード同様sh系はアドレスが2の倍数、swはアドレスが4の倍数でないと例外が発生してバグる。

特殊ロード命令(lwl,lwr)

  • lwはアドレスが4の倍数でないと例外が発生してバグるが、lwlとlwrをセットで使うことで4の倍数でなくても読み込める。
    バグる
    lw r1,0x1(base)
    バグらない
    lwl r1,0x1(base)
    lwr r1,0x1(base)
  • セットで使わないと正しく読み込まれないので注意。

特殊ストア命令(swl,swr)

  • lwl,lwrのストア版。
  • セットで使わないと正しく読み込まれないので注意。

比較演算

  • uが付くと符号なし整数として比較を行い、u無しだと符号あり整数として比較する。
    オペコードオペランド効果
    slt(u)r1,r2,r3r1←(r2>=r3で0、それ以外で1)
    slti(u)r1,r2,immr1←(r2>=immで0、それ以外で1)
  • 比較したとき、左にある方が右以上の場合は0が格納され、右の方が大きい時は1が格納される。
  • 条件分岐命令(特にbeq系,bne系)の条件としてよく使用される。
  • slti,sltiuはどちらもimmを符号拡張する

条件分岐命令

  • どの命令も条件を満たすことで別の場所に処理を移すことができる。
  • どの命令も遅延スロットを持つが少し特殊(後述)。
    オペコードオペランド効果
    b(l)addresspc←address
    beq(l)r1,r2,addressr1==r2ならpc←address
    bne(l)r1,r2,addressr1!=r2ならpc←address
    bltz(l)r1,addressr1<0ならpc←address
    blez(l)r1,addressr1<=0ならpc←address
    bgtz(l)r1,addressr1>0ならpc←address
    bgez(l)r1,addressr1>=0ならpc←address
  • いわゆる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の省略表記。

ジャンプ命令

  • どのジャンプ命令も遅延スロットを持つ。
    オペコードオペランド効果
    jaddresspc←address
    jaladdressra←pc+8,pc←address
    jalrr1ra←pc+8,pc←r1
    jrr1pc←r1

j命令

  • 好きな場所にジャンプする。
  • bで始まる条件分岐命令とは違い任意の場所に跳べる。

jal命令

  • サブルーチンを呼び出す。
  • jとは違い、raレジスタに遅延スロットの次の命令のアドレスを格納するので戻ってこれる。

jalr命令

  • 同様にサブルーチンを呼び出す。
  • raレジスタに遅延スロットの次の命令のアドレスを格納するので戻ってこれる。
  • jalrのおかげで関数ポインタを実現できる(特にポリモーフィズムの仮想関数など)。

jr命令

  • レジスタのアドレスにジャンプする。
  • ほとんどの場合、jr raとしてサブルーチンの最後で見かける。
  • ra以外のレジスタを使っている場合、大体はswitch文の実現。