前回:6. 制御構造 - if文 while文 次回:8.配列と構造体、その他のインデクシングモード
目次(記事一覧)
※この記事はRoger Ferrer IbáñezさんのブログARM assembler in Raspberry Pi – Chapter 7の翻訳です。
ARMアーキテクチャは、長年組み込みシステムを対象としてきました。組み込みシステムは通常、大量生産される製品(食器洗い機、携帯電話、テレビなど)に使用されますが、売価と原価の差が非常に小さいため、設計者は可能な限り構成部品を節約しようとします(数十万、あるいは数百万の製品から1セントを節約するだけでも利益があるかもしれません)。メモリは日々価格が下がってきているとはいえ、部品の中でも比較的高価な部類に入ります。とにかく、メモリに制約がある環境ではメモリを節約できるに越したことはありません。ARMの命令セットはそのようなことを念頭において設計されています。これらのテクニックをすべて学ぶためにはあと何章か必要です。今回は「オペランドのシフト」と呼ばれる機能から始めます。
インデクシングモード
これまでにわかったことは、ARM命令は、ロード(ldr
)、ストア(str
)、分岐(b
または bXX
)を除き、オペランドとしてレジスタか即値のいずれかを取る、ということです。また、最初のオペランドは通常、デスティネーション(destination 目的値)レジスタであることもわかりました。ただしstr
は例外で、目的値がメモリのため最初のオペランドはソースの役割です。mov
命令は目的値に加えもう一つレジスタか即値のオペランドを取ります。add
やand
など算術命令は目的値に加えもう2つのソースレジスタを取り、1つ目は必ずレジスタで、2つ目はレジスタか即値です。
命令において許可されるこのようなオペランドの組は、まとめてインデクシングモードと呼ばれます。今日では、この概念は何もインデックス(索引付け)しないため少し見当違いな名前です。インデクシングという名前はメモリオペランドでは意味を持ちますが、ARM命令はロードとストアを除いてメモリオペランドを取りません。とはいえARMのドキュメントにある用語であるためその呼び方をするのが懸命でしょう。
ほとんどのARM命令の構文は次のパターンでまとめられます。
instruction Rdest, Rsource1, source2
これには例外があり、主にムーブ(mov
)、分岐、ロード、ストアが該当します。実際のところムーブ命令はそこまで違いはありません。
mov Rdest, source2
Rdest
とRsource1
は必ずレジスタです。次節でsource2
について述べます。
ロード命令とストア命令のインデクシングモードについては今後の記事で議論します。一方、分岐命令は驚くほど単純で、唯一のオペランドがプログラム中のラベルのためインデクシングモードについて説明することはほとんどありません。
オペランドのシフト
上記の命令パターンにおけるこの不思議なsource2
は何でしょうか?前章では、レジスタか即値を使いました。つまり、少なくともsource2
はレジスタか即値です。source2
の部分にはレジスタか即値が使用できます。以下に前章でも使われた例を示します。
mov r0, #1
mov r1, r0
add r2, r1, r0
add r2, r3, #4
しかし、source2
は単なるレジスタや即値以上のものになり得ます。実際、レジスタの場合はシフト演算と組み合わせることができます。すでにこれらのシフト演算の1つは第6章で見ました。全てではありませんがこのようなものがあります。
構文 | 由来 | 意味 | 解説 |
---|---|---|---|
LSL #n | Logical Shift Left | 論理左シフト | ビットをn回左にシフトする。左端からn個のビットは失われ、右端からn個のビットに0がセットされる。 |
LSL Rsource3 | - | - | 上と同様だが、レジスタの下位バイトがシフト量を決める。 |
LSR #n | Logical Shift Right | 論理右シフト | ビットをn回右にシフトする。右端からn個のビットは失われ、左端からn個のビットに0がセットされる。 |
LSR Rsource3 | - | - | 上と同様だが、レジスタの下位バイトがシフト量を決める。 |
ASR #n | Arithmetic Shift Right | 算術右シフト | LSRと同様だが左端からn個のビットにセットするのは0ではなく、シフト前の左端のビットとなる。 |
ASR Rsource3 | - | - | 上と同様だが、レジスタの下位バイトがシフト量を決める。 |
ROR #n | Rotate Right | 右ローテート | LSRと同様だが、右端からn個のビットは失われず、左端からn個のビットにセットされる。 |
ROR Rsource3 | - | - | 上と同様だが、即値の代わりにレジスタの下位バイトを使用する。 |
上の表におけるnは1から31までの即値です。これらの追加の演算は、2番目のソースレジスタの値(レジスタそのものではない)に適用できるため、1つの命令で複数の操作を行えることになります。ARMには直接シフトする命令はありません。mov
命令を使います。
mov r1, r2, LSL #1
なぜレジスタの値を左または右にシフトしたいのかと疑問に思うかもしれません。第6章では値を左にシフト(LSL)することで2を掛けるのと同じ値が得られることがわかりました。逆に、2の補数表現(符号あり整数)ならASRで、その他の場合はLSRで右にシフトをすると2で割ることができます。シフトは結果的に値を$2^n$で乗算または除算しています。
mov r1, r2, LSL #1 /* r1 ← (r2*2) */
mov r1, r2, LSL #2 /* r1 ← (r2*4) */
mov r1, r3, ASR #3 /* r1 ← (r3/8) */
mov r3, #4
mov r1, r2, LSL r3 /* r1 ← (r2*16) */
add
と組み合わせてこんなこともできます。
add r1, r2, r2, LSL #1 /* r1 ← r2 + (r2*2) つまり r1 ← r2*3 */
add r1, r2, r2, LSL #2 /* r1 ← r2 + (r2*4) つまり r1 ← r2*5 */
sub
でも似たことができます。
sub r1, r2, r2, LSL #3 /* r1 ← r2 - (r2*8) つまり r1 ← r2*(-7) */
ARMには便利なrsb
(Reverse Substract)命令があります。sub
がRdest ← Rsource1 - source2
を計算するのに対してrsb
はRdest ← source2 - Rsource1
を計算します。
rsb r1, r2, r2, LSL #3 /* r1 ← (r2*8) - r2 つまり r1 ← r2*7 */
次の例は少々凝っています。
/* r1の初期値に42を掛ける = 7*3*2 */
rsb r1, r1, r1, LSL #3 /* r1 ← (r1*8) - r1 つまり r1 ← 7*r1 */
add r1, r1, r1, LSL #1 /* r1 ← r1 + (2*r1) つまり r1 ← 3*r1 */
add r1, r1, r1 /* r1 ← r1 + r1 つまり r1 ← 2*r1 */
なぜシフトを使用して乗算をしようとするのか疑問かもしれません。一般的な乗算命令はもちろんありますが、ARMプロセッサーによる計算はシフト演算よりはるかに重く時間ががかかることもあります。それを使うしか選択肢がないこともありますが、たいてい使用される小さな定数値の場合、単一の命令のほうが効率的です。
ローテーションは日常の使用においてシフトよりも有用ではありません。ローテーションは通常、暗号化においてビット列を並び替えて「スクランブル」するために使用されます。ARMは左にローテートする命令を用意していませんが、左にn回転することは右に32-n
回転することで実現できます。
/* r1の初期値が0x12345678だったとする */
mov r1, r1, ROR #1 /* r1 ← r1 ror 1. これは r1 ← 0x91a2b3c */
mov r1, r1, ROR #31 /* r1 ← r1 ror 31. これは r1 ← 0x12345678 */
今日はここまで。