mov命令 -bit幅を広げたり狭めたり-

# はじめに
 - この記事は[ひとりNEONアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/neon-only) 19日目の記事です
 - [昨日は固定小数点演算用命令](https://qiita.com/tomoaki_teshima/items/fb16de6360b17351e9c9)を紹介した
 - 今日は`mov`命令を紹介する

![mov.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/6f47aeaa-0453-fd47-fe17-c9e03eedaab9.png)


```
$  grep -e ^vmovn -e vmovun -e ^vmovl -e ^vqmov /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h
vmovl_high_s8 (int8x16_t a)
vmovl_high_s16 (int16x8_t a)
vmovl_high_s32 (int32x4_t a)
vmovl_high_u8 (uint8x16_t a)
vmovl_high_u16 (uint16x8_t a)
vmovl_high_u32 (uint32x4_t a)
vmovl_s8 (int8x8_t a)
vmovl_s16 (int16x4_t a)
vmovl_s32 (int32x2_t a)
vmovl_u8 (uint8x8_t a)
vmovl_u16 (uint16x4_t a)
vmovl_u32 (uint32x2_t a)
vmovn_high_s16 (int8x8_t a, int16x8_t b)
vmovn_high_s32 (int16x4_t a, int32x4_t b)
vmovn_high_s64 (int32x2_t a, int64x2_t b)
vmovn_high_u16 (uint8x8_t a, uint16x8_t b)
vmovn_high_u32 (uint16x4_t a, uint32x4_t b)
vmovn_high_u64 (uint32x2_t a, uint64x2_t b)
vmovn_s16 (int16x8_t a)
vmovn_s32 (int32x4_t a)
vmovn_s64 (int64x2_t a)
vmovn_u16 (uint16x8_t a)
vmovn_u32 (uint32x4_t a)
vmovn_u64 (uint64x2_t a)
vqmovn_high_s16 (int8x8_t a, int16x8_t b)
vqmovn_high_s32 (int16x4_t a, int32x4_t b)
vqmovn_high_s64 (int32x2_t a, int64x2_t b)
vqmovn_high_u16 (uint8x8_t a, uint16x8_t b)
vqmovn_high_u32 (uint16x4_t a, uint32x4_t b)
vqmovn_high_u64 (uint32x2_t a, uint64x2_t b)
vqmovun_high_s16 (uint8x8_t a, int16x8_t b)
vqmovun_high_s32 (uint16x4_t a, int32x4_t b)
vqmovun_high_s64 (uint32x2_t a, int64x2_t b)
vqmovn_s16 (int16x8_t __a)
vqmovn_s32 (int32x4_t __a)
vqmovn_s64 (int64x2_t __a)
vqmovn_u16 (uint16x8_t __a)
vqmovn_u32 (uint32x4_t __a)
vqmovn_u64 (uint64x2_t __a)
vqmovnh_s16 (int16_t __a)
vqmovns_s32 (int32_t __a)
vqmovnd_s64 (int64_t __a)
vqmovnh_u16 (uint16_t __a)
vqmovns_u32 (uint32_t __a)
vqmovnd_u64 (uint64_t __a)
vqmovun_s16 (int16x8_t __a)
vqmovun_s32 (int32x4_t __a)
vqmovun_s64 (int64x2_t __a)
vqmovunh_s16 (int16_t __a)
vqmovuns_s32 (int32_t __a)
vqmovund_s64 (int64_t __a)
```

 - 全般的に言えることなのだが、`mov`命令はx86系の`mov`命令とは全然ちがう
 - `movn`と`movl`及び`qmovn`と`qmovun`がある
 - また、浮動小数点型用の命令は存在しない。全部整数型のみ

# `movn`命令
 - ベクトルの各レーンのbit幅を、半分の幅に縮める
 - 要素の型で言うと、`int32_t`を`int16_t`に、`int16_t`を`int8_t`にする

```cpp
    int16_t src[] = {130,256,257,103,-132,-126,-125,-124,};
    int16x8_t vsrc = vld1q_s16(src);
    int8x8_t vdst0 = vmovn_s16(vsrc);
```
 - 演算結果

```
0:-126
1:0
2:1
3:103
4:124
5:-126
6:-125
7:-124
```

 - レーン数はそのまま、要素のbit幅だけ縮めるので、結果的に引数が単体の128bit幅ベクトル、戻り値が64bit幅ベクトルとなる
 - bit幅を縮めてオーバーフローする場合は、[`add`命令](https://qiita.com/tomoaki_teshima/items/79faa3581c18abce0a3c#vadd)のように単純に出力幅分のbitだけ、LSBからマスキングして出力される
    - 例1：先程のベクトルの要素0番は`130`、16進数表記で`0x0082`である。符号あり8bitの正の最大値`127`を超えているが、結果は下位8bit`0x82`を符号あり8bit整数型として扱って、`-126`となっている
    - 例2：先程のベクトルの要素1番は`256`、16進数表記で`0x0100`である。符号あり8bitの正の最大値`127`を超えているが、結果は下位8bit`0x00`を符号あり8bit整数型として扱って、`0`となっている
    - 例3：先程のベクトルの要素4番は`-132`、16進数表記で`0xff7c`である。符号あり8bitの負の最大値`-128`を超えているが、結果は下位8bit`0x7c`を符号あり8bit整数型として扱って、`-124`となっている

# `qmovn`命令
 - `add`命令のときと同じく、命令の先頭に`q`が付くと飽和演算になる
 - 先程の`movn`命令は単純に下位`n`bitを抽出して狭い型の整数型として解釈していたが、`qmovn`は入力値が結果のbit幅で結果がオーバーフローする際、最大値もしくは最小値でクランプする

```qmovn.cpp
    int16_t src[] = {130,256,257,103,-132,-126,-125,-124,};
    int16x8_t vsrc = vld1q_s16(src);
    int8x8_t vdst1 = vqmovn_s16(vsrc);
```
 - 演算結果

```
0:127
1:127
2:127
3:103
4:-128
5:-126
6:-125
7:-124
```
 - 要素の`0`番、`1`番、`2`番はそれぞれ`127`に、`4`番は`-128`にクランプされている

# `movn_high`命令、`qmovn_high`命令
 - おなじみ、後ろに`high`が付く派生形
 - 引数を2つ取り、第1引数は64bit幅ベクトル、第2引数が128bit幅ベクトル
 - 第2引数を狭めて64bit幅ベクトルを生成し、第1引数と連結して128bit幅ベクトルを返す
 - 縮める演算は`high`がついてない基本形と全く同じ

# `movl`命令
 - 狭める命令があれば、広げる命令もある。それが`movl`命令
 - 引数が64bit幅ベクトルで出力が128bit幅ベクトル
 - 符号あり整数型を引数に取る場合は、負数のbit拡張をちゃんとやってくれる
    - 8bitの`-1`(`0xff`)を16bitの`-1`(`0xffff`)にしてくれる
 - 符号のありなしは引数と戻り値で共通

# `movl_high`命令
 - 128bit幅ベクトルを取り、128bit幅ベクトルを返す
 - ただし、演算に使われるのは、入力ベクトルの後半、上位64bit分だけ

# `qmovun`命令
 - `qmovn`命令に近いが、符号あり整数型を、符号なし整数型に縮める
    - `int64x2_t`型を`uint32x2_t`型に、`int32x4_t`を`uint16x4_t`型に、`int16x8_t`型を`uint8x8_t`型に変換する
 - 引数は128bit幅ベクトル、戻り値は64bit幅ベクトル

```qmovun.cpp
    int16_t src[] = {130,256,257,103,-132,-126,-125,-124,};
    int16x8_t vsrc = vld1q_s16(src);
    int8x8_t vdst2 = vqmovun_s16(vsrc);
```
 - 演算結果

```
0:130
1:255
2:255
3:103
4:0
5:0
6:0
7:0
```
 - 負数は全て`0`に、`256`以上の数字は`255`に丸められている
 - なお、やはり`high`が末尾につく派生形もある

# 早見表

| 拡張/収縮 | 飽和演算 | 符号無し飽和演算 | 命令 |
|:-:|:-:|:-:|:-:|
| 拡張 | - | - |`movl`|
| 収縮 |`N`| - |`movn`|
| 収縮 |`Y`|`N`|`qmovn`|
| 収縮 |`Y`|`Y`|`qmovun`|


# その他
 - 図では省略したが、単体用の命令もある
 - 単体の要素を取る`mov_n`、`mov_n_high`命令は、調べた限りでは`dup_n`、`dup_n_high`命令のエイリアス

# おわりに
 - 整数型のbit幅を変換する命令`movn`、`movl`とその仲間たちを紹介した。
 - 明日も手島の担当で、Arm v8.2の内積命令を紹介する予定