
今回も対称性(直交性)は破れているの回です。前回のアキュムレート付きシフトは全て右シフトでした。今回から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即値シフト・ロング化一族
ソースのビット幅の倍幅のデスティネーションになる「ロング」化をする命令を抜き出したものが以下です。
Lng=Longのつもりデス。そしてLft=leftです。先に述べたとおり全て左シフトです。今回は上記のうち上3行の合計6命令を練習してみます。下の2行はExtend付きなのでまた次回。
Long化一族の場合、ソースが半分のビット幅です。そのため、同じシフト操作でも、ソースをSIMDレジスタの下から採ってくるか、上から採ってくるかの2通りあります。「無印」は下から、ニーモニック末尾に「2」がついているのは上からです。
今回練習する合計6命令ですが、符号付、符号無の区別を下2行の4命令でカバーしています。一番上のSHLLとかSHLL2って何よ?というと微妙に即値シフト量が異なっていたりします。
下の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
即値シフトLongの練習、意外と淡々と実施できたな。
