velesdb-core 5.1.0

High-performance vector database engine written in Rust
Documentation
//! NEON SIMD implementations for ARM64 (EPIC-054 US-001).
//!
//! This module provides NEON-optimized distance calculations for aarch64 targets.
//! Performance is comparable to `x86_64` AVX2 (≥95% parity).

// Wildcard import of NEON intrinsics is the idiomatic pattern for SIMD kernels.
#![allow(clippy::wildcard_imports)]

#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;

/// NEON-optimized dot product for f32 vectors.
///
/// # Safety
/// Requires aarch64 target with NEON support.
/// Input slices must have equal length.
///
/// # Performance
/// - Uses `vfmaq_f32` (fused multiply-add)
/// - Processes 4 elements per iteration
/// - ~3-4x faster than scalar on M1/M2
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
#[must_use]
pub unsafe fn dot_product_neon(a: &[f32], b: &[f32]) -> f32 {
    debug_assert_eq!(a.len(), b.len());

    let len = a.len();
    if len == 0 {
        return 0.0;
    }

    let chunks = len / 4;
    let remainder = len % 4;

    // Main SIMD loop
    let mut sum = vdupq_n_f32(0.0);

    for i in 0..chunks {
        let offset = i * 4;
        // SAFETY: NEON load and FMA require in-bounds pointers.
        // - Condition 1: Loop invariant `offset + 4 <= chunks * 4 <= len` keeps loads in bounds.
        // - Condition 2: `a` and `b` have equal length (debug assertion at entry).
        // SAFETY: Use NEON intrinsics for vectorized multiply-accumulate.
        let va = vld1q_f32(a.as_ptr().add(offset));
        let vb = vld1q_f32(b.as_ptr().add(offset));
        sum = vfmaq_f32(sum, va, vb); // sum += va * vb
    }

    // Horizontal sum of SIMD register
    let mut result = vaddvq_f32(sum);

    // Handle remainder (if len not divisible by 4) - unrolled for performance
    let base = chunks * 4;
    if remainder == 3 {
        result += a[base] * b[base] + a[base + 1] * b[base + 1] + a[base + 2] * b[base + 2];
    } else if remainder == 2 {
        result += a[base] * b[base] + a[base + 1] * b[base + 1];
    } else if remainder == 1 {
        result += a[base] * b[base];
    }

    result
}

/// NEON-optimized squared Euclidean distance.
///
/// # Safety
/// Requires aarch64 target with NEON support.
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
#[must_use]
pub unsafe fn euclidean_squared_neon(a: &[f32], b: &[f32]) -> f32 {
    debug_assert_eq!(a.len(), b.len());

    let len = a.len();
    if len == 0 {
        return 0.0;
    }

    let chunks = len / 4;
    let remainder = len % 4;

    let mut sum = vdupq_n_f32(0.0);

    for i in 0..chunks {
        let offset = i * 4;
        // SAFETY: NEON load/sub/FMA require in-bounds pointers.
        // - Condition 1: Loop invariant `offset + 4 <= chunks * 4 <= len` keeps loads in bounds.
        // - Condition 2: `a` and `b` have equal length (debug assertion at entry).
        // SAFETY: SIMD distance accumulation is required for NEON fast path.
        let va = vld1q_f32(a.as_ptr().add(offset));
        let vb = vld1q_f32(b.as_ptr().add(offset));
        let diff = vsubq_f32(va, vb);
        sum = vfmaq_f32(sum, diff, diff); // sum += diff * diff
    }

    let mut result = vaddvq_f32(sum);

    let base = chunks * 4;
    if remainder == 3 {
        let d0 = a[base] - b[base];
        let d1 = a[base + 1] - b[base + 1];
        let d2 = a[base + 2] - b[base + 2];
        result += d0 * d0 + d1 * d1 + d2 * d2;
    } else if remainder == 2 {
        let d0 = a[base] - b[base];
        let d1 = a[base + 1] - b[base + 1];
        result += d0 * d0 + d1 * d1;
    } else if remainder == 1 {
        let d = a[base] - b[base];
        result += d * d;
    }

    result
}

/// NEON-optimized Euclidean distance (with sqrt).
///
/// # Safety
/// Requires aarch64 target with NEON support.
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
#[must_use]
pub unsafe fn euclidean_neon(a: &[f32], b: &[f32]) -> f32 {
    euclidean_squared_neon(a, b).sqrt()
}

/// NEON-optimized cosine similarity.
///
/// # Safety
/// Requires aarch64 target with NEON support.
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
#[must_use]
pub unsafe fn cosine_neon(a: &[f32], b: &[f32]) -> f32 {
    let dot = dot_product_neon(a, b);
    let norm_a = dot_product_neon(a, a).sqrt();
    let norm_b = dot_product_neon(b, b).sqrt();

    if norm_a == 0.0 || norm_b == 0.0 {
        0.0
    } else {
        dot / (norm_a * norm_b)
    }
}

/// NEON-optimized cosine similarity for pre-normalized vectors.
///
/// # Safety
/// Requires aarch64 target with NEON support.
/// Vectors must be pre-normalized to unit length.
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
#[must_use]
pub unsafe fn cosine_normalized_neon(a: &[f32], b: &[f32]) -> f32 {
    // For normalized vectors, cosine = dot product
    dot_product_neon(a, b)
}

// =============================================================================
// Wrapper functions for dispatch (safe API)
// =============================================================================

/// Safe wrapper for dot product NEON.
#[cfg(target_arch = "aarch64")]
#[inline]
#[must_use]
pub fn dot_product_neon_safe(a: &[f32], b: &[f32]) -> f32 {
    // SAFETY: Calling `dot_product_neon` requires NEON target support.
    // - Condition 1: This function is compiled only for `target_arch = "aarch64"`.
    // - Condition 2: AArch64 guarantees NEON availability.
    // SAFETY: Safe wrapper delegates to NEON implementation without repeating checks.
    unsafe { dot_product_neon(a, b) }
}

/// Safe wrapper for euclidean NEON.
#[cfg(target_arch = "aarch64")]
#[inline]
#[must_use]
pub fn euclidean_neon_safe(a: &[f32], b: &[f32]) -> f32 {
    // SAFETY: Calling `euclidean_neon` requires NEON target support.
    // - Condition 1: This function is compiled only for `target_arch = "aarch64"`.
    // - Condition 2: AArch64 guarantees NEON availability.
    // SAFETY: Safe wrapper delegates to NEON implementation without repeating checks.
    unsafe { euclidean_neon(a, b) }
}

/// Safe wrapper for cosine NEON.
#[cfg(target_arch = "aarch64")]
#[inline]
#[must_use]
pub fn cosine_neon_safe(a: &[f32], b: &[f32]) -> f32 {
    // SAFETY: Calling `cosine_neon` requires NEON target support.
    // - Condition 1: This function is compiled only for `target_arch = "aarch64"`.
    // - Condition 2: AArch64 guarantees NEON availability.
    // SAFETY: Safe wrapper delegates to NEON implementation without repeating checks.
    unsafe { cosine_neon(a, b) }
}

/// Safe wrapper for cosine normalized NEON.
#[cfg(target_arch = "aarch64")]
#[inline]
#[must_use]
pub fn cosine_normalized_neon_safe(a: &[f32], b: &[f32]) -> f32 {
    // SAFETY: Calling `cosine_normalized_neon` requires NEON target support.
    // - Condition 1: This function is compiled only for `target_arch = "aarch64"`.
    // - Condition 2: AArch64 guarantees NEON availability.
    // SAFETY: Safe wrapper delegates to NEON implementation without repeating checks.
    unsafe { cosine_normalized_neon(a, b) }
}

// =============================================================================
// Tests
// =============================================================================

#[cfg(all(test, target_arch = "aarch64"))]
#[path = "simd_neon_tests.rs"]
mod tests;