ARMショートコーディング(コード短縮)技法
はじめに
ショートコーディングとは、ソースコードをいかに短くできるかというものである。今回はARMアセンブリにおいて、どのようにすればコードを短くできるか、色々と考えてみる。
条件実行の短縮
mov r0, #0 @ r0 = 0 cmp r0, #0 @ if(r0 == 0) beq <label> @ goto <label>
上記のようなコードがあったとする。「条件実行はcmp命令(論理演算であればtst命令)を使えばよい」と無条件に考える人は多いが、必ずしもそうではない。もちろん間違いではないが、ショートコーディングにおいては仇となる場合がある。
もう少し詳しく見てみよう。cmp命令は内部的には以下のようなことを行っている。
1. 第1オペランドの値から第2オペランドの値を引く。 2. 演算結果をcpsr(フラグやプロセッサモードの情報が格納されているレジスタ)に反映する。
例えば上記の例だとこうなる。
1. r0 - #0を計算。(r0には既に0が代入されているため、演算結果は0) 2. 演算結果が0になるため、Zフラグが設定される。 3. `eq`の実行はZフラグが設定されていることが条件であるため、Zフラグが設定されているとき`beq <label>`ではlabelへと分岐する。
ここで本題へ戻る。上記の例は一見無駄がないように見えるかもしれない。ここで登場するのが、フラグフィールドを更新できる便利なサフィックスs
。通常の命令の末尾にs
をつけることで、演算結果をcpsrに反映できる。
これを踏まえて上記のコードを書き直してみる。
movs r0, #0 beq <label>
1行減らすことができた。movsを使用した時点で、movsの第一オペランドにある演算結果はcpsrに反映される。今回の演算結果は0であるため、わざわざcmp命令を使用しなくてもZフラグを設定することができる。
今回の例ではたまたま演算結果が0であったため分岐に使用できたが、毎回そううまくいくとは限らない。また、cmp命令を省くとあるときに比べて読みづらくなる場合がある。あくまでショートコーディングの観点からすれば、こういう方法もある、ということである。
バレルシフタの活用
バレルシフタとは、シフト処理を行うデジタル回路のことである。様々な命令に使用することができ、これを活用することでレジスタの値をシフト処理して使用したり、即値に指定できない値を指定したりすることができる。なお、バレルシフタが利用できる命令はムーブ命令(mov, mvn)、加算命令(add, adc)、減算命令(sub, sbc, rsb, rsc)、比較命令(cmp, cmn)、テスト命令(tst, teq)、論理演算命令(and, orr, eor, bic)である。
例えば以下のコードは1命令で実行できる。
mov r0, #0x80000000
「即値には8bitしか指定出来ないのでは?」と思った方がいるかもしれないが、これがバレルシフタのトリック。このコードはmov r0, #2, #2
と同義で、「2を2bit右ローテートした結果(=0x80000000)をr0に代入」という意味になる。
他にも、r1の4倍とr0を比較したいとき、
lsl r1, r1, #2 @r1を4倍 cmp r0, r1 @r0とr1を比較
と書くところを、
cmp r0, r1, lsl #2
とすれば1行減らすことができる。また、r1の値も書き換えずに済む。
アドレッシングモードの活用
これはロード・ストア命令で使用することができる。以下にその種類を挙げる。
プリインデックス
皆さんおなじみの記法。先に[]内を演算してメモリアドレスを算出し、そのメモリアドレスから値を取得する。
ldr r0, [r1, #4] @r1+4の位置から取得した値をr0に格納
ライトバック付きプリインデックス
ライトバック(!)を付けると、ベースアドレスを[]内の演算結果に書き換えることができる。
ldr r0, [r1, #4]! @r1+4の位置から取得した値をr0に格納、r1にr1+4の値を格納
ポストインデックス
こちらは先にベースアドレスをロードし、その後ベースアドレスを書き換える。
ldr r0, [r1], #4 @r1のメモリアドレスにある値をr0に格納、r1にr1+4の値を格納
アドレッシングモードを活用すれば、通常ならadd命令などで加算しなければいけない部分を短縮することができる。
まとめ
アセンブリで短いコードを書くことは、バイナリサイズの縮小や高速化、脆弱性のコード実証においても非常に重要である。日ごろから心がけるようにしよう。