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

Joseph Halfmoon

前回は符号付きのビットフィールドMOV命令、SBFMのエクササイズでした。今回は符号無のビットフィールドMOV命令、UBFMです。これまた多くのエイリアスの名のもとにいろいろな命令に化ける命令です。似た命令であるSBFMと「対称」と思って見に行くと期待を裏切られます。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
UBFM命令とそのエイリアス群

今回練習してみるUBFM(符号無ビットフィールド転送)命令を以下の表中に赤枠で示しました。UBFM_alias

前回のSBFM命令とは、以下に列挙している点が「非対称」です。最初はシフトの件から。

    • SBFMは、ASR(算術右シフト)に化ける
    • UBFMは、LSL(論理左シフト)とLSR(論理右シフト)に化ける

ここは算術左シフトという操作が存在しないので、どのプロセッサにも存在する致し方ない非対称です。

しかし以下については、ボーっとしてエイリアス命令を書くとマニュアルに存在しない組み合わせを使ってしまうことになります(使ってもエラーにならないことがあるのが微妙です。また意図と違うコードが吐き出されることがあるようです。知らんけど。)

    • SBFMは、SXTB(バイトからの符号拡張)、SXTH(ハーフワードからの符号拡張)、SXTW(ワードからの符号拡張)の3つがある
    • UBFMは、UXTB(バイトからの拡張)、UXTH(ハーフワードからの拡張)の2つはあるが、ワードからの拡張は無い。

エイリアス的な非対称性に加えて、オペランドに関しても

    • SBFMのエイリアスである符号拡張は、Wレジスタ(32ビット)とXレジスタ(64)ビットのどちらもデスティネーションにとることができる
    • UBFMのエイリアスである符号無拡張は、Wレジスタ(32ビット)しかデスティネーションにとらない

という制約もあります。このどちらも64ビットモード下のW(32ビット)レジスタ操作時に操作対象のWレジスタを含むX(64ビット)レジスタの上位ワードはゼロクリアされるという点が分かっていると、なぜ UXTWエイリアスが定義されていないのか、また、符号無拡張ではWレジスタしかデスティネーションにとれないのか、合点がいくと思います(でも後でみますがアセンブラ、gasの挙動はArmのマニュアルとチト違ったりします。)

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

例によって1命令=1被テスト関数のスタイルです。今回もエイリアス命令がある場合、基本、同一命令を2個重ねるスタイルでエンコードを確認できるようにしてあります。

    • UBFM命令での表記法が先
    • エイリアス命令での表記法が後

まずはソースの先頭付近においた、論理シフト、左、右です。冒頭のアイキャッチ画像に掲げたUBFM命令の動作説明図を眺めれば ubfm 表記も理解できないこともありませんが(自分で書いてるし)、やっぱり lsl や lsr というエイリアス表記の方がずっと分かり易いです。

.globl	lslW, lslX, lsrW, lsrX, ubfizW, ubfizX, ubfxW, ubfxX, uxtbW, uxthW, uxtwX
.text
.balign	4

lslW:
    ubfm	w0, w1, #24, #23
    lsl		w0, w1, #8
    ret

lslX:
    ubfm	x0, x1, #56, #55
    lsl		x0, x1, #8
    ret

lsrW:
    ubfm	w0, w1, #8, #31
    lsr		w0, w1, #8
    ret

lsrX:
    ubfm	x0, x1, #8, #63
    lsr		x0, x1, #8
    ret

ならべて書いた2命令が実は同じ機械語に落ちていることが、以下のディスアセンブル・リストを見ると分かります。なお、ディスアセンブラはエイリアスがあればエイリアス優先で表現してくれます。disLSLLSR

続いて、ゼロ詰めのビットフィールド挿入、ビットフィールド抽出命令です。とっつき悪い奴らですが、意外にもここは符号付きの挿入、抽出と対称にできているので、そのつもりで前回と比べて見ればお楽。

ubfizW:
    ubfm	w0, w1, #24, #3
    ubfiz	w0, w1, #8, #4
    ret

ubfizX:
    ubfm	x0, x1, #56, #3
    ubfiz	x0, x1, #8, #4
    ret

ubfxW:
    ubfm	w0, w1, #8, #11
    ubfx	w0, w1, #8, #4
    ret

ubfxX:
    ubfm	x0, x1, #8, #11
    ubfx	x0, x1, #8, #4
    ret

ディスアセンブル・リストが以下に。元命令とエイリアスが同じ機械語に落ちることが分かりますが、どっちもどっち、メンドイ奴らです。disUBFIZ

最後は符号無の拡張命令です。先に説明したとおり、wレジスタだけ操作すれば、実はxレジスタも操作したことになるので、デスティネーションはWでもXでも一緒だ、という自在さ加減です。

なお、Armのマニュアル(Arm Architecture Reference Manual Armv8, for Armv8-A architecture profile)上は uxtw などというエイリアスは無い筈なのです。しかし以下では書いてみてます。バイトとハーフワードがあって、ワードが無いというのは片手落ち感があるので、もしかして gas はそんな気持ちを汲み取ってくれるんじゃ、という期待です。

uxtbW:
    ubfm	w0, w1, #0, #7
    uxtb	w0, w1
    ret

uxthW:
    ubfm	w0, w1, #0, #15
    uxth	w0, w1
    ret

uxtwX:
    uxtw	x0, w1
    ret

以下、逆アセンブルリストを御覧じろ。uxtwが無いのは片手落ちと gas も思っているのか、アセンブル通っています。しかし、UBFM命令に帰着させるわけではなく、単なるmovです(単なる mov は以前やったとおり、add命令のエイリアスです。)まあ確かにこれで上位32ビットはクリアされるので動作的には問題ないっすけど。

disUXT

 

アセンブル関数を呼び出してテストするCのコード

相変わらずな「とりあえず各関数1例だけ」触ってみるコードが以下に。ぶっちゃけ前回SBFMのときの使い回し。非対称などと言う割には同じコードが使えます。根は同根なのよ。

#include <stdio.h>
#include <stdint.h>

extern uint32_t lslW(uint32_t, uint32_t);
extern uint64_t lslX(uint64_t, uint64_t);
extern uint32_t lsrW(uint32_t, uint32_t);
extern uint64_t lsrX(uint64_t, uint64_t);
extern uint32_t ubfizW(uint32_t, uint32_t);
extern uint64_t ubfizX(uint64_t, uint64_t);
extern uint32_t ubfxW(uint32_t, uint32_t);
extern uint64_t ubfxX(uint64_t, uint64_t);
extern uint32_t uxtbW(uint32_t, uint32_t);
extern uint32_t uxthW(uint32_t, uint32_t);
extern uint64_t uxtwX(uint64_t, uint64_t);

int main(void)
{
    uint32_t result;
    uint64_t resultX;

    result = lslW(0, 0x87654321);
    printf ("lslW: %08x\n", result);
    resultX = lslX(0, 0x87654321A5A5A5A5);
    printf ("lslX %lx\n", resultX);
    result = lsrW(0, 0x87654321);
    printf ("lsrW: %08x\n", result);
    resultX = lsrX(0, 0x87654321A5A5A5A5);
    printf ("lsrX: %lx\n", resultX);

    result = ubfizW(0x0000FFFF, 0xFEDCBA98);
    printf ("ubfizW: %08x\n", result);
    resultX = ubfizX(0x0000FFFF, 0xFEDCBA98);
    printf ("ubfizX: %lx\n", resultX);
    result = ubfxW(0x0000FFFF, 0x89ABCDEF);
    printf ("ubfxW: %08x\n", result);
    resultX = ubfxX(0x0000FFFF, 0xFEDCBA98);
    printf ("ubfxX: %lx\n", resultX);

    result = uxtbW(0, 0x89);
    printf ("uxtbW: %08x\n", result);
    result = uxthW(0, 0x8912);
    printf ("ubthW: %08x\n", result);
    resultX = uxtwX(0, 0xABCD1234);
    printf ("ubttwX: %lx\n", resultX);

    return 0;
}
ビルドして実行

数回前からツールチェーンを gcc に変えてますので以下でビルド

$ gcc -g -O0 -o ubfm ubfm.c ubfm.s

実行結果が以下に。Result

最後のやつ ubttwXじゃなく ubtwXとすべきじゃないか。タイポです。すみません。どうせマニュアル上は「無い」命令なのだけれども。。。

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

ぐだぐだ低レベルプログラミング(77)ARM64(AArch64)、EXTR、RORでもある へ進む