
SIMDの「整数変換系」まだあると思ったらFRINT32X一族はARMv8.0には存在せず。ラッキー?前回で整数変換系の練習は終わりであります。そこで次の単元?に入ったですが、今度はSIMDのMUL系、まだ練習してないことに気づきました。もっとムツカシー奴らは練習していたのにシンプルなMULやってなかったのね。。。
※「ぐだぐだ低レベルプログラミング」投稿順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の「整数変換系」最後に以下の奴らをやっつけたいとおもったら、ARMv8p0(ARMv8.0)には不在。以下が使えるのはARMv8.5以降じゃということであります。
-
- FRINT32X
- FRINT32Z
- FRINT64X
- FRINT64Z
そこで今回からSIMDのelement arithmeticに進むことになりました。
element arithmetic
今回 SIMDのMUL命令を練習いたします。今まではこんな感じでした。
MUL v0.4S, v1.4S, v2.4S
上記は、v1レジスタに入っている4つの32ビット整数とv2レジスタに入っている4つの32ビット整数をそれぞれ「レーン毎」に掛け合わせ、その結果をv0レジスタへ格納する場合の命令です。4つの要素が同時に求まりますが、それぞれの要素(レーン)は独立してました。しかし、今回から練習するelement arithmetic系の命令どもは一味違います。
MUL v0.4S, v1.4S, v2.4S[1]
上記ではv1レジスタに入っている4つの32ビット整数にv2レジスタの「第1要素」の32ビット整数を掛け合わせ、その結果をv0レジスタへ格納します。4個の要素があるけれど、第2ソースは一つの値です。[ ]の中にインデックス番号を書いておけばその要素が使われます。上記の場合4Sなので0から3のどれかね。
実際、SIMDで演算を行っていると同じ値をSIMD要素全てに作用させたい、ということはよくあると思います。element arithmetic頻出ね。
MUL、MLA、MLS
その1番手として練習するのは以下の3つです。
-
- MUL、第1ソースと第2ソースを乗じた結果をデスティネーションへ
- MLA、第1ソースと第2ソースを乗じた結果をデスティネーションと足し合わせたものをデスティネーションへ
- MLS、第1ソースと第2ソースを乗じた結果をデスティネーションから引いたものをデスティネーションへ
3種類とも、フツーのSIMD処理(レーン独立)と、element arithmeticの両方を練習していきたいと思います。
実験につかったアセンブリ言語記述の被テスト関数
例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。オペランドはハーフワード(16ビット)とワード(32ビット)がとれるのですが、手抜きなのでワードだけ練習してます。バイトやダブルワードが無いのは「大人の事情」(多分積和演算器の事情?)じゃないかと。知らんけど。
.globl mul4V, mul4V1, mla4V, mla4V1, mls4V, mls4V1
.text
.balign 4
mul4V:
ld1 {V0.4S, v1.4S, v2.4S}, [x0]
mul v0.4S, v1.4S, v2.4S
st1 {v0.4S}, [x0]
ret
mul4V1:
ld1 {V0.4S, v1.4S, v2.4S}, [x0]
mul v0.4S, v1.4S, v2.4S[1]
st1 {v0.4S}, [x0]
ret
mla4V:
ld1 {V0.4S, v1.4S, v2.4S}, [x0]
mla v0.4S, v1.4S, v2.4S
st1 {v0.4S}, [x0]
ret
mla4V1:
ld1 {V0.4S, v1.4S, v2.4S}, [x0]
mla v0.4S, v1.4S, v2.4S[1]
st1 {v0.4S}, [x0]
ret
mls4V:
ld1 {V0.4S, v1.4S, v2.4S}, [x0]
mls v0.4S, v1.4S, v2.4S
st1 {v0.4S}, [x0]
ret
mls4V1:
ld1 {V0.4S, v1.4S, v2.4S}, [x0]
mls v0.4S, v1.4S, v2.4S[1]
st1 {v0.4S}, [x0]
ret
C言語記述のmain関数
上記のアセンブリ言語関数を呼び出すmain関数が以下に。
#include <stdio.h>
#include <stdint.h>
#define MAXMEM (12)
uint32_t TargetMEM[MAXMEM];
extern void mul4V(uint32_t *);
extern void mul4V1(uint32_t *);
extern void mla4V(uint32_t *);
extern void mla4V1(uint32_t *);
extern void mls4V(uint32_t *);
extern void mls4V1(uint32_t *);
void initTGT() {
TargetMEM[0] = 0x00060000;
TargetMEM[1] = 0x00060000;
TargetMEM[2] = 0x00060000;
TargetMEM[3] = 0x00060000;
TargetMEM[4] = 0x00010001;
TargetMEM[5] = 0x00010002;
TargetMEM[6] = 0x00010003;
TargetMEM[7] = 0x00010004;
TargetMEM[8] = 0x00000002;
TargetMEM[9] = 0x00000003;
TargetMEM[10] = 0x00000004;
TargetMEM[11] = 0x00000005;
}
void dumpTGT(const char *arg) {
printf("%s\n", arg);
for (int i=0; i < 4; i++) {
printf("%02d: 0x%08x opr 0x%08x -> 0x%08x\n", i, TargetMEM[i+4], TargetMEM[i+8], TargetMEM[i]);
}
}
void dumpTGT1(const char *arg, const int idx) {
printf("%s\n", arg);
for (int i=0; i < 4; i++) {
printf("%02d: 0x%08x opr 0x%08x -> 0x%08x\n", i, TargetMEM[i+4], TargetMEM[idx+8], TargetMEM[i]);
}
}
int main(void) {
initTGT();
mul4V(TargetMEM);
dumpTGT("mul");
initTGT();
mul4V1(TargetMEM);
dumpTGT1("mul [1]", 1);
initTGT();
mla4V(TargetMEM);
dumpTGT("mla");
initTGT();
mla4V1(TargetMEM);
dumpTGT1("mla [1]", 1);
initTGT();
mls4V(TargetMEM);
dumpTGT("mls");
initTGT();
mls4V1(TargetMEM);
dumpTGT1("mls [1]", 1);
return 0;
}
実機実行結果の確認
以下のようにしてビルドして実行しています。
$ gcc -g -O0 simdMUL.c simdMUL.s $ ./a.out
結果が以下に。
予定通りの結果のようだね。あたりまえか。
