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

Joseph Halfmoon

SIMDの即値シフト命令の練習3回目です。前回は「一番ちょろい」命令3つばかりを練習してお茶を濁しました。今回は「ちょっと複雑な」命令に入っていきたいと思います。ただし、ナローとかロングとかは無。インサートとアキュムレートも無。それでも飽和と丸めが有り。その上、符号付だか符号無だかハッキリしろいと言いたくなる奴あり。

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

今回練習の命令5つ

前々回作成の表から今回練習部分を抜き出しました。SFTimm3INSN

5命令あり、大分「X」印が増えて(該当して)います。ざっくり言うと、

    • 左シフトの奴らは皆「Q付」ニーモニックでサチュレーション(飽和)
    • 右シフトの奴らは皆「R付」ニーモニックでラウンディング(丸め)

ということになります。上記それぞれにデータを「符号付」とみるか「符号無」とみるかの命令があるので「S」と「U」で合計4命令っと。

しかし1命令「符号付」なんだか「符号無」なんだかハッキリしろい、と言いたくなる命令があるのです。SQSHLU命令です。Sで始まっている「ソースのデータを符号付き」として解釈する命令のハズですが、末尾にU「符号無」が指定されておる、という命令です。勝手な事を書かせてもらうと

    1. 符号付データを即値シフトする(符号は保存)
    2. シフトした結果を飽和させるときには符号無の範囲に飽和させる

という釈然としない命令です。つまり符号付きとしてシフトしておくのだけれども、最後の最後で結果は符号無(シフト結果がマイナスだったときには結果はゼロに飽和、シフト結果がプラスでもオーバーフローがあればオール1に飽和)ってことですかい。知らんけど。

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

例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。SIMD要素の幅は、バイト、ハーフワード、ワード、ダブルワードをとれるのですが、これまた手抜きでワード(32ビット)のみ練習してます。シフト量は16ビットきめうち、HEX表記したときに見やすいからね。。。

.globl	sqshl4V, uqshl4V, sqshlu4V, srshr4V, urshr4V  
.text
.balign	4

sqshl4V:
    ld1  {v0.4S, v1.4S}, [x0]
    sqshl  v0.4S, v1.4S, #16
    st1  {v0.4S}, [x0]
    ret

uqshl4V:
    ld1  {v0.4S, v1.4S}, [x0]
    uqshl  v0.4S, v1.4S, #16
    st1  {v0.4S}, [x0]
    ret

sqshlu4V:
    ld1  {v0.4S, v1.4S}, [x0]
    sqshlu  v0.4S, v1.4S, #16
    st1  {v0.4S}, [x0]
    ret

srshr4V:
    ld1  {v0.4S, v1.4S}, [x0]
    srshr  v0.4S, v1.4S, #16
    st1  {v0.4S}, [x0]
    ret

urshr4V:
    ld1  {v0.4S, v1.4S}, [x0]
    urshr  v0.4S, v1.4S, #16
    st1  {v0.4S}, [x0]
    ret
C言語記述のmain関数

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

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

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

extern void sqshl4V(uint32_t *);
extern void uqshl4V(uint32_t *);
extern void sqshlu4V(uint32_t *);
extern void srshr4V(uint32_t *);
extern void urshr4V(uint32_t *);

void initTGT() {
    TargetMEM[0]   = 0x0;
    TargetMEM[1]   = 0x0;
    TargetMEM[2]   = 0x0;
    TargetMEM[3]   = 0x0;
    TargetMEM[4]   = 0x00004321;
    TargetMEM[5]   = 0x87654321;
    TargetMEM[6]   = 0x5A5A5A5A;
    TargetMEM[7]   = 0xFFFFFFFF;
}

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();
    sqshl4V(TargetMEM);
    dumpTGT("sqshl v0.4S, V1.4S, #16");

    initTGT();
    uqshl4V(TargetMEM);
    dumpTGT("uqshl v0.4S, V1.4S, #16");

    initTGT();
    sqshlu4V(TargetMEM);
    dumpTGT("sqshlu v0.4S, V1.4S, #16");

    initTGT();
    srshr4V(TargetMEM);
    dumpTGT("srshr v0.4S, V1.4S, #16");

    initTGT();
    urshr4V(TargetMEM);
    dumpTGT("urshr v0.4S, V1.4S, #16");

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

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

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

実行結果が以下に。SFTimm3Results

左シフトの飽和の様子と、右シフトの丸めの様子、興味深いっす。とくに末尾のurshrの3番目、右シフトの結果は0xFFFFだけれども、その直下にビット「1」があるので「丸め」発動の結果0x10000となっておると。手順を追って考えたらどれも当たり前だけれども、パッと見「あれって」感じす。

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

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