Skip to content

feat: add Windows ARM64 (MSVC) build support - #352

Open
Aanerud wants to merge 13 commits into
alibaba:mainfrom
Aanerud:feat/windows-arm64-support
Open

feat: add Windows ARM64 (MSVC) build support#352
Aanerud wants to merge 13 commits into
alibaba:mainfrom
Aanerud:feat/windows-arm64-support

Conversation

@Aanerud

@Aanerud Aanerud commented Apr 18, 2026

Copy link
Copy Markdown

Summary

Enables pip install . to produce a native ARM64 Python wheel on Windows ARM64 with MSVC + Visual Studio Build Tools 2022. Four small, targeted changes — three in zvec, one new patch file applied to the bundled Arrow 21.0 tree.

The Python SDK build docs currently list Windows as x86_64-only with MSVC 2022+. This PR adds the ARM64 arm of that — everything else (Linux ARM64, macOS ARM64) is already working.

Changes

File Why
src/ailego/CMakeLists.txt The arm/arm64 branch of AUTO_DETECT_ARCH had if(MSVC) return() endif(), which bailed before cc_library(zvec_ailego ...) was reached — downstream then failed with No target "zvec_ailego". The -march=armv8-a / NEON glob setup is still GCC/Clang-only, so wrap just that in if(NOT MSVC) and let the target definition proceed.
src/ailego/internal/cpu_features.cc MSVC branch unconditionally called __cpuidex (x86/x64 intrinsic); GCC branch was gated only by !__ARM_ARCH. MSVC ARM64 matched neither and failed with __cpuidex: identifier not found. Scope both branches to x86/x64 explicitly so MSVC ARM64 falls through to the existing empty-stub ctor.
thirdparty/arrow/CMakeLists.txt Arrow 21.0 ships xsimd 13.0, which does not implement xsimd::make_sized_batch_t for MSVC ARM64. Pass -DARROW_SIMD_LEVEL=NONE -DARROW_RUNTIME_SIMD_LEVEL=NONE only when CMAKE_SYSTEM_PROCESSOR is ARM64 — x64 MSVC keeps its default SSE4.2 path untouched. Also wires in the new patch below.
thirdparty/arrow/arrow.windows-arm64.patch (new) Arrow's vendored PCG header (arrow/vendored/pcg/pcg_uint128.hpp) uses an x86-only endianness check and calls _umul128, which is not available on MSVC ARM64 — the equivalent intrinsic is __umulh. Add _M_ARM64/_M_ARM/__aarch64__/__arm__ to the little-endian case, branch to __umulh on ARM, and guard #pragma intrinsic(_umul128) so it is not referenced on ARM. Applied via the existing apply_patch_once mechanism, scoped to MSVC+ARM64.

Non-goals

  • x64 MSVC behavior is unchanged. All new logic is gated on CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$".
  • No SIMD path for MSVC ARM64 yet. This PR deliberately disables Arrow's xsimd SIMD and skips zvec's NEON math glob for MSVC. A follow-up could add a NEON path guarded on _M_ARM64 using MSVC's <arm_neon.h>, but that's additive and out of scope here.
  • CPU feature detection returns all-zero on MSVC ARM64. That's correct — x86 features like SSE/AVX don't exist on ARM. A future change could populate ARM-specific flags via IsProcessorFeaturePresent or compile-time __ARM_FEATURE_* macros.

Test plan

  • Built on Windows 11 ARM64 (native) with MSVC 14.44 (VS Build Tools 2022, Microsoft.VisualStudio.Component.VC.Tools.ARM64) — pip install . succeeds and produces a working wheel
  • python -c "import zvec; print(zvec)" imports cleanly on the resulting venv (zvec: OK (0.3.2.dev2))
  • git apply --check thirdparty/arrow/arrow.windows-arm64.patch clean against the bundled Arrow 21.0 tree
  • Needs validation: Windows x64 MSVC still builds unchanged (should be — all changes are gated)
  • Needs validation: Linux/macOS builds unchanged (should be — all changes gated on MSVC)

Error trail for reviewers

For context on what each change fixes (in build order):

  1. CMake Error at src/binding/c/CMakeLists.txt:109 (target_link_options): No target "zvec_ailego" → fixed by ailego CMakeLists change.
  2. error C3861: '__cpuidex': identifier not found → fixed by cpu_features.cc change.
  3. error C2039: 'make_sized_batch_t': is not a member of 'xsimd' (~20 errors in arrow_compute_core_static and arrow_util_static) → fixed by ARROW_SIMD_LEVEL=NONE.
  4. error C1189: #error: Unable to determine target endianness in arrow/vendored/pcg/pcg_uint128.hpp → fixed by endianness hunk of the new arrow patch.
  5. error C3861: '_umul128': identifier not found in the same file → fixed by the __umulh hunk of the new arrow patch.

After those five errors are resolved, the rest of the build (protobuf, rocksdb, arrow, zvec_core, zvec_db, zvec_ailego, zvec_turbo, roaring, Python binding) completes cleanly on MSVC ARM64.

@Aanerud
Aanerud requested review from chinaux and iaojnh as code owners April 18, 2026 21:05
@CLAassistant

CLAassistant commented Apr 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@feihongxu0824

Copy link
Copy Markdown
Collaborator

hi, @Aanerud, it would be beneficial to introduce a CI workflow for Windows ARM64 to guarantee ongoing integration support.

Aanerud pushed a commit to Aanerud/zvec that referenced this pull request Apr 21, 2026
Extends the existing Windows job in 05-windows-build.yml to also cover
`windows-11-arm` so the MSVC ARM64 build path is exercised on every PR,
per request from @feihongxu0824 on alibaba#352.

Changes:

* Add a third row to the matrix with `platform: windows-11-arm`,
  `msvc_arch: arm64`, and `python_version: '3.11'` (Python 3.10 has no
  official Windows-on-ARM installer; 3.11 is the first).
* Parameterize the existing `ilammy/msvc-dev-cmd@v1` step on
  `matrix.msvc_arch` instead of hard-coded `x64`, and the
  `actions/setup-python@v6` step on `matrix.python_version`.

No changes to the x64 rows (still Python 3.10 + MSVC x64) and no
changes to the build/test steps themselves — same `pip install -v .`,
same C++ unittest run, same pytest, same examples. `fail-fast: false`
was already set so an ARM64 regression will not hide x64 regressions
and vice versa.
@Aanerud
Aanerud requested a review from Cuiyus as a code owner April 21, 2026 19:29
@Aanerud

Aanerud commented Apr 21, 2026

Copy link
Copy Markdown
Author

Thanks for the review @feihongxu0824 — CLA signed, and just pushed 3f586d1 which adds windows-11-arm to the matrix in .github/workflows/05-windows-build.yml.

Kept it minimal to match the existing style:

  • Third row in the existing matrix: platform: windows-11-arm, msvc_arch: arm64, python_version: '3.11' (Python 3.10 has no official Windows-on-ARM installer; 3.11 is the first).
  • Parameterized ilammy/msvc-dev-cmd on matrix.msvc_arch and actions/setup-python on matrix.python_version. x64 rows still use Python 3.10 + MSVC x64 — no change to existing behavior.
  • Build / C++ unittest / pytest / examples steps are identical; fail-fast: false was already set, so an ARM64 regression won't mask x64 and vice versa.

Happy to split the ARM64 row into its own reusable workflow file (e.g. 07-windows-arm64-build.yml, mirroring how 03-macos-linux-build.yml is parameterized in 01-ci-pipeline.yml) if that's a better fit for your CI organization — just let me know.

Aanerud pushed a commit to Aanerud/zvec that referenced this pull request Apr 21, 2026
Extends the existing Windows job in 05-windows-build.yml to also cover
`windows-11-arm` so the MSVC ARM64 build path is exercised on every PR,
per request from @feihongxu0824 on alibaba#352.

Changes:

* Add a third row to the matrix with `platform: windows-11-arm`,
  `msvc_arch: arm64`, and `python_version: '3.11'` (Python 3.10 has no
  official Windows-on-ARM installer; 3.11 is the first).
* Parameterize the existing `ilammy/msvc-dev-cmd@v1` step on
  `matrix.msvc_arch` instead of hard-coded `x64`, and the
  `actions/setup-python@v6` step on `matrix.python_version`.

No changes to the x64 rows (still Python 3.10 + MSVC x64) and no
changes to the build/test steps themselves — same `pip install -v .`,
same C++ unittest run, same pytest, same examples. `fail-fast: false`
was already set so an ARM64 regression will not hide x64 regressions
and vice versa.
@Aanerud
Aanerud force-pushed the feat/windows-arm64-support branch from 3f586d1 to 3350494 Compare April 21, 2026 19:33
@Aanerud

Aanerud commented Apr 23, 2026

Copy link
Copy Markdown
Author

The windows-11-arm run surfaced two failing tests in hnsw_streamer_test:

  • TestKnnSearchCosine — expected i=150, got 149
  • TestFetchVectorCosine — expected i=174, got 173

Both asserts are ASSERT_EQ(i, linearResult[0].key()) after a brute-force cosine search where vector i is the query, i.e. they expect the query vector to be its own top-1. On MSVC ARM64 the top-1 comes back as i - 1 for one or two indices.

Root cause is test sensitivity, not an index bug. The dataset vectors are constructed with small inter-vector deltas (fixed_value + i * add_on), so after cosine normalization dot(v[i], v[i]) and dot(v[i], v[i-1]) are within ~1 ULP. The NEON math kernels in src/ailego/math/*_neon.cc are gated on __ARM_NEON, which MSVC does not predefine (MSVC uses _M_ARM64), so the scalar fallback runs on MSVC ARM64 and happens to hit a tie on these two queries.

Signal pattern that makes me confident in this diagnosis:

  • Same two tests pass on linux-arm64 and macos-arm64 (GCC/Clang with NEON) and on x64 MSVC (SSE/AVX2) in this same run.
  • The other five cosine variants on MSVC ARM64 — TestFetchVectorCosineHalfFloatConverter, Fp16Converter, Int8Converter, Int4Converter, TestKnnSearchL2 etc. — all pass.

Just pushed 3d13553 which adds GTEST_SKIP() to those two tests under #if defined(_MSC_VER) && defined(_M_ARM64) with a TODO pointing at the right follow-up: wire up a MSVC-ARM64 NEON kernel that uses <arm_neon.h> gated on _M_ARM64 instead of __ARM_NEON. Once that's in, the skips come off and these two tests should pass for real.

If you'd rather I do the MSVC-ARM64 NEON work in this PR instead of skipping + follow-up, I'm happy to — let me know.

Comment thread src/ailego/CMakeLists.txt
Comment thread tests/core/algorithm/hnsw/hnsw_streamer_test.cc Outdated
@feihongxu0824

Copy link
Copy Markdown
Collaborator

Apologies for the delay in reviewing this PR. Thank you for your contribution and for providing such a clear description. I’ve submitted some suggestions for your reference. Also, I noticed that the CLA hasn’t been signed yet.

@Aanerud
Aanerud force-pushed the feat/windows-arm64-support branch from 3d13553 to aa76ffa Compare May 20, 2026 18:41
Aanerud added a commit to Aanerud/zvec that referenced this pull request May 20, 2026
Extends the existing Windows job in 05-windows-build.yml to also cover
`windows-11-arm` so the MSVC ARM64 build path is exercised on every PR,
per request from @feihongxu0824 on alibaba#352.

Changes:

* Add a third row to the matrix with `platform: windows-11-arm`,
  `msvc_arch: arm64`, and `python_version: '3.11'` (Python 3.10 has no
  official Windows-on-ARM installer; 3.11 is the first).
* Parameterize the existing `ilammy/msvc-dev-cmd@v1` step on
  `matrix.msvc_arch` instead of hard-coded `x64`, and the
  `actions/setup-python@v6` step on `matrix.python_version`.

No changes to the x64 rows (still Python 3.10 + MSVC x64) and no
changes to the build/test steps themselves — same `pip install -v .`,
same C++ unittest run, same pytest, same examples. `fail-fast: false`
was already set so an ARM64 regression will not hide x64 regressions
and vice versa.
Aanerud added a commit to Aanerud/zvec that referenced this pull request May 20, 2026
Addresses @feihongxu0824's review comment on alibaba#352. Previously the
*_neon.cc files compiled into empty translation units on MSVC ARM64
because their guards (`__ARM_NEON`, `__aarch64__`) are GCC/Clang-only
macros, leaving zvec to use the scalar fallback. That fallback hits
~1 ULP precision drift versus the NEON path, which surfaced as two
HnswStreamerTest cosine failures and a `NormMatrix.Norm1_General`
failure on the `windows-11-arm` CI runner.

Changes:

* Expand `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) || defined(_M_ARM64))`
  at the 51 source sites that gate ARM NEON kernels (math, math_batch,
  utility, normalizer, platform headers, dispatch tables, version probe).
* Expand `defined(__aarch64__)` -> `(defined(__aarch64__) || defined(_M_ARM64))`
  at the 26 sites that distinguish AArch64 from ARMv7 NEON — MSVC ARM64
  is AArch64 but does not predefine `__aarch64__`. As a side effect the
  ARMv7-only polyfills (`vaddvq_f32`/`vaddvq_s32` shims in
  `distance_matrix_accum_fp32.i`, `distance_matrix_fp32.i`) are correctly
  skipped under MSVC ARM64, where those intrinsics are built in.
* `src/include/zvec/ailego/internal/platform.h`: include `<arm_neon.h>`
  on the MSVC branch when `_M_ARM64` is defined (it was previously gated
  behind `!_MSC_VER`, so MSVC ARM64 saw no NEON types).
* `src/ailego/CMakeLists.txt`: keep the existing `if(NOT MSVC)` wrapper
  around the GCC-only `-march=armv8-a` flag, and add an explicit `else()`
  branch with a comment explaining MSVC ARM64 does not need `-march`
  (NEON is the ARMv8 baseline on MSVC and the kernels are now picked up
  via the ALL_SRCS glob with the macro guards above).

This supersedes the earlier `test(hnsw): skip two cosine self-match
tests on MSVC ARM64` commit (which has been dropped from the branch).
The NEON math kernels now use the same precision path as Linux/macOS
ARM64, so those tests should pass natively on `windows-11-arm`.
@Aanerud

Aanerud commented May 20, 2026

Copy link
Copy Markdown
Author

Rebased onto current main (51c6d9e) and force-pushed with three commits:

  1. b37e324 — feat: original Windows ARM64 build support (zvec_ailego target, cpu_features stub, arrow SIMD/patch)
  2. 97d6ba6 — ci: add windows-11-arm to the build matrix (rebased over the recent pytest-xdist + zvec.dll-copy CI changes — clean merge)
  3. aa76ffa — feat(ailego): enable NEON math kernels on MSVC ARM64 per your inline review on src/ailego/CMakeLists.txt

For commit 3, instead of leaving the kernels gated on the GCC/Clang-only macros, I expanded the source guards as you suggested:

  • defined(__ARM_NEON)(defined(__ARM_NEON) || defined(_M_ARM64)) at 51 sites
  • defined(__aarch64__)(defined(__aarch64__) || defined(_M_ARM64)) at 26 sites (so the AArch64-specific NEON paths like vaddvq_f32 get used, and the ARMv7-only polyfills get correctly skipped under MSVC ARM64)
  • <arm_neon.h> now included on the MSVC branch of platform.h when _M_ARM64 is defined
  • src/ailego/CMakeLists.txt: added an explicit else() branch after the existing if(NOT MSVC) with a comment explaining MSVC ARM64 doesn't need -march (NEON is the ARMv8 baseline)

One subtlety: __fp16 is a GCC/Clang extension type that MSVC does not provide even on ARM64. The Float16 wrapper class in src/include/zvec/ailego/utility/float_helper.h and the helper functions in src/ailego/utility/float_helper.cc were previously gated only on __aarch64__, so my generalized sed accidentally tried to compile the __fp16 variants under MSVC ARM64. Those two specific guards now read ... && !defined(_MSC_VER), so MSVC ARM64 falls through to the uint16_t-storage Float16 and the F16C/scalar conversion paths. The FP16 NEON math kernels (*_fp16_neon.cc) themselves operate on float16_t from <arm_neon.h> and only enter the ARMv8.2 FP16-arithmetic path when __ARM_FEATURE_FP16_VECTOR_ARITHMETIC is defined (which MSVC does not predefine), so they should fall through to the FP32-conversion variant on MSVC ARM64.

I also dropped the old test(hnsw): skip ... cosine self-match tests on MSVC ARM64 commit, since the NEON enablement should make those tests pass natively rather than skipping them.

The CLA-bot pending status was because my earlier commits were authored as aaanerud@users.noreply.github.com (a noreply that doesn't actually map to a GitHub account). All three commits in this push are now authored as Andreas Martin Aanerud <a.m.aanerud@gmail.com>, which is the email I signed the CLA with on my personal Aanerud account.

Waiting on the windows-11-arm CI run to confirm the NEON path compiles cleanly and the previously-failing HnswStreamerTest.TestKnnSearchCosine, TestFetchVectorCosine, and NormMatrix.Norm1_General now pass. If anything else surfaces I'll iterate.

Aanerud added a commit to Aanerud/zvec that referenced this pull request May 21, 2026
Addresses @feihongxu0824's review comment on alibaba#352. Previously the
*_neon.cc files compiled into empty translation units on MSVC ARM64
because their guards (`__ARM_NEON`, `__aarch64__`) are GCC/Clang-only
macros, leaving zvec to use the scalar fallback. That fallback hits
~1 ULP precision drift versus the NEON path, which surfaced as two
HnswStreamerTest cosine failures and a `NormMatrix.Norm1_General`
failure on the `windows-11-arm` CI runner.

Changes:

* Expand `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) || defined(_M_ARM64))`
  at the 51 source sites that gate ARM NEON kernels (math, math_batch,
  utility, normalizer, platform headers, dispatch tables, version probe).
* Expand `defined(__aarch64__)` -> `(defined(__aarch64__) || defined(_M_ARM64))`
  at the 26 sites that distinguish AArch64 from ARMv7 NEON — MSVC ARM64
  is AArch64 but does not predefine `__aarch64__`. As a side effect the
  ARMv7-only polyfills (`vaddvq_f32`/`vaddvq_s32` shims in
  `distance_matrix_accum_fp32.i`, `distance_matrix_fp32.i`) are correctly
  skipped under MSVC ARM64, where those intrinsics are built in.
* `src/include/zvec/ailego/internal/platform.h`: include `<arm_neon.h>`
  on the MSVC branch when `_M_ARM64` is defined (it was previously gated
  behind `!_MSC_VER`, so MSVC ARM64 saw no NEON types).
* `src/ailego/CMakeLists.txt`: keep the existing `if(NOT MSVC)` wrapper
  around the GCC-only `-march=armv8-a` flag, and add an explicit `else()`
  branch with a comment explaining MSVC ARM64 does not need `-march`
  (NEON is the ARMv8 baseline on MSVC and the kernels are now picked up
  via the ALL_SRCS glob with the macro guards above).

This supersedes the earlier `test(hnsw): skip two cosine self-match
tests on MSVC ARM64` commit (which has been dropped from the branch).
The NEON math kernels now use the same precision path as Linux/macOS
ARM64, so those tests should pass natively on `windows-11-arm`.
@Aanerud
Aanerud force-pushed the feat/windows-arm64-support branch from aa76ffa to 0dc8e61 Compare May 21, 2026 05:47
Aanerud added a commit to Aanerud/zvec that referenced this pull request May 22, 2026
Addresses @feihongxu0824's review comment on alibaba#352. Previously the
*_neon.cc files compiled into empty translation units on MSVC ARM64
because their guards (`__ARM_NEON`, `__aarch64__`) are GCC/Clang-only
macros, leaving zvec to use the scalar fallback. That fallback hits
~1 ULP precision drift versus the NEON path, which surfaced as two
HnswStreamerTest cosine failures and a `NormMatrix.Norm1_General`
failure on the `windows-11-arm` CI runner.

Changes:

* Expand `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) || defined(_M_ARM64))`
  at the 51 source sites that gate ARM NEON kernels (math, math_batch,
  utility, normalizer, platform headers, dispatch tables, version probe).
* Expand `defined(__aarch64__)` -> `(defined(__aarch64__) || defined(_M_ARM64))`
  at the 26 sites that distinguish AArch64 from ARMv7 NEON — MSVC ARM64
  is AArch64 but does not predefine `__aarch64__`. As a side effect the
  ARMv7-only polyfills (`vaddvq_f32`/`vaddvq_s32` shims in
  `distance_matrix_accum_fp32.i`, `distance_matrix_fp32.i`) are correctly
  skipped under MSVC ARM64, where those intrinsics are built in.
* `src/include/zvec/ailego/internal/platform.h`: include `<arm_neon.h>`
  on the MSVC branch when `_M_ARM64` is defined (it was previously gated
  behind `!_MSC_VER`, so MSVC ARM64 saw no NEON types).
* `src/ailego/CMakeLists.txt`: keep the existing `if(NOT MSVC)` wrapper
  around the GCC-only `-march=armv8-a` flag, and add an explicit `else()`
  branch with a comment explaining MSVC ARM64 does not need `-march`
  (NEON is the ARMv8 baseline on MSVC and the kernels are now picked up
  via the ALL_SRCS glob with the macro guards above).

This supersedes the earlier `test(hnsw): skip two cosine self-match
tests on MSVC ARM64` commit (which has been dropped from the branch).
The NEON math kernels now use the same precision path as Linux/macOS
ARM64, so those tests should pass natively on `windows-11-arm`.
@Aanerud
Aanerud force-pushed the feat/windows-arm64-support branch from 0dc8e61 to afb6654 Compare May 22, 2026 06:47
Aanerud added a commit to Aanerud/zvec that referenced this pull request May 27, 2026
Addresses @feihongxu0824's review comment on alibaba#352. Previously the
*_neon.cc files compiled into empty translation units on MSVC ARM64
because their guards (`__ARM_NEON`, `__aarch64__`) are GCC/Clang-only
macros, leaving zvec to use the scalar fallback. That fallback hits
~1 ULP precision drift versus the NEON path, which surfaced as two
HnswStreamerTest cosine failures and a `NormMatrix.Norm1_General`
failure on the `windows-11-arm` CI runner.

Changes:

* Expand `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) || defined(_M_ARM64))`
  at the 51 source sites that gate ARM NEON kernels (math, math_batch,
  utility, normalizer, platform headers, dispatch tables, version probe).
* Expand `defined(__aarch64__)` -> `(defined(__aarch64__) || defined(_M_ARM64))`
  at the 26 sites that distinguish AArch64 from ARMv7 NEON — MSVC ARM64
  is AArch64 but does not predefine `__aarch64__`. As a side effect the
  ARMv7-only polyfills (`vaddvq_f32`/`vaddvq_s32` shims in
  `distance_matrix_accum_fp32.i`, `distance_matrix_fp32.i`) are correctly
  skipped under MSVC ARM64, where those intrinsics are built in.
* `src/include/zvec/ailego/internal/platform.h`: include `<arm_neon.h>`
  on the MSVC branch when `_M_ARM64` is defined (it was previously gated
  behind `!_MSC_VER`, so MSVC ARM64 saw no NEON types).
* `src/ailego/CMakeLists.txt`: keep the existing `if(NOT MSVC)` wrapper
  around the GCC-only `-march=armv8-a` flag, and add an explicit `else()`
  branch with a comment explaining MSVC ARM64 does not need `-march`
  (NEON is the ARMv8 baseline on MSVC and the kernels are now picked up
  via the ALL_SRCS glob with the macro guards above).

This supersedes the earlier `test(hnsw): skip two cosine self-match
tests on MSVC ARM64` commit (which has been dropped from the branch).
The NEON math kernels now use the same precision path as Linux/macOS
ARM64, so those tests should pass natively on `windows-11-arm`.
Aanerud added 2 commits July 21, 2026 21:17
Addresses @feihongxu0824's review comment on alibaba#352. Previously the
*_neon.cc files compiled into empty translation units on MSVC ARM64
because their guards (`__ARM_NEON`, `__aarch64__`) are GCC/Clang-only
macros, leaving zvec to use the scalar fallback. That fallback hits
~1 ULP precision drift versus the NEON path, which surfaced as two
HnswStreamerTest cosine failures and a `NormMatrix.Norm1_General`
failure on the `windows-11-arm` CI runner.

Changes:

* Expand `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) || defined(_M_ARM64))`
  at the 51 source sites that gate ARM NEON kernels (math, math_batch,
  utility, normalizer, platform headers, dispatch tables, version probe).
* Expand `defined(__aarch64__)` -> `(defined(__aarch64__) || defined(_M_ARM64))`
  at the 26 sites that distinguish AArch64 from ARMv7 NEON — MSVC ARM64
  is AArch64 but does not predefine `__aarch64__`. As a side effect the
  ARMv7-only polyfills (`vaddvq_f32`/`vaddvq_s32` shims in
  `distance_matrix_accum_fp32.i`, `distance_matrix_fp32.i`) are correctly
  skipped under MSVC ARM64, where those intrinsics are built in.
* `src/include/zvec/ailego/internal/platform.h`: include `<arm_neon.h>`
  on the MSVC branch when `_M_ARM64` is defined (it was previously gated
  behind `!_MSC_VER`, so MSVC ARM64 saw no NEON types).
* `src/ailego/CMakeLists.txt`: keep the existing `if(NOT MSVC)` wrapper
  around the GCC-only `-march=armv8-a` flag, and add an explicit `else()`
  branch with a comment explaining MSVC ARM64 does not need `-march`
  (NEON is the ARMv8 baseline on MSVC and the kernels are now picked up
  via the ALL_SRCS glob with the macro guards above).

This supersedes the earlier `test(hnsw): skip two cosine self-match
tests on MSVC ARM64` commit (which has been dropped from the branch).
The NEON math kernels now use the same precision path as Linux/macOS
ARM64, so those tests should pass natively on `windows-11-arm`.
Addresses review feedback that the inline _M_ARM64 preprocessor guards
were confusing and needed simplifying. The NEON-enablement change had
mechanically expanded `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) ||
defined(_M_ARM64))` and `defined(__aarch64__)` -> `(defined(__aarch64__)
|| defined(_M_ARM64))` at every site, producing long, hard-to-read compound
expressions -- one of which (`(NEON||M) && !(aarch64||M)`) was even
logically dead on MSVC ARM64.

Introduce two self-documenting feature macros in platform.h, defined once:

  AILEGO_ARM64     - 64-bit ARM (AArch64): __aarch64__ (GCC/Clang) or
                     _M_ARM64 (MSVC)
  AILEGO_HAVE_NEON - NEON intrinsics available: __ARM_NEON (GCC/Clang) or
                     _M_ARM64 (MSVC ARM64, ARMv8 baseline)

and replace the verbose guards across the ailego math kernels, platform.h,
cpu_features.cc and bitset_helper.cc with them. This is a pure readability
change -- every guard expands to the same truth value on every target as
before, so behaviour is unchanged. cpu_features.cc now includes platform.h
so it can use the macros.

Verified on Windows ARM64 (MSVC): zvec_ailego compiles, and a preprocessor
probe confirms both macros resolve true so the NEON kernels are still
compiled in (not the scalar fallback).
Copilot AI review requested due to automatic review settings July 21, 2026 19:45
@Aanerud
Aanerud force-pushed the feat/windows-arm64-support branch from bb076a7 to 333ac6e Compare July 21, 2026 19:45
@Aanerud

Aanerud commented Jul 21, 2026

Copy link
Copy Markdown
Author

Thanks for the review @JalinWang. Addressed the _M_ARM64 readability feedback in 333ac6e and rebased the branch onto current main (it was ~65 commits behind).

What changed

The NEON-enablement commit had mechanically expanded the guards (defined(__ARM_NEON) -> (defined(__ARM_NEON) || defined(_M_ARM64)) etc.) at every site, which read badly. Replaced that with two documented feature macros defined once in platform.h:

macro true when
AILEGO_ARM64 __aarch64__ (GCC/Clang) or _M_ARM64 (MSVC)
AILEGO_HAVE_NEON __ARM_NEON (GCC/Clang) or _M_ARM64 (MSVC ARM64 — NEON is the ARMv8 baseline)

All ~25 guard sites across the ailego math kernels, platform.h, cpu_features.cc and bitset_helper.cc now use these, e.g.:

  • (NEON||M) && !(aarch64||M) -> defined(AILEGO_HAVE_NEON) && !defined(AILEGO_ARM64)
  • __SSE__ || ((NEON||M) && (aarch64||M)) -> defined(__SSE__) || (defined(AILEGO_HAVE_NEON) && defined(AILEGO_ARM64))

It's a pure readability change — every guard expands to the same truth value on every target as before.

Verification (local, Windows ARM64 / MSVC)

  • zvec_ailego compiles clean
  • a preprocessor probe confirms AILEGO_ARM64 and AILEGO_HAVE_NEON both resolve true, so the NEON kernels are still compiled in (not the scalar fallback)
  • clang-format (Google style) clean on the changed files

All 4 commits are authored with my CLA-signed email. Waiting on the windows-11-arm CI run — will iterate if anything surfaces.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends zvec’s build system to support producing native Windows ARM64 Python wheels with MSVC/VS 2022, including targeted architecture-guard fixes in ailego, Arrow configuration changes for ARM64+MSVC, and CI matrix expansion for Windows ARM64.

Changes:

  • Introduces centralized ARM64/NEON feature macros (AILEGO_ARM64, AILEGO_HAVE_NEON) and updates ailego NEON guards to work on MSVC ARM64.
  • Adjusts build configuration for Windows ARM64: fixes ailego CMake early-return, disables Arrow SIMD only on ARM64, and applies an ARM64-specific Arrow patch.
  • Updates the Windows GitHub Actions workflow to include an ARM64 job (Python 3.11, MSVC arm64).

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
thirdparty/FastPFOR/CMakeLists.txt Marks SIMDe include dir as SYSTEM for consumers to avoid MSVC ARM64 warnings-as-errors.
thirdparty/arrow/CMakeLists.txt Applies an ARM64 MSVC patch and disables Arrow SIMD only on ARM64 MSVC.
thirdparty/arrow/arrow.windows-arm64.patch Patches Arrow’s vendored PCG uint128 implementation for MSVC ARM/ARM64 (endianness + mul intrinsic).
src/include/zvec/ailego/utility/float_helper.h Ensures MSVC ARM64 uses the uint16 storage path (MSVC lacks __fp16).
src/include/zvec/ailego/internal/platform.h Adds unified ARM64/NEON feature macros and MSVC ARM64 NEON header inclusion.
src/include/zvec/ailego/buffer/concurrentqueue.h Adjusts thread-local guards to account for MSVC ARM64 macro definitions.
src/ailego/version.i Switches SIMD version reporting to the new AILEGO_HAVE_NEON macro.
src/ailego/utility/float_helper.cc Avoids __fp16 path on MSVC ARM64 by tightening the __aarch64__ guard.
src/ailego/utility/bitset_helper.cc Uses the new NEON/ARM64 macros for NEON code paths.
src/ailego/math/normalizer.h Updates NEON gating for FP32 normalization via AILEGO_* macros and documents FP16 constraints.
src/ailego/math/normalizer.cc Updates NEON gating and excludes FP16 NEON implementations on MSVC ARM64.
src/ailego/math/norm2_matrix.h Updates NEON/ARM64 guards for norm2 matrix specializations.
src/ailego/math/norm2_matrix_fp32.cc Updates runtime dispatch to use AILEGO_HAVE_NEON.
src/ailego/math/norm2_matrix_fp16.cc Documents and gates FP16 NEON implementation away from MSVC ARM64.
src/ailego/math/norm1_matrix.h Updates NEON/ARM64 guards for norm1 matrix specializations.
src/ailego/math/norm1_matrix_fp32.cc Updates runtime dispatch to use AILEGO_HAVE_NEON.
src/ailego/math/norm1_matrix_fp16.cc Documents and gates FP16 NEON implementation away from MSVC ARM64.
src/ailego/math/mips_euclidean_distance_matrix_fp32_neon.cc Switches NEON compilation guard to AILEGO_HAVE_NEON.
src/ailego/math/mips_euclidean_distance_matrix_fp32_dispatch.cc Switches NEON dispatch guard to AILEGO_HAVE_NEON.
src/ailego/math/inner_product_matrix_fp32_neon.cc Switches NEON compilation guard to AILEGO_HAVE_NEON.
src/ailego/math/inner_product_matrix_fp32_dispatch.cc Switches NEON dispatch guard to AILEGO_HAVE_NEON.
src/ailego/math/euclidean_distance_matrix_fp32_neon.cc Switches NEON compilation guard to AILEGO_HAVE_NEON.
src/ailego/math/euclidean_distance_matrix_fp32_dispatch.cc Switches NEON dispatch guard to AILEGO_HAVE_NEON.
src/ailego/math/distance_matrix_fp32.i Updates NEON/AArch32 helper gating to use AILEGO_* macros.
src/ailego/math/distance_matrix_accum_fp32.i Updates NEON/AArch32 vs AArch64 gating to use AILEGO_* macros.
src/ailego/internal/cpu_features.cc Updates CPU feature detection guards to avoid x86 intrinsics on MSVC ARM64 (but needs an additional x86-only fix).
src/ailego/CMakeLists.txt Removes MSVC-ARM early return so zvec_ailego target is always defined; keeps GCC/Clang-only -march handling.
.github/workflows/05-windows-build.yml Expands Windows CI matrix to include an ARM64 job and parameterizes Python/MSVC arch per matrix entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ailego/internal/cpu_features.cc
Comment thread src/ailego/internal/cpu_features.cc Outdated
Copilot AI review requested due to automatic review settings July 21, 2026 20:46
cpu_features.cc selected the `__get_cpuid` / `__cpuid_count` constructor
(and included <cpuid.h>) under `!defined(_MSC_VER) && !defined(__ARM_ARCH) &&
!defined(AILEGO_ARM64)`. That denylist is true on non-x86, non-ARM targets
such as riscv64 and ppc64, where <cpuid.h> does not exist and the __get_cpuid
intrinsics are unavailable -- a compile break (the linux-riscv CI job builds
this file).

Restore the original x86 allowlist `defined(__x86_64__) || defined(__i386__)`
for both the include and the constructor. This still lands MSVC ARM64 on the
empty-stub constructor (it defines neither __x86_64__ nor __i386__), which was
the intent, while non-x86/non-ARM arches correctly fall through to the stub
again.

Reported by the Copilot PR reviewer.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 31, 2026 07:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@JalinWang JalinWang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! Could you also help update the newly merged code (e.g., fht*.cc) along with this?

Also, since the semantic AILEGO_HAVE_NEON / AILEGO_ARM64 macros are already introduced, would it be better to also wrap __ARM_NEON and aarch64 behind semantic macros? (e.g., AILEGO_ARM64_GNU_LIKE — feel free to suggest a better name if you have one.)

Comment thread src/ailego/math/normalizer.h Outdated
JalinWang pushed a commit to JalinWang/zvec that referenced this pull request Aug 12, 2026
Extends the existing Windows job in 05-windows-build.yml to also cover
`windows-11-arm` so the MSVC ARM64 build path is exercised on every PR,
per request from @feihongxu0824 on alibaba#352.

Changes:

* Add a third row to the matrix with `platform: windows-11-arm`,
  `msvc_arch: arm64`, and `python_version: '3.11'` (Python 3.10 has no
  official Windows-on-ARM installer; 3.11 is the first).
* Parameterize the existing `ilammy/msvc-dev-cmd@v1` step on
  `matrix.msvc_arch` instead of hard-coded `x64`, and the
  `actions/setup-python@v6` step on `matrix.python_version`.

No changes to the x64 rows (still Python 3.10 + MSVC x64) and no
changes to the build/test steps themselves — same `pip install -v .`,
same C++ unittest run, same pytest, same examples. `fail-fast: false`
was already set so an ARM64 regression will not hide x64 regressions
and vice versa.
JalinWang pushed a commit to JalinWang/zvec that referenced this pull request Aug 12, 2026
Addresses @feihongxu0824's review comment on alibaba#352. Previously the
*_neon.cc files compiled into empty translation units on MSVC ARM64
because their guards (`__ARM_NEON`, `__aarch64__`) are GCC/Clang-only
macros, leaving zvec to use the scalar fallback. That fallback hits
~1 ULP precision drift versus the NEON path, which surfaced as two
HnswStreamerTest cosine failures and a `NormMatrix.Norm1_General`
failure on the `windows-11-arm` CI runner.

Changes:

* Expand `defined(__ARM_NEON)` -> `(defined(__ARM_NEON) || defined(_M_ARM64))`
  at the 51 source sites that gate ARM NEON kernels (math, math_batch,
  utility, normalizer, platform headers, dispatch tables, version probe).
* Expand `defined(__aarch64__)` -> `(defined(__aarch64__) || defined(_M_ARM64))`
  at the 26 sites that distinguish AArch64 from ARMv7 NEON — MSVC ARM64
  is AArch64 but does not predefine `__aarch64__`. As a side effect the
  ARMv7-only polyfills (`vaddvq_f32`/`vaddvq_s32` shims in
  `distance_matrix_accum_fp32.i`, `distance_matrix_fp32.i`) are correctly
  skipped under MSVC ARM64, where those intrinsics are built in.
* `src/include/zvec/ailego/internal/platform.h`: include `<arm_neon.h>`
  on the MSVC branch when `_M_ARM64` is defined (it was previously gated
  behind `!_MSC_VER`, so MSVC ARM64 saw no NEON types).
* `src/ailego/CMakeLists.txt`: keep the existing `if(NOT MSVC)` wrapper
  around the GCC-only `-march=armv8-a` flag, and add an explicit `else()`
  branch with a comment explaining MSVC ARM64 does not need `-march`
  (NEON is the ARMv8 baseline on MSVC and the kernels are now picked up
  via the ALL_SRCS glob with the macro guards above).

This supersedes the earlier `test(hnsw): skip two cosine self-match
tests on MSVC ARM64` commit (which has been dropped from the branch).
The NEON math kernels now use the same precision path as Linux/macOS
ARM64, so those tests should pass natively on `windows-11-arm`.
Aanerud and others added 2 commits August 13, 2026 21:03
The FHT rotator merged from main guards every `zvec::turbo::neon` function
body with `__ARM_NEON && __aarch64__` and falls back to a no-op `(void)`
stub. MSVC ARM64 defines neither macro, so `fht_rotate_neon` /
`fht_unrotate_neon` returned without ever writing `out`, leaving the
caller's buffer uninitialized rather than rotated.

This was masked only by accident: `CpuFeatures::NEON()` also tested
`__ARM_NEON`, so it reported false on MSVC ARM64 and turbo.cc fell back to
the scalar rotator. That made `NEON()` disagree with `Intrinsics()` and
`version.i`, which already report "Neon" from `AILEGO_HAVE_NEON` — so the
obvious follow-up fix to `NEON()` would have activated the no-op path and
silently corrupted results.

Fix both halves together:

- Guard the turbo NEON kernels with `AILEGO_HAVE_NEON && AILEGO_ARM64` so
  the real intrinsics compile on MSVC ARM64, and make every `#else` branch
  delegate to the scalar implementation instead of doing nothing.
- Make `CpuFeatures::NEON()` use `AILEGO_HAVE_NEON`, consistent with
  `Intrinsics()` / `version.i`.
- Build the sign-flip lane mask with `vld1q_u32`; MSVC models `uint32x4_t`
  as a union, so GCC-style brace initialization does not compile there.
  This surfaced once the kernels were actually built for MSVC ARM64.

Also apply the same macros to the ailego FHT kernels (`fht_neon.cc`,
`fht_dispatch.cc`), which were likewise left on the raw macros by the
merge and therefore fell back to scalar on MSVC ARM64, and fix
`mips_euclidean_distance_matrix_fp32_dispatch.cc`, where one call site
still tested `__ARM_NEON` while its own forward declaration and sibling
function already used `AILEGO_HAVE_NEON`.

Verified on Windows ARM64 (MSVC): the NEON kernels and rotator now match
the scalar reference bit-for-bit across power-of-2, non-power-of-2 and
tail-remainder dimensions, and rotate/unrotate round-trips recover the
input. Against the previous code the same check reports all-zero output,
confirming the no-op. Changed files also compile clean for x64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses review feedback on alibaba#352.

`__ARM_NEON && __aarch64__` appeared in ~10 guards meaning "AArch64 NEON
under GCC/Clang", which is narrower than `AILEGO_HAVE_NEON &&
AILEGO_ARM64`: MSVC ARM64 has NEON but exposes neither `float16_t` nor the
`v*_f16` intrinsics unless built for ARMv8.2 FP16. Give that condition a
name, `AILEGO_ARM64_GNU_LIKE`, and document when to reach for it (FP16
kernels) versus `AILEGO_HAVE_NEON` (FP32 kernels, which MSVC supports).

Substitutions are 1:1, so behaviour is unchanged on every target. The
inner `#if defined(__ARM_NEON)` branch selectors inside FP16 blocks already
gated on `(__F16C__ && __AVX__) || AILEGO_ARM64_GNU_LIKE` are equivalent
within that block — `__F16C__` is x86-only and `__ARM_NEON` is ARM-only —
so they move to the named macro too.

Also refresh the trailing `#endif` comments left naming the old macros
after the earlier conversion to `AILEGO_HAVE_NEON` / `AILEGO_ARM64`, which
no longer matched the conditions directly above them.

Left alone deliberately: the bare `__ARM_NEON` guards in the FP16
distance kernels (`*_fp16_neon.cc`, `*_fp16_dispatch.cc`). Those admit
32-bit ARMv7 as well as AArch64, so narrowing them to
`AILEGO_ARM64_GNU_LIKE` would change behaviour on a platform this PR does
not target and CI does not cover. They already evaluate false on MSVC
ARM64, which is the correct result.

Verified on Windows ARM64 (MSVC): `AILEGO_ARM64=1`, `AILEGO_HAVE_NEON=1`,
`AILEGO_ARM64_GNU_LIKE=0`, so FP32 NEON kernels compile in while the FP16
ones stay excluded. Touched files compile clean for both ARM64 and x64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Aanerud
Aanerud requested a review from richyreachy as a code owner August 13, 2026 19:04
@Aanerud

Aanerud commented Aug 13, 2026

Copy link
Copy Markdown
Author

Thanks for the review @JalinWang — good catches, all addressed in 92c690f and 16aa7e4. Chasing the fht*.cc item turned up a real bug, so let me start there.

The fht*.cc files were hiding a latent bug

src/turbo/distance/neon/rotate/fht/fht.cc guards every function body with __ARM_NEON && __aarch64__ and falls back to an empty #else:

#else
  (void)in;
  (void)out;
#endif

MSVC ARM64 defines neither macro, so fht_rotate_neon returned without ever writing out — the caller gets its uninitialized buffer back, not a rotated vector.

It was masked purely by accident. CpuFeatures::NEON() also tested __ARM_NEON, so it reported false on MSVC ARM64 and turbo.cc:172 picked the scalar rotator. But that made NEON() disagree with Intrinsics() and version.i, which already derive from AILEGO_HAVE_NEON and report "Neon" on this target. So the natural follow-up fix — making NEON() consistent — would have flipped the dispatcher onto the no-op path and started silently emitting garbage vectors. I confirmed this by building the pre-fix file and running it: every dimension returns all zeros.

Both halves are therefore fixed together in one commit:

  • turbo NEON kernels guard on AILEGO_HAVE_NEON && AILEGO_ARM64 so the real intrinsics compile under MSVC, and every #else now delegates to scalar:: rather than doing nothing;
  • CpuFeatures::NEON() uses AILEGO_HAVE_NEON, consistent with Intrinsics()/version.i.

The ailego-side fht_neon.cc / fht_dispatch.cc had the same raw guards. Those degrade safely (dispatch just picks scalar), but they were silently opting Windows ARM64 out of NEON, so they are converted as well.

One MSVC portability wrinkle surfaced once the kernels actually started compiling there: MSVC models uint32x4_t as a union, so the GCC-style uint32x4_t bit_mask = {b0, b1, b2, b3} is rejected with C2078. Now built via vld1q_u32, which is identical on GCC/Clang.

While in the area I also found mips_euclidean_distance_matrix_fp32_dispatch.cc:66 still testing __ARM_NEON while its own forward declaration (line 21) and sibling function (line 101) had already moved to AILEGO_HAVE_NEON — a site my earlier pass missed.

AILEGO_ARM64_GNU_LIKE

Added in platform.h as you suggested, and applied to all ~10 FP16 gating sites. Documented next to the definition, since the distinction is easy to get wrong: it is strictly narrower than AILEGO_HAVE_NEON && AILEGO_ARM64 because MSVC ARM64 has NEON but exposes neither float16_t nor the v*_f16 intrinsics without /arch:armv8.2 and _M_ARM64FP16. FP32 kernels want AILEGO_HAVE_NEON; FP16 kernels want AILEGO_ARM64_GNU_LIKE.

All substitutions are 1:1, so no target changes behaviour. The inner #if defined(__ARM_NEON) selectors sitting inside blocks already gated on (__F16C__ && __AVX__) || AILEGO_ARM64_GNU_LIKE are equivalent to the named macro within that block — __F16C__ is x86-only, __ARM_NEON is ARM-only — so they moved too.

One deliberate exception: I left the bare __ARM_NEON guards in the standalone FP16 distance kernels (inner_product_matrix_fp16_neon.cc:22, euclidean_distance_matrix_fp16_neon.cc:22, and the three *_fp16_dispatch.cc). Those mean "any NEON, including 32-bit ARMv7", and narrowing them would change behaviour on a platform this PR does not target and CI does not cover. They already evaluate false on MSVC ARM64, which is the outcome we want. Happy to convert them in a follow-up if you would rather have ARMv7 dropped explicitly.

Stale #endif comments

Fixed repo-wide, including the normalizer.h:81 one you flagged inline.

Verification

On Windows ARM64 with MSVC: AILEGO_ARM64=1, AILEGO_HAVE_NEON=1, AILEGO_ARM64_GNU_LIKE=0 — FP32 NEON kernels compile in, FP16 stays excluded, exactly as intended.

Equivalence-checked the newly-live NEON paths against the scalar reference across power-of-2, non-power-of-2 and tail-remainder dimensions (4, 7, 8, 13, 16, 24, 31, 64, 100, 128, 129, 257): ailego FHT 32/32 exact, turbo rotate/unrotate 24/24 exact with round-trips recovering the input. The same harness against the pre-fix rotator fails every case with all-zero output, which is what confirms the no-op was real rather than theoretical. All touched files also compile clean for x64.

Worth noting tests/turbo/turbo_fht_rotator_test.cc will now exercise the NEON path on ARM64 CI rather than silently testing scalar, so the windows-11-arm job is now meaningfully covering this code.

Comment thread src/ailego/utility/float_helper.cc Outdated
Comment thread src/include/zvec/ailego/internal/platform.h Outdated
Aanerud and others added 3 commits August 15, 2026 23:37
Review follow-ups on alibaba#352.

Add two macros to platform.h and use them everywhere:

- `AILEGO_ARM` for `__arm__ || AILEGO_ARM64`, the `ailego_yield()` guard.
- `AILEGO_ARM64_NEON` for `AILEGO_HAVE_NEON && AILEGO_ARM64`, which the
  FP32 kernels repeated at 16 sites.

Switch the `__fp16` paths in float_helper to `AILEGO_ARM64_GNU_LIKE`. The
header and the source hold inverse guards and must agree, since they pick
`Float16::value_`'s storage type, so float_helper.h now includes
platform.h and names the same macro.

Also reflow `fht_rotate_neon`'s signature, which drifted past clang-format
when `out_dim` was un-commented, and drop the stray CRLF two lines of
normalizer.cc picked up from an editor. Both broke the lint job.

Substitutions are 1:1 on every supported target. Verified with
clang-format 18.1.8, the version CI pins: all 784 checked files clean.
On MSVC ARM64 `AILEGO_ARM64`, `AILEGO_ARM`, `AILEGO_HAVE_NEON` and
`AILEGO_ARM64_NEON` are set and `AILEGO_ARM64_GNU_LIKE` is not; the turbo
rotator's assembly listing carries NEON vector instructions, confirming
the kernels compile in rather than falling back. Touched files build clean
under /W4 for ARM64 and x64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Windows CI exposed two defects after CpuFeatures::NEON() began reporting
true on MSVC ARM64.

neon/pq_quantizer_int8/pq_distance.cc guarded on __ARM_NEON && __aarch64__,
which MSVC never defines, and its #else branches returned without writing
*out. Enabling NEON selected those stubs, so the ADC tests read uninitialised
memory. The guards now use AILEGO_ARM64_NEON and the fallbacks delegate to the
scalar kernels.

pq_adc_int8_batch_distance_neon also scored every lane against chunk m's
sub-table instead of chunks m..m+3, so results diverged from scalar once
num_chunk reached 4. Each lane now loads its own table. A scalar-equivalence
harness over 363 shape and value combinations reports no mismatches.

fht_sse.cc, fht_avx2.cc and fht_avx512.cc test __SSE2__/__AVX2__/__AVX512F__
but did not include platform.h, which synthesises those macros for MSVC.
fht_dispatch.cc did include it, so it called SSE entry points that compiled
away to nothing (LNK2019). Including platform.h in the ISA files restores
agreement and gives Windows x64 the SSE FHT path Linux x64 already uses.

MSVC models NEON vector types as unions, so the GCC-style brace initialisers
were replaced with vld1q_f32 over plain arrays.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Aanerud

Aanerud commented Aug 17, 2026

Copy link
Copy Markdown
Author

Merged main and fixed the three Windows failures.

NEON PQ kernels (neon/pq_quantizer_int8/pq_distance.cc) — same defect as the FHT rotator, arriving with #554. The guards test __ARM_NEON && __aarch64__, which MSVC never defines, and the #else branches return without writing *out. They were harmless only while CpuFeatures::NEON() reported false; correcting that flag selected them, and the ADC tests read uninitialised memory. Guards now use AILEGO_ARM64_NEON, and the fallbacks delegate to the scalar kernels.

pq_adc_int8_batch_distance_neon — a separate, pre-existing bug. The inner loop takes tab = lut + m * kNumCentroids and then reads tab[c[m]], tab[c[m+1]], tab[c[m+2]], tab[c[m+3]], so all four lanes score against chunk m's sub-table. Lanes must read chunks m..m+3, as the single-candidate kernel does. Results diverge from scalar from num_chunk = 4 upward. Each lane now loads its own table; a scalar-equivalence harness over 363 shape and value combinations reports no mismatches. This affects every AArch64 target, not just Windows — it is latent elsewhere only because no test exercises the batch path.

fht_sse.cc / fht_avx2.cc / fht_avx512.cc — these test __SSE2__, __AVX2__ and __AVX512F__ but never included platform.h, which synthesises those macros for MSVC. fht_dispatch.cc did include it, so it called SSE entry points that had compiled away to nothing (LNK2019). Adding the include restores agreement and gives Windows x64 the SSE FHT path Linux x64 already takes.

Also replaced the GCC-style vector brace initialisers, which MSVC rejects because it models NEON types as unions.

Verified with clang-format 18.1.8 (clean), MSVC ARM64 and x64 compiles, and a link-and-run test of the FHT chain on x64.

The #else-returns-nothing pattern recurs across the AVX2, AVX-512 and SSE kernels too. Those are safe today because CMake compiles each with the matching -m//arch: flag, so the macro is always defined — but the failure mode is silent, and a guard that no longer matches the build flags would corrupt results rather than fail to link. A scalar-equivalence test over the turbo kernels would catch this class in CI. Happy to add one here or in a follow-up, whichever you prefer.

Aanerud and others added 2 commits August 17, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants