アセンブリの課題を解くために使った命令のメモ。動かしてみて多分こういう動作だろうという感じで書いているので、詳しくはマニュアル(PDF)や補足資料(PDF)を必ず参照してください。
間違っているかもしれません。間違いに気がついたらTwitterか何かで教えてください!
演算命令
ニーモニック | 用法 | よく使う動作 | 名前の由来(予想) |
li | li rD, value | rD ← value | Load Immidiate |
mr | mr rA, rB | rA ← rB | Move Register |
addi | addi rD, rA, SIMM | rD ← rA + SIMM | ADD Immidiate |
addc | addc rD, rA, rB | rD ← rA + rB (内容に応じてキャリーフラグが変わる) | ADD Carrying |
adde | adde rD, rA, rB | rD ← rA + rB + XER[CA] (キャリーフラグを含めて足す) | ADD Extended |
add | add rA, rB, rC | rA ← rB + rC | ADD |
fadd | fadd fA, fB, fC | fA ← fB + fC | Floating ADD |
and | and rA, rB, rC | rA ← rB & rC | AND |
fmul | fmul fA, fB, fC | fA ← fB * fC | Floating Multiply |
mullw | mulli rD, rA, rB | rD ← rA * rB (int型が入ったレジスタ同士の乗算はこれっぽい) | MULtipuly Low Word |
mulli | mulli rD, rA, SIMM | rD ← rA * SIMM | MULtipuly Low Immidiate |
sldi | sldi rA, rB, value | rA ← rB << value | Shift Left Double word Immidiate |
or | or rA, rB, rC | rA ← rB or rC | OR |
比較命令
ニーモニック | 用法 | よく使う動作 | 名前の由来(予想) |
cmpwi | cmpwi rX, value | rXとvalueを比較しcr0を更新(32bit) | CoMPare Word Immidiate |
cmpw | cmpwi rA, rB | rAとrBを比較しcr0を更新(32bit) | CoMPare Word |
分岐命令
ニーモニック | 用法 | よく使う動作 | 名前の由来(予想) |
b | b target | 無条件にジャンプ | Branch |
bl | bl target | LINKを更新してジャンプ(関数呼び出し) | Branch Link(?) |
beq | beq target | 等しければジャンプ(cr0を見る、以下同様) | Branch if EQual |
bne | bne target | 等しくなければジャンプ | Branch if Not Equal |
blt | blt target | 小さければジャンプ | Branch if Less Than |
ble | ble target | 小さいか等しければジャンプ | Branch if Less than or Equal |
bgt | bgt target | 大きければジャンプ | Branch if Greater Than |
bge | bge target | 大きいか等しければジャンプ | Branch if Greater than or Equal |
メモリ転送命令
ニーモニック | 用法 | よく使う動作 | 名前の由来(予想) |
lbz | lbz rT, d(rA) | rT ← M[rA+d] | Load Byte and Zero |
lbzu | lbzu rT, d(rA) | rT ← M[rA+d], rA ← rA + d | Load Byte and Zero with Update |
lbzx | lbzu rT, rA, rB | rT ← M[rA+rB] | Load Byte and Zero indeXed |
ld | ld rT, d(rA) | rT ← M[rA+d] (64bit) | Load Doubleword |
lfd | lfd fT, d(rA) | fT ← M[rA+d] (浮動小数点数) | Load Floating Doubleword |
lfdx | lfdx fT, rA, rB | fT ← M[rA+rB] (浮動小数点数) | Load Floating Doubleword indeXed |
stb | stb rS, d(rA) | M[rA+d] ← rS | STore Byte |
stbu | stbu rS, d(rA) | M[rA+d] ← rS, rA ← rA + d | STore Byte with Update |
std | std rS, d(rA) | M[rA+d] ← rS (64bit) | STore Doubuleword |
stdu | std rS, d(rA) | M[rA+d] ← rS, rA ← rA + d (64bit) | STore Doubuleword with Update |
リンクレジスタに関係する命令
ニーモニック | 用法 | よく使う動作 | 名前の由来(予想) |
mflr | mflr rA | rA ← LINK | Move From Link Register |
mtlr | mrlr rA | LINK ← rA | Move To Link Register |
関数呼び出しに関する補足
関数を呼び出すと、呼び出された関数内でレジスタが使われてしまい、呼び出しの前後でレジスタの値が変わってしまうことがあります。それを避けるためにレジスタを関数内でどのように使うかが決められています。
関数呼び出しの前後で値が変化する可能性のあるレジスタをvolatileなレジスタと呼び、値が変化しないレジスタをnon-volatileなレジスタと呼びます。保存する必要のある値が値が破壊されてしまうレジスタに入っている場合は一時的にメモリに待避させて保持する必要がありますが、volatileなレジスタに関しては呼び出す側の関数が、non-volatileなレジスタに関しては呼び出される側の関数が、待避の責任を負います。
PowerPCの場合レジスタごとのvolatile, non-volatileの分類は用途ごとに次のようになっています。
GPR0 | volatile | addなどの一部の命令では値が0と見なされる |
GPR1 | スタックポインタ | |
GPR2 | table of contents pointer | |
GPR3-10 | volatile | 引数に使う (GPR3は返り値として使う) |
GPR11, 12 | volatile | 関数のリンクなどに使われる |
GPR13 | non-volatile | small data area pointer |
GPR14-31 | non-volatile | ローカル変数として使う |
LR | volatile | Link Register. 分岐命令から戻るアドレスを記録する |
CTR | volatile | CounTer Register. ループのカウンタに使う |
CR0, 1, CR5-7 | volatile | Condition Register. |
CR2-4 | non-volatile | Condition Register. |
(講義資料より引用)
レジスタの待避はスタックを用いて行います。スタックはメモリ上の空間で表現されています。構造は
High Address +-> Back chain | Floating point register save area | General register save area | VRSAVE save word (32-bits) | Alignment padding (4 or 12 bytes) | Vector register save area (quadword aligned) | Local variable space | Parameter save area (SP + 48) | TOC save area (SP + 40) | link editor doubleword (SP + 32) | compiler doubleword (SP + 24) | LR save area (SP + 16) | CR save area (SP + 8) SP ---> +-- Back chain (SP + 0) Low Address
のようになっていて、上位アドレスから下位アドレスに向かって伸びていきます。
待避に使うのは主にLR save areaとGeneral register save areaです。
関数内でさらに関数を呼び出す際にvolatileなレジスタに入っている値を待避させるには、LR save areaにLINKレジスタの値を待避したあと、スタックポインタを減らすことでスタック領域を拡張した後で、必要なvolatileレジスタの値をGeneral register save areaに待避します。
IBMの資料によるとGeneral register save area以外の領域に112 byte必要で、さらにスタックポインタは16の倍数でないといけないそうなのでレジスタの値を待避するには最低128 byteスタックポインタを変更する必要がありそうです。それ以上必要な場合は拡張するサイズを1レジスタにつき8byteずつ(64bitアーキテクチャなので)増やしていけばいいかと思います。
これを念頭において関数呼び出しの例を見ていきます。簡単な例として階乗を計算するfact()をアセンブリで書いてみます。
#include <stdio.h> int fact(int i) { if (i==0) return 1; else return i * fact(i-1); } int main(void) { int n; scanf("%d", &n); printf("%d\n", fact(n)); return 0; }
fact()内で新たにfact()を呼び出すときにvolatileな%r3に入っているもとの関数の引数iが破壊されてしまいますが、関数の呼び出しが終わった後に引数のiを掛ける処理が必要になるため、iを保存しておく必要があります。また、fact()の呼び出しでLINKレジスタが変更されてしまうのでmain関数に戻るためにはLINKレジスタの値も保持する必要があります。
これを実現するのが以下のコードです。
.text .align 2 .globl fact fact: cmpwi %r3, 0 # if(i==0)に対応 beq .ret # 等しければ1を返す処理へ飛ぶ mflr %r0 # LINKレジスタの値をr0にコピー std %r0, 16(%r1) # r0をr1+16(LR save area)にコピー stdu %r1, -128(%r1) # M[r1-128]に現在のSPを保存, SPを128減らしてスタックを拡張 std %r3, 112(%r1) # SP+112(General register save area)にr3の値をコピー addi %r3, %r3, -1 # r3の値(i)を1減らす bl fact # factを呼び出す ld %r4, 112(%r1) # 待避しておいたiをr4にコピー mullw %r3, %r3, %r4 # r3 <- r3 * r4 addi %r1, %r1, 128 # SPを128増やして関数呼び出し前に戻す ld %r0, 16(%r1) # 待避しておいたLINKレジスタの値をr0にコピー mtlr %r0 # LINKレジスタにr0をコピー blr # LINKレジスタのアドレスにジャンプ .ret: # 1を返す処理 li %r3, 1 # 返り値となるr3に1を入れて blr # 元の位置に戻る
このようにスタックを確保→待避→関数呼び出し→待避した値を使う→スタックを元に戻すの流れをやっていけば関数呼び出しに関しては問題なく課題が解けます。