add (1) -加算命令系-

# はじめに
 - この記事は[ひとりNEONアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/neon-only) 3日目の記事です
 - [昨日](https://qiita.com/tomoaki_teshima/items/877776f259bd3c1939e7)はサンプルコードでお試し、な感じだったが、今日から命令を選んで紹介していく
 - 今日は基本中の基本、加算命令の一部を紹介する。

![add1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/66cdd62e-a333-29ee-8fbb-3f88bd45afa0.png)

 - いきなりバカでかい図を出したが、全部列挙するとアホみたいに`add`命令とその仲間たちがたくさんあるので、`suffix`である程度グルーピングした。

 - 次のような図は、
![vadd_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/1a9be8e5-2c93-03f1-97a4-cd97016a0f2d.png)
 - 以下のような命令をまとめた表記とした。

```
vaddq_s8
vaddq_s16
vaddq_s32
vaddq_s64
vaddq_u8
vaddq_u16
vaddq_u32
vaddq_u64
vaddq_f16
vaddq_f32
vaddq_f64
```

 - 以下のように、`add`命令は種類がたくさんある

```
  $ grep ^v.*add /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h | cut -f 1 -d _ | sort | uniq -c 
     11 vadd
      2 vaddd
     12 vaddhn
     12 vaddl
      6 vaddlv
      6 vaddlvq
     11 vaddq
      7 vaddv
     10 vaddvq
     12 vaddw
      6 vhadd
      6 vhaddq
      8 vpadd
      3 vpaddd
      6 vpaddl
      6 vpaddlq
     11 vpaddq
      1 vpadds
      8 vqadd
      2 vqaddb
      2 vqaddd
      2 vqaddh
      8 vqaddq
      2 vqadds
     12 vraddhn
      6 vrhadd
      6 vrhaddq
      4 vsqadd
      1 vsqaddb
      1 vsqaddd
      1 vsqaddh
      4 vsqaddq
      1 vsqadds
      4 vuqadd
      1 vuqaddb
      1 vuqaddd
      1 vuqaddh
      4 vuqaddq
      1 vuqadds
```

 - 今日はこの中から、`vadd`、`vqadd`、`vaddl`、`vaddw`を紹介する

# `vadd`
 - 2日目で触れたが、`vadd`命令と`vaddq`命令は64bit/128bitの幅の違いだけである。
 - `vadd`命令は整数型、浮動小数点型どれでも加算する。
    - なお、`vadd_f16`および`vaddq_f16`命令だけはArm v8.2命令セットで追加された拡張命令で、半精度浮動小数点数のまま加算ができる命令である。
 - 次節の`vqadd`で触れるが、加算結果がオーバーフローする場合はオーバーフローした分だけが格納される

```vaddq.cpp
    unsigned char src0[] = {200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215};
    unsigned char src1[] = {100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115};
    unsigned char dst [] = {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0};
    uint8x16_t s0 = vld1q_u8(src0);
    uint8x16_t s1 = vld1q_u8(src1);
    uint8x16_t d  = vaddq_u8(s0, s1); // ここで足し算
```
 - 演算結果

```
0       200      100     44
1       201      101     46
2       202      102     48
3       203      103     50
4       204      104     52
5       205      105     54
6       206      106     56
7       207      107     58
8       208      108     60
9       209      109     62
10      210      110     64
11      211      111     66
12      212      112     68
13      213      113     70
14      214      114     72
15      215      115     74
```

 - 例えば、最初の要素は`200+100=300`と、演算結果が符号なし8bit整数の最大値`255`を超えるが、演算結果は`300 % 256 = 44`が格納される
 - また、`vadd`命令は、ベクトルでなく、単体の64bit整数を計算する命令（つまり厳密には SI **M** D命令ではない）`vaddd_s64`、`vaddd_u64`もあり、それぞれ符号あり/なし64bit整数を計算する


# `vqadd`
 - `vqdd`命令は飽和加算命令で、加算結果がオーバーフローした際、最大値もしくは最小値でクランプする


```vqaddq.cpp
    unsigned char src0[] = {200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215};
    unsigned char src1[] = {100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115};
    unsigned char dst [] = {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0};
    uint8x16_t s0 = vld1q_u8(src0);
    uint8x16_t s1 = vld1q_u8(src1);
    uint8x16_t d  = vqaddq_u8(s0, s1); // ここで足し算
```
 - このサンプルコードでは演算結果はすべて符号なし8bit整数の最大値`256`を超えるが、`vqadd`命令では演算結果を`255`に丸める
 - また、符号あり型の場合は、マイナスの演算結果も最も小さい値に丸め込まれる(例:`int8_t`型ならば`-128`)
 - 演算結果

```
0       200      100     255
1       201      101     255
2       202      102     255
3       203      103     255
4       204      104     255
5       205      105     255
6       206      106     255
7       207      107     255
8       208      108     255
9       209      109     255
10      210      110     255
11      211      111     255
12      212      112     255
13      213      113     255
14      214      114     255
15      215      115     255
```

 - また、`vqadd`命令には、8bit/16bit/32bit/64bitそれぞれの符号あり/なし整数型単体を計算する命令（つまり厳密にはSI **M** D命令はない）`vqaddb_s8`、`vqaddh_s16`、`vqadds_s32`、`vqaddd_s64`、`vqaddb_u8`、`vqaddh_u16`、`vqadds_u32`、`vqaddd_u64`が存在する
 - 多分、末尾のアルファベットはそれぞれ**B**yte、**H**alf word、**S**ingle word、**D**ouble word を表してるのだと推察する。
 - 参考までに、筆者がよく使うOpenCVでも、内部にNEON実装があり、[これ](https://github.com/opencv/opencv/blob/e726ff3296b9e382371a778ad807d37a2e49b979/modules/core/include/opencv2/core/hal/intrin_neon.hpp#L459-L479)によれば
    - `u8``s8``u16``s16`では飽和加算(`vqadd`)命令を使う
    - それ以外(`s32`、`u32`、`s64`、`u64`と浮動小数点型)では通常の加算命令(`vadd`)命令を使う
    - もともと浮動小数点数型には飽和加算命令が無いのだが、32bit/64bit整数型でもオーバーフローを気にしない実装となっている。

```intrin_neon.hpp
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_uint8x16, vqaddq_u8)    // 飽和加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_uint8x16, vqsubq_u8)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_int8x16, vqaddq_s8)     // 飽和加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_int8x16, vqsubq_s8)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_uint16x8, vqaddq_u16)   // 飽和加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_uint16x8, vqsubq_u16)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_int16x8, vqaddq_s16)    // 飽和加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_int16x8, vqsubq_s16)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_int32x4, vaddq_s32)     // 加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_int32x4, vsubq_s32)
OPENCV_HAL_IMPL_NEON_BIN_OP(*, v_int32x4, vmulq_s32)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_uint32x4, vaddq_u32)    // 加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_uint32x4, vsubq_u32)
OPENCV_HAL_IMPL_NEON_BIN_OP(*, v_uint32x4, vmulq_u32)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_float32x4, vaddq_f32)   // 加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_float32x4, vsubq_f32)
OPENCV_HAL_IMPL_NEON_BIN_OP(*, v_float32x4, vmulq_f32)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_int64x2, vaddq_s64)     // 加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_int64x2, vsubq_s64)
OPENCV_HAL_IMPL_NEON_BIN_OP(+, v_uint64x2, vaddq_u64)    // 加算命令
OPENCV_HAL_IMPL_NEON_BIN_OP(-, v_uint64x2, vsubq_u64)
```


# `vaddl`
 - `vaddl`命令は、64bit幅のレジスタ2つを引数に取り、128bitレジスタを結果として返す
 - 加算結果がオーバーフローしないように、出力結果の各要素は、入力レジスタの各要素の倍の幅を持つ
    - 結果、取りうる引数は符号あり/なしの整数型の8、16、32bitの合計6種類

```addl.cpp
    int8_t src0[] = {100,101,102,103,-127,-126,-125,-124,};
    int8_t src1[] = { 50, 50, 50, 50, -10, -10, -10, -10,};
    int8x8_t  vsrc0 = vld1_s8(src0);
    int8x8_t  vsrc1 = vld1_s8(src1);
    int16x8_t vdst  = vaddl_s8(vsrc0, vsrc1);
```
 - 演算結果

```
0    100     50     150
1    101     50     151
2    102     50     152
3    103     50     153
4    -127    -10    -137
5    -126    -10    -136
6    -125    -10    -135
7    -124    -10    -134
```
 - 入力は符号あり8bit整数型だが、計算結果はその範囲を超えている
 - しかし、演算の結果、各要素は16bit整数型に変わったため、演算結果にオーバーフローの影響は出ていない

# `vaddw`
 - `vaddl`命令に非常によく似ているが、引数2つの型が最初から違うのが特徴

```arm_neon.h
int16x8_t
vaddw_s8 (int16x8_t __a, int8x8_t __b)
```
 - こういう具合に、第1引数は16bit整数型8個で128bitのレジスタ、第2引数は8bit整数型8個で64bitのレジスタ、戻り値は第1引数と同じ128bitのレジスタ
 - Accumulatorみたいに、ベクトルをどんどん足し合わせる際に、積み上がっていく結果をもともと128bit幅で用意しておけば、結果がオーバーフローしないという配慮がなされている。
 - また、`vaddw`には、`high`が末尾に付いた命令も存在する

```arm_neon.h
int16x8_t
vaddw_high_s8 (int16x8_t __a, int8x16_t __b)
```

 - こちらは、第2引数も128bit幅になっているが、この内使われるのは上位64bitのみ
 - 上位というのは、8bit整数ならば、8番目から15番目、の具合に、後方の64bitレジスタのみ使われる。
 - もともと、`NEON`は、64bitレジスタが基本で使われていた歴史的背景があり、こうやって64bit幅レジスタで使う方法が多数サポートされている。
 - また、引数の幅が64bit/128bitで食い違う場合、`vaddw_high`命令のように、`high`が末尾に付いた命令が提供される


# おわりに
 - `add`命令だけで力尽きそうなので、3日目はここで終了。
 - 明日も引き続き筆者の執筆で、水平加算系の命令を紹介する予定