前々回から条件フラグNZCV関連の命令に入っておりますが、非常に充実してます。これは32ビット時代のArmがほぼ全ての命令に条件判断を付与できるという過激なあり方だったのを64ビット化するときに廃止してしまった「罪滅ぼし」なんじゃないかと密かに思います。知らんけど。今回は条件付き選択命令。多すぎて1回では終わらず。
※「ぐだぐだ低レベルプログラミング」投稿順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
条件選択命令
条件選択命令は、Cの3項演算子に似ています。条件が真のときと偽のときで結果として返る値を選択するもの。A64の命令セットを見るならば全部で9個もの命令が存在してます。その全てにデータ幅32ビットか64ビットかの指定ができるので合計は18個とな。
しかし、例によって9個のうちの5個はエイリアスです。実体は4個。9個を一度にやるのは年寄りには辛いので、今回は「実体」命令4個を先にやっつけてしまいます。
-
- CSEL命令、条件が一致すればソース1を、不一致ならばソース2をデスティネーションに代入
- CSINC命令、条件が一致すればソース1を、不一致ならばソース2を「インクリメント」してデスティネーションに代入
- CSINV命令、条件が一致すればソース1を、不一致ならばソース2を「反転」してデスティネーションに代入
- CSNEG命令、条件が一致すればソース1を、不一致ならばソース2を「ネゲート(2の補数)」にしてデスティネーションに代入
実験に使用したアセンブラ・ソース
今回は全関数、2段構えです。
-
- 最初のCMP命令で「条件」フラグを設定する
- 次のターゲット命令、CSELなどで上記条件により選択する。
条件をいろいろ変えているとメンドイので、今回は全て EQ としてます。ゼロフラグのみです。
4命令ですが、32ビット(Wレジスタ)、64ビット(Xレジスタ)の種別があるので合計8関数を定義しています。
.globl cselW, cselX, csincW, csincX, csinvW, csinvX, csnegW, csnegX .text .balign 4 cselW: cmp w0, w1 csel w0, w2, w3, EQ ret cselX: cmp x0, x1 csel x0, x2, x3, EQ ret csincW: cmp w0, w1 csinc w0, w2, w3, EQ ret csincX: cmp x0, x1 csinc x0, x2, x3, EQ ret csinvW: cmp w0, w1 csinv w0, w2, w3, EQ ret csinvX: cmp x0, x1 csinv x0, x2, x3, EQ ret csnegW: cmp w0, w1 csneg w0, w2, w3, EQ ret csnegX: cmp x0, x1 csneg x0, x2, x3, EQ ret
実験に使用したC言語ソース
本来は符号有の方が用途にあっている気もするのですが、エイヤーで全アセンブラ関数の引数、戻り値とも符号無でインタフェースとることにいたしました。
全関数とも2段構えです。
-
- 第1、第2引数を比較して条件を作る(EQしか見てないので一致すれば条件真、不一致ならば条件偽)
- 上記の結果で真なら第3引数、偽なら第4引数(を加工した値)が返る
テスト・ケースは手抜き、以下のケースのみです。
-
- (1, 0, 2, 3) 1と0を比較してEQでないので3または3を加工した値が返る
- (1, 1, 2, 3) 1と1を比較してEQなので2が返る
#include <stdio.h> #include <stdint.h> extern uint32_t cselW(uint32_t, uint32_t, uint32_t, uint32_t); extern uint64_t cselX(uint64_t, uint64_t, uint64_t, uint64_t); extern uint32_t csincW(uint32_t, uint32_t, uint32_t, uint32_t); extern uint64_t csincX(uint64_t, uint64_t, uint64_t, uint64_t); extern uint32_t csinvW(uint32_t, uint32_t, uint32_t, uint32_t); extern uint64_t csinvX(uint64_t, uint64_t, uint64_t, uint64_t); extern uint32_t csnegW(uint32_t, uint32_t, uint32_t, uint32_t); extern uint64_t csnegX(uint64_t, uint64_t, uint64_t, uint64_t); int main(void) { uint32_t uresult; uint64_t uresultX; uresult = cselW(1, 0, 2, 3); printf ("cselW(1, 0, 2, 3): %d\n", uresult); uresult = cselW(1, 1, 2, 3); printf ("cselW(1, 1, 2, 3): %d\n", uresult); uresultX = cselX(1, 0, 2, 3); printf ("cselX(1, 0, 2, 3): %ld\n", uresultX); uresultX = cselX(1, 1, 2, 3); printf ("cselX(1, 1, 2, 3): %ld\n", uresultX); uresult = csincW(1, 0, 2, 3); printf ("csincW(1, 0, 2, 3): %d\n", uresult); uresult = csincW(1, 1, 2, 3); printf ("csincW(1, 1, 2, 3): %d\n", uresult); uresultX = csincX(1, 0, 2, 3); printf ("csincX(1, 0, 2, 3): %ld\n", uresultX); uresultX = csincX(1, 1, 2, 3); printf ("csincX(1, 1, 2, 3): %ld\n", uresultX); uresult = csinvW(1, 0, 2, 3); printf ("csinvW(1, 0, 2, 3): %08x\n", uresult); uresult = csinvW(1, 1, 2, 3); printf ("csinvW(1, 1, 2, 3): %08x\n", uresult); uresultX = csinvX(1, 0, 2, 3); printf ("csinvX(1, 0, 2, 3): %016lx\n", uresultX); uresultX = csinvX(1, 1, 2, 3); printf ("csinvX(1, 1, 2, 3): %016lx\n", uresultX); uresult = csnegW(1, 0, 2, 3); printf ("csnegW(1, 0, 2, 3): %08x\n", uresult); uresult = csnegW(1, 1, 2, 3); printf ("csnegW(1, 1, 2, 3): %08x\n", uresult); uresultX = csnegX(1, 0, 2, 3); printf ("csnegX(1, 0, 2, 3): %016lx\n", uresultX); uresultX = csnegX(1, 1, 2, 3); printf ("csnegX(1, 1, 2, 3): %016lx\n", uresultX); return 0; }
実行確認
ビルドのコマンドラインは以下に。
gcc -g -O0 -o csel csel.c csel.s
実行結果が以下に。
所望の結果が返ってきております。当たり前か。