3. 演算命令と分岐命令

ARM CPUの命令セットのうち、ARMステートに属する命令の概要をまとめてみました。 Linuxのユーザモードでよく使う基本命令を重要な順に解説します。 ARMの命令(コプロセッサ用、Thumbステート用は除く) は全体で50種類ほどですが、 普通のプログラムでは30種類程度の命令で十分です。 最近のPentium系のCPUと比べて命令数は少ないのですが、 条件実行が可能なことや演算命令でフラグレジスタへの反映の有無を指定できることから、 それらの組み合わせでニーモニック(命令の表現)が多く感じます。 また各命令が操作の対象とするレジスタやメモリの指定方法「アドレッシング」が 命令ごとに少しずつ異なるため複雑に感じます。 ARMの命令の内部表現は、1ビットも無駄にしないように考えて作られているためのようです。 慣れるまでの我慢が大切です。

詳細は前に紹介した書籍や文書を参考にして下さい。

演算命令

コンピュータの元になる compute という単語は「計算する」という意味です。 そこで演算命令を最初に紹介することにします。ARMではレジスタ間転送命令(MOV) もシフト演算が可能たため演算命令に分類されます。

演算命令とは加減算、論理演算、比較などを行う命令群です。 次のように3つのレジスタを指定することができます。 3番目の位置には、レジスタ以外に定数やレジスタの内容をシフトした値を指定することもできます。

add  r1, r2, r3           @ r1 = r2 + r3

上の例では、r1 はデスティネーションレジスタ (Rd) で演算結果が最終的に代入されます。 r2 は第1ソースオペランドレジスタ (Rn) 、r3 の部分は第2オペランドと呼んで、 レジスタ、メモリアドレス、定数値を指定できます。

@ すべてを同じレジスタにすることも可能
add  r1, r1, r1           @ r1 = r1 + r1 = r1 * 2

演算命令の場合、第2オペランドは「そのオペランドレジスタの内容をシフトしたもの」 という指定が可能で、次のように1命令でシフト演算まで実行することができます。

add  r1, r2, r3 LSL #20   @ r1 = r2 + (r3 << 20)

指定できるシフト演算は次の5種類を指定することができます。RRXはシフト量を
指定できません(後述)。

LSL (Logical Shift Left) 左シフト
LSR (Logical Shift Right) 右シフト
ASR (Arithmetic Shift Right) 算術右シフト(符号を保存)
ROR (Rotate Right) 右ローテイト
RRX (Rotate Right with extend) 右ローテイト(含キャリーフラグ)

シフト量は5ビットイミディエイト(0-32の定数値) か、別のレジスタの最下位 バイトで指定します。

演算命令の一覧です。表中で <cd> はアルファベット2文字から成る 条件指定のサフィックスを示します。 また、<S> は、演算命令の場合は条件指定のサフィックスの後ろに、 命令の実行結果によって ステータスフラグを更新する指定である S を示します。 どちらもオプションです。

オペコード オペランド 動作 意味
ADD<cd><S> Rd, Rn, <sft> Rd=Rn+<sft> 加算
ADC<cd><S> Rd, Rn, <sft> Rd=Rn+<sft>+Cy キャリー込加算
SUB<cd><S> Rd, Rn, <sft> Rd=Rn - <sft> 減算
SBC<cd><S> Rd, Rn, <sft> Rd=Rn - <sft>+Cy キャリー込減算
RSB<cd><S> Rd, Rn, <sft> Rd=<sft> - Rn 逆減算
RSC<cd><S> Rd, Rn, <sft> Rd=<sft> - Rn+Cy キャリー込逆減算
AND<cd><S> Rd, Rn, <sft> Rd=Rn & <sft> 論理積
ORR<cd><S> Rd, Rn, <sft> Rd=Rn | <sft> 論理和
EOR<cd><S> Rd, Rn, <sft> Rd=Rn ^ <sft> 排他的論理和
BIC<cd><S> Rd, Rn, <sft> Rd=Rn & !<sft> ビットクリア
CMN<cd> Rn, <sft> <flags>=Rn+<sft> 逆比較
CMP<cd> Rn, <sft> <flags>=Rn - <sft> 比較
TEQ<cd> Rn, <sft> <flags>=Rn ^ <sft> EORテスト
TST<cd> Rn, <sft> <flags>=Rn & <sft> ANDテスト
MOV<cd><S> Rd, <sft> Rd=<sft> 代入
MVN<cd><S> Rd, <sft> Rd=0xFFFFFFFF EOR <sft> 1の補数の代入

第2オペランドの位置にある <sft> は次のように定数値、レジスタ、 レジスタの内容をシフトしたものを指定することができます。

<sft>
  #8ビット定数(4bitのローテート可)
  Rm
  Rm, LSL #5ビット定数   
  Rm, LSL Rs         
  Rm, LSR #5ビット定数
  Rm, LSR Rs         
  Rm, ASR #5ビット定数
  Rm, ASR Rs         
  Rm, ROR #5ビット定数
  Rm, ROR Rs         
  Rm, RRX

ARM のアセンブラでは、定数は前に「#」を付加します。シフトするビット数は 5ビットの定数 (0-31)またはレジスタ(Rs)の値で指定しますが、 RRX の場合はキャリーフラグも含めて33ビットで右ローテートするシフト演算子で、 シフト量は指定できず常に1ビットです。

演算命令で使用できるイミディエイト(定数)は 8ビットまでですが、 次のような方法で 0 から 255 より大きい定数を扱えるようになっています。 第2オペランドがイミディエイト(定数)である場合、命令中の8ビットの値を 命令中の別の4ビットの値(を2倍したもの)で右ローテートしたものが、 第2オペランドの値となります。 以上の操作で、8ビットの値をローテートして得られる多くの定数を生成すること ができます。この計算はアセンブラに任せます。 指定した定数が生成できない場合には、アセンブラがエラーで知らせます。 任意の定数を使う場合には複数の命令に分けて生成するか、メモリに書き込まれた値を ロード命令 (後述) でレジスタに読み込む方法を用います。

分岐命令

条件によって処理を変えたり、繰り返しをする場合に使う命令です。 分岐命令は3種類ですが、条件指定のサフィックスを付けられるため、 BEQ、BNE、BGEなどのような条件分岐命令になります。分岐先のアドレスは4で割り切れる (word-aligned) 必要があります。

オペコード オペランド 動作 意味
B<cd> <label> PC=PC+<24bit offset> 分岐
BL<cd> <label> LR=PC+4; PC=PC+<oft_24> CALL (リンク+分岐)
BX<cd> Rm PC=Rm; Mode=Thumb 分岐+Thumb移行

分岐命令には符号付き24ビットのオフセットが含まれ、32ビットに拡張して、 2ビット左シフト(4倍)したものをプログラムカウンタ(r15)に加えて 飛び先アドレスとします。プログラムカウンタは分岐命令自身のアドレス に8を加えたものとなっています。分岐できる範囲は約±32MBです。 指定したラベルまで分岐するための計算はアセンブラが行うので 気にする必要はありません

BL命令の場合はリターンアドレスをリンクレジスタ (r14 または lr) に設定した後に 分岐します。サブルーチン呼び出しに使います。サブルーチンからの復帰は リンクレジスタをプログラムカウンタ (pc) に代入します。この方法ではサブルーチン がサブルーチンを呼び出すような場合に対応できないため、通常はサブルーチンの 入り口で lr をスタックに保存し、出口でスタックから pc に戻す方法を使います。

BX命令は、分岐の後命令セットの切り替えも行います。指定したアドレスの LSB(ビット0)によって、分岐先の命令がARM命令セットかTHUMB命令セットか が決まります。

        mov     r0, #0          @ r0 = 0
Next:   bl      Inc             @ 戻りアドレスを lr に入れ Inc に分岐
        cmp     r0, #10         @ r0 と 10 を比較
        bne     Next            @ r0 が 10 でなければNextへ分岐
        b       End             @ End に無条件分岐
Inc:    add     r0, r0, #1      @ r0 = r0 + 1
        mov     pc, lr          @ lr を pc に代入 (リターン)
End:

これまでに出てきた命令を使った例です。単にレジスタ r0 に 0 を代入し、10 に なるまで 1 を加えます。「Next:」のように行頭でコロンを伴う文字列をラベルと呼びます。 分岐命令の飛び先を指定しています。「@」から行末まではコメントです。 7行目の「mov pc, lr」は bl 命令で lr (r14) に保存された戻りアドレスをプログラム カウンタ (pc または r15) に代入することで bl 命令の次の命令から実行を始めます。 命令やレジスタ名に小文字を使っているのは私の趣味です。大文字でもかまいません。