Skip to content

Enable native code on AARCH64 - #723

Open
r-devulap wants to merge 14 commits into
mainfrom
arm64
Open

Enable native code on AARCH64 #723
r-devulap wants to merge 14 commits into
mainfrom
arm64

Conversation

@r-devulap

@r-devulap r-devulap commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

This pull request adds full support for running and building jvector-native on AArch64 (ARM64) systems, including both NEON/SVE/SVE2 SIMD instruction sets. The changes introduce architecture detection macros, update build scripts and CI workflows for ARM64, and extend runtime detection and dispatch of SIMD capabilities to support ARM64 alongside x86_64. This enables native vectorization and testing on ARM64 platforms such as AWS Graviton and Apple Silicon.

Key changes:

ARM64 (AArch64) support:

  • Added architecture detection macros (JV_ARCH_X86_64, JV_ARCH_AARCH64) in new header jvector_arch.h and refactored source code to use these macros for architecture-specific logic.
  • Implemented runtime CPU feature detection for NEON, SVE, and SVE2 on ARM64, supporting both Linux (using getauxval) and macOS (using sysctlbyname).
  • Updated the native dispatch logic to handle ARM64 SIMD tiers (NEON, SVE and SVE2), mirroring the x86_64 dispatch mechanism.
  • Kernels needed minor changes to adapt to vector length agnostic SIMD implementations (28d0a84)

Build system and CI improvements:

  • Modified meson.build to add ARM64 ISA variants, select appropriate test sources, and error on unsupported architectures.
  • Added a new GitHub Actions workflow to run unit tests on ARM64 runners, building and testing all supported ISAs and JDK versions.

Java integration:

  • Updated NativeVectorizationProvider to allow native SIMD operations on both x86_64 and aarch64, not just x86_64.
  • Relaxed the Maven profile activation in pom.xml to allow non-amd64 Unix builds (enabling ARM64 builds).

Gate x86-64-specific isa_variants and the avx3_dl/avx3_spr extra static
libs behind cpu == 'x86_64'. Add a parallel aarch64 block with three tiers:

  neon  (-march=armv8-a+crypto)      — baseline, all Graviton + Apple Silicon
  sve   (-march=armv8.4-a+sve)       — Graviton 3 / Neoverse V1 (Linux only;
                                        Highway marks SVE broken on HWY_OS_APPLE)
  sve2  (-march=armv9-a+sve2)        — Graviton 4 / Neoverse V2/N2 (Linux only;
                                        requires GCC >= 10 or Clang >= 22)

The three ARM static libs are appended to isa_libs and linked into
libjvector.so via link_whole, exactly as the x86 tiers already are.
An unsupported cpu_family() triggers a hard meson error.
…h #if/#else/#endif

Add jvector_arch.h as the single source of truth for JV_ARCH_X86_64 and
JV_ARCH_AARCH64 macros, derived from compiler predefined macros
(__x86_64__ / __aarch64__).

jvector_simd_kernels.h: declare ISA namespaces under a single
  #if JV_ARCH_X86_64 ... #else ... #endif block so only the
  namespaces for the current build target are visible to the compiler.

jvector_simd.cpp:
- MaxIsa enum and its static_assert are now separate per-arch definitions
  under #if JV_ARCH_X86_64 / #else / #endif — each side carries only the
  tiers relevant to it (SSE42..AVX3_SPR on x86; NEON/SVE/SVE2 on AArch64).
- read_max_isa(), vtable definitions, dispatch_kernels(), and
  jvector_simd_get_active_isa() all use the same #if/#else/#endif pattern.
- Add AArch64 vtable stubs: NEON_vtable (baseline), SVE_vtable (inherits
  NEON), SVE2_vtable (inherits SVE) — mirroring the x86 inheritance pattern.
- Add AArch64 dispatch branch in dispatch_kernels() probing CpuFeature::SVE2,
  SVE, NEON in descending order (feature detection wired up in next commit).
…RCH_AARCH64 / #endif

assert_hwy_targets.h:
  - Replace raw __x86_64__ guard with JV_ARCH_X86_64 from jvector_arch.h.
  - Add #elif JV_ARCH_AARCH64 block with exact single-constant assertions:
      JV_REQUIRE_HWY_NEON  → HWY_STATIC_TARGET == HWY_NEON   (-march=armv8-a+crypto
                             does not enable BF16/dotprod/I8MM so HWY_NEON_BF16 is
                             never selected; assert the precise constant)
      JV_REQUIRE_HWY_SVE   → HWY_STATIC_TARGET == HWY_SVE    (-march=armv8.4-a+sve
                             carries no fixed-width hint so HWY_SVE_256 is not used)
      JV_REQUIRE_HWY_SVE2  → HWY_STATIC_TARGET == HWY_SVE2   (-march=armv9-a+sve2
                             carries no fixed-width hint so HWY_SVE2_128 is not used)

jvector_simd_kernels.h / jvector_simd.cpp:
  - Convert all back-to-back #if/#if and #if/#else pairs to the canonical
    #if JV_ARCH_X86_64 / #elif JV_ARCH_AARCH64 / #endif pattern.
Extend CpuFeature enum with AArch64 tier flags (guarded by
#if JV_ARCH_X86_64 / #elif JV_ARCH_AARCH64 / #endif):
  NEON = 200  — NEON + AES, baseline for all AArch64 CPUs
  SVE  = 201  — Scalable Vector Extension (Graviton 3+, Linux only)
  SVE2 = 202  — SVE2 + SVE2-AES (Graviton 4+, Linux only)

Scope the arch-specific includes the same way:
  x86-64:   <intrin.h> or <cpuid.h>
  AArch64:  <sys/sysctl.h> on Apple; <sys/auxv.h> + <asm/hwcap.h> on Linux
  Fallback macros for HWCAP_SVE, HWCAP2_SVE2, HWCAP2_SVEAES copied from
  highway/hwy/targets.cc for older sysroots.

populate_cpu_features() AArch64 branch:
  Linux:  getauxval(AT_HWCAP)  → NEON (HWCAP_AES), SVE (HWCAP_SVE)
          getauxval(AT_HWCAP2) → SVE2 (HWCAP2_SVE2 | HWCAP2_SVEAES)
  macOS:  sysctlbyname("hw.optional.arm.FEAT_AES") → NEON
          SVE/SVE2 left false (no Apple Silicon through M4 has SVE)
Extend the os.arch guard to accept 'aarch64' alongside 'amd64'/'x86_64'
so the Java layer can load and use libjvector.so on Graviton and Apple
Silicon once the library is present.
- Add test_aarch64_cpu_features.cpp: validates populate_cpu_features()
  detects NEON/SVE/SVE2 correctly, checks dispatcher does not exceed
  hardware capability, and verifies JVECTOR_MAX_ISA=neon cap is honoured.
  Uses /proc/cpuinfo 'Features' line as ground truth on Linux, and
  sysctlbyname on macOS.

- meson.build: route cpu-features test source to the arch-specific file
  (test_x86_cpu_features.cpp on x86_64, test_aarch64_cpu_features.cpp
  on aarch64).

- test_x86_cpu_features.cpp: renamed from test_cpu_features.cpp, no
  functional change.

- test_helpers.h / test_helpers.cpp: extract parse_cpuinfo_line() and
  make_vec() as shared helpers used by both arch-specific test files.

- test_similarity.cpp (IsaDispatch/MaxIsaEnvHonoured): replace the
  hardcoded x86-only tier array with an #ifdef __aarch64__ guard that
  swaps in {"neon", "sve", "sve2"} on ARM and keeps the existing
  x86 list on x86_64.

- test_elementwise.cpp: add IsaDispatch/ActiveIsaIsKnownTier test that
  confirms the active ISA name is in the architecture-appropriate tier
  list on both x86_64 and aarch64.
All three AArch64 tiers are now pinned to fixed-width Highway targets:
  NEON    : -march=armv8-a+crypto             → HWY_NEON
  SVE     : -march=armv8.4-a+sve
              -msve-vector-bits=256           → HWY_SVE_256 (MaxLanes=8)
  SVE2    : -march=armv9-a+sve2
              -msve-vector-bits=128           → HWY_SVE2_128 (MaxLanes=4)

Using fixed-width targets gives Highway a concrete compile-time MaxLanes,
so all calculate_partial_sums fast-paths (Shuffle2301, Shuffle1032,
SwapAdjacentBlocks, LoadDup256) work identically to x86 without any
HWY_HAVE_SCALABLE guards or workarounds.

meson.build: add -msve-vector-bits=128 to sve2 args; update comments.
assert_hwy_targets.h: assert HWY_SVE2_128 (was HWY_SVE2) for the SVE2 tier.
jvector_simd_kernels.cpp: remove the #if !HWY_HAVE_SCALABLE guards that
  were added as a temporary workaround; no longer needed.
jvector_simd.cpp: update dispatch comment to name the fixed-width targets.
HWY_SVE2_128 requires i8mm and bf16 features in addition to sve2
(documented in highway/hwy/ops/set_macros-inl.h:662, issue #2973).
Without them, GCC raises 'target specific option mismatch' because
the per-function target attributes added by the SVE2_128 header
include +i8mm+bf16 but the TU-level -march= did not enable them.

Fix: change -march=armv9-a+sve2 to -march=armv9-a+sve2+i8mm+bf16.
Update the assert_hwy_targets.h error message to document the full flag.
SVE vector types (svfloat32_t etc.) are compiler built-ins, not C++
structs, so operator+/-/* cannot be defined for them — unlike NEON and
x86 which wrap their intrinsics in Vec128<T,N>/Vec256<T> structs that
do define these operators.  This affects all SVE targets including the
fixed-width HWY_SVE_256 and HWY_SVE2_128.

Replace all vector operator- usages in L2SquareDistanceImpl with
hn::Sub(), and the one operator+ in calculate_partial_sums_f32 with
hn::Add().  hn::Add/Sub are portable across every Highway backend.
…_256

hn::LoadDup128 emits the SVE ld1rq instruction, which triggers an
internal compiler error (ICE in convert_move, expr.cc:301) in GCC 14
when compiled with -msve-vector-bits=256 (fixed-width SVE mode).

Add BroadcastDup128(), a local helper that achieves the same broadcast
using LoadU on a narrower tag + Combine, avoiding ld1rq entirely:
  D = 4 lanes  → plain LoadU
  D = 8 lanes  → LoadU(Half<D>) + Combine
  D = 16 lanes → LoadU(Quarter<D>) + Combine twice

Replace both hn::LoadDup128 call sites in calculate_partial_sums_f32
(size==2 and size==4 fast-paths) with BroadcastDup128. The name is
deliberately distinct from hn::LoadDup128 to avoid the ambiguous
overload error that occurs on x86_512 targets where hn::LoadDup128
is also defined in the same namespace.
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Before you submit for review:

  • Does your PR follow guidelines from CONTRIBUTIONS.md?
  • Did you summarize what this PR does clearly and concisely?
  • Did you include performance data for changes which may be performance impacting?
  • Did you include useful docs for any user-facing changes or features?
  • Did you include useful javadocs for developer oriented changes, explaining new concepts or key changes?
  • Did you rebase your branch onto the latest main for regression testing and PR submission?
  • Did you trigger regression testing via Run Bench Main and review results?
  • Did you adhere to the code formatting guidelines (TBD)
  • Did you group your changes for easy review, providing meaningful descriptions for each commit?
  • Did you ensure that all files contain the correct copyright header?
  • Did you add documentation for this feature to the release notes directory?

If you did not complete any of these, then please explain below.

Switch the AArch64 SVE and SVE2 builds from fixed-width Highway targets
(HWY_SVE_256 / HWY_SVE2_128) to scalable ones (HWY_SVE / HWY_SVE2).

meson.build:
- sve:  drop -msve-vector-bits=256; Highway now selects HWY_SVE
- sve2: drop -msve-vector-bits=128 and +i8mm+bf16 (only required by
        HWY_SVE2_128); Highway now selects HWY_SVE2

assert_hwy_targets.h:
- Update static-target assertions from HWY_SVE_256/HWY_SVE2_128 to
  HWY_SVE/HWY_SVE2 and remove stale flag references from error messages.

jvector_simd_kernels.cpp:
- BroadcastDup128 / LoadDup256: wrap in #if !HWY_HAVE_SCALABLE with a
  comment that they are written for fixed vector lengths and must not be
  instantiated on scalable targets.
- calculate_partial_sums_f32 / calculate_partial_sums_self_magnitude_f32:
  wrap all fixed-width fast-paths (Shuffle2301/Shuffle1032/SwapAdjacentBlocks
  horizontal reductions) in #if !HWY_HAVE_SCALABLE; on scalable SVE
  execution falls through to the generic per-centroid fallback.
  Fix the general fallback to use const lanes = hn::Lanes() instead of
  constexpr kLanes = MaxLanes() as the inner loop stride.
- NVQ kernels: replace constexpr kLanes = MaxLanes() with
  const kLanes = Lanes() for all loop counters; keep constexpr kMaxLanes
  for stack-array sizing where a compile-time bound is required.

jvector_simd.cpp:
- Update file-top dispatch comment to reflect HWY_SVE/HWY_SVE2 and
  scalable (runtime Lanes()) semantics.

On x86 and NEON (HWY_HAVE_SCALABLE=0) MaxLanes==Lanes so all existing
behaviour is preserved exactly.
jvector-native/src/main/native/README.md:
- Platform support blurb: drop 'ARM support planned' — NEON, SVE, SVE2
  are now live.
- Architecture diagram: add AArch64 dispatch tree (SVE2 > SVE > NEON)
  alongside the existing x86-64 tree; correct the x86-64 -march= flags
  (-march=sapphirerapids / -march=icelake-server).
- ISA dispatch section: note getauxval for AArch64 alongside CPUID/XGETBV
  for x86; list both dispatch chains.
- ISA cap section: add AArch64 JVECTOR_MAX_ISA examples (sve2, sve, neon)
  and update the accepted-values sentence.

README.md:
- Building native libraries: add 'Linux AArch64 (NEON, SVE, SVE2)' to the
  supported-platforms line.
@r-devulap r-devulap changed the title Enable Native code on AARCH64 Enable native code on AARCH64 Sep 1, 2026
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.

1 participant