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

Joseph Halfmoon

毎度ですがA64の命令多すぎ。今回練習するのはSIMDのシフト命令です。符合付/符号無、サチュレーションの有無、丸めの有無で2の3乗、合計8種のニーモニックが存在します。そしてニーモニック上はLEFTと読めるので左シフトだけかと思えば「負の左シフトは右シフト」ということで右シフトも出来。でもこれだけじゃなかったんだ。

※「ぐだぐだ低レベルプログラミング」投稿順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のシフト演算

前回はサチュレーション演算の練習とて、加減算についてサチュレーションの有無を含めて合計6個のニーモニックを演習してみました。これだって少なくはないですが、今回のシフト演算はニーモニック8個です。内訳はこんな感じ。

操作 符号付 符号無
サチュレーション左シフト SQSHL UQSHL
サチュレーション左シフト丸め SQRSHL UQRSHL
左シフト SSHL USHL
左シフト丸め SRSHL URSHL

ニーモニック的には左シフトばかりに見えますが、上記命令のシフト回数はソース第2オペランドのレジスタで指定です。先ほど書いたとおり「負の左シフトは右シフト」というロジックなんであります。引数によっては右シフトにもなります。ややこしい?

なお、ソース第1オペランドの内容を、ソース第2オペランドで指定するビット数シフトしてデスティネーションに書き込む形です。

SIMDレジスタ的にはバイト、ハーフワード、ワード、ダブルワードのSIMD要素幅と、レジスタ幅64ビット/128ビットの選択が可能なので計8種類の「幅」に対応してます。

命令セットは上記の部分は「対称で綺麗」です。符合付か符号無か、サチューレーションするかしないか、丸めるか丸めないか。しかし「騙されて」はいけませぬぞ。この部分が綺麗な対称であるだけで、まだシフト系の命令は沢山隠れているのであります。引数がレジスタ指定でないものが。まあ今回は練習しないので流しますが。。。

実験につかったアセンブリ言語記述の被テスト関数

例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。例によってメンドクセーので、バイト幅エレメントの半幅レジスタのみで実習をしてみます。

.globl	sqshl8V, uqshl8V, sqrshl8V, uqrshl8V, sshl8V, ushl8V, srshl8V, urshl8V 
.text
.balign	4

sqshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    sqshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

uqshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    uqshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

sqrshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    sqrshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

uqrshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    uqrshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

sshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    sshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

ushl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    ushl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

srshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    srshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret

urshl8V:
    ld1  {v1.8B, v2.8B}, [x0], #16
    urshl  v0.8B,  v1.8B, v2.8B
    st1  {v0.8B}, [x0]
    ret
C言語記述のmain関数

上記のアセンブリ言語関数を呼び出すmain関数が以下に。これまたいつもの通りの手抜きで前回ソースのチョイ変デス。再度お断りすると sが頭についているニーモニックは符合付なのですが、C言語レベルでは全てuint8_t引数に対して操作させてます(どうせアセンブラにはCのレベルなど関係ねー。)

#include <stdio.h>
#include <stdint.h>

#define MAXMEM	(24)
uint8_t TargetMEM[MAXMEM];

extern void sqshl8V(uint8_t *);
extern void uqshl8V(uint8_t *);
extern void sqrshl8V(uint8_t *);
extern void uqrshl8V(uint8_t *);
extern void sshl8V(uint8_t *);
extern void ushl8V(uint8_t *);
extern void srshl8V(uint8_t *);
extern void urshl8V(uint8_t *);

void initTGT() {
    TargetMEM[0] =0x7F;
    TargetMEM[1] =0xFF;
    TargetMEM[2] =0x02;
    TargetMEM[3] =0x01;
    TargetMEM[4] =0x7F;
    TargetMEM[5] =0xFF;
    TargetMEM[6] =0x02;
    TargetMEM[7] =0x01;
    TargetMEM[8] =0x01;
    TargetMEM[9] =0x01;
    TargetMEM[10]=0x01;
    TargetMEM[11]=0x01;
    TargetMEM[12]=0xFF;
    TargetMEM[13]=0xFF;
    TargetMEM[14]=0xFF;
    TargetMEM[15]=0xFF;
}

void dumpTGT(const char *arg) {
    printf("%s\n", arg);
    for (int i=0; i < 8; i++) {
        printf("%02d: 0x%02x opr 0x%02x -> 0x%02x \n", i, TargetMEM[i], TargetMEM[i+8], TargetMEM[i+16]);
    }
}

int main(void) {
    initTGT();

    sqshl8V(TargetMEM);
    dumpTGT("sqshl");
    uqshl8V(TargetMEM);
    dumpTGT("uqshl");

    sqrshl8V(TargetMEM);
    dumpTGT("sqrshl");
    uqrshl8V(TargetMEM);
    dumpTGT("uqrshl");

    sshl8V(TargetMEM);
    dumpTGT("sshl");
    ushl8V(TargetMEM);
    dumpTGT("ushl");

    srshl8V(TargetMEM);
    dumpTGT("srshl");
    urshl8V(TargetMEM);
    dumpTGT("urshl");

    return 0;
}
実機実行結果の確認

以下のようにしてビルドして実行しています。

$ gcc -g -O0 sqshl.c sqshl.s
$ ./a.out

標準出力に「ダラダラ」現れた結果を、上下左右比べ易いように折りたたんだものが以下に。sqrshl_table

上下を見比べると符号付き、符号無の挙動の差が、左右4列を見比べると、サチュレーションの有無、丸めの有無の挙動の差が見えるかと。こうして見比べればその差は明らかだけれど、そうじゃなければ見逃してる?

ぐだぐだ低レベルプログラミング(137)ARM64(AArach64)SIMD sqadd へ戻る

ぐだぐだ低レベルプログラミング(139)ARM64(AArach64)SIMDsqdmulh へ進む