今回は前回に引き続き第72回で調べたビットフィールドMOV命令をエクササイズしたいと思います。今回の命令はSBFM命令(サイン付きBFM)です。SBFM命令の動作説明図を上に再掲載します。しかし図を見てもなんだかよく分からない命令デス。しかしこれが変幻自在、エイリアスの隠れ蓑の下で多数の「重要命令」の実体となります。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※以下は下記にて動作確認しています。
-
- Raspberry Pi 4 model B、Cortex-A72コア ARMv8-A
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
SBFM命令とそのエイリアス群
今回練習してみるSBFM(符号付きビットフィールド転送)命令を以下の表中に赤枠で示しました。今回のSBFM命令は、6種類のエイリアスでも表現されます。その中にはasr(算術右シフト)とか、sxtb(バイトからの符号拡張)など、他のプロセッサでは「実体命令」かつ「必須の重要命令」であるものが含まれてます。
実験用アセンブリ言語ソース
例によって1命令=1被テスト関数のスタイルで書いてありますが、今回は全てエイリアス命令があるので、同じ命令を2個重ねるスタイルでエンコードを確認できるようにしてあります。
-
- SBFM命令での表記法が先
- エイリアス命令での表記法が後
まずはソースファイルの冒頭に書きました asr(算術右シフト)命令です。引数レジスタに w をとるのが32ビット命令、xをとるのが64ビット命令です。
.globl asrW, asrX, sbfizW, sbfizX, sbfxW, sbfxX, sxtbW, sxtbX, sxthW, sxthX, sxtwX .text .balign 4 asrW: sbfm w0, w1, #4, #31 asr w0, w1, #4 ret asrX: sbfm x0, x1, #8, #63 asr x0, x1, #8 ret
上記のように、sbfm命令の第2即値にレジスタのMSBビット位置を指定したものがasrの実体となります。並んだ2つの命令が同一の機械語コードに落ちていることを以下の逆アセンブルにて確認できます。なお逆アセンブラは可能な限りSBFMを使わずエイリアスで表示しようとします(分かり易いからかな~。)
次は、分かり難いことは本体SBFM命令とドッコイドッコイに思われる、sbfiz(符号付きビットフィールドのゼロ中へのコピー)と、sbfx(符号付きビットフィールド抽出)です。アセンブラ用の記述が以下に。
sbfizW: sbfm w0, w1, #24, #3 sbfiz w0, w1, #8, #4 ret sbfizX: sbfm x0, x1, #56, #3 sbfiz x0, x1, #8, #4 ret sbfxW: sbfm w0, w1, #8, #11 sbfx w0, w1, #8, #4 ret sbfxX: sbfm x0, x1, #8, #11 sbfx x0, x1, #8, #4 ret
上記を逆アセンブルしたリストが以下に。結局即値の指定がメンドイのですが、エイリアスの方がちょっと分かり易い?
最後に符号拡張系のエイリアスをまとめてアセンブルしてます。符号拡張命令(エイリアス)は即値を取りませんが、結局特定の即値のSBFM命令にエンコードされていることが分かります。
Armのポリシーなのか、符号拡張命令(エイリアス)については
-
- 32ビットレジスタをデスティネーションとする符号拡張のソースはW指定
- 64ビットレジスタをデスティネーションとする符号拡張のソースもW指定
です。しかし、元のSBFM命令の記法では、同じ命令について
-
- 32ビットのレジスタをデスティネーションとするソースはWを指定
- 64ビットのレジスタをデスティネーションとするソースはXを指定
です。XにするのかWにするのか記法が分かれます。まあどうでもよいっちゃ良いのだけれど、私はココでアセンブラに弾かれてしげしげとマニュアルを見てしまいましたぜ。
なお、ワードからの符号拡張はデスティネーションXしかありません。当たりまえか。
sxtbW: sbfm w0, w1, #0, #7 sxtb w0, w1 ret sxtbX: sbfm x0, x1, #0, #7 sxtb x0, w1 ret sxthW: sbfm w0, w1, #0, #15 sxth w0, w1 ret sxthX: sbfm x0, x1, #0, #15 sxth x0, w1 ret sxtwX: sbfm x0, x1, #0, #31 sxtw x0, w1 ret
上記をディスアセンブルしたものが以下に。
アセンブラ関数を呼び出してテストするCのコード
とりあえず各関数1回だけ触ってみるコードが以下に。通り一遍触るだけでもメンドイ。
#include <stdio.h> #include <stdint.h> extern uint32_t asrW(uint32_t, uint32_t); extern uint64_t asrX(uint64_t, uint64_t); extern uint32_t sbfizW(uint32_t, uint32_t); extern uint64_t sbfizX(uint64_t, uint64_t); extern uint32_t sbfxW(uint32_t, uint32_t); extern uint64_t sbfxX(uint64_t, uint64_t); extern uint32_t sxtbW(uint32_t, uint32_t); extern uint64_t sxtbX(uint64_t, uint64_t); extern uint32_t sxthW(uint32_t, uint32_t); extern uint64_t sxthX(uint64_t, uint64_t); extern uint64_t sxtwX(uint64_t, uint64_t); int main(void) { uint32_t result; uint64_t resultX; result = asrW(0, 0x87654321); printf ("asrW: %08x\n", result); resultX = asrX(0, 0x87654321A5A5A5A5); printf ("asrW: %lx\n", resultX); result = sbfizW(0x0000FFFF, 0xFEDCBA98); printf ("sbfizW: %08x\n", result); resultX = sbfizX(0x0000FFFF, 0xFEDCBA98); printf ("sbfizX: %lx\n", resultX); result = sbfxW(0x0000FFFF, 0x89ABCDEF); printf ("sbfxW: %08x\n", result); resultX = sbfxX(0x0000FFFF, 0xFEDCBA98); printf ("sbfxX: %lx\n", resultX); result = sxtbW(0, 0x89); printf ("sxtbW: %08x\n", result); resultX = sxtbX(0, 0xCD); printf ("sxtbX: %lx\n", resultX); result = sxthW(0, 0x8912); printf ("sbthW: %08x\n", result); resultX = sxthX(0, 0xCD12); printf ("sbthX: %lx\n", resultX); resultX = sxtwX(0, 0xABCD1234); printf ("sbttwX: %lx\n", resultX); return 0; }
ビルドして実行
以下のようにしてビルドいたしました。
$ gcc -g -O0 -o sbfm sbfm.c sbfm.s
まあね、意図通りに動いているみたいデス。知らんけど。