store命令 -scatterを添えて-


# はじめに
 - この記事は[ひとりNEONアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/neon-only) 10日目の記事です
 - [昨日](https://qiita.com/tomoaki_teshima/items/4edde0fcd6436889ccd6)は`load`命令を紹介した。
 - 今日は`load`と対をなす`store`命令を紹介する

![store.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/1fc3887e-3cb2-35bc-2385-4c94a7ccf893.png)


```
$ grep ^vst[1234] /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h  | cut -f 1 -d ' ' | sed -e 's/_[spfu][0-9]\+//g' | sort | uniq -c
     14 vst1
     14 vst1_lane
     14 vst1q
     14 vst1q_lane
     14 vst2
      1 vst2_lane_
     14 vst2q
      1 vst2q_lane_
     14 vst3
      1 vst3_lane_
     14 vst3q
      1 vst3q_lane_
     14 vst4
      1 vst4_lane_
     14 vst4q
      1 vst4q_lane_
```

# `vst1` (`vstn`)
 - `load`命令と対をなす。
 - `vst1`命令だとただメモリに保存する通常の`store`命令
 - `store`命令は`load`と同じく`vst2`、`vst3`、`vst4`まで存在する
 - `load`ではgatherを実現していたが、`store`命令ではscatterを実現する

```st3q.cpp
        uint8_t data[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,};
        uint8_t dst[48] = { 255 };
        uint8x16x3_t vsrc;
        vsrc.val[0] = vld1q_u8(data     );
        vsrc.val[1] = vld1q_u8(data + 16);
        vsrc.val[2] = vld1q_u8(data + 32);
        vst3q_u8(dst , vsrc);
```
 - 演算結果

```
0       10      100     1       11      101     2       12      102     3       13      103     4       14      104     5
15      105     6       16      106     7       17      107     8       18      108     9       19      109     10      20
110     11      21      111     12      22      112     13      23      113     14      24      114     15      25      115
```
 - 見事に、`vld3q`命令の逆が行われ、`0`と`10`と`100`から始まる数列がそれぞれ順番に並べられている様子が分かる
 - `vld3q`命令でロードしたベクトルに対して演算を行い、最後に`vst3q`命令で書き込めば、RGB画像において、各色独立の演算を施した上で同じフォーマットでメモリ上に書き戻せる
 - `vld3q`も神とたたえたが、この`vst3q`命令も神の如き尊さである。

# `vst1_lane`、`vst1q_lane` (`vstn_lane`)
 - この`lane`がついた命令は、実際どう振る舞うのか
 - 動かして試してみよう

```vst2q.cpp
        float data[] = { 1.0,  2.0,  3.0,  4.0, 10.0, 20.0, 30.0, 40.0};
        float res [] = { -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f};
        float32x4x2_t a;
        a.val[0] = vld1q_f32(data    );
        a.val[1] = vld1q_f32(data + 4);
        vst2q_lane_f32(res, a, 2);
```
 - 使い方は`vld2q_lane`命令同じで、`vst2q_lane`命令には`float32x4x2_t`型のように、ベクトルを束ねた型を使う
    - 第3引数は0オリジンでレーン番号を指定する。
    - 当然範囲外を叩くとコンパイルエラーだし、数値はコンパイル時定数の必要がある。
 - 演算結果

```
3       30      -1      -1      -1      -1      -1      -1
```
 - 意外というかなんというか。
 - `lane`番号で指定した部分だけが書き出される。
 - なので、確保した配列`res[8]`のうち、後ろの6要素は触れられないままである

# アライメントについて
 - SSEと違い、アライン(先頭アドレスが8 byteの倍数になっているか)を心配しなくてもHW側でよしなにやってくれる。[^1]
 - ただし、[Githubのissueにコメント](https://github.com/opencv/opencv/pull/15555#issuecomment-564908674)したが、32bit OS上で`unaligned`なアドレスから、`u64`、`s64`のロードをすると、実行時エラー`SIGBUS`が起きる。
 - 解せないのは、同アドレスから`vld1q_f32`など、別の命令で適当にロードしても実行時エラーは起きず、ロードに`vreinterpretq_xx`で型を変えることが可能である
 - つまり「同じアドレス」から全く問題なくロードができる。
 - 解せないけれど、HWの仕様っぽいので割り切るしか無い。

| チップの命令セット | OSの32bit/64bit |ベクトルレジスタの型| 8byte alignment | 結果|
|:--|:--|:--|:-:|:-:|
|Armv8|64bit|`int32x4_t`|Y   |Success|
|Armv8|64bit|`int32x4_t`|N   |Success|
|Armv8|64bit|`int64x2_t`|Y   |Success|
|Armv8|64bit|`int64x2_t`|N   |Success|
|Armv8|32bit|`int32x4_t`|Y   |Success|
|Armv8|32bit|`int32x4_t`|N   |Success|
|Armv8|32bit|`int64x2_t`|Y   |Success|
|Armv8|32bit|`int64x2_t`|N   |Error (`SIGBUS`)|
|Armv7|32bit|`int32x4_t`|Y   |Success|
|Armv7|32bit|`int32x4_t`|N   |Success|
|Armv7|32bit|`int64x2_t`|Y   |Success|
|Armv7|32bit|`int64x2_t`|N   |Error (`SIGBUS`)|

 - アセンブラレベルで見てみると、Arm v8 では当然違いは無い
 - Arm v7 のアセンブラを見てみると、
 - `int64x2_t`の場合(含む`uint64x2_t`、`int64x1_t`、`uint64x1_t`)

```
vld1.64 {d16-d17}, [r3:64]
```
 - それ以外の型の場合

```
vld1.32 {d16-d17}, [r3]
```

 - `{d16-d17}`は2本の64bitレジスタを指定している。
 - 命令のオペランドの`[r3]`は一般レジスタの`r3`に入ってるアドレスからロードする、という意味
 - 違いは
    - `vld1.64`か`vld1.32`という末尾についてる要素の型
    - `[r3]`というアドレス指定が`[r3:64]`となっている
    - 多分、この`:64`が末尾についてることで8byte境界を期待しているのだと推測する
 - 何にしろそれ以外のロードに関してはアライメントの心配は要らない。

# おわりに
 - `store`命令の`vst`とその派生形を紹介しました
 - 明日も手島の執筆の予定で、積和命令を紹介予定

[^1]: SSEでは16byte境界だが、NEONでは、前述の通りごく一部の命令に限って8byte境界の必要性がある