前回は符号付きのビットフィールド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(符号無ビットフィールド転送)命令を以下の表中に赤枠で示しました。
前回の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命令が実は同じ機械語に落ちていることが、以下のディスアセンブル・リストを見ると分かります。なお、ディスアセンブラはエイリアスがあればエイリアス優先で表現してくれます。
続いて、ゼロ詰めのビットフィールド挿入、ビットフィールド抽出命令です。とっつき悪い奴らですが、意外にもここは符号付きの挿入、抽出と対称にできているので、そのつもりで前回と比べて見ればお楽。
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
ディスアセンブル・リストが以下に。元命令とエイリアスが同じ機械語に落ちることが分かりますが、どっちもどっち、メンドイ奴らです。
最後は符号無の拡張命令です。先に説明したとおり、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ビットはクリアされるので動作的には問題ないっすけど。
アセンブル関数を呼び出してテストする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
最後のやつ ubttwXじゃなく ubtwXとすべきじゃないか。タイポです。すみません。どうせマニュアル上は「無い」命令なのだけれども。。。