ぐだぐだ低レベルプログラミング(169)ARM64(AArach64)SIMD即値シフト5

Joseph Halfmoon

今回も対称性(直交性)は破れているの回です。前回のアキュムレート付きシフトは全て右シフトでした。今回から2回にわけて練習する予定のロング化(デスティネーションがソースのビット幅の倍のビット幅になる)シフトは全て左シフトです。当たり前っちゃ当たり前だけれども。ただロング化一族の中でも微妙に凸凹あり。

※「ぐだぐだ低レベルプログラミング」投稿順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即値シフト・ロング化一族

ソースのビット幅の倍幅のデスティネーションになる「ロング」化をする命令を抜き出したものが以下です。SIMD_shift_imm_long

Lng=Longのつもりデス。そしてLft=leftです。先に述べたとおり全て左シフトです。今回は上記のうち上3行の合計6命令を練習してみます。下の2行はExtend付きなのでまた次回。

Long化一族の場合、ソースが半分のビット幅です。そのため、同じシフト操作でも、ソースをSIMDレジスタの下から採ってくるか、上から採ってくるかの2通りあります。「無印」は下から、ニーモニック末尾に「2」がついているのは上からです。

今回練習する合計6命令ですが、符号付、符号無の区別を下2行の4命令でカバーしています。一番上のSHLLとかSHLL2って何よ?というと微妙に即値シフト量が異なっていたりします。SIMD_shift_imm5_table

下の2行の系統はフツーの1ビット単位のシフトです。それに対して上のSHLLとSHLL2は、

ソースの内容を倍幅のレジスタの上側にはめ込む(下はクリア)

操作しかできません。こういう「至れり尽くせり、命令数が多くなってもOKよ」な感じがA64です。RISC-Vとは対極の行き方。

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

例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。今回はLONGなので、SIMD要素は、バイトからハーフワード、ハーフワードからワード、ワードからダブルワードという3通りをとれます。いつもの手抜きでハーフワード(16ビット)からワード(32ビット)のみ練習してます。

.globl	shll4V, shll24V, sshll4V, sshll24V, ushll4V, ushll24V  
.text
.balign	4

shll4V:
    ld1  {v0.4S, v1.4S}, [x0]
    shll  v0.4S, v1.4H, #16
    st1  {v0.4S}, [x0]
    ret

shll24V:
    ld1  {v0.4S, v1.4S}, [x0]
    shll2  v0.4S, v1.8H, #16
    st1  {v0.4S}, [x0]
    ret

sshll4V:
    ld1  {v0.4S, v1.4S}, [x0]
    sshll  v0.4S, v1.4H, #4
    st1  {v0.4S}, [x0]
    ret

sshll24V:
    ld1  {v0.4S, v1.4S}, [x0]
    sshll2  v0.4S, v1.8H, #4
    st1  {v0.4S}, [x0]
    ret

ushll4V:
    ld1  {v0.4S, v1.4S}, [x0]
    ushll  v0.4S, v1.4H, #4
    st1  {v0.4S}, [x0]
    ret

ushll24V:
    ld1  {v0.4S, v1.4S}, [x0]
    ushll2  v0.4S, v1.8H, #4
    st1  {v0.4S}, [x0]
    ret

なお、例によってshll2のような「2」付の命令の引数の記法はちょっとクセ強です。

C言語記述のmain関数

上記のアセンブリ言語関数を呼び出すmain関数が以下に。命令により符号付、符号無と扱うデータは異なりますが、例のごとくC言語レベルでは皆 uint32_t で宣言してます。手抜きだよ。

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

#define MAXMEM	(8)
uint32_t TargetMEM[MAXMEM];

extern void shll4V(uint32_t *);
extern void shll24V(uint32_t *);
extern void sshll4V(uint32_t *);
extern void sshll24V(uint32_t *);
extern void ushll4V(uint32_t *);
extern void ushll24V(uint32_t *);

void initTGT() {
    TargetMEM[0]   = 0x00000000;
    TargetMEM[1]   = 0x00000000;
    TargetMEM[2]   = 0x00000000;
    TargetMEM[3]   = 0x00000000;
    TargetMEM[4]   = 0x12345678;
    TargetMEM[5]   = 0x9ABCDEF0;
    TargetMEM[6]   = 0x11112222;
    TargetMEM[7]   = 0xFFFFEEEE;
}

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

int main(void) {
    initTGT();
    shll4V(TargetMEM);
    dumpTGT("shll v0.4S, V1.4H, #16");

    initTGT();
    shll24V(TargetMEM);
    dumpTGT("shll2 v0.4S, V1.8H, #16");

    initTGT();
    sshll4V(TargetMEM);
    dumpTGT("sshll v0.4S, V1.4H, #4");

    initTGT();
    sshll24V(TargetMEM);
    dumpTGT("sshll2 v0.4S, V1.8H, #4");

    initTGT();
    ushll4V(TargetMEM);
    dumpTGT("ushll v0.4S, V1.4H, #4");

    initTGT();
    ushll24V(TargetMEM);
    dumpTGT("ushll2 v0.4S, V1.8H, #4");

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

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

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

実行結果が以下に。SIMD_shift_imm5_Results

即値シフトLongの練習、意外と淡々と実施できたな。

ぐだぐだ低レベルプログラミング(168)ARM64(AArach64)SIMD即値シフト4 へ戻る

ぐだぐだ低レベルプログラミング(170)ARM64(AArach64)SIMD即値シフト6 へ進む