Visual Studio + ARM64 でNEONをビルドしようとしたら思ったより穴が深かった件

# はじめに
- すごい小ネタですが、完走のために投稿します。
- [前回](https://qiita.com/tomoaki_teshima/items/83787031bde304b4fc87)の修正分をOpenCVに[PRとして](https://github.com/opencv/opencv/pull/24698)投げてみた。
- その上で、AVX命令を呼んでるところを修正しようと思ったら思った以上に泥沼な作業が発生した話


# 先駆者がいた話

- 前回、`Windows + ARM64 + Visual Studio`でビルドする際に参考にしたスクリプトは mshabuninさんのものだったが、どうやらその前に[PR](https://github.com/opencv/opencv/pull/21630)で [shibayanさん(Github上のアカウント)](https://github.com/shibayan)が言及していた。
- で、ここに不思議なマクロが現れる

```
# define _ARM64_DISTINCT_NEON_TYPES
```

- なお、基準にしているOpenCVは4.8.0と4.8.1の間の開発版(git コミット `6ee71fee88d26df529eaad7209dbdcd3baf86577`)である

- このマクロはOpenCVの`cmake/checks/cpu_neon.cpp`で定義されている他、`cmake/OpenCVCompilerOption.cmake`の中でVisual Studioにも渡されている
- このマクロを追加したのが前述のshiabayanさんの[PR](https://github.com/opencv/opencv/pull/21630)である。
- ところで、このマクロは一体なんなのか？shibayanさんの[PR](https://github.com/opencv/opencv/pull/21630)では

> the blocking of NEON support in builds for Windows on ARM has been resolved.

- と書かれてある。ちょっと調べてみた

# 調べてみた
- 結論から述べると、Visual Studio 17.2**より前**だと、NEONのベクトルの型をただの128bitベクトルとみなす挙動になっていた模様である。

## Microsoft 公式ブログ
- 前述のマクロを検索すると、まず[Microsoftのコミュニティフォーラム](https://developercommunity.visualstudio.com/t/DirectX-Math-headers-in-the-Windows-SDK/)が検索に引っかかる
- そこでは前述のマクロに関して、[Microsoftの公式ブログ](https://devblogs.microsoft.com/cppblog/msvc-backend-updates-in-visual-studio-2022-version-17-2/)を引用している
- で[公式ブログ](https://devblogs.microsoft.com/cppblog/msvc-backend-updates-in-visual-studio-2022-version-17-2/)ではVisual Studio 17.2 でARM64コンパイラについて以下のことが言及されている
  - オプション`/Zc:arm64-aliased-neon-types-`が追加されたこと、
  - このオプションにより「NEONの型を、GCCやClangと同じく区別された型とみなすようになる」と書かれている
  - 従来の挙動がデフォルトで、その場合は`/Zc:arm64-aliased-neon-types`(末尾にマイナス無し)を指定したことと等価になる、とある。
- では、公式の言う「NEONの型を区別された型とみなす」とはどういうことだろうか。
- PRやログメッセージを読み解くと、Visual Studio 17.2より前のARM64コンパイラは、NEON用ベクトル型は、幅でしか区別していなかったようである。つまり、
  - `int32x4_t`や`float32x4_t`は、ARMの定義する標準では「**別の型**」とみなされるが、Visual Studio ではまとめて"**128bit幅のベクトル**"としか扱っていなかった。
  - 結果、OpenCVに実装されている、NEONのベクトルを受け取る関数が、全て重複したシグネチャとみなされる、という挙動となっていた。
- どういう経緯でこういう挙動になっているのかは不明だが、個人的には`SSE`では中身によらず`__m128`/`__m128i`という型だけでハンドリングしていた挙動を連想した。
- そして、どうも諸々のメッセージを読み解くと、
  - Visual Studio に`/Zc:arm64-aliased-neon-types-`オプションを渡すと、
  - Visual Studio の内部的に`_ARM64_DISTINCT_NEON_TYPES`マクロが定義される
  - これにより、各NEONの方が独立した型として扱われる
  - という挙動になっているらしい

# Visual Studio 17.2 で区別するようにしたよ！（ただし全機能サポートするとは言ってない）

- ここまで読んだら、あとはFP16の部分だけ修正すれば良いや、と思い立ち、ポチポチとPRを作ってみたのですが、何故かこんなメッセージに見舞われます。

```
error C3861: 'vcvt_f16_f32': identifier not found
error C3861: 'vst1_f16': identifier not found
```

- なにやら`f16`関連の型、関数がことごとく見つかりません。 [^KEY]
[^KEY]: : 1つ目が`F32`から`F16`に変換する命令、2つ目が`F16`4つ分のベクトルをメモリに書き出す命令です
- 参考までに、以下のようなコードを試しにコンパイルしてみたときのエラーメッセージです。

```cpp
    const float src[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    __n16 dst[8]{};
    float32x4_t v_src = *(float32x4_t*)src;
    float16x4_t v_dst = vcvt_f16_f32(v_src);
    vst1_f16(dst, v_dst);
```
- **ドウシテコウナッタ。。。**

# 個人的な結論
- どうやら現状Visual Studio では、`/Zc:arm64-aliased-neon-types-`を定義すると、`FP16`関連命令、型が全て無効になる挙動のようです。
- 先程の[フォーラムのコメント](https://developercommunity.visualstudio.com/t/DirectX-Math-headers-in-the-Windows-SDK/10193111#T-N10195842)にもその旨書かれています。

> There is, however, still a compiler issue here: When using /Zc:arm64-aliased-neon-types-, all of the half-precision intrinsics are missing: vcvt_f32_f16, vcvt_f16_f32, etc.

- また、実際にコンパイラ付属のヘッダファイルを覗いてみるとその旨確認できます。
- 以下、Visual Studio 付属の`arm64_neon.h`の内容を一部抜粋したものです
- この`arm64_neon.h`は`arm_neon.h`からincludeされ、ARM64用のNEONのヘッダファイルです。

```
#if !defined(_ARM64_DISTINCT_NEON_TYPES)
#define vget_lane_f16(Dm, lane)     neon_dups16((Dm), (lane))

#define vcvt_f32_f16(src)               __n128_to_float32x4_t(neon_fcvtl_32(__float16x4_t_to_n64(src)))

#define vcvt_f16_f32(src)               __n64_to_float16x4_t(neon_fcvtn_32(__float32x4_t_to_n128(src)))

#endif  /* !_ARM64_DISTINCT_NEON_TYPES */
```

- この`_ARM64_DISTINCT_NEON_TYPES`は`/Zc:arm64-aliased-neon-types-`オプションを渡したときに定義されるマクロです。
- OpenCVをビルドするためにはこのオプションが必須であります。
- 結果マクロは定義されており、前述の`#if`から`#endif`までまるっとプリプロセッサにより無効化されます
- 念のため、`/Zc:arm64-aliased-neon-types-`オプションを外してビルドを試してみましたが、今度はやはり多重定義のエラーが多発し、ビルドが通りません。

# 後日談というか今回のオチ
- 前回の記事に書いた迂回策に則って、`FP16`命令だけ無効化する

```
$ cmake -G"Visual Studio 17 2022" \
        -A ARM64 \
        -DCMAKE_SYSTEM_NAME=Windows \
        -DCMAKE_SYSTEM_PROCESSOR=ARM64 \
        -DPYTHON2_EXECUTABLE= \
        -DCPU_BASELINE=NEON \
         ..
```

- これはVisual Studio + ARM64の組み合わせで発生している挙動が原因なので、コードでのFIXは現実的ではない

- すごい小ネタですが、完走のために投稿します。