diff --git a/docs/packages/pylance.yaml b/docs/packages/pylance.yaml index 4af4ac1229..53079a60ea 100644 --- a/docs/packages/pylance.yaml +++ b/docs/packages/pylance.yaml @@ -8,3 +8,4 @@ versions: - filename: pylance-11.0.0-cp310-abi3-manylinux_2_39_riscv64.whl sha256: ee4640ceee7d7439976de496e140c4a63f44cbceed1d1329f22107b68955eb7d requires-python: '>=3.10' +- version: 12.0.0 diff --git a/patches/pylance/12.0.0/0001-lance-linalg-add-a-portable-riscv64-simd-fallback.patch b/patches/pylance/12.0.0/0001-lance-linalg-add-a-portable-riscv64-simd-fallback.patch new file mode 100644 index 0000000000..0708da295a --- /dev/null +++ b/patches/pylance/12.0.0/0001-lance-linalg-add-a-portable-riscv64-simd-fallback.patch @@ -0,0 +1,1292 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 8 Sep 2026 00:00:00 +0000 +Subject: [PATCH] lance-linalg: add a portable riscv64 SIMD fallback + +lance-core's SIMD_SUPPORT static already carries a portable riscv64 +fallback (rust/lance-core/src/utils/cpu.rs), but lance-linalg's +f32x8/f32x16/i32x8/f64x4/f64x8 SIMD types still hardcode x86_64 +(AVX/AVX2/AVX-512), aarch64 (NEON) and loongarch64 (LSX/LASX) as the +only supported architectures, with no portable fallback -- unlike +u8x16/u8x32, which already carry one (rust/lance-linalg/src/simd/u8.rs). +This adds the same shape of fallback for the missing types: an +array-backed struct plus a scalar implementation of every method the +SIMD/FloatSimd traits require, gated +`#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64")))]` +beside the existing per-architecture arms, which are otherwise +untouched. Add/Sub/Mul on i32x8 use wrapping arithmetic to match the +silent-wraparound semantics of the SIMD instructions they replace. + +Verified with `cargo check --target riscv64gc-unknown-linux-gnu` (and, +as a regression check, `--target aarch64-apple-darwin` and +`--target x86_64-unknown-linux-gnu`) against an extracted copy of +lance-linalg's simd module. + +Upstream-Status: To upstream [not yet submitted to lancedb/lance] + +Signed-off-by: Ludovic Henry +--- + rust/lance-linalg/src/simd/f32.rs | 383 ++++++++++++++++++++++++++++++ + rust/lance-linalg/src/simd/f64.rs | 364 ++++++++++++++++++++++++++++ + rust/lance-linalg/src/simd/i32.rs | 196 +++++++++++++++ + 3 files changed, 943 insertions(+) + +diff --git a/rust/lance-linalg/src/simd/f32.rs b/rust/lance-linalg/src/simd/f32.rs +index 434a1ef..141185d 100644 +--- a/rust/lance-linalg/src/simd/f32.rs ++++ b/rust/lance-linalg/src/simd/f32.rs +@@ -35,6 +35,15 @@ pub struct f32x8(float32x4x2_t); + #[derive(Clone, Copy)] + pub struct f32x8(v8f32); + ++#[allow(non_camel_case_types)] ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++#[derive(Clone, Copy)] ++pub struct f32x8([f32; 8]); ++ + impl std::fmt::Debug for f32x8 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut arr = [0.0_f32; 8]; +@@ -45,6 +54,7 @@ impl std::fmt::Debug for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl f32x8 { + /// Gather 8 f32 values from `slice` at the offsets in `indices`. + /// +@@ -163,6 +173,7 @@ impl<'a> From<&'a [f32; 8]> for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SIMD for f32x8 { + fn splat(val: f32) -> Self { + #[cfg(target_arch = "x86_64")] +@@ -371,6 +382,7 @@ impl SIMD for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl FloatSimd for f32x8 { + fn multiply_add(&mut self, a: Self, b: Self) { + #[cfg(target_arch = "x86_64")] +@@ -389,6 +401,7 @@ impl FloatSimd for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Add for f32x8 { + type Output = Self; + +@@ -412,6 +425,7 @@ impl Add for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl AddAssign for f32x8 { + #[inline] + fn add_assign(&mut self, rhs: Self) { +@@ -431,6 +445,7 @@ impl AddAssign for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Sub for f32x8 { + type Output = Self; + +@@ -454,6 +469,7 @@ impl Sub for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SubAssign for f32x8 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { +@@ -473,6 +489,7 @@ impl SubAssign for f32x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Mul for f32x8 { + type Output = Self; + +@@ -521,6 +538,15 @@ pub struct f32x16(float32x4x4_t); + #[derive(Clone, Copy)] + pub struct f32x16(v8f32, v8f32); + ++#[allow(non_camel_case_types)] ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++#[derive(Clone, Copy)] ++pub struct f32x16([f32; 16]); ++ + impl std::fmt::Debug for f32x16 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut arr = [0.0_f32; 16]; +@@ -548,6 +574,7 @@ impl<'a> From<&'a [f32; 16]> for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SIMD for f32x16 { + #[inline] + fn splat(val: f32) -> Self { +@@ -795,6 +822,7 @@ impl SIMD for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl FloatSimd for f32x16 { + #[inline] + fn multiply_add(&mut self, a: Self, b: Self) { +@@ -818,6 +846,7 @@ impl FloatSimd for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Add for f32x16 { + type Output = Self; + +@@ -843,6 +872,7 @@ impl Add for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl AddAssign for f32x16 { + #[inline] + fn add_assign(&mut self, rhs: Self) { +@@ -866,6 +896,7 @@ impl AddAssign for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Mul for f32x16 { + type Output = Self; + +@@ -891,6 +922,7 @@ impl Mul for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Sub for f32x16 { + type Output = Self; + +@@ -916,6 +948,7 @@ impl Sub for f32x16 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SubAssign for f32x16 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { +@@ -939,6 +972,356 @@ impl SubAssign for f32x16 { + } + } + ++ ++// --------------------------------------------------------------------------- ++// Portable scalar fallback for architectures with no dedicated SIMD kernel ++// above (e.g. riscv64). Correctness-first, no intrinsics. ++// --------------------------------------------------------------------------- ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl f32x8 { ++ /// Gather 8 f32 values from `slice` at the offsets in `indices`. ++ /// ++ /// # Panics ++ /// ++ /// If any index is negative or lands outside `slice`. ++ #[inline] ++ pub fn gather(slice: &[f32], indices: &[i32; 8]) -> Self { ++ let values = indices.map(|i| slice[i as usize]); ++ unsafe { Self::load_unaligned(values.as_ptr()) } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SIMD for f32x8 { ++ #[inline] ++ fn splat(val: f32) -> Self { ++ Self([val; 8]) ++ } ++ ++ #[inline] ++ fn zeros() -> Self { ++ Self([0.0; 8]) ++ } ++ ++ #[inline] ++ unsafe fn load(ptr: *const f32) -> Self { ++ unsafe { Self::load_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn load_unaligned(ptr: *const f32) -> Self { ++ let mut arr = [0.0_f32; 8]; ++ unsafe { ++ std::ptr::copy_nonoverlapping(ptr, arr.as_mut_ptr(), 8); ++ } ++ Self(arr) ++ } ++ ++ #[inline] ++ unsafe fn store(&self, ptr: *mut f32) { ++ unsafe { self.store_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn store_unaligned(&self, ptr: *mut f32) { ++ unsafe { ++ std::ptr::copy_nonoverlapping(self.0.as_ptr(), ptr, 8); ++ } ++ } ++ ++ #[inline] ++ fn reduce_sum(&self) -> f32 { ++ self.0.iter().sum() ++ } ++ ++ #[inline] ++ fn reduce_min(&self) -> f32 { ++ self.0.iter().copied().fold(f32::INFINITY, f32::min) ++ } ++ ++ #[inline] ++ fn min(&self, rhs: &Self) -> Self { ++ let mut result = [0.0_f32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i].min(rhs.0[i]); ++ } ++ Self(result) ++ } ++ ++ #[inline] ++ fn find(&self, val: f32) -> Option { ++ self.0.iter().position(|&x| x == val).map(|i| i as i32) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl FloatSimd for f32x8 { ++ #[inline] ++ fn multiply_add(&mut self, a: Self, b: Self) { ++ for i in 0..8 { ++ self.0[i] += a.0[i] * b.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Add for f32x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn add(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i] + rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl AddAssign for f32x8 { ++ #[inline] ++ fn add_assign(&mut self, rhs: Self) { ++ for i in 0..8 { ++ self.0[i] += rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Sub for f32x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn sub(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i] - rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SubAssign for f32x8 { ++ #[inline] ++ fn sub_assign(&mut self, rhs: Self) { ++ for i in 0..8 { ++ self.0[i] -= rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Mul for f32x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn mul(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i] * rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SIMD for f32x16 { ++ #[inline] ++ fn splat(val: f32) -> Self { ++ Self([val; 16]) ++ } ++ ++ #[inline] ++ fn zeros() -> Self { ++ Self([0.0; 16]) ++ } ++ ++ #[inline] ++ unsafe fn load(ptr: *const f32) -> Self { ++ unsafe { Self::load_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn load_unaligned(ptr: *const f32) -> Self { ++ let mut arr = [0.0_f32; 16]; ++ unsafe { ++ std::ptr::copy_nonoverlapping(ptr, arr.as_mut_ptr(), 16); ++ } ++ Self(arr) ++ } ++ ++ #[inline] ++ unsafe fn store(&self, ptr: *mut f32) { ++ unsafe { self.store_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn store_unaligned(&self, ptr: *mut f32) { ++ unsafe { ++ std::ptr::copy_nonoverlapping(self.0.as_ptr(), ptr, 16); ++ } ++ } ++ ++ #[inline] ++ fn reduce_sum(&self) -> f32 { ++ self.0.iter().sum() ++ } ++ ++ #[inline] ++ fn reduce_min(&self) -> f32 { ++ self.0.iter().copied().fold(f32::INFINITY, f32::min) ++ } ++ ++ #[inline] ++ fn min(&self, rhs: &Self) -> Self { ++ let mut result = [0.0_f32; 16]; ++ for i in 0..16 { ++ result[i] = self.0[i].min(rhs.0[i]); ++ } ++ Self(result) ++ } ++ ++ #[inline] ++ fn find(&self, val: f32) -> Option { ++ self.0.iter().position(|&x| x == val).map(|i| i as i32) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl FloatSimd for f32x16 { ++ #[inline] ++ fn multiply_add(&mut self, a: Self, b: Self) { ++ for i in 0..16 { ++ self.0[i] += a.0[i] * b.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Add for f32x16 { ++ type Output = Self; ++ ++ #[inline] ++ fn add(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f32; 16]; ++ for i in 0..16 { ++ result[i] = self.0[i] + rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl AddAssign for f32x16 { ++ #[inline] ++ fn add_assign(&mut self, rhs: Self) { ++ for i in 0..16 { ++ self.0[i] += rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Sub for f32x16 { ++ type Output = Self; ++ ++ #[inline] ++ fn sub(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f32; 16]; ++ for i in 0..16 { ++ result[i] = self.0[i] - rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SubAssign for f32x16 { ++ #[inline] ++ fn sub_assign(&mut self, rhs: Self) { ++ for i in 0..16 { ++ self.0[i] -= rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Mul for f32x16 { ++ type Output = Self; ++ ++ #[inline] ++ fn mul(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f32; 16]; ++ for i in 0..16 { ++ result[i] = self.0[i] * rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ + #[cfg(test)] + mod tests { + +diff --git a/rust/lance-linalg/src/simd/f64.rs b/rust/lance-linalg/src/simd/f64.rs +index 129b2f0..a2b5f02 100644 +--- a/rust/lance-linalg/src/simd/f64.rs ++++ b/rust/lance-linalg/src/simd/f64.rs +@@ -33,6 +33,15 @@ pub struct f64x4(float64x2x2_t); + #[derive(Clone, Copy)] + pub struct f64x4(v4f64); + ++#[allow(non_camel_case_types)] ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++#[derive(Clone, Copy)] ++pub struct f64x4([f64; 4]); ++ + impl std::fmt::Debug for f64x4 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut arr = [0.0_f64; 4]; +@@ -60,6 +69,7 @@ impl<'a> From<&'a [f64; 4]> for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SIMD for f64x4 { + fn splat(val: f64) -> Self { + #[cfg(target_arch = "x86_64")] +@@ -231,6 +241,7 @@ impl SIMD for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl FloatSimd for f64x4 { + fn multiply_add(&mut self, a: Self, b: Self) { + #[cfg(target_arch = "x86_64")] +@@ -249,6 +260,7 @@ impl FloatSimd for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Add for f64x4 { + type Output = Self; + +@@ -272,6 +284,7 @@ impl Add for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl AddAssign for f64x4 { + #[inline] + fn add_assign(&mut self, rhs: Self) { +@@ -291,6 +304,7 @@ impl AddAssign for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Sub for f64x4 { + type Output = Self; + +@@ -314,6 +328,7 @@ impl Sub for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SubAssign for f64x4 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { +@@ -333,6 +348,7 @@ impl SubAssign for f64x4 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Mul for f64x4 { + type Output = Self; + +@@ -382,6 +398,15 @@ pub struct f64x8(float64x2x2_t, float64x2x2_t); + #[derive(Clone, Copy)] + pub struct f64x8(v4f64, v4f64); + ++#[allow(non_camel_case_types)] ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++#[derive(Clone, Copy)] ++pub struct f64x8([f64; 8]); ++ + impl std::fmt::Debug for f64x8 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut arr = [0.0_f64; 8]; +@@ -409,6 +434,7 @@ impl<'a> From<&'a [f64; 8]> for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SIMD for f64x8 { + #[inline] + fn splat(val: f64) -> Self { +@@ -602,6 +628,7 @@ impl SIMD for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl FloatSimd for f64x8 { + #[inline] + fn multiply_add(&mut self, a: Self, b: Self) { +@@ -625,6 +652,7 @@ impl FloatSimd for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Add for f64x8 { + type Output = Self; + +@@ -648,6 +676,7 @@ impl Add for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl AddAssign for f64x8 { + #[inline] + fn add_assign(&mut self, rhs: Self) { +@@ -671,6 +700,7 @@ impl AddAssign for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Mul for f64x8 { + type Output = Self; + +@@ -694,6 +724,7 @@ impl Mul for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl Sub for f64x8 { + type Output = Self; + +@@ -717,6 +748,7 @@ impl Sub for f64x8 { + } + } + ++#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64"))] + impl SubAssign for f64x8 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { +@@ -740,6 +772,338 @@ impl SubAssign for f64x8 { + } + } + ++ ++// --------------------------------------------------------------------------- ++// Portable scalar fallback for architectures with no dedicated SIMD kernel ++// above (e.g. riscv64). Correctness-first, no intrinsics. ++// --------------------------------------------------------------------------- ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SIMD for f64x4 { ++ #[inline] ++ fn splat(val: f64) -> Self { ++ Self([val; 4]) ++ } ++ ++ #[inline] ++ fn zeros() -> Self { ++ Self([0.0; 4]) ++ } ++ ++ #[inline] ++ unsafe fn load(ptr: *const f64) -> Self { ++ unsafe { Self::load_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn load_unaligned(ptr: *const f64) -> Self { ++ let mut arr = [0.0_f64; 4]; ++ unsafe { ++ std::ptr::copy_nonoverlapping(ptr, arr.as_mut_ptr(), 4); ++ } ++ Self(arr) ++ } ++ ++ #[inline] ++ unsafe fn store(&self, ptr: *mut f64) { ++ unsafe { self.store_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn store_unaligned(&self, ptr: *mut f64) { ++ unsafe { ++ std::ptr::copy_nonoverlapping(self.0.as_ptr(), ptr, 4); ++ } ++ } ++ ++ #[inline] ++ fn reduce_sum(&self) -> f64 { ++ self.0.iter().sum() ++ } ++ ++ #[inline] ++ fn reduce_min(&self) -> f64 { ++ self.0.iter().copied().fold(f64::INFINITY, f64::min) ++ } ++ ++ #[inline] ++ fn min(&self, rhs: &Self) -> Self { ++ let mut result = [0.0_f64; 4]; ++ for i in 0..4 { ++ result[i] = self.0[i].min(rhs.0[i]); ++ } ++ Self(result) ++ } ++ ++ #[inline] ++ fn find(&self, val: f64) -> Option { ++ self.0.iter().position(|&x| x == val).map(|i| i as i32) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl FloatSimd for f64x4 { ++ #[inline] ++ fn multiply_add(&mut self, a: Self, b: Self) { ++ for i in 0..4 { ++ self.0[i] += a.0[i] * b.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Add for f64x4 { ++ type Output = Self; ++ ++ #[inline] ++ fn add(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f64; 4]; ++ for i in 0..4 { ++ result[i] = self.0[i] + rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl AddAssign for f64x4 { ++ #[inline] ++ fn add_assign(&mut self, rhs: Self) { ++ for i in 0..4 { ++ self.0[i] += rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Sub for f64x4 { ++ type Output = Self; ++ ++ #[inline] ++ fn sub(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f64; 4]; ++ for i in 0..4 { ++ result[i] = self.0[i] - rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SubAssign for f64x4 { ++ #[inline] ++ fn sub_assign(&mut self, rhs: Self) { ++ for i in 0..4 { ++ self.0[i] -= rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Mul for f64x4 { ++ type Output = Self; ++ ++ #[inline] ++ fn mul(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f64; 4]; ++ for i in 0..4 { ++ result[i] = self.0[i] * rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SIMD for f64x8 { ++ #[inline] ++ fn splat(val: f64) -> Self { ++ Self([val; 8]) ++ } ++ ++ #[inline] ++ fn zeros() -> Self { ++ Self([0.0; 8]) ++ } ++ ++ #[inline] ++ unsafe fn load(ptr: *const f64) -> Self { ++ unsafe { Self::load_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn load_unaligned(ptr: *const f64) -> Self { ++ let mut arr = [0.0_f64; 8]; ++ unsafe { ++ std::ptr::copy_nonoverlapping(ptr, arr.as_mut_ptr(), 8); ++ } ++ Self(arr) ++ } ++ ++ #[inline] ++ unsafe fn store(&self, ptr: *mut f64) { ++ unsafe { self.store_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn store_unaligned(&self, ptr: *mut f64) { ++ unsafe { ++ std::ptr::copy_nonoverlapping(self.0.as_ptr(), ptr, 8); ++ } ++ } ++ ++ #[inline] ++ fn reduce_sum(&self) -> f64 { ++ self.0.iter().sum() ++ } ++ ++ #[inline] ++ fn reduce_min(&self) -> f64 { ++ self.0.iter().copied().fold(f64::INFINITY, f64::min) ++ } ++ ++ #[inline] ++ fn min(&self, rhs: &Self) -> Self { ++ let mut result = [0.0_f64; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i].min(rhs.0[i]); ++ } ++ Self(result) ++ } ++ ++ #[inline] ++ fn find(&self, val: f64) -> Option { ++ self.0.iter().position(|&x| x == val).map(|i| i as i32) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl FloatSimd for f64x8 { ++ #[inline] ++ fn multiply_add(&mut self, a: Self, b: Self) { ++ for i in 0..8 { ++ self.0[i] += a.0[i] * b.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Add for f64x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn add(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f64; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i] + rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl AddAssign for f64x8 { ++ #[inline] ++ fn add_assign(&mut self, rhs: Self) { ++ for i in 0..8 { ++ self.0[i] += rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Sub for f64x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn sub(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f64; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i] - rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SubAssign for f64x8 { ++ #[inline] ++ fn sub_assign(&mut self, rhs: Self) { ++ for i in 0..8 { ++ self.0[i] -= rhs.0[i]; ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Mul for f64x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn mul(self, rhs: Self) -> Self::Output { ++ let mut result = [0.0_f64; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i] * rhs.0[i]; ++ } ++ Self(result) ++ } ++} ++ + #[cfg(test)] + mod tests { + use super::*; +diff --git a/rust/lance-linalg/src/simd/i32.rs b/rust/lance-linalg/src/simd/i32.rs +index 6e08129..0b6dc14 100644 +--- a/rust/lance-linalg/src/simd/i32.rs ++++ b/rust/lance-linalg/src/simd/i32.rs +@@ -38,6 +38,17 @@ pub struct i32x8(int32x4x2_t); + #[derive(Clone, Copy)] + pub struct i32x8(v8i32); + ++/// Portable scalar fallback for architectures with no dedicated SIMD kernel ++/// above (e.g. riscv64). Correctness-first, no intrinsics. ++#[allow(non_camel_case_types)] ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++#[derive(Clone, Copy)] ++pub struct i32x8([i32; 8]); ++ + impl std::fmt::Debug for i32x8 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut arr = [0; 8]; +@@ -65,6 +76,11 @@ impl From<&[i32; 8]> for i32x8 { + } + } + ++#[cfg(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++))] + impl SIMD for i32x8 { + #[inline] + fn splat(val: i32) -> Self { +@@ -223,6 +239,11 @@ impl SIMD for i32x8 { + } + } + ++#[cfg(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++))] + impl Add for i32x8 { + type Output = Self; + +@@ -246,6 +267,11 @@ impl Add for i32x8 { + } + } + ++#[cfg(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++))] + impl AddAssign for i32x8 { + #[inline] + fn add_assign(&mut self, rhs: Self) { +@@ -265,6 +291,11 @@ impl AddAssign for i32x8 { + } + } + ++#[cfg(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++))] + impl Sub for i32x8 { + type Output = Self; + +@@ -288,6 +319,11 @@ impl Sub for i32x8 { + } + } + ++#[cfg(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++))] + impl SubAssign for i32x8 { + #[inline] + fn sub_assign(&mut self, rhs: Self) { +@@ -307,6 +343,11 @@ impl SubAssign for i32x8 { + } + } + ++#[cfg(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++))] + impl Mul for i32x8 { + type Output = Self; + +@@ -343,6 +384,161 @@ impl Mul for i32x8 { + } + } + ++// Portable scalar fallback for architectures with no dedicated SIMD kernel ++// above (e.g. riscv64). Correctness-first, no intrinsics. Add/Sub/Mul use ++// wrapping arithmetic to mirror the silent-wraparound semantics of the ++// `_mm256_{add,sub,mul}_epi32` / `vaddq_s32` / `lasx_xv{add,sub,mul}_w` ++// instructions above. ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SIMD for i32x8 { ++ #[inline] ++ fn splat(val: i32) -> Self { ++ Self([val; 8]) ++ } ++ ++ #[inline] ++ fn zeros() -> Self { ++ Self([0; 8]) ++ } ++ ++ #[inline] ++ unsafe fn load(ptr: *const i32) -> Self { ++ unsafe { Self::load_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn load_unaligned(ptr: *const i32) -> Self { ++ let mut arr = [0_i32; 8]; ++ unsafe { ++ std::ptr::copy_nonoverlapping(ptr, arr.as_mut_ptr(), 8); ++ } ++ Self(arr) ++ } ++ ++ #[inline] ++ unsafe fn store(&self, ptr: *mut i32) { ++ unsafe { self.store_unaligned(ptr) } ++ } ++ ++ #[inline] ++ unsafe fn store_unaligned(&self, ptr: *mut i32) { ++ unsafe { ++ std::ptr::copy_nonoverlapping(self.0.as_ptr(), ptr, 8); ++ } ++ } ++ ++ #[inline] ++ fn reduce_sum(&self) -> i32 { ++ self.0.iter().sum() ++ } ++ ++ #[inline] ++ fn reduce_min(&self) -> i32 { ++ todo!() ++ } ++ ++ #[inline] ++ fn min(&self, rhs: &Self) -> Self { ++ let mut result = [0_i32; 8]; ++ for i in 0..8 { ++ result[i] = std::cmp::min(self.0[i], rhs.0[i]); ++ } ++ Self(result) ++ } ++ ++ #[inline] ++ fn find(&self, val: i32) -> Option { ++ self.0.iter().position(|&x| x == val).map(|i| i as i32) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Add for i32x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn add(self, rhs: Self) -> Self::Output { ++ let mut result = [0_i32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i].wrapping_add(rhs.0[i]); ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl AddAssign for i32x8 { ++ #[inline] ++ fn add_assign(&mut self, rhs: Self) { ++ for i in 0..8 { ++ self.0[i] = self.0[i].wrapping_add(rhs.0[i]); ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Sub for i32x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn sub(self, rhs: Self) -> Self::Output { ++ let mut result = [0_i32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i].wrapping_sub(rhs.0[i]); ++ } ++ Self(result) ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl SubAssign for i32x8 { ++ #[inline] ++ fn sub_assign(&mut self, rhs: Self) { ++ for i in 0..8 { ++ self.0[i] = self.0[i].wrapping_sub(rhs.0[i]); ++ } ++ } ++} ++ ++#[cfg(not(any( ++ target_arch = "x86_64", ++ target_arch = "aarch64", ++ target_arch = "loongarch64" ++)))] ++impl Mul for i32x8 { ++ type Output = Self; ++ ++ #[inline] ++ fn mul(self, rhs: Self) -> Self::Output { ++ let mut result = [0_i32; 8]; ++ for i in 0..8 { ++ result[i] = self.0[i].wrapping_mul(rhs.0[i]); ++ } ++ Self(result) ++ } ++} ++ + #[cfg(test)] + mod tests { + use super::*; +-- +2.50.1 (Apple Git-155) +