ARM NEON intrinsic を書く

ARM NEON の intrinsic を書くことはしばしばあるかもしれないのでまとめておきます．どちらかというと作業記録に近いかもしれない．

# 基本的な情報

NEON は ARMv7 の SIMD 命令セットです． [^v6SIMD]
NEON に使用するレジスタは通常のレジスタとは別であり， 64bit のレジスタが32本 (=128bitのレジスタが16本) あります．また， VFP (浮動小数点コプロセッサ) とレジスタを共有しています．
浮動小数点計算は倍精度には非対応です．

[^v6SIMD]: ARMv6にも SIMD 命令セットがあるらしいですが，こちらは NEON とは言いません．

基本的にコンパイラオプションを指定することにより自動的にベクタライズされ SIMD 命令が生成されますが，思うようにうまく行かず自分で記述したい場合は intrinsic を記述するかアセンブラを書くことになります．以下は intrinsic を記述した例です．

# 実行環境

- Raspbian (32bit)
- Raspberry Pi 3 (armv8)
- gcc 4.9.2

手頃な値段で 64bitのSIMD で遊べる勝つる！！と思って買ったのですが，結局まだ書けずじまいです．かなしい．

# 方法
intrinsic の関数一覧はここに載っている:
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/ARM-NEON-Intrinsics.html#ARM-NEON-Intrinsics

arm_neon.h というヘッダファイルがあり，これをインクルードしてコンパイラオプションを指定すれば良い．
手元の環境では arm_neon.h はここにあった:
`/usr/lib/gcc/arm-linux-gnueabihf/4.9.2/include/arm_neon.h`

# 実験

32bitの符号無し整数x2を加算する例です．
NEON では `uint32x2_t` というように `<データ型>x<(レーン)数>_t` という形で表します．
`vld1_*` で通常のレジスタから NEON レジスタにロードして演算， `vst1_*` で計算結果をストアします．

```c:add.c
#include <arm_neon.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
  uint32x2_t vx, vy, vz;
  uint32_t x[2] = {2, 4};
  uint32_t y[2] = {4, 8};
  uint32_t z[2];

  vx = vld1_u32(x);
  vy = vld1_u32(y);
  vz = vadd_u32(vx, vy);
  vst1_u32(z, vz);
  printf("z: %u %u\n", z[0], z[1]);
}
```

## 実行結果
できた．オプションはもう少しいじった方が良いかもしれない

```shell-session
raspberrypi:~/tmp/add> gcc --version
gcc (Raspbian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

raspberrypi:~/tmp> gcc -S add.c -mfpu=neon -march=armv7-a -O3
raspberrypi:~/tmp> gcc -o add add.s
raspberrypi:~/tmp> ./add
z: 6 12
raspberrypi:~/tmp> 
```

## 出力
上記のように生成したアセンブリを下記に示します．(結果は抜粋したもの)
`vld1.32` で `d16` レジスタにロードして `vadd.i32` で加算， `vst1.32` でストアしていることが確認できます．だいたい NEON の命令には `v` の接頭辞がついているので分かりやすいです．(必ず全てかは確認していない) [^prefixV8]
`d16` というのは NEON レジスタで，通常のレジスタの2つ分の長さ  (=64bit) を持っています．また，4つ分の長さ (=128bit) レジスタは `q8` といった表現になります． [^registerQ]

[^prefixV8]: ちなみに ARMv8 の SIMD 命令はその接頭辞が消えます

[^registerQ]: ただし手元で実行したら `{d16-d17}` といった表記になったんですけどこれはアセンブラの表記の揺れ?

```asm:add.s
main:
	@ args = 0, pretend = 0, frame = 24
	@ frame_needed = 0, uses_anonymous_args = 0
	movw	r3, #:lower16:.LANCHOR0
	movt	r3, #:upper16:.LANCHOR0
	add	ip, r3, #8
	ldmia	r3, {r0, r1}
	str	lr, [sp, #-4]!
	sub	sp, sp, #28
	add	r3, sp, #8
	stmia	sp, {r0, r1}
	ldmia	ip, {r0, r1}
	vld1.32	{d16}, [sp:64]
	stmia	r3, {r0, r1}
	movw	r0, #:lower16:.LC2
	vld1.32	{d17}, [r3:64]
	add	r3, sp, #16
	vadd.i32	d16, d16, d17
	movt	r0, #:upper16:.LC2
	vst1.32	{d16}, [r3:64]
	ldr	r1, [sp, #16]
	ldr	r2, [sp, #20]
	bl	printf
	add	sp, sp, #28
	@ sp needed
	ldr	pc, [sp], #4
	.size	main, .-main
	.section	.rodata
	.align	2
.LANCHOR0 = . + 0
.LC0:
	.word	2
	.word	4
.LC1:
	.word	4
	.word	8
	.section	.rodata.str1.4,"aMS",%progbits,1
	.align	2
.LC2:
	.ascii	"z: %u %u\012\000"
	.ident	"GCC: (Raspbian 4.9.2-10) 4.9.2"
	.section	.note.GNU-stack,"",%progbits
```

# 調べたら追記したい
- [ ] clang/LLVMだと?
- [ ] コンパイラオプションについて
- [ ] 32bit/64bit

# 参考資料
ちゃんとググると沢山出るとは思いますが...

- https://ja.wikipedia.org/wiki/ARMアーキテクチャ#Advanced_SIMD_.28NEON.29
- https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/ARM-NEON-Intrinsics.html#ARM-NEON-Intrinsics
- http://www.slideshare.net/linaroorg/intrinsics-demo
- http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489ej/CJAJIIGG.html