読者です 読者をやめる 読者になる 読者になる

kivantium活動日記

プログラムを使っていろいろやります

PowerPCアセンブリでよく使う命令

アセンブリの課題を解くために使った命令のメモ。動かしてみて多分こういう動作だろうという感じで書いているので、詳しくはマニュアル(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           # 元の位置に戻る

このようにスタックを確保→待避→関数呼び出し→待避した値を使う→スタックを元に戻すの流れをやっていけば関数呼び出しに関しては問題なく課題が解けます。

浮動小数点数について

浮動小数点数は専用のレジスタを用いて演算を行います。

関数の戻り値が整数型の場合は%r3に入りますが、戻り値が浮動小数点数の場合は%f1に入ります。
%r3などの汎用レジスタから浮動小数点数に移動する命令がない(要出典!!あったら教えて!!)ので、整数型を演算して一度メモリに保存した後、lfdなどの命令で%f1に入れればそれっぽい結果が出ます。

課題が進んだら更新していきます。