ぐだぐだ低レベルプログラミング(75)ARM64(AArch64)、SBFM命令、変幻自在

Joseph Halfmoon

今回は前回に引き続き第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(バイトからの符号拡張)など、他のプロセッサでは「実体命令」かつ「必須の重要命令」であるものが含まれてます。SBFM_alias

実験用アセンブリ言語ソース

例によって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を使わずエイリアスで表示しようとします(分かり易いからかな~。)

asrDIS

次は、分かり難いことは本体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

上記を逆アセンブルしたリストが以下に。結局即値の指定がメンドイのですが、エイリアスの方がちょっと分かり易い?

sbfizDIS

最後に符号拡張系のエイリアスをまとめてアセンブルしてます。符号拡張命令(エイリアス)は即値を取りませんが、結局特定の即値の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

上記をディスアセンブルしたものが以下に。

sextDIS

アセンブラ関数を呼び出してテストする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

実行結果が以下に。
Result

まあね、意図通りに動いているみたいデス。知らんけど。

ぐだぐだ低レベルプログラミング(74)ARM64(AArch64)、BFM命令、別名不在 へ戻る

ぐだぐだ低レベルプログラミング(76)ARM64(AArch64)、UBFM命令、伸縮自在 へ進む