mul命令

# はじめに
 - この記事は[ひとりNEONアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/neon-only) 5日目の記事です
 - 昨日は多すぎる[add命令](https://qiita.com/tomoaki_teshima/items/2be2376464e4decaa624)を紹介したが、`mul`命令も多すぎである
 - というわけで今日は乗算の`mul`命令（の一部）を紹介する

![vmul.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/9b9d9c29-1e6b-410d-08d9-d47de4c12995.png)

```
$ egrep ^v.*mul /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h  | cut -f 1 -d _  | sort | uniq -c
     31 vmul
      2 vmuld
      2 vmulh
     24 vmull
     31 vmulq
      2 vmuls
     10 vmulx
      3 vmulxd
      2 vmulxh
     10 vmulxq
      3 vmulxs
      8 vqdmulh
      3 vqdmulhh
      8 vqdmulhq
      3 vqdmulhs
     16 vqdmull
      3 vqdmullh
      3 vqdmulls
      8 vqrdmulh
      3 vqrdmulhh
      8 vqrdmulhq
      3 vqrdmulhs

```

 - 本日は`vmul`命令、`vmulx`命令、`vmull`命令を紹介する

# `vmul`命令
 - 基本的な乗算命令
 - 型も、`s8`から`f64`まで、9種類ちゃんと揃えてある
    - 繰り返しになるが、`vmul_f16`、`vmulq_f16`、及びそれらの派生系命令はArm v8.2 拡張命令である
 - 派生型、と記載したけれど、`vmul_lane`命令、`vmul_n`命令が該当する
 - `vmul_lane`命令は、引数を3つとる。1つ目が64bit/128bit幅レジスタ、2つ目が64bit/128bit幅レジスタ、3つ目が指定する要素の位置である
    - ex1: `vmul_lane_f32 (float32x2_t __a, float32x2_t __b, const int __lane)`
    - ex2: `vmul_laneq_f32 (float32x2_t __a, float32x4_t __b, const int __lane)`
    - ex3: `vmulq_lane_f32 (float32x4_t __a, float32x2_t __b, const int __lane)`
    - ex4: `vmulq_laneq_f32 (float32x4_t __a, float32x4_t __b, const int __lane)`
    - 非常に似た関数シグネチャだが、`q`が付く場所と、引数レジスタの幅に注目
    - 第3引数の`lane`はコンパイル時定数の必要があり、0オリジンで第2引数の要素を指定する
    - 当然第2引数の範囲外を指定するコンパイルエラーになる
    - 第1引数の各要素に、第2引数と第3引数で指定した数値を掛ける。
 - `vmul_n`命令は`vmul_lane`命令みたいに面倒なことをしないで、直接乗算する数値を引数にわたす
    - ex: `vmulq_n_s16 (int16x8_t __a, int16_t __b)`
    - 画像処理屋からすると、フィルタの係数を全画素に乗算する場合とかに使える

```mul.cpp
    float src0[] = { 1.f/0.f,        0.f,   -1.f/0.f, 84800.38f,}; // 0番目の要素は正の無限大、2番目の要素は負の無限大
    float src1[] = { 0.000f,  -3.141592f, 24717.005f, 87145.40f,};
    float32x4_t  vsrc0 = vld1q_f32(src0);
    float32x4_t  vsrc1 = vld1q_f32(src1);
    float32x4_t vdst  = vmulq_f32(vsrc0, vsrc1);
```
 - 演算結果

```
0:nan
1:-0
2:-inf
3:7.38996e+09
```

# `vmulx`命令
 - 調べたのだが、[公式のドキュメント](https://developer.arm.com/architectures/instruction-sets/simd-isas/neon/intrinsics?search=vmulx) もイマイチ的を得ない
 - 個人的に調べた結果を述べると、$0 \times \infty $ が発生した場合、演算結果が `2`になるという謎命令

```mulx.cpp
    float src0[] = { 1.f/0.f,        0.f,   -1.f/0.f, 84800.38f,}; // 0番目の要素は正の無限大、2番目の要素は負の無限大
    float src1[] = { 0.000f,  -3.141592f, 24717.005f, 87145.40f,};
    float32x4_t  vsrc0 = vld1q_f32(src0);
    float32x4_t  vsrc1 = vld1q_f32(src1);
    float32x4_t vdst  = vmulxq_f32(vsrc0, vsrc1);
```
 - 前節のサンプルコードと全く同じデータに対して、`vmulx`命令を使用してみた
 - 演算結果

```
0:2
1:-0
2:-inf
3:7.38996e+09
```
 - 最初の演算結果が`2`になっている
 - それ以外の結果は`vmul`(通常の乗算)と同じ


# `vmull`命令
 - おなじみ、末尾に`l`(エル)が付いた派生系
 - 引数は64bit幅レジスタが2つ。戻り値は128bitレジスタとなる
    - ex: `int32x4_t vmull_s16 (int16x4_t a, int16x4_t b)`
    - オーバーフローに配慮した優しい実装となっております
 - `vmull`命令には、`vmull_high`命令、`vmull_n`命令の派生系もあり、なんとその組み合わせの`vmull_high_n`命令もある
    - 仕組みはそれぞれ単純で、`vmull_high`命令では第2引数が128bit幅のレジスタになるが、使われるのは上位64bitだけ
    - `vmull_n`命令では、第2引数がベクトルでなく、単体の数値になる。同じ値を第1引数の各要素に掛け合わせる

# おわりに
 - `mul`命令も多すぎである
 - 明日も私の予定で、執筆時点では比較命令系を紹介する予定である