前回、前々回でSIMD要素のビット幅が狭く(narrow)なる、広く(wide/long)なる命令群のうち「加算系」を練習してみました。加算あれば減算あり、とは言え加算命令群と「対称な」減算命令群ならパスしても良いのでは?と思ったら「減算系」には差を取った後、絶対値をとる命令群もあるのです。命令多過ぎA64。
※「ぐだぐだ低レベルプログラミング」投稿順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
減算以外に「差分絶対値」演算があるのだ
加算あれば減算あり、ということで、以下はnarrow加算系と対称なnarrow減算系の命令の表です。
result | to the lower half | to the upper half |
---|---|---|
truncated | SUBHN | SUBHN2 |
rounded | RSUBHN | RSUBHN2 |
幅が広がる方のLongとWideにも加算系と対称な減算系命令が存在します。
sign/unsign | from the lower half | from the upper half |
---|---|---|
signed Long | SSUBL | SSUBL2 |
signed Wide | SSUBW | SSUBW2 |
unsigned Long | USUBL | USUBL2 |
unsigned Wide | USUBW | USUBW2 |
そして加算系には無かった、減算(差分)した後絶対値を求める系統の命令群があるのです。そのうえ結果をそのまま求めるものだけでなく積算する命令まであります。ただしLongのみ。Wideはありません。でもこれで8命令もあるよ。
sign/unsign | from the lower half | from the upper half |
---|---|---|
signed accumulate | SABAL | SABAL2 |
signed | SABDL | SABDL2 |
unsigned accumulate | UABAL | UABAL2 |
unsigned | UABDL | UABDL2 |
実験に使ったアセンブリ言語記述の被テスト関数
加算系と対称な減算系命令は、メンドイのでバッサリです。手抜き。今回は「差分絶対値」の方だけ練習します。また、「2」付の命令はソースレジスタの置き方のみの違いなので、これまた割愛。よって2が付かないニーモニックのみ練習します。それでも符合付/符号無、積算あり/なしの種類があるので合計4つ。
いつものように手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下です。
.globl uabal8V, uabdl8V, sabal8V, sabdl8V, .text .balign 4 uabal8V: ld1 {V0.8H, v1.8H, v2.8H}, [x0] uabal v0.8H, v1.8B, v2.8B st1 {v0.8H}, [x0] ret uabdl8V: ld1 {v0.8H, v1.8H, v2.8H}, [x0] uabdl v0.8H, v1.8B, v2.8B st1 {v0.8H}, [x0] ret sabal8V: ld1 {v0.8H, v1.8H, v2.8H}, [x0] sabal v0.8H, v1.8B, v2.8B st1 {v0.8H}, [x0] ret sabdl8V: ld1 {v0.8H, v1.8H, v2.8H}, [x0] sabdl v0.8H, v1.8B, v2.8B st1 {v0.8H}, [x0] ret
C言語記述のmain関数
上記のアセンブリ言語関数を呼び出すmain関数が以下に。実際のアセンブリ言語命令では符号付き命令でも、Cのレベルでは全てuint16_t型で割り切って書いているもの。
#include <stdio.h> #include <stdint.h> #define MAXMEM (24) uint16_t TargetMEM[MAXMEM]; extern void uabal8V(uint16_t *); extern void uabdl8V(uint16_t *); extern void sabal8V(uint16_t *); extern void sabdl8V(uint16_t *); void initTGT1() { TargetMEM[0] = 0x0100; TargetMEM[1] = 0x0100; TargetMEM[2] = 0x0100; TargetMEM[3] = 0x0100; TargetMEM[4] = 0x0100; TargetMEM[5] = 0x0100; TargetMEM[6] = 0x0100; TargetMEM[7] = 0x0100; TargetMEM[8] = 0x1010; TargetMEM[9] = 0x2020; TargetMEM[10] = 0x8080; TargetMEM[11] = 0x9090; TargetMEM[12] = 0x0000; TargetMEM[13] = 0x0000; TargetMEM[14] = 0x0000; TargetMEM[15] = 0x0000; TargetMEM[16] = 0x1701; TargetMEM[17] = 0x8121; TargetMEM[18] = 0x8701; TargetMEM[19] = 0xFF91; TargetMEM[20] = 0x0000; TargetMEM[21] = 0x0000; TargetMEM[22] = 0x0000; TargetMEM[23] = 0x0000; } uint16_t getLow(int idx, int pos) { if (idx & 0x01) { return (TargetMEM[(idx>>1)+pos] >> 8) & 0xFF; } else { return TargetMEM[(idx>>1)+pos] & 0xFF; } } void dumpTGTL3(const char *arg) { printf("%s\n", arg); for (int i=0; i < 8; i++) { printf("%02d: 0x%02x opr 0x%02x -> 0x%04x\n", i, getLow(i, 8), getLow(i, 16), TargetMEM[i]); } } int main(void) { initTGT1(); uabdl8V(TargetMEM); dumpTGTL3("uabdl"); initTGT1(); uabal8V(TargetMEM); dumpTGTL3("uabal"); initTGT1(); sabdl8V(TargetMEM); dumpTGTL3("sabdl"); initTGT1(); sabal8V(TargetMEM); dumpTGTL3("sabal"); return 0; }
実機実行結果の確認
以下のようにしてビルドして実行しています。
$ gcc -g -O0 simduabal.c simduabal.s $ ./a.out
実行結果が以下に。符合付、符号無で差がでるところに赤線引いてあります。また、積算あり、なしでは、ありの方に積算初期値0x0100が加算されているので、上下の差は明らかかと。
どんだけ命令あるんだA64.