今回は「表引き」命令群です。「群」といっても2命令だけ。SIMD(ベクトル)ソースレジスタのバイト要素をインデックスとして、複数のSIMDレジスタを「バイト・テーブル」としてアクセスし、引いた結果をデスティネーションのSIMDレジスタに書き込むという命令です。1度に最大16個の表引きをできるもの。単純だけれど強力?
※「ぐだぐだ低レベルプログラミング」投稿順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
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
※A64の最新のマニュアルは以下でダウンロード可能です。
Arm Architecture Reference Manual for A-profile architecture
SIMD表引き命令群
表引き命令の動作「1要素分」を図にすると以下のようです。以下ではソースの最下位要素に0x11というインデックスが書き込まれていた場合に、4個のベクトルレジスタで構成されたテーブルを引き、そこで見つかった0xFAという値をデスティネーションの最下位要素に書き込んでいます。
SIMDなので、同時にソースの全16要素に対して同様な操作が行われます。単純だけれどもメンドイ表引きを一気に出来るので強力っす。
以下の2命令があります。
-
- TBL
- TBX
その違いはインデックス値がテーブルの中を指さない場合の動作です。TBL命令の場合、インデックスがテーブルの中に無かった場合、結果0を返しますが、TBX命令の場合はデスティネーションの元の値が維持されます。TBXを使えば、複数のテーブルレジスタを切り替えながら表引きすることも可能なハズ。
実験につかったアセンブリ言語記述の被テスト関数
例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。以下ではSIMD(ベクトル)レジスタ幅は128ビット、テーブルレジスタは2本という設定です。
.globl tbl32V, tbx32V .text .balign 4 tbl32V: ld1 {v0.16B, v1.16B, v2.16B, v3.16B}, [x0] tbl v0.16B, {v1.16B, v2.16B}, v3.16B st1 {v0.4S}, [x0] ret tbx32V: ld1 {v0.16B, v1.16B, v2.16B, v3.16B}, [x0] tbx v0.16B, {v1.16B, v2.16B}, v3.16B st1 {v0.4S}, [x0] ret
C言語記述のmain関数
上記のアセンブリ言語関数を呼び出すmain関数が以下に。符号無バイト操作の命令どもですが、バイトを並べると書くのがメンドイという理由だけで、C言語上は符号無32ビットで記述してます。
#include <stdio.h> #include <stdint.h> #include <math.h> #define MAXMEM (16) uint32_t TargetMEM[MAXMEM]; extern void tbl32V(uint32_t *); extern void tbx32V(uint32_t *); void initTGT() { TargetMEM[0] = 0xFFFFFFFF; TargetMEM[1] = 0xFFFFFFFF; TargetMEM[2] = 0xFFFFFFFF; TargetMEM[3] = 0xFFFFFFFF; TargetMEM[4] = 0x87654321; TargetMEM[5] = 0xFEDCBA87; TargetMEM[6] = 0xA5A5A5A5; TargetMEM[7] = 0x5A5A5A5A; TargetMEM[8] = 0x11111111; TargetMEM[9] = 0x22222222; TargetMEM[10] = 0x33333333; TargetMEM[11] = 0x44444444; TargetMEM[12] = 0x01000001; TargetMEM[13] = 0x04050607; TargetMEM[14] = 0x1C181410; TargetMEM[15] = 0x1F022021; } void dumpTGT(const char *arg) { printf("%s\n", arg); for (int i=0; i<4; i++) { printf("%02d: %08x \n", i, TargetMEM[i]); } } int main(void) { initTGT(); tbl32V(TargetMEM); dumpTGT("tbl"); initTGT(); tbx32V(TargetMEM); dumpTGT("tbx"); return 0; }
実機実行結果の確認
以下のようにビルドして実行しています。
$ gcc -g -O0 simdTBL.c simdTBL.s $ ./a.out
標準出力に現れた結果が以下に。赤枠の2バイトは、tbl命令においてインデックスがテーブル内を指していないところで、結果は0。黄色枠の2バイトは同じ「ハズレ」インデックスのtbx命令での結果です。0xffは元の値です。
ちゃんと表引きできているみたい。あたりまえか。