今回も対称性(直交性)は破れているの回です。前回のアキュムレート付きシフトは全て右シフトでした。今回から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の練習、意外と淡々と実施できたな。