R3000命令解説



プレステのアセンブリ言語であるR3000の
基本命令についての解説です。
一部難しい説明がありますがどうかお許しを(--;
(特に後半・・・)



凡例

書式
命令の書式です。
r1,r2,r3はレジスタ名、
$nnnnは16ビットの即値、
$8aaaaaaaとなっているものはアドレス値です。
データ変換
R3000の命令から16進数の機械語に変換する際
どのように対応しているかの計算方法です。
R3000ではすべての命令が4バイトで構成されています。
(WINDOWSの8086系だとこうはいかない)
レジスタ名とその番号の変換は以下の通り。
zero:00H( 0) t0  :08H( 8) s0  :10H(16) t8    :18H(24)
at  :01H( 1) t1  :09H( 9) s1  :11H(17) t9    :19H(25)
v0  :02H( 2) t2  :0AH(10) s2  :12H(18) k0    :1AH(26)
v1  :03H( 3) t3  :0BH(11) s3  :13H(19) k1    :1BH(27)
a0  :04H( 4) t4  :0CH(12) s4  :14H(20) gp    :1CH(28)
a1  :05H( 5) t5  :0DH(13) s5  :15H(21) sp    :1DH(29)
a2  :06H( 6) t6  :0EH(14) s6  :16H(22) s8(fp):1EH(30)
a3  :07H( 7) t7  :0FH(15) s7  :17H(23) ra    :1FH(31)

補足
使用上の注意や、実際のプログラムではどういう局面で
使用されるかなど、とりあえず覚えておいて損はないです(^-^;
ただし、無理に飲み込もうとするのは厳禁!(笑)


1.加減算命令

addi  命令
addiu 命令

書式
addi    r1,r2,$nnnn
addiu   r1,r2,$nnnn
意味
r2レジスタの値に16ビットの値nnnn(16進表記)を加算して
r1レジスタに格納する。
データ変換
addi    +20000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*加算する値(nnnn=0000FFFFHまで)

addiu   +24000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*加算する値(nnnn=0000FFFFHまで)
補足
加算する値は符号拡張されるため、
$8000$ffffはそれぞれ$ffff8000(-32768)〜$ffffffff(-1)になる。
これによって負の値を加算することで減算することが可能。
なお、addiでは計算結果が$7fffffff(2147483647)を超えた時と
$80000000(-2147483648)より小さくなった場合に例外処理が起こり、
プレステがハングしてしまう。
addiuでは例外処理は発生しない。
ただ、加算する値はやはり符号拡張されてしまう点に注意。
結局addiはほとんど使わなかったりする・・・(^-^;

また、32768以上の大きな値を加算する場合はlui命令、ori命令、addu命令の
3つを使用する必要が出てくる。

add  命令
addu 命令

書式
add     r1,r2,r3
addu    r1,r2,r3
意味
r2レジスタの値にr3レジスタの値を加算してr1レジスタに格納する。
r1=r2+r3と覚えておくと便利。
データ変換
add     +00000020H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号

addu    +00000021H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号

addi命令,addiu命令とはデータ変換がかなり異なるので注意。
補足
addaddiと同様、計算結果が桁あふれを起こした際に例外処理で
プレステがハングしてしまう。
加算命令とはいっても負の値($80000000$ffffffff)を
加算すれば結果的に減算することになる点に注目。
実際プログラム部分でも結構見かけるようである。

sub  命令
subu 命令

書式
sub     r1,r2,r3
subu    r1,r2,r3
意味
r2レジスタの値にr3レジスタの値を減算してr1レジスタに格納する。
r1=r2-r3と覚えておくと便利。
データ変換
sub     +00000022H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号

subu    +00000023H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号
補足
add,adduとはちょうど対称的な命令。
subadd同様、計算結果が桁あふれを起こした際に例外処理で
プレステがハングしてしまう。
一応負の値を減算すれば結果的には加算していることになる。
減算命令は加算命令と比較して使用頻度が低いため、
subに至ってはプログラム全体を探しても数えるほどしかない(^-^;

lui 命令


書式
lui     r1,$nnnn
意味
r1レジスタの上位16ビット(16ビットは2バイトに相当する)に
nnnnの値を代入し、下位16ビットには0000が入る。
データ変換
lui     +3C000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*加算する値
補足
32ビットの値をレジスタに代入するのには、
lui命令とori命令を組み合わせる必要がある。
(ori命令はaddiu命令でも代用できるが、符号拡張を考慮する必要がある。)
その際、lui命令は下位16ビットをクリアしてしまうため、
lui命令→ori命令の順でなければならない。
プログラム部分では、アドレス値($80000000$801fffff)
の代入をするために頻繁に利用されている。

li命令

書式
li      r1,$nnnn
意味
r1レジスタに16ビットの値nnnnを代入する。
データ変換
(符号拡張あり)
li      +24000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*加算する値(nnnn=0000FFFFHまで)

(符号拡張なし)
li      +34000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*加算する値(nnnn=0000FFFFHまで)
補足
li命令は、「addiu r1,zero,$nnnn」の表記を変えたものと
ori r1,zero,$nnnn」の表記を変えたものの2種類がある。
前者は元がaddiu命令なので、nnnnの値は符号拡張される。
FFFF8000h(-32768)〜FFFFFFFFH(-1)までの負の値を代入可能
後者は元がori命令、これはビット演算命令なので
符号拡張の懸念がない。
00008000H(32768)〜0000FFFFH(65535)までの正の値を代入可能
ただし、32ビットの値を代入するにはlui命令、ori命令の
2つを使用しなくてはならない。

move 命令

書式
move    r1,r2
意味
r1レジスタにr2レジスタの値を格納する。
データ変換
move    +00000021H(+00000020Hでも可)
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
補足
move命令は、「add r1,r2,zero」・「addu r1,r2,zero」(r1=r2+0)
と同じもので、逆アセンブラの表記の違いだけである。
プログラム上では、レジスタの値をクリアする意味で
move v0,zero
と使うことが多い。
ちなみに手元の逆アセンブラは「addu r1,zero,r2」という記述だと
何故かmove命令に変換してくれない・・・(笑)

negu 命令

書式
negu    r1,r2
意味
r2レジスタの値の符号を反転してr1レジスタに格納する。
データ変換
negu    +00000023H(+00000022Hでも可)
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
補足
negu命令は、「subu r1,zero,r2」の表記を変えたものである。
逆アセンブラによっては「sub r1,zero,r2」を
neg命令と表記するものもあるが、
内容はnegu命令と全く同じである。


2.ビット演算命令

andi 命令


書式
andi    r1,r2,$nnnn
意味
r2レジスタの値と16ビットの値nnnnとの論理積を
r1レジスタに格納する。
データ変換
andi    +30000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*論理積をとる値(nnnn=0000FFFFHまで)
補足
不要なビットをクリアする場合などに使う。
全般にビット操作の命令はフラグデータの処理で多用する。
使用例
andi v0,v0,$00ff
v0レジスタの下位8ビット以外をすべてクリアする

16ビットより大きい値との論理積をとる場合は
lui命令、ori命令、and命令を使用する。

and命令

書式
and     r1,r2,r3
意味
r2レジスタの値とr3レジスタの値との論理積を
r1レジスタに格納する。
データ変換
and     +00000024H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号
補足
特定のビットをOFFにしたい場合などに役立つ。
32ビットの値との論理積を求める場合に
lui命令、ori命令と一緒に使うことが多い。

ori 命令

書式
ori    r1,r2,$nnnn
意味
r2レジスタの値と16ビットの値nnnnとの論理和を
r1レジスタに格納する。
データ変換
ori     +34000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*論理積をとる値(nnnn=0000FFFFHまで)
補足
ビット操作の中でもこの命令だけは使用頻度がかなり高い。
32ビットの値を代入する場合や、フラグの設定などに利用する。
16ビットより大きい値との論理和をとる場合は
lui命令、ori命令、or命令を使用する。

or 命令

書式
or      r1,r2,r3
意味
r2レジスタの値とr3レジスタの値との論理和を
r1レジスタに格納する。
データ変換
or      +00000025H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号
補足
and命令とは逆に、特定のビットをONにしたい場合に使用する。
and命令やor命令があまりにも多いプログラムは、
それだけビット操作が激しいということで、見破るのが
難しくなってくる。

xori 命令

書式
xori    r1,r2,$nnnn
意味
r2レジスタの値と16ビットの値nnnnとの排他的論理和を
r1レジスタに格納する。
データ変換
xori    +38000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*論理積をとる値(nnnn=0000FFFFHまで)
補足
特定のビットを反転したい場合に使用する。
全部のビットを反転したい時はnor命令を使った方が便利。
16ビットより大きい値との排他的論理積をとる場合は
lui命令、ori命令、xor命令を使用する。

xor 命令

書式
xor     r1,r2,r3
意味
r2レジスタの値とr3レジスタの値との排他的論理和を
r1レジスタに格納する。
データ変換
xor     +00000026H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号
補足
出番の少ない命令ではあるが、
次のような使い方がある。
xor v0,a0,a1
a0の値とa1の値が同じならv0の値がゼロに、
違えばv0の値がゼロ以外になる

セーブデータのチェックサム判定に使うケースである。

nor 命令

書式
nor     r1,r2,r3
意味
r2レジスタの値とr3レジスタの値との論理和を取り、
その値のビットを反転してr1レジスタに格納する。
データ変換
nor     +00000027H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号
補足
なぜビットの論理和と反転が一緒なのかがちょっと謎(笑)
普通は単純に値を反転するためだけに使うと思う。
使用例
nor v0,v0,zero
v0の値を反転してv0に格納する

ちなみに、即値との否定論理和を取る「nori命令」は
残念ながら存在しない・・・


3.数値比較命令

slti  命令
sltiu 命令

書式
slti    r1,r2,$nnnn
sltiu   r1,r2,$nnnn
意味
r2レジスタの値と16ビットの値$nnnnを比較し、
r2の値がnnnn以上ならばr1の値にが、
r2の値がnnnn未満ならばr1の値にが代入される。
データ変換
slti    +28000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*比較する値(nnnn=0000FFFFHまで)

sltiu   +2C000000H
r1      +00010000H*レジスタ番号
r2      +00200000H*レジスタ番号
$nnnn   +00000001H*比較する値(nnnn=0000FFFFHまで)
補足
slti命令では符号を考慮して比較するため、
$80000000$ffffffffは負の値と認識され、
ゼロより小さいと判定される。
sltiu命令では符号は考慮されず、
すべての値が正の数として判定される。
これらの命令は、beq命令・bne命令などの条件分岐命令と
組み合わせることが多く、
使用例では、ループ回数の判定や
パラメータの上限チェックによく用いられる。
16ビットより大きい値との比較をする場合は
lui命令、ori命令、slt命令(sltu命令)を使用する。

slt  命令
sltu 命令

書式
slt    r1,r2,r3
sltu   r1,r2,r3
意味
r2レジスタの値とr3レジスタの値とを比較し、
r2の値がr3の値以上ならばr1の値にが、
r2の値がr3の値未満ならばr1の値にが代入される。
データ変換
slt     +0000002AH
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号

sltu    +0000002BH
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
r3      +00010000H*レジスタ番号
補足
slt命令もslti命令と同様、符号を考慮して比較するため、
$80000000$ffffffffを負の値と認識して判定される。
sltu命令は符号は考慮せずに、すべての値を正の数として判定される。
例えば、
sltでは$7fffffff$80000000よりも大きいとみなされるが、
sltuでは$80000000のほうが大きいという結果になる。


4.ビットシフト命令

sll 命令

書式
sll     r1,r2,$nn
意味
r2レジスタのビット値をnn回左シフトして
r1レジスタに格納する。
データ変換
sll     +00000000H
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
$nn     +00000040H*左シフトする回数(nn=001FHまで)
補足
ビット値を1回左シフトするたびに、その値は2倍になる。
つまり、左シフト2回で4倍、8回なら256倍、16回なら65536倍である。
ただし、桁をあふれた部分は切り捨てられる。
例えば$1234567816回左シフトすると$56780000となる。
使用例では、sll命令とaddu命令・sub命令を組み合わせて
簡単な掛け算をすることがある。

例:v0の値を100倍する
sll    v1,v0,$1 ;v1=v0*2
addu   v1,v1,v0 ;v1=v0*3
sll    v1,v1,$3 ;v1=v0*24
addu   v1,v1,v0 ;v1=v0*25
sll    v0,v1,$2 ;v0=v0*100

乗算命令(mult,mflo)を使った方が命令は短くなるが、
乗算命令は実行時間がかかり、
このことから実行速度を重視していることが伺える。

sllv 命令

書式
sllv    r1,r2,r3
意味
r2レジスタのビット値をr3の値の回数分左シフトして
r1レジスタに格納する。
データ変換
sllv    +00000004H
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
r3      +00200000H*レジスタ番号
補足
左シフトする回数はr3の値の下位5ビット分で決まる。
例えばr3=$12345678の場合は18H(24)回シフトが行われることになる。
r2の値はである場合が多いようだ。
主におまけCG等のフラグデータを「設定」する場合に使われる。

srl 命令
sra 命令

書式
srl    r1,r2,$nn
sra    r1,r2,$nn
意味
r2レジスタのビット値をnn回右シフトして
r1レジスタに格納する。
データ変換
srl     +00000002H
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
$nn     +00000040H*右シフトする回数(nn=001FHまで)

sra     +00000003H
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
$nn     +00000040H*右シフトする回数(nn=001FHまで)
補足
ビット値を1回右シフトするたびに、その値は1/2倍になる。
つまり、右シフト2回で1/4倍、8回なら1/256倍、
16回なら1/65536倍である。
なお、少数点以下の部分はすべて切り捨てられる。
srlは論理右シフトと言い、符号を考慮しない。
一方sraは算術右シフトと言い、符号を考慮し、何回シフトを行っても
符号は変化しないようになっている。
例えば、$87654321という値を16回右シフトした場合、
srlでは$00008765
sraでは$ffff8765という結果になる。
sra命令は乗算命令の補数処理で使われることがある。

srlv 命令
srav 命令

書式
srlv    r1,r2,r3
srav    r1,r2,r3
意味
r2レジスタのビット値をr3の値の回数分右シフトして
r1レジスタに格納する。
データ変換
srlv    +00000006H
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
r3      +00200000H*レジスタ番号

srav    +00000007H
r1      +00000800H*レジスタ番号
r2      +00010000H*レジスタ番号
r3      +00200000H*レジスタ番号
補足
右シフトする回数はr3の値の下位5ビット分で決まる。
srlvは符号を考慮しないが、sravは符号を考慮するので、
何度もシフトを実行しているうちに
いずれ正の値は、負の値は−1で固定されてしまう。
ちなみにsllv,srlv,sravの3命令は逆アセンブラによっては
r2r3を逆に表示してしまうものがあるので注意。

srlv命令はフラグデータの値を「所得」するのに使うことがある。

nop 命令

書式
nop
意味
何もしない
データ変換
nop     +00000000H
補足
本当に何もしない命令なのかと言うと、実は
sll zero,zero,$0」(zeroレジスタの値を0回左シフト)
という命令を実行している。
もっとも値自体変化していないし、
zeroレジスタは値を変化させようとしても常にゼロという頑固者のため(笑)
他には何の影響も生じていない。
nop命令はロード命令のロード待ちや分岐系命令で生じる
遅延スロットを潰すためによく使う。
実際、R3000以外のアセンブラではロード待ちや遅延スロットがないため
nop命令はあっても全くといっていいほど使われていない。


5.乗算・除算命令

mult  命令
multu 命令

書式
mult    r1,r2
multu   r1,r2
意味
r1の値とr2の値で掛け算を実行する
データ変換
mult    +00000018H
r1      +00200000H*レジスタ番号
r2      +00010000H*レジスタ番号

multu   +00000019H
r1      +00200000H*レジスタ番号
r2      +00010000H*レジスタ番号
補足
この命令は掛け算を実行するだけのもので、
計算結果を得るにはmflo命令、mfhi命令を使う必要がある。
mfloでは計算結果の下位32ビットが、
mfhiでは上位32ビットを得ることができる。
R3000では基本的にどの命令も実行時間は同じだが、
掛け算・割り算は時間がかかるため、
mflo命令、mfhi命令を使用した時点で計算が終了するまでの間
プログラムの実行が停止する。
ちなみに掛け算の実行時間は独自に調べたところでは、
20〜25命令分と推測される。

mult命令は符号を考慮し、$80000000$ffffffffの値は
負の値として計算する。
multu命令は符号を考慮しないが、どちらの命令を使用しても
計算結果の下位32ビットは同じになる。
また、r1r2を逆に指定しても計算結果は等しくなる。

div  命令
divu 命令

書式
div     r1,r2
divu    r1,r2
v 意味
r1の値をr2の値で除算する
データ変換
div     +0000001AH
r1      +00200000H*レジスタ番号
r2      +00010000H*レジスタ番号

divu    +0000001BH
r1      +00200000H*レジスタ番号
r2      +00010000H*レジスタ番号
補足
この命令は割り算を実行するだけのもので、
計算結果を得るにはmflo命令、mfhi命令を使う必要がある。
mfloでは割り算の商(結果)が、
mfhiでは余りを得ることができる。

div命令は符号を考慮し、$80000000$ffffffffの値は
負の値として計算する。
そのため結果や余りが負の値になる場合もある。
divu命令は符号を考慮せず、結果・余りは常に正の値になる。
ところで、r2(割る数)にゼロを指定した場合、
エラーでハングしたりはせず、結果が$ffffffff(-1)、
余りはr1(元の値)になる。

割り算の実行時間は独自に調べたところでは、
35〜40命令分と推測される。
掛け算よりさらに時間がかかるようで、
例えば10で割る場合は0.1で掛け算するケースが目立つ。
小数の掛け算の仕方は、下のように行う。

例:v0の値を0.1倍にする
lui     v1,$1999
ori     v1,v1,$999A ;v1=$1999999A
mult    v0,v1
mfhi    v0

2の32乗の10分の1の$1999999A(429496730)
で掛け算し、上位32ビットの値を得ることで0.1倍を実現している。
実際には誤差を小さくするため、できる限り大きい値で
掛け算して、計算結果を右シフトする方法が一般的なようだ。

mfhi 命令
mflo 命令

書式
mfhi    r1
mflo    r1
意味
特殊レジスタlo,hiの値をr1レジスタに格納する
データ変換
mfhi    +00000010H
r1      +00000800H*レジスタ番号

mflo    +00000012H
r1      +00000800H*レジスタ番号
補足
特殊レジスタhi,loの値は掛け算・割り算の計算結果が
格納される。
掛け算・割り算を実行した後はこれらの命令を使って
その計算結果を得ることがてきる。
ただし、計算が終わらないうちにこれらの命令が実行されると、
計算が終わるまでプログラムが停止する。
そこで、この計算時間の間に別の処理を進めるようにすると
全体の処理速度を最適化することができる。

mthi 命令
mtlo 命令

書式
mthi    r1
mtlo    r1
意味
特殊レジスタlo,hiにr1レジスタの値を転送する
データ変換
mthi    +00000011H
r1      +00200000H*レジスタ番号

mtlo    +00000013H
r1      +00200000H*レジスタ番号
補足
特殊レジスタは掛け算・割り算以外にも特殊な用途で
使用するようで、値を転送する命令というものがある。
これを使うことでhiレジスタ・loレジスタに
一時的に値を保持させるという強引なこともできるが、
乗除算命令を実行するとその値が破棄されてしまうので注意が必要。


6.ロード・ストア命令

lb  命令
lbu 命令

書式
lb      r1,$nnnn(r2)
lbu     r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスから
1バイトの値をr1レジスタにロードする
v データ変換
lb      +80000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号

lbu     +90000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。
単にr2の値のアドレスからロードしたい場合は
lb(lbur1,$0000(r2)
である。

lbはロードした値が符号拡張されてr1に代入される。
($80$ffの場合はそれぞれ$ffffff80$ffffffffになる)
lbuはロードした値はそのままで、r1の上位3バイトはクリアされる。
例えば、'FFH'の値をロードした場合
lbr1=$fffffffflbur1=$000000ffとなる。
負の値を扱うデータはlb命令で、扱わないデータはlbu命令で
ロードすれば管理し易くなる。

ロード命令は全般に値のロードが完了する前に
1つ先の命令が実行されてしまう性質がある。
そのため、ロードした値を直後に使いたい場合は
ロード命令の下にnop命令を挿入する必要が出てくる。

sb 命令

書式
sb      r1,$nnnn(r2)
意味
v メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスに
r1レジスタの下位1バイト分の値をストア(書き込み)する
データ変換
sb      +A0000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
メモリ内のデータをロードする命令があれば、その反対
メモリ内にデータをストアする命令も存在する。

$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。
1バイトで表せる値は0〜255までなので、
ステージ数や残機数などの比較的小さい値を管理するのに
lb(lbu)とsbを使う。

lh  命令
lhu 命令

書式
lh      r1,$nnnn(r2)
lhu     r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスから
2バイトの値をr1レジスタにロードする
データ変換
lh      +84000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号

lhu     +94000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。

lhはロードした値が符号拡張されてr1に代入される。
($8000$ffffの場合はそれぞれ$ffff8000$ffffffffになる)
lhuはロードした値はそのままで、r1の上位2バイトはクリアされる。

lh,lhu命令は偶数アドレスに対してしか使用することができない。
奇数アドレスのデータをロードしようとすると、アドレスエラー例外で
プレステがハングしてしまう。

sh 命令

書式
sh      r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスに
r1レジスタの下位2バイト分の値をストア(書き込み)する
データ変換
sh      +A4000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。

sh命令もlh,lhu命令同様、偶数アドレスに対してしか
使用することができない。

2バイトでは0〜65535までの値を表すことができる。
そのため、HPや能力値などはlh(lhu),sh命令で
管理されていることが多い。

lw 命令

書式
lw      r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスから
4バイトの値をr1レジスタにロードする
データ変換
lw      +8C000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。

lwは4バイトの値をロードする命令なので、符号拡張などはない。
また、lw命令はアドレスの末尾が0,4,8,Cでなければ
使用することができない。
それ以外アドレスのデータをロードしようとすると、
やはりアドレスエラー例外でプレステがハングしてしまう。

もう一つ、すべてのロード・ストア命令に共通することで、
無効なメモリ領域(例えば$80200000など)
にアクセスしようとしてもプレステがハングする。
プログラムコードのテストでハングする場合はロード・ストア命令に
原因があることが多い。

sw 命令

書式
sw      r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスに
r1レジスタの4バイト分の値をストア(書き込み)する
データ変換
sw      +AC000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。

sw命令もlw命令同様、アドレスの末尾が0,4,8,Cでなければ
使用することができない。

4バイトでは0〜4294967295までの値を表すことができる。
所持金や経験値などの他、アドレス値を管理するのも
lwsw命令である。

lwl 命令
lwr 命令

書式
lwl     r1,$nnnn(r2)
lwr     r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスから
4バイトの値をr1レジスタにロードする
データ変換
lwl     +88000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号

lwr     +98000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。

lw命令との違いは、アドレスの末尾に関係なく値をロードできること。
ただし、lwl命令とlwr命令はセットで使用しなければならず、(lwlを先にすること)
単独で使用しても効果はない。
また、$nnnnの値はlwr命令には実際にアクセスするアドレスを入れ、
lwl命令にはそれより0003H多い値を入れる必要がある。

何かと制約が多いが、実際この2つの命令は
滅多に使わないので心配は無用(^-^;

swl 命令
swr 命令

書式
swl     r1,$nnnn(r2)
swr     r1,$nnnn(r2)
意味
メモリ内のr2レジスタの値+16ビットの値$nnnn番地のアドレスに
r1レジスタの4バイト分の値をストア(書き込み)する
データ変換
swl     +A8000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号

swr     +B8000000H
r1      +00010000H*レジスタ番号
$nnnn   +00000001H*アドレスに加算する値(nnnn=0000FFFFHまで)
r2      +00200000H*レジスタ番号
補足
$nnnnの値はaddiu命令と同じく、符号拡張される。
これによってr2の値より手前のアドレスにもアクセスができる。

lwl,lwr命令と同様に、アドレスの末尾に関係なく
値をストアできる代わりに、
swl命令とswr命令をセットで使用しなければならず、(swlを先にすること)
$nnnnの値はswr命令には実際にアクセスするアドレスを入れ、
swl命令にはそれより0003H多い値を入れて使う。

lwl,lwrswl,swrはメモリ内の比較的広範囲の
データをコピーするのに使うようである。


7.プログラム分岐操作命令

j 命令

書式
j       $8aaaaaaa
意味
プログラムをアドレス$8aaaaaaaへ分岐させる
データ変換
j         +08000000H
$8aaaaaaa +00000001H*n

n=分岐先アドレス/4(0000003FFFFFHまで)

例:
nの値   分岐先アドレス
010000H  $80040000
058000H  $80160000
002800H  $8000A000
補足
プログラム分岐系の命令は共通点があって、
実際にプログラムが分岐する前に分岐命令の1つ先の命令が
実行されてしまう(遅延スロットという)
この遅延スロットに分岐命令を置いてはならない。
(分岐命令を2つ以上並べてはならない)

通常のメモリ領域は$80000000$801fffffまでで、
それ以外のアドレスに分岐させてしまうと
プレステがハングしてしまうので注意。

j命令は、例えば自作プログラムを埋め込みたい場合に
未使用メモリ領域にプログラム制御を移し、
そこに自作プログラムのデータを埋め込み、
自作プログラムの最後で元のプログラム位置の
2つ先(遅延スロットが生じているため)の命令に制御を戻すという、
高度なことをする時に必要になる。

※未使用メモリ領域は$80007600$800083ff
$8000a000$8000afffの2箇所がある


beq 命令

書式
beq     r1,r2,$8aaaaaaa
意味
r1レジスタとr2レジスタの値が等しければ
プログラムをアドレス$8aaaaaaaへ分岐する
データ変換
beq       +10000000H
r1        +00200000H*レジスタ番号
r2        +00010000H*レジスタ番号
$8aaaaaaa +00000001H*n

n=次の命令の実行アドレスから分岐先の実行アドレスまでの命令数
例:
プログラムの実行位置    nの値       分岐先アドレス
$80012300                0000    $80012304
$80012300                0010    $80012344
$80012300                FFF8    $800122E4
補足
レジスタの値の比較と分岐を1つの命令でまとめてしてしまう、
というもの。
nの値は符号拡張されるので、負の値なら手前の命令へ
分岐させることができる。
b」で始まる命令は条件分岐命令で、比較の条件が異なる他は
どれもほとんど同じである。

ちなみに、
レジスタの値の評価→遅延命令の実行→(r1=r2なら)プログラムの分岐
というステップで行われているので、遅延命令で
r1r2の値を変化させてしまっても差し支えはない。

使用例では、任意の条件分岐命令を「beq zero,zero,$8aaaaaaa」に変更して
必ず分岐が起こるようにさせてしまう、という使い方がある。

bne 命令

書式
bne     r1,r2,$8aaaaaaa
意味
r1レジスタとr2レジスタの値が等しくなければ
プログラムをアドレス$8aaaaaaaへ分岐する
データ変換
bne       +14000000H
r1        +00200000H*レジスタ番号
r2        +00010000H*レジスタ番号
$8aaaaaaa +00000001H*n

n=次の命令の実行アドレスから分岐先の実行アドレスまでの命令数
補足
beq命令とは正反対の命令である。
slt,sltu,slti,sltiu命令との組み合わせで、かつ分岐先が現在位置より手前ならば
それはループ処理である。

使用例では、条件分岐命令を潰したい時に
任意の条件分岐命令を「bne zero,zero,$8aaaaaaa」に変更して
分岐を起こらなくさせる使い方がある。
実はnop命令に変更しても条件分岐潰しは成立するが・・・
やはり「条件分岐を潰している」というのが分かるようにする
やり方のほうが好ましいとは思う。

また、beq命令とbne命令はr2zeroレジスタの場合、
beqz r1,$8aaaaaaa」・「bnez r1,$8aaaaaaa」と訳する逆アセンブラがある。

bltz 命令
bgez 命令

書式
bltz    r1,$8aaaaaaa
bgez    r1,$8aaaaaaa
意味
r1レジスタの値がゼロ未満(bltzの場合)・ゼロ以上(bgezの場合)
の場合、プログラムをアドレス$8aaaaaaaへ分岐する
データ変換
bltz      +04000000H
r1        +00200000H*レジスタ番号
$8aaaaaaa +00000001H*n

bgez      +04010000H
r1        +00200000H*レジスタ番号
$8aaaaaaa +00000001H*n

n=次の命令の実行アドレスから分岐先の実行アドレスまでの命令数
補足
bltz命令はr1が負の値の場合、bgez命令はr1がゼロか正の値の場合に
それぞれ分岐するようになっている。
これら2つはパラメータの下限チェック(下限ゼロ)に使われる。
例えばHPが3のときに5のダメージを受けてHPが−2と
表示されたらかなり間抜けなので(笑)
そういう場面で下限チェックが行われているのである。

bgez命令は逆アセンブラによってはbeqz命令と解釈してしまうものが
あり、これが分からないと混乱する恐れがあるが、
データ変換は違うのでデータ値から判断すれば区別がつく。

blez 命令
bgtz 命令

書式
blez    r1,$8aaaaaaa
bgtz    r1,$8aaaaaaa
意味
r1レジスタの値がゼロ以下(blezの場合)・ゼロより大きい(bgtzの場合)
場合、プログラムをアドレス$8aaaaaaaへ分岐する
データ変換
blez      +18000000H
r1        +00200000H*レジスタ番号
$8aaaaaaa +00000001H*n

bgtz      +1C000000H
r1        +00200000H*レジスタ番号
$8aaaaaaa +00000001H*n

n=次の命令の実行アドレスから分岐先の実行アドレスまでの命令数
補足
bltz,bgez命令と似ているが、データ変換がやや異なるので注意。
blezbgtzの2つは下限1の下限チェックなどに使うと便利。

ちなみに、PS X-LINKの逆アセンブラは条件分岐命令のnの値が
負の値だった場合に分岐先の表示がおかしくなるが
(例えば実行アドレス=$80012300,n=FFF8Hの場合、分岐先は$800122e4だが、
$800522e4と表示されてしまう)

決してそれに騙されてはいけない。

jal 命令

書式
jal     $8aaaaaaa
意味
プログラムをアドレス$8aaaaaaaへ分岐させる
その際に、復帰アドレスがraレジスタに格納される
データ変換
jal       +0C000000H
$8aaaaaaa +00000001H*n

n=分岐先アドレス/4(0000003FFFFFHまで)
補足
復帰アドレスは分岐命令の2つ先の命令になる。
基本的にはj命令と一緒だが、こちらはサブルーチン呼び出しに使う。
サブルーチンからの復帰は「jr ra」という命令で行われる。

サブルーチン呼び出しのルールとして、パラメータ
(例えばアイテムの個数を所得するサブルーチンの場合、
何番のアイテムなのかなど)
をa0〜a3レジスタに設定し、結果(アイテムの個数)がv0レジスタに
格納される。この時、s0〜s8以外のレジスタはサブルーチン内で
使われてしまう可能性があるので、どうしても保持したい値は
sレジスタに入れておく必要がある。
逆に、サブルーチン内でsレジスタを使用する時は事前に
その値をメモリ内へ保存してやらなくてはならない。

jr 命令

書式
jr      r1
意味
プログラムをr1レジスタの値の示すアドレスへ分岐させる
データ変換
jr      +00000008H
r1      +00200000H*レジスタ番号
補足
最もよく使われるのがサブルーチンから復帰するために使う「jr ra」である。
この命令は1つのサブルーチンの終わりを示すものと言える。
raレジスタにはサブルーチンの呼び出し元のアドレスが格納されているが、
サブルーチンを呼び出しを行うとraの値が変わってしまい、
呼び出し元のアドレスへ復帰できなくなってしまう。
そこで、raの値はサブルーチンの最初のほうでメモリ内に保存しておく
必要性が出てくる。

別の使用法としては、例えばRPGなどで数百種類ものアイテムの
使用効果を管理する際に、いちいちアイテム番号を判定していては
非常に実行時間とプログラム容量がかさんでしまう。
そこで、アイテム番号ごとに使用時のプログラムの分岐先を
登録していくと効率的に進めることができる。
こういう目的で使用する場合は、「jr v0」となっていることが多い。

jalr 命令

書式
jalr    r1,r2
意味
プログラムをr2レジスタの値の示すアドレスへ分岐させる
その際に、復帰アドレスがr1レジスタに格納される
データ変換
jalr    +00000009H
r1      +00000800H*レジスタ番号
r2      +00200000H*レジスタ番号
補足
やはりサブルーチン呼び出しに使うが、
こちらはかなり複雑な分岐のある部分や
または通常のメモリ領域とは別の領域にあるサブルーチンを
呼び出す時に使う。

ちなみにr1がraレジスタの場合、「jalr r2」と省略される
(一部省略されない逆アセンブラもある)
ただ、どのサブルーチンも「jr ra」で終わっている以上、
r1にra以外のレジスタは入れようがないような気もするが・・・

bltzal 命令
bgezal 命令

書式
bltzal  r1,$8aaaaaaa
bgezal  r1,$8aaaaaaa
意味
r1レジスタの値がゼロ未満(bltzalの場合)・ゼロ以上(bgezalの場合)
の場合、プログラムをアドレス$8aaaaaaaへ分岐させる
その際に、復帰アドレスがraレジスタに格納される
データ変換
bltzal    +04100000H
r1        +00200000H*レジスタ番号
$8aaaaaaa +00000001H*n

bgezal    +04110000H
r1        +00200000H*レジスタ番号
$8aaaaaaa +00000001H*n

n=次の命令の実行アドレスから分岐先の実行アドレスまでの命令数
補足
条件付きのサブルーチン呼び出し命令であるが、
呼び出せる範囲が現在のプログラム位置から±20000Hと狭く、使い勝手が悪い。
また、beqalとかbnealなんて命令は存在しない。
実際、これら2つの命令は全く見かけたことがない。


この解説は今までの逆アセンブルの経験で
得たことや思ったことをそのまま書いております。
よって、全然間違っている説明がたくさんある可能性があります。
もしお気づきの点がございましたら掲示板まで
一言でもかまいませんのでご指摘お願いします。



戻る