transpose -転置命令-

# はじめに
 - この記事は[ひとりNEONアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/neon-only) 23日目の記事です
 - [昨日は`zip`命令](https://qiita.com/tomoaki_teshima/items/939618c7cca560dd79b3)を紹介した
 - 今日は、転置命令の`trn`命令[^note]を紹介する
 - 多分に`zip`命令とかぶる部分があるのだが、分量の問題で分けた

[^note]: 多分`transpose`の略

```arm_neon.hpp
__extension__ extern __inline float32x4x2_t
__attribute__ ((__always_inline__, __gnu_inline__, __artificial__))
vtrnq_f32 (float32x4_t a, float32x4_t b)
{
  return (float32x4x2_t) {vtrn1q_f32 (a, b), vtrn2q_f32 (a, b)};
}
```

# "転置"たる所以
 - この命令は、2つのベクトルを引数に取るが、他の記事で使ってるような、`src`と`dst`の組み合わせだとあまり伝わらないので、下図のように引数ベクトルを上下に並べる
 - 下図では128bitベクトルを2つ取り、32bit幅のレーン4つで分けられている状況(`float32x4_t`など)を表している
 - 処理前

![trn_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/17c7db27-f2e9-e629-0a83-a19e0fc1a24b.png)

 - 演算結果

![trn_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/16ffadee-d932-bcef-c899-cc1733cb5bd0.png)

 - 参考までに、`int8x16_t`型を引数に取る場合は、以下のような結果になる

![trn16_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/5137ce35-31a2-ac4f-14af-8ec1cff3964b.png)

 - このように、2つのベクトル引数を、$2\times2$行列の集合とみなし、要素を転置する
 - `zip`命令と同じく、128bitベクトル2つを引数に取り、128bitベクトル2つ分の結果を返す命令で、返し方がArm v7 とArm v8で違う

## Arm v7 の場合
 - `zip`命令のときと同じように、戻り値は`x2_t`が末尾に付いた形になる

```trn.cpp
    int16x8_t src0, src1;
    int16x8x2_t dst = vtrnq_s16(src0, src1);
```
 - `zip`命令と同じく、アセンブラでは2レジスタを引数にとり、破壊的操作によりレジスタの内容は結果で上書きされる

## Arm v8 の場合
 - `trn`命令はラッパーとして提供されるが、Arm v8では正しくは`trn1`命令と`trn2`命令で提供される
 - `trn1`命令では、先程の前半だけ、`trn2`命令では後半だけ処理される

# `float32x4_t`の転置
 - 転置行列が必要な場面として真面目に考えられるのは、`float`を16個集めて$4\times4$行列としてレジスタ内に格納した上で転置する場合である[^vld4]
 - 昔[拙wikiで解説](http://tessy.org/wiki/index.php?NEON%A4%C732bit%A4%CE%C5%BE%C3%D6)したことがあるが、、その時の結論は、
    - 最高速を考えるならばインラインアセンブラだが、使い勝手が良くない
    - `vget_low`と`vget_high`と`vcombine`を組み合わせてレジスタを連結し直す方が、ポータビリティ(Arm v7とArm v8間)が良いので、おすすめ
    - であった
 - しかし、その当時の結論としてはArm v7のNEONに64bit幅の`trn`命令がなかったためであり、あればそれに越したことはなかった
 - そして、このアドベントカレンダーを書いていて64bit幅の`trn`命令がArm v8に存在することを発見したので、4年越しに書き換えることにした


[^vld4]: メモリからロードできるのであれば、`ld4`命令を使えばレジスタに転置した状態でロードできるが、ここではすでにロードされてる状態を考える

```trn.cpp
        float src[]   = {  1.0,  2.0,  3.0,  4.0, 11.0, 12.0, 13.0, 14.0, 100.0,101.0,102.0,103.0, 999.0,998.0,997.0,996.0,};
        float dst[16] = { 0 };
        float32x4_t vsrc0 = vld1q_f32(src);
        float32x4_t vsrc1 = vld1q_f32(src + 4);
        float32x4_t vsrc2 = vld1q_f32(src + 8);
        float32x4_t vsrc3 = vld1q_f32(src + 12);
        float32x4_t vsrc00 = vtrn1q_f32(vsrc0, vsrc1);
        float32x4_t vsrc01 = vtrn2q_f32(vsrc0, vsrc1);
        float32x4_t vsrc10 = vtrn1q_f32(vsrc2, vsrc3);
        float32x4_t vsrc11 = vtrn2q_f32(vsrc2, vsrc3);
        float64x2_t vdst0 = vtrn1q_f64(vreinterpretq_f64_f32(vsrc00), vreinterpretq_f64_f32(vsrc10));
        float64x2_t vdst1 = vtrn1q_f64(vreinterpretq_f64_f32(vsrc01), vreinterpretq_f64_f32(vsrc11));
        float64x2_t vdst2 = vtrn2q_f64(vreinterpretq_f64_f32(vsrc00), vreinterpretq_f64_f32(vsrc10));
        float64x2_t vdst3 = vtrn2q_f64(vreinterpretq_f64_f32(vsrc01), vreinterpretq_f64_f32(vsrc11));
        vst1q_f32(dst,      vreinterpretq_f32_f64(vdst0));
        vst1q_f32(dst + 4,  vreinterpretq_f32_f64(vdst1));
        vst1q_f32(dst + 8,  vreinterpretq_f32_f64(vdst2));
        vst1q_f32(dst + 12, vreinterpretq_f32_f64(vdst3));
```
 - 実行結果

```
src:
[1      2       3       4]
[11     12      13      14]
[100    101     102     103]
[999    998     997     996]
dst:
[1      11      100     999]
[2      12      101     998]
[3      13      102     997]
[4      14      103     996]
```

 - ツッコミを入れるならば、`trn1q_f64`などの命令はArm v7には存在しないので、その時点でポータビリティもへったくれも無い
 - ならば、インラインアセンブラで書いてもポータビリティの観点からは同じ
 - なお、レイテンシのレベルでまだ最適化の余地は残っていると思う

# おわりに
 - 転置命令と呼ばれる`trn`、`trn1`、`trn2`命令を紹介した
 - 図は、実は各要素を左右反転して表示するのが正しい(0番要素が一番右側、最後の要素が一番左側)
    - 要素をまたいだbit shiftとかすると挙動が分かる
    - が、そこは本編とは関係ないので割愛する
 - 明日も手島の執筆の予定で、ポエムを書く予定です