WindowsでARM64向けにOpenCVをビルドしてみた

はじめに
=
- 今日はWindows + ARM64 + Visual Studioという組み合わせでビルドに挑戦します。
- 小ネタですが完走のために[OpenCV Advent Calendar 2023](https://qiita.com/advent-calendar/2023/opencv) 14日目の記事として投稿します
- 他の記事は[目次](https://qiita.com/advent-calendar/2023/opencv)からご覧ください

準備編
=
- まずはVisual Studio をARM64用に追加セットアップします。手元のVisual Studio はCommunity Edition 2022でした。ビルド用マシンが`x86_64`、ターゲットのシステムが`ARM64`です。
- `ARM64`環境がセットアップされてる場合はスキップして問題ありません。
- Visual Studio Installer (VisualStudioSetup.exe) を起動し、インストールされてるVisual Studioを表示します。
![Screenshot 2023-12-13 212244.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/c9ea7ddf-f3b1-ba23-b54f-4a46c653a974.png)
- 右側のModifyをクリックすると、コンポーネントごとに追加でインストールや削除ができます
![Screenshot 2023-12-13 212804.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/201d27b6-fd8c-2576-79c4-5e2bfa1ecc0e.png)

- 緑色でハイライトしたIndivisual Componentsでコンポーネントを出し、`ARM64`で検索してみましょう。ARM64、Aarch64、Armv8や64bit Armなど表記は色々あるのですが、Visual StudioではARM64表記で統一しているようです。
![Screenshot 2023-12-13 212130.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/f42d80b0-3e3b-d1e0-06ce-22aee7cc39e3.png)

- リストの中から`MSVC v143 - VS 2022 C++ ARM64 build tools (Latest)`と `MSVC v143 - VS 2022 C++ ARM64 Spectre-mitigated libs (Latest)`を選択し、`Modify`を選択して順次インストールします。
- [ITMediaさんの記事](https://atmarkit.itmedia.co.jp/ait/articles/1811/19/news070.html)によるとVisual Studio 2017 から対応していたそうです。
- 新規インストールする場合も同様のメニューからコンポーネントの追加が可能です。


ビルド(1回目)、コケる
=
- 続いて、CMakeでアーキテクチャを指定します。CMakeを起動して、buildディレクトリを適当に指定し、OpenCVのソースディレクトリを指定しましょう。なお本記事はgit版のソースコード`6ee71fee88d26df529eaad7209dbdcd3baf86577` を利用しました。4.8.1と4.9.0のリリースの間の開発版です。
- Configure を選んだときにコンパイラはVisual Studio、アーキテクチャは`ARM64`を選択します
![Screenshot 2023-12-12 130234.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12162/4f253532-1bcc-1abf-3e56-02412af7d914.png)
- `Finish`をクリックします

```
Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/arm64/cl.exe - skipped
Detecting CXX compile features
Detecting CXX compile features - done
Detecting C compiler ABI info
Detecting C compiler ABI info - done
Check for working C compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/arm64/cl.exe - skipped
Detecting C compile features
Detecting C compile features - done
Detected processor: AMD64 ←おや？

Performing Test HAVE_CPU_SSE3_SUPPORT (check file: cmake/checks/cpu_sse3.cpp)
Performing Test HAVE_CPU_SSE3_SUPPORT - Failed ←おや？
SSE3 is not supported by C++ compiler
```

- Configureは完走するのですが、前述のログを覗くと`AMD64`とか`SSE`とか、なんかおかしなキーワードが出てきます。
- これについてググると色々出てくるのですが、OpenCVの中の人の一人、[mshabuninさんが公開してるスクリプト](https://gist.github.com/mshabunin/4db712dca7835b9073688c0932afc269)が助けてくれます。

```bash
cmake -G"Visual Studio 16 2019" \
  -A ARM64 \
  -DCMAKE_SYSTEM_NAME=Windows \
  -DCMAKE_SYSTEM_VERSION=10.0 \
  -DCMAKE_SYSTEM_PROCESSOR=ARM64 \
  -DWITH_IPP=OFF \
  -DWITH_FFMPEG=OFF \
  ../opencv
  ```
  - どうやらコンパイラとアーキテクチャを指定するだけでなく、
      - `CMAKE_SYSTEM_NAME=Windows`と
      - `CMAKE_SYSTEM_PROCESSOR=ARM64`を設定する必要があるみたいです。
      - また他にも環境に応じてビルド対象のモジュールを`ON/OFF`する必要があるようです。
  - では早速設定してリトライしてみましょう。なお、肝となる`CMAKE_SYSTEM_NAME`オプションと`CMAKE_SYSTEM_PROCESSOR`オプションはConfigure前に指定する必要があり、筆者の知る限りGUI版のCMakeからこのオプションを指定することはできません。ですので、ここではGit For Windowsに同梱されてるbashで作業を続けます

ビルド(2回目)、CMakeの一部でコケる
=
- コマンド
```bash
$ cmake -G"Visual Studio 17 2022" \
        -A ARM64 \
        -DCMAKE_SYSTEM_NAME=Windows \
        -DCMAKE_SYSTEM_PROCESSOR=ARM64 \
        -DPYTHON2_EXECUTABLE= \
         ..
```

- ここでオプションを適切に選んで実行すると、以下のようにConfigureが完走します。が、、、

```
-- Performing Test HAVE_CPU_NEON_SUPPORT (check file: cmake/checks/cpu_neon.cpp)
-- Performing Test HAVE_CPU_NEON_SUPPORT - Success
-- Performing Test HAVE_CPU_FP16_SUPPORT (check file: cmake/checks/cpu_fp16.cpp)
-- Performing Test HAVE_CPU_FP16_SUPPORT - Failed
-- FP16 is not supported by C++ compiler
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+DOTPROD (check file: cmake/checks/cpu_neon_dotprod.cpp)
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+DOTPROD - Failed
-- NEON_DOTPROD is not supported by C++ compiler
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16 (check file: cmake/checks/cpu_neon_fp16.cpp)
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16 - Failed
-- NEON_FP16 is not supported by C++ compiler
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16+BF16 (check file: cmake/checks/cpu_neon_bf16.cpp)
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16+BF16 - Failed
-- NEON_BF16 is not supported by C++ compiler
-- Optimization FP16 is not available, skipped
-- Dispatch optimization NEON_FP16 is not available, skipped
-- Dispatch optimization NEON_BF16 is not available, skipped
-- Dispatch optimization NEON_DOTPROD is not available, skipped

--   CPU/HW features:
--     Baseline:                    NEON
--       requested:                 NEON FP16
--     Dispatched code generation:
--       requested:                 NEON_FP16 NEON_BF16 NEON_DOTPROD
```

- mashabuninさんに感謝しつつcmakeを走らせると、無事完走してソリューションファイルが作られるものの、途中のログに**不穏なメッセージ**が出ています。

```
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+DOTPROD - Failed
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16 - Failed
-- Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16+BF16 - Failed
```

- この抜粋した3行は、CMakeによるコンパイラがオプションに対応しているかどうかをチェックしている部分です。
- 具体的には次に示す表の用に、「コンパイラオプション」から自動的にメッセージが生成さて、CMakeの変数としてコンパイルの可否が保存されます。賢いですね！

|メッセージ|コンパイラオプション|結果|
|--|--|--|
|Performing Test HAVE_CXX_MARCH_ARMV8_2_A+DOTPROD - Failed|-march=armv8.2-a+dotprod|Failed|
|Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16 - Failed|-march=armv8.2-a+fp16|Failed|
|Performing Test HAVE_CXX_MARCH_ARMV8_2_A+FP16+BF16 - Failed|-march=armv8.2-a+fp16+BF16|Failed|

- 不穏なメッセージと呼んだのは、「コンパイラオプション」の列で、こちらは見てわかるように**GCC/Clang用のオプションがテスト**されています。奇しくも[12日目の記事](https://qiita.com/tomoaki_teshima/items/679fec72aa3cb8357e1c)で筆者が触れた通りの状況にぶち当たりました

> (引用) 現状のOpenCVのコードは、Aarch64用のコードをVisual Studioでビルドする想定に全くなってない

- 以下該当する部分の一部抜粋とコメント追記

```cmake/OpencvCompilerOptimization.cmake
elseif(ARM OR AARCH64) # 32bit Arm及び64bit Arm用設定
  ocv_update(CPU_NEON_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon.cpp")
  ocv_update(CPU_FP16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_fp16.cpp")
  ocv_update(CPU_NEON_FP16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon_fp16.cpp")
  ocv_update(CPU_NEON_BF16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon_bf16.cpp")
  ocv_update(CPU_NEON_DOTPROD_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon_dotprod.cpp")
  if(NOT AARCH64)
    # 32bit Arm用設定
  else()
    # 64bit Arm用設定
    ocv_update(CPU_KNOWN_OPTIMIZATIONS "NEON;FP16;NEON_DOTPROD;NEON_FP16;NEON_BF16")
    ocv_update(CPU_NEON_FLAGS_ON "")
    ocv_update(CPU_FP16_IMPLIES "NEON")
    ocv_update(CPU_NEON_DOTPROD_FLAGS_ON "-march=armv8.2-a+dotprod")
    ocv_update(CPU_NEON_DOTPROD_IMPLIES "NEON")
    ocv_update(CPU_NEON_FP16_FLAGS_ON "-march=armv8.2-a+fp16")
    ocv_update(CPU_NEON_FP16_IMPLIES "NEON")
    ocv_update(CPU_NEON_BF16_FLAGS_ON "-march=armv8.2-a+fp16+bf16")
    ocv_update(CPU_NEON_BF16_IMPLIES "NEON")
    set(CPU_BASELINE "NEON;FP16" CACHE STRING "${HELP_CPU_BASELINE}")
    set(CPU_DISPATCH "NEON_FP16;NEON_BF16;NEON_DOTPROD" CACHE STRING "${HELP_CPU_DISPATCH}")
  endif()
elseif(MIPS)
```

- これだけ見ると特に違和感がないのですが、実は`x86_64`部分と見比べると、`Arm`**部分の最適化のオプションはGCC/Clangしか想定していない**ことが伺い知れます

```OpenCVCompilerOptimizations.cmake
if(X86 OR X86_64)
  ocv_update(CPU_KNOWN_OPTIMIZATIONS "SSE;SSE2;SSE3;SSSE3;SSE4_1;POPCNT;SSE4_2;FP16;FMA3;AVX;AVX2;AVX_512F;AVX512_COMMON;AVX512_KNL;AVX512_KNM;AVX512_SKX;AVX512_CNL;AVX512_CLX;AVX512_ICL")
  # 以下既存のAVX命令一覧を羅列

  ocv_update(CPU_SSE_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_sse.cpp")
  # 以下各命令チェック用のファイル一覧を羅列

  if(CV_ICC OR CV_ICX)
    # ICC Intel C++ Compiler 用の設定
  elseif(CV_GCC OR CV_CLANG OR CV_ICX)
    # GCC, Clang, Intel LLVM 用の設定
  elseif(MSVC)
    # Visual Studio 用の設定
  else()
    message(WARNING "TODO: Unsupported compiler")
  endif()
  # 一部省略
elseif(ARM OR AARCH64)

```

- この用に、`x86_64`では各コンパイラごとにセクション分けされているものの、現状`ARM64`はGCC or Clang でLinux用にビルドしか想定されてないように見えます[^KEY]
- ですので、まずはGCC用のコンパイルオプションをVisual Studioに渡さないように修正します

[^KEY]: ARM64 + Android用のビルドも成功報告があるので、Androidも想定されているのですが筆者の守備範囲外なので今日のところは割愛します

```diff
   if(NOT AARCH64)
     # 中略
   else()
     ocv_update(CPU_KNOWN_OPTIMIZATIONS "NEON;FP16;NEON_DOTPROD;NEON_FP16;NEON_BF16")
     ocv_update(CPU_NEON_FLAGS_ON "")
     ocv_update(CPU_FP16_IMPLIES "NEON")
+    if(NOT MSVC)
     ocv_update(CPU_NEON_DOTPROD_FLAGS_ON "-march=armv8.2-a+dotprod")
     ocv_update(CPU_NEON_DOTPROD_IMPLIES "NEON")
     ocv_update(CPU_NEON_FP16_FLAGS_ON "-march=armv8.2-a+fp16")
     ocv_update(CPU_NEON_FP16_IMPLIES "NEON")
     ocv_update(CPU_NEON_BF16_FLAGS_ON "-march=armv8.2-a+fp16+bf16")
     ocv_update(CPU_NEON_BF16_IMPLIES "NEON")
+    else()
+    ocv_update(CPU_NEON_DOTPROD_FLAGS_ON "")
+    ocv_update(CPU_NEON_DOTPROD_IMPLIES "NEON")
+    ocv_update(CPU_NEON_FP16_FLAGS_ON "")
+    ocv_update(CPU_NEON_FP16_IMPLIES "NEON")
+    ocv_update(CPU_NEON_BF16_FLAGS_ON "")
+    ocv_update(CPU_NEON_BF16_IMPLIES "NEON")
+    endif()
```
- 色々試したのですが、Visual Studio 2022 だと、`NEON`、`FP16`、`NEON_DOTPROD`、`NEON_FP16`あたりは特にオプションを指定せずともコンパイルできるようです

ビルド(3回目)、CMakeの一部でコケる(2回目)
=
- キャッシュをクリアして再度cmakeを走らせましょう。

```
$ rm -rf *
$ cmake -G"Visual Studio 17 2022" \
        -A ARM64 \
        -DCMAKE_SYSTEM_NAME=Windows \
        -DCMAKE_SYSTEM_PROCESSOR=ARM64 \
        -DPYTHON2_EXECUTABLE= \
         ..

<中略>

-- Performing Test HAVE_CPU_NEON_SUPPORT (check file: cmake/checks/cpu_neon.cpp)
-- Performing Test HAVE_CPU_NEON_SUPPORT - Success
-- Performing Test HAVE_CPU_FP16_SUPPORT (check file: cmake/checks/cpu_fp16.cpp)
-- Performing Test HAVE_CPU_FP16_SUPPORT - Failed
-- FP16 is not supported by C++ compiler
-- Performing Test HAVE_CPU_NEON_DOTPROD_SUPPORT (check file: cmake/checks/cpu_neon_dotprod.cpp)
-- Performing Test HAVE_CPU_NEON_DOTPROD_SUPPORT - Failed
-- NEON_DOTPROD is not supported by C++ compiler
-- Performing Test HAVE_CPU_NEON_FP16_SUPPORT (check file: cmake/checks/cpu_neon_fp16.cpp)
-- Performing Test HAVE_CPU_NEON_FP16_SUPPORT - Failed
-- NEON_FP16 is not supported by C++ compiler
-- Performing Test HAVE_CPU_NEON_BF16_SUPPORT (check file: cmake/checks/cpu_neon_bf16.cpp)
-- Performing Test HAVE_CPU_NEON_BF16_SUPPORT - Failed
-- NEON_BF16 is not supported by C++ compiler
-- Optimization FP16 is not available, skipped
-- Dispatch optimization NEON_FP16 is not available, skipped
-- Dispatch optimization NEON_BF16 is not available, skipped
-- Dispatch optimization NEON_DOTPROD is not available, skipped

<中略>

--   CPU/HW features:
--     Baseline:                    NEON
--       requested:                 NEON FP16
--     Dispatched code generation:
--       requested:                 NEON_FP16 NEON_BF16 NEON_DOTPROD
```

- とりあえずCMakeは完走するのですが、今度は`FP16`が対応していないと主張します。

```
--     Baseline:                    NEON
--       requested:                 NEON FP16
```

- 前述のリストが表すのは
    - OpenCVとしてはARM64では`NEON`と`FP16`命令は無条件で使えると仮定している
    - が、`FP16`命令を使ったコンパイルに失敗したので`FP16`命令は使わない
- と判断された、ということです。とはいえ、`FP16`命令は実質変換の2命令しかないのに、何故コケるのでしょうか？もう少し覗いてみましょう。


OpenCVのビルドの裏側を支えるCMake
==
- GCCやClangは様々なプラットフォーム用にコンパイルすることを想定しているため、ターゲットのシステムに応じてコンパイルオプションが細々と設定されております。
- コンパイラがそもそも当該オプションを受け付けるか？というのをチェックするため、OpenCVにはチェック用のコードが書かれた小さいファイルたちが存在します。
- 前述のVisual Studio用に編集したCMakeの少し前を見てみましょう。

```cmake/OpenCVCompilerOptimizations.cmake
elseif(ARM OR AARCH64)
  ocv_update(CPU_NEON_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon.cpp")
  ocv_update(CPU_FP16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_fp16.cpp")
  ocv_update(CPU_NEON_FP16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon_fp16.cpp")
  ocv_update(CPU_NEON_BF16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon_bf16.cpp")
  ocv_update(CPU_NEON_DOTPROD_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_neon_dotprod.cpp")
  if(NOT AARCH64)
    # 32bit Arm向けなので省略
  else()
    ocv_update(CPU_KNOWN_OPTIMIZATIONS "NEON;FP16;NEON_DOTPROD;NEON_FP16;NEON_BF16")
    if(NOT MSVC)
       # GCC/Clang用のコードなので省略
    else()
    ocv_update(CPU_NEON_FLAGS_ON "")
    ocv_update(CPU_FP16_IMPLIES "NEON")
    ocv_update(CPU_NEON_DOTPROD_FLAGS_ON "")
    ocv_update(CPU_NEON_DOTPROD_IMPLIES "NEON")
    ocv_update(CPU_NEON_FP16_FLAGS_ON "")
    ocv_update(CPU_NEON_FP16_IMPLIES "NEON")
    ocv_update(CPU_NEON_BF16_FLAGS_ON "")
    ocv_update(CPU_NEON_BF16_IMPLIES "NEON")
    endif()
```

- 何やら、`CPU_FP16_TEST_FILE`といった変数が設定され、ファイル名が設定されているのが見えます

```cmake
ocv_update(CPU_FP16_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_fp16.cpp") 
```

- 実際にOpenCVのソースコードを覗くと、当該ファイルが`cmake/checks`ディレクトリ以下に確認できます。実はOpenCVのCMakeステージの裏側ではこのテストファイルをコンパイラでコンパイルし、コンパイルに成功しなければ当該拡張命令を諦める、という仕事をします（CMakeが）
- 今失敗している`FP16`命令をチェックするコードを見てみましょう

```cmake/checks/cpu_fp16.cpp
#include <stdio.h>

#if defined __F16C__ || (defined _MSC_VER && _MSC_VER >= 1700 && defined __AVX__) || (defined __INTEL_COMPILER && defined __AVX__)
#include <immintrin.h>
int test()
{
    const float src[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    short dst[8];
    __m128 v_src = _mm_load_ps(src);
    __m128i v_dst = _mm_cvtps_ph(v_src, 0);
    _mm_storel_epi64((__m128i*)dst, v_dst);
    return (int)dst[0];
}
#elif defined __GNUC__ && (defined __arm__ || defined __aarch64__)
#include "arm_neon.h"
int test()
{
    const float src[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    short dst[8];
    float32x4_t v_src = *(float32x4_t*)src;
    float16x4_t v_dst = vcvt_f16_f32(v_src);
    *(float16x4_t*)dst = v_dst;
    return (int)dst[0];
}
#else
#error "FP16 is not supported"
#endif

int main()
{
  printf("%d\n", test());
  return 0;
}
```

- このコードを眺めると途中で`vcvt_f16_f32`命令を呼んでいます。この命令は`float`のベクトルを受け取り、各要素を`half`に変換して返す命令です。

```cpp
    float16x4_t v_dst = vcvt_f16_f32(v_src);
```
- ところが、プリプロセッサを読み解くと、ARM64用の設定箇所にVisual Studioでは到達できません。
```
#if defined __F16C__ || (defined _MSC_VER && _MSC_VER >= 1700 && defined __AVX__) || (defined __INTEL_COMPILER && defined __AVX__)
// ここはx86_64 + (GCC/Clang/Visual Studio/Intel Compiler) 用設定
#elif defined __GNUC__ && (defined __arm__ || defined __aarch64__)
// ここは(ARM 32bit / ARM64) + GCC 向け設定
#else
#error "FP16 is not supported" ←今回はここにたどり着き、コンパイルに失敗する
#endif
```
- x86_64用設定があるのは、全く同じサブセットがたまたま存在していたからなのですが、Visual Studio でのビルドはx86_64しか想定されていません。ここはプリプロセッサを書き換えてあげましょう。コンパイラは`_MSC_VER`でVisual Studioと分かるし、Visual StudioでARM64向けにビルドするときは`_M_ARM64`が[定義されます](https://learn.microsoft.com/ja-jp/cpp/preprocessor/predefined-macros?view=msvc-170)

>(引用) _M_ARM64 ARM64 を対象とするコンパイルでは 1 として定義されます。 それ以外の場合は、定義されません。

- 他にも、内積命令、bfloat16用命令などのテストコードがありますが、どれもプリプロセッサがVisual Studioを想定していないので、合計4ファイル修正しましょう。

- `cmake/checks/cpu_fp16.cpp`
```diff
-#elif defined __GNUC__ && (defined __arm__ || defined __aarch64__)
+#elif (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64)
```
- `cmake/checks/cpu_neon_{dotprod,fp16,bf16}.cpp`いずれも同様
```diff
-#if defined __GNUC__ && (defined __arm__ || defined __aarch64__)
+#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64)
```
- なお、筆者の試した範囲だとVisual Studdio 2022 だと `bf16`用拡張命令だけまだ対応していないようです。有効にするオプションがどこか別にあったりするんでしょうか？それともVisual Studioのバージョンを上げると対応する？

ビルド(4回目)、CMake通る、コンパイルできたか！？（できてない）
=

```
-- Performing Test HAVE_CPU_NEON_SUPPORT (check file: cmake/checks/cpu_neon.cpp)
-- Performing Test HAVE_CPU_NEON_SUPPORT - Success
-- Performing Test HAVE_CPU_FP16_SUPPORT (check file: cmake/checks/cpu_fp16.cpp)
-- Performing Test HAVE_CPU_FP16_SUPPORT - Success
-- Performing Test HAVE_CPU_NEON_DOTPROD_SUPPORT (check file: cmake/checks/cpu_neon_dotprod.cpp)
-- Performing Test HAVE_CPU_NEON_DOTPROD_SUPPORT - Success
-- Performing Test HAVE_CPU_NEON_FP16_SUPPORT (check file: cmake/checks/cpu_neon_fp16.cpp)
-- Performing Test HAVE_CPU_NEON_FP16_SUPPORT - Success
-- Performing Test HAVE_CPU_NEON_BF16_SUPPORT (check file: cmake/checks/cpu_neon_bf16.cpp)
-- Performing Test HAVE_CPU_NEON_BF16_SUPPORT - Failed
-- NEON_BF16 is not supported by C++ compiler
-- Dispatch optimization NEON_BF16 is not available, skipped

--   CPU/HW features:
--     Baseline:                    NEON FP16
--     Dispatched code generation:  NEON_DOTPROD NEON_FP16
--       requested:                 NEON_FP16 NEON_BF16 NEON_DOTPROD
--       NEON_DOTPROD (1 files):    + NEON_DOTPROD
--       NEON_FP16 (2 files):       + NEON_FP16
--
```

- CMakeも無事完走しますので、ビルドしましょう。前節で触れた通り、`NEON_BF16`はコンパイラが対応していないと判断されて当該拡張命令がオフにされます。とりあえず今はビルドできるか試してみましょう。

```
zlib.vcxproj -> C:\opencv\cross_build\3rdparty\lib\Release\zlib.lib
------ Build started: Project: opencv_core_NEON_DOTPROD, Configuration: Release ARM64 ------
Building Custom Rule C:/opencv/modules/core/CMakeLists.txt
matmul.neon_dotprod.cpp
C:\opencv\modules\core\include\opencv2/core/cvdef.h(904,9): error C2065: '__m128': undeclared identifier
C:\opencv\modules\core\include\opencv2/core/cvdef.h(904,16): error C2146: syntax error: missing ';' before identifier 'v'
C:\opencv\modules\core\include\opencv2/core/cvdef.h(904,16): error C2065: 'v': undeclared identifier
C:\opencv\modules\core\include\opencv2/core/cvdef.h(904,20): error C3861: '_mm_load_ss': identifier not found
C:\opencv\modules\core\include\opencv2/core/cvdef.h(905,52): error C2065: 'v': undeclared identifier
C:\opencv\modules\core\include\opencv2/core/cvdef.h(905,39): error C3861: '_mm_cvtps_ph': identifier not found
C:\opencv\modules\core\include\opencv2/core/cvdef.h(905,21): error C3861: '_mm_cvtsi128_si32': identifier not found
C:\opencv\modules\core\include\opencv2/core/cvdef.h(936,39): error C3861: '_mm_cvtsi32_si128': identifier not found
C:\opencv\modules\core\include\opencv2/core/cvdef.h(936,26): error C3861: '_mm_cvtph_ps': identifier not found
C:\opencv\modules\core\include\opencv2/core/cvdef.h(936,9): error C3861: '_mm_store_ss': identifier not found
```

- おや、大量のビルドエラーが出ます。`cvdef.h`内でコンパイルエラーが起きている模様です。少し覗いてみましょう。

```cvdef.h
#if defined __ARM_FP16_FORMAT_IEEE \
    && !defined __CUDACC__
#  define CV_FP16_TYPE 1
#else
#  define CV_FP16_TYPE 0
#endif

namespace cv
{

class float16_t
{
public:
#if CV_FP16_TYPE
  // CV_FP16_TYPE は0と定義されていた
#else
    float16_t() : w(0) {}
    explicit float16_t(float x)
    {
    #if CV_FP16
        __m128 v = _mm_load_ss(&x); // ←ここでコンパイルにこける
        w = (ushort)_mm_cvtsi128_si32(_mm_cvtps_ph(v, 0));
```
- `_mm_load_ss`！！！まさかの`x86_64`命令に突き当たります！**ドウシテコウナッタ**。。。。
- コンパイルは`cv::float16_t`クラスのコンパイルでコケています。
    - これは`ARM/ARM64`向けのGCCでは`float16_t`という型がすでに**ネイティブにサポート**されています。
    - しかし`x86_64`向けのコンパイラ、及び`ARM/ARM64`向けでもVisual Studioではネイティブにサポートされていない型ですので、代わりとして**ラッパークラスの`cv::float16_t`が定義**されています。
    - ラッパークラスが定義されますが、基本`ARM64`向けコンパイラはネイティブに`float16_t`型を**サポートするだろう**、つまり **`float16_t`をサポートしないコンパイラは`x86_64`向けであろう** というロジックに基づき、`x86_64`用命令に突き当たります

一応の迂回策
=
- 本記事執筆にあたりあーだこーだ色々試してみたのですが、前述のCMakeの修正を施した上で、以下のコンフィグをするとビルドは完了します

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

- ただし筆者がARM64が動くWindowマシンを持ってないため、実際にロジックが正しいか、テストがPASSするかどうかは確認が取れていません。あしからず

おわりに
=
- やはりARM64向け+Visual Studioのビルドはあまり想定されていないように見える
- `cmake`ディレクトリにある以下のファイルたちは修正が必要
    - `OpenCVCompilerOptimizations.cmake`
    - `checks/cpu_fp16.cpp`
    - `checks/cpu_neon_bf16.cpp`
    - `checks/cpu_neon_fp16.cpp`
    - `checks/cpu_neon_dotprod.cpp`
- それに加え、CMakeのオプションをコマンドラインから以下のように叩く
```bash
$ cmake -G"Visual Studio 17 2022" \
        -A ARM64 \
        -DCMAKE_SYSTEM_NAME=Windows \
        -DCMAKE_SYSTEM_PROCESSOR=ARM64 \
        -DPYTHON2_EXECUTABLE= \
        -DCPU_BASELINE=NEON \
         ..
```

- 誰か検証用に`ARM64`で動くWindowsの実機を下さい。
- 明日はfukushima1981先生の予定で、記事執筆時点でのタイトルは「bfloat16を対応していないCPUで使う方法（OpenCVはMatしか使わない）」です。みんな大好き`bfloat16`！次回もサービスサービスゥ！

再現環境
=
- OS: Windows 11
- Compiler: Visual Studio 17 2022 Community Edition (17.4.3)
- CMake: 3.25.1 [^cmake] 
- OpenCV: 4.8.0-dev (commit `6ee71fee88d26df529eaad7209dbdcd3baf86577` 4.8.1と4.9.0の間の開発版)
 [^cmake]: 某氏に指摘されたのですが、CMake 3.25.0ピンポイントで`try_compile`のバグがあるようで、一部の3rdpartyディレクトリ以下のビルドに失敗するそうです。なので、3.25.1以降を使うことをおすすめします。
<details><summary>ビルド完走したときのCMake Configuration</summary>


```
-- General configuration for OpenCV 4.8.0-dev =====================================
--   Version control:               4.8.0-496-g6ee71fee88-dirty
--
--   Platform:
--     Timestamp:                   2023-12-13T14:00:20Z
--     Host:                        Windows 10.0.22621 AMD64
--     Target:                      Windows ARM64
--     CMake:                       3.25.1
--     CMake generator:             Visual Studio 17 2022
--     CMake build tool:            C:/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/amd64/MSBuild.exe
--     MSVC:                        1934
--     Configuration:               Debug Release
--
--   CPU/HW features:
--     Baseline:                    NEON
--     Dispatched code generation:  NEON_DOTPROD NEON_FP16
--       requested:                 NEON_FP16 NEON_BF16 NEON_DOTPROD
--       NEON_DOTPROD (1 files):    + NEON_DOTPROD
--       NEON_FP16 (2 files):       + NEON_FP16
--
--   C/C++:
--     Built as dynamic libs?:      YES
--     C++ standard:                11
--     C++ Compiler:                C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/arm64/cl.exe  (ver 19.34.31937.0)
--     C++ flags (Release):         /DWIN32 /D_WINDOWS /W4 /GR  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /D _ARM64_DISTINCT_NEON_TYPES /Oi  /fp:precise   /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /wd4819 /MP  /MD /O2 /Ob2 /DNDEBUG
--     C++ flags (Debug):           /DWIN32 /D_WINDOWS /W4 /GR  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /D _ARM64_DISTINCT_NEON_TYPES /Oi  /fp:precise   /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /wd4819 /MP  /MDd /Zi /Ob0 /Od /RTC1
--     C Compiler:                  C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/arm64/cl.exe
--     C flags (Release):           /DWIN32 /D_WINDOWS /W3  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /D _ARM64_DISTINCT_NEON_TYPES /Oi  /fp:precise   /MP   /MD /O2 /Ob2 /DNDEBUG
--     C flags (Debug):             /DWIN32 /D_WINDOWS /W3  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /D _ARM64_DISTINCT_NEON_TYPES /Oi  /fp:precise   /MP /MDd /Zi /Ob0 /Od /RTC1
--     Linker flags (Release):      /machine:ARM64  /INCREMENTAL:NO
--     Linker flags (Debug):        /machine:ARM64  /debug /INCREMENTAL
--     ccache:                      NO
--     Precompiled headers:         YES
--     Extra dependencies:
--     3rdparty dependencies:
--
--   OpenCV modules:
--     To be built:                 calib3d core dnn features2d flann gapi highgui imgcodecs imgproc ml objdetect photo stitching ts video videoio
--     Disabled:                    world
--     Disabled by dependency:      -
--     Unavailable:                 java python2 python3
--     Applications:                tests perf_tests apps
--     Documentation:               NO
--     Non-free algorithms:         NO
--
--   Windows RT support:            NO
--
--   GUI:                           WIN32UI
--     Win32 UI:                    YES
--
--   Media I/O:
--     ZLib:                        build (ver 1.2.13)
--     JPEG:                        build-libjpeg-turbo (ver 2.1.3-62)
--       SIMD Support Request:      YES
--       SIMD Support:              YES
--     WEBP:                        build (ver encoder: 0x020f)
--     PNG:                         build (ver 1.6.37)
--     TIFF:                        build (ver 42 - 4.2.0)
--     JPEG 2000:                   build (ver 2.5.0)
--     OpenEXR:                     build (ver 2.3.0)
--     HDR:                         YES
--     SUNRASTER:                   YES
--     PXM:                         YES
--     PFM:                         YES
--
--   Video I/O:
--     DC1394:                      NO
--     FFMPEG:                      YES (prebuilt binaries)
--       avcodec:                   YES (58.134.100)
--       avformat:                  YES (58.76.100)
--       avutil:                    YES (56.70.100)
--       swscale:                   YES (5.9.100)
--       avresample:                YES (4.0.0)
--     GStreamer:                   NO
--     DirectShow:                  YES
--     Media Foundation:            YES
--       DXVA:                      YES
--
--   Parallel framework:            Concurrency
--
--   Trace:                         YES (with Intel ITT)
--
--   Other third-party libraries:
--     Lapack:                      NO
--     Custom HAL:                  YES (carotene (ver 0.0.1))
--     Protobuf:                    build (3.19.1)
--     Flatbuffers:                 builtin/3rdparty (23.5.9)
--
--   OpenCL:                        YES (NVD3D11)
--     Include path:                C:/opencv/3rdparty/include/opencl/1.2
--     Link libraries:              Dynamic load
--
--   Install to:                    C:/opencv/cross_build/install
-- -----------------------------------------------------------------

```
</details>