
今回は「表引き」命令群です。「群」といっても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は元の値です。
ちゃんと表引きできているみたい。あたりまえか。