ぐだぐだ低レベルプログラミング(156)ARM64(AArach64)SIMD MUL

Joseph Halfmoon

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

結果が以下に。

MULresults

予定通りの結果のようだね。あたりまえか。

ぐだぐだ低レベルプログラミング(155)ARM64(AArach64)SIMD FCVTxy に戻る

ぐだぐだ低レベルプログラミング(157)ARM64(AArach64)SIMD 要素毎MUL へ進む