ぐだぐだ低レベルプログラミング(144)ARM64(AArach64)SIMD ビット幅変3

Joseph Halfmoon

前回前々回で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が加算されているので、上下の差は明らかかと。uabalResults

どんだけ命令あるんだA64.

ぐだぐだ低レベルプログラミング(143)ARM64(AArach64)SIMD ビット幅変2 へ戻る

ぐだぐだ低レベルプログラミング(145)ARM64(AArach64)SIMD ビット幅変4 へ進む