ぐだぐだ低レベルプログラミング(150)ARM64(AArach64)SIMD 拡張系

Joseph Halfmoon

今回練習するのはSIMDの符号拡張、ゼロ拡張命令です。ソースの整数要素のビット幅を符号拡張またはゼロ拡張により倍幅にするもの。勝手に「拡張系」とグループ化。しかしてその実体は、だいぶ以前に練習済のSIMDのシフト命令です。「拡張系」はシフト命令のエイリアス、幻ってことかい?拡張するときはこっちの方が分かり易いですけど。

※「ぐだぐだ低レベルプログラミング」投稿順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の符合拡張、ゼロ拡張

以下の4ニーモニックを「拡張系」に勝手分類いたしました。

    • SXTL
    • SXTL2
    • UXTL
    • UXTL2

SXTLとSXTL2が符合拡張、UXTLとUXTLがゼロ拡張であります。どちらもソースがバイトならデスティネーションはハーフワードという具合に要素のビット幅を倍にしてデスティネーションに送ります。末尾に2がついていないSXTLとUXTLはSIMDレジスタのLSB側に詰まっているソース要素を拡張するのに対し、末尾に2がついているSXTL2とUXTL2はSIMDレジスタのMSB側に詰まっているソース要素を拡張する命令となります。

そしてSXTLの実体はSSHLL(SXTL2はSSHLL2)命令、UXTLの実体はUSHLL(UXTL2はUSHLL2)命令です。SIMDのシフト命令については以下の過去回で練習しました。

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

ただし、過去回ではシフト量の指定をするのにレジスタを使うタイプだけ練習していました。今回のエイリアス共はシフト量の指定に即値を使います。シフト量#0とな。シフトしてないじゃん。でもそこにタネがあるわけね。

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

例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。例によってメンドクセーので、ソースはバイト幅エレメント、デスティネーションはハーフワード幅のエレメント、SIMDレジスタは128ビット幅という設定のみで実習をしてみます。

.globl	sxtl8V, sxtl2_8V, uxtl8V, uxtl2_8V  
.text
.balign	4

sxtl8V:
    ld1  {v1.16B}, [x0], #16
    sxtl  v0.8H,  v1.8B
    st1  {v0.8H}, [x0]
    ret

sxtl2_8V:
    ld1  {v1.16B}, [x0], #16
    sxtl2  v0.8H,  v1.16B
    st1  {v0.8H}, [x0]
    ret

uxtl8V:
    ld1  {v1.16B}, [x0], #16
    uxtl  v0.8H,  v1.8B
    st1  {v0.8H}, [x0]
    ret

uxtl2_8V:
    ld1  {v1.16B}, [x0], #16
    uxtl2  v0.8H,  v1.16B
    st1  {v0.8H}, [x0]
    ret

オペランドの指定の仕方が微妙に非対称なので、引っかかるものがあるのですが、この書き方で問題なしっと。

C言語記述のmain関数

上記のアセンブリ言語関数を呼び出すmain関数が以下に。これまたいつもの通りの手抜きです。符合無、符号あり、バイト、ハーフワード関係なくC言語レベルでは全てuint16_t引数に対して操作させてます(どうせアセンブラにはCの変数型など関係ねー。)

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

#define MAXMEM	(16)
uint16_t TargetMEM[MAXMEM];

extern void sxtl8V(uint16_t *);
extern void sxtl2_8V(uint16_t *);
extern void uxtl8V(uint16_t *);
extern void uxtl2_8V(uint16_t *);

void initTGT() {
    TargetMEM[0] =0x0100;
    TargetMEM[1] =0x7F7E;
    TargetMEM[2] =0x8180;
    TargetMEM[3] =0xFFFE;
    TargetMEM[4] =0xFFFD;
    TargetMEM[5] =0x8280;
    TargetMEM[6] =0x7F7D;
    TargetMEM[7] =0x0200;
    TargetMEM[8] =0x0000;
    TargetMEM[9] =0x0000;
    TargetMEM[10]=0x0000;
    TargetMEM[11]=0x0000;
    TargetMEM[12]=0x0000;
    TargetMEM[13]=0x0000;
    TargetMEM[14]=0x0000;
    TargetMEM[15]=0x0000;
}

uint8_t getLow(int idx) {
    if (idx & 0x01) {
        return (TargetMEM[(idx>>1)] >> 8) & 0xFF; 
    } else {
        return TargetMEM[(idx>>1)] & 0xFF;
    }
}

uint8_t getHigh(int idx) {
    if (idx & 0x01) {
        return (TargetMEM[(idx>>1)+4] >> 8) & 0xFF; 
    } else {
        return TargetMEM[(idx>>1)+4] & 0xFF;
    }
}

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

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

int main(void) {
    initTGT();
    sxtl8V(TargetMEM);
    dumpTGTL("sxtl");

    initTGT();
    sxtl2_8V(TargetMEM);
    dumpTGTH("sxtl2");

    initTGT();
    uxtl8V(TargetMEM);
    dumpTGTL("uxtl");

    initTGT();
    uxtl2_8V(TargetMEM);
    dumpTGTH("uxtl2");

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

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

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

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

符号拡張、ゼロ拡張は分かり易くてよき。

ぐだぐだ低レベルプログラミング(149)ARM64(AArach64)SIMD 逆数系 へ戻る

ぐだぐだ低レベルプログラミング(151)ARM64(AArach64)SIMD 抽出系 へ進む