diff --git a/.github/workflows/build-lancedb.yml b/.github/workflows/build-lancedb.yml index 683d76042a3..813f46d0344 100644 --- a/.github/workflows/build-lancedb.yml +++ b/.github/workflows/build-lancedb.yml @@ -77,16 +77,23 @@ jobs: - name: Apply lancedb patches run: git apply -v python-wheels/patches/lancedb/${{ env.LANCEDB_VERSION }}/0001*.patch + # lancedb pins lance-core/lance-linalg to a different exact version per + # release (10.0.0 for v0.37.1, 11.0.0 for v0.38.0, 12.0.0 for v0.39.0), + # so the sibling checkout below must track whatever this lancedb tag's + # own Cargo.toml pins rather than a fixed tag. + - name: Determine the lance-core/lance-linalg version lancedb pins + run: echo "LANCE_VERSION=$(grep -m1 '^lance-core = ' Cargo.toml | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" >> "$GITHUB_ENV" + # lance-core's SIMD_SUPPORT static and lance-linalg's f32x8/f32x16/ # i32x8/f64x4/f64x8 SIMD types compile only for x86_64/aarch64/ # loongarch64 upstream; the 0002 patch adds a portable riscv64 fallback # to this checkout, and Cargo.toml's `[patch.crates-io]` (0001) redirects # both crates here. - - name: Checkout lance v10.0.0 and add a riscv64 SIMD fallback + - name: Checkout lance v${{ env.LANCE_VERSION }} and add a riscv64 SIMD fallback uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: lancedb/lance - ref: v10.0.0 + ref: v${{ env.LANCE_VERSION }} path: lance persist-credentials: false @@ -144,18 +151,38 @@ jobs: CARGO_BUILD_JOBS=2 PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ # Without this pip prefers PyPI's releases, which have no riscv64 - # wheel for these, and source-builds them in the container. - CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,pandas,pyarrow,duckdb,pydantic-core - CIBW_TEST_REQUIRES: pytest pytest-asyncio numpy pandas pyarrow duckdb + # wheel for these, and source-builds them in the container. pylance + # is pinned to our registry's exact published version (not just + # named) per gotcha 30: with PIP_EXTRA_INDEX_URL alone pip picks the + # highest version across both indexes, and PyPI's latest pylance is + # newer than what pypi.riseproject.dev hosts, so an unpinned + # `pylance` would resolve to a PyPI release with no riscv64 wheel + # and source-build the whole lance Rust workspace here. + CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,pandas,pyarrow,duckdb,pydantic-core,pylance + CIBW_TEST_REQUIRES: pytest pytest-asyncio numpy pandas pyarrow duckdb pylance==11.0.0 CIBW_TEST_SOURCES: python/pyproject.toml python/python/tests # test_table.py, test_util.py, test_embeddings.py and # test_namespace_integration.py import `polars` and/or `lance` - # (pylance) unconditionally at module scope, as do - # docs/test_guide_tables.py and docs/test_python.py; neither package - # has a riscv64 wheel and both are large enough Rust projects in - # their own right that building them from source here is out of - # scope, so those six files fail collection and are excluded rather - # than left to error. Otherwise mirrors run_tests/action.yml's + # (pylance) and/or `lance_namespace` unconditionally at module + # scope, as do docs/test_guide_tables.py and docs/test_python.py; + # polars has no riscv64 wheel and is a large enough Rust project in + # its own right that building it from source here is out of scope, + # so those six files fail collection and stay excluded. This was + # not re-checked file-by-file against pylance's newer availability + # below -- only test_blob.py, the one file CI actually reported + # failing here, was fixed rather than excluded. + # + # test_blob.py imports `lance` (the pylance package) unconditionally + # at module scope too, but is new in 0.38.0/0.39.0 (not present in + # 0.37.1, when the six-file exclusion above was established) so it + # was never checked against pylance's availability until now. It + # isn't excluded because pypi.riseproject.dev already hosts a + # pylance riscv64 wheel (see docs/packages/pylance.yaml, added to + # CIBW_TEST_REQUIRES above) and lance's python/python/lance/blob.py + # is unchanged between the v9.0.0 pylance/lance release lancedb's + # own pyproject.toml `tests` extra pins and the v11.0.0 this repo + # publishes, aside from an added BlobFile.read_ranges method + # test_blob.py doesn't use. Otherwise mirrors run_tests/action.yml's # non-integration path (`-m "not slow and not s3_test"`, no docker # compose/localstack). # @@ -179,6 +206,19 @@ jobs: # identically either way) and the tests run and fail anyway, as seen # in CI run 33843581031. `cd python` first puts pyproject.toml back # in the search path so --deselect (and the -m marker filter) work. + # + # test_first_class_function_slice1.py/slice2.py are new (0.38.0/ + # 0.39.0) and hit the exact same class of trap as + # test_pyo3_abi_matches_minimum_supported_python above: they read + # golden fixtures from outside the CIBW_TEST_SOURCES tree -- + # docs/src/python/python.md and rust/lancedb/tests/fixtures/ + # first_class_functions/v1/*.json, both siblings of python/ in the + # full checkout but not copied into the isolated test dir (only + # pyproject.toml and python/tests are) -- and fail with the same + # FileNotFoundError regardless of platform. They assert Python/Rust + # golden-contract and docs consistency, not installed-wheel runtime + # behavior, so ignore both files rather than widen + # CIBW_TEST_SOURCES to mirror docs/ and rust/'s layout. CIBW_TEST_COMMAND: >- cd python && python -m pytest python/tests -vv --durations=30 @@ -189,6 +229,8 @@ jobs: --ignore=python/tests/test_namespace_integration.py --ignore=python/tests/docs/test_guide_tables.py --ignore=python/tests/docs/test_python.py + --ignore=python/tests/test_first_class_function_slice1.py + --ignore=python/tests/test_first_class_function_slice2.py --deselect=python/tests/test_db.py::test_create_table_stable_row_ids_via_storage_options --deselect=python/tests/test_db.py::test_create_table_stable_row_ids_via_storage_options_sync --deselect=python/tests/test_db.py::test_create_table_stable_row_ids_table_level_override diff --git a/docs/packages/lancedb.yaml b/docs/packages/lancedb.yaml index 0009159b229..fb0e0a4745c 100644 --- a/docs/packages/lancedb.yaml +++ b/docs/packages/lancedb.yaml @@ -9,3 +9,5 @@ versions: - filename: lancedb-0.37.1-cp310-abi3-manylinux_2_39_riscv64.whl sha256: 405656a126ed0c70028834c5370463e3e2b5f9b53dc7ef36a6d79b000ee276ac requires-python: '>=3.10' +- version: 0.38.0 +- version: 0.39.0 diff --git a/patches/lancedb/0.38.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch b/patches/lancedb/0.38.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch new file mode 100644 index 00000000000..4be13c59701 --- /dev/null +++ b/patches/lancedb/0.38.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 03 Sep 2026 00:00:00 +0000 +Subject: [PATCH] cargo: redirect lance-core/lance-linalg to a riscv64-patched + checkout + +lance-core's SIMD_SUPPORT static (rust/lance-core/src/utils/cpu.rs) and +lance-linalg's f32x8/f32x16/i32x8/f64x4/f64x8 SIMD types +(rust/lance-linalg/src/simd/{f32,i32,f64}.rs) are compiled for +x86_64/aarch64/loongarch64 only, with no fallback arm for any other +architecture: the SIMD_SUPPORT closure has no trailing expression on +riscv64 ("expected SimdSupport, found ()"), and the SIMD type structs are +defined only under those three `#[cfg(target_arch = ...)]` attributes +while every method implementing them is unconditional, so riscv64 hits +"cannot find type `f32x8` in this scope" as soon as lance-linalg is +built. + +Both crates are pulled in from crates.io at a pinned `=10.0.0`, so the +fix cannot be applied in place. Redirect them to a checkout of the same +v10.0.0 tag carrying a portable scalar fallback for both, see +0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch. `exclude` +keeps that checkout's own [workspace] from being silently absorbed into +this one, which would drop its `[workspace.package]` inheritance +(version, edition, ...) for lance-core and lance-linalg. + +Only the source line of the lance-core/lance-linalg entries changes in +Cargo.lock, so every other dependency stays pinned exactly as upstream +released it. + +Upstream-Status: Inappropriate [redirects two pinned dependencies to a local checkout; the fix itself belongs in lancedb/lance] + +Signed-off-by: Ludovic Henry +--- + Cargo.toml | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/Cargo.toml b/Cargo.toml +index a47e59e..ab6cf5c 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -1,5 +1,10 @@ + [workspace] + members = ["rust/lancedb", "nodejs", "python"] ++# ./lance (see [patch.crates-io] below) carries its own [workspace]; excluding ++# it keeps that workspace independent instead of being silently absorbed into ++# this one, which would drop its `[workspace.package]` inheritance (version, ++# edition, ...) for lance-core and lance-linalg. ++exclude = ["lance"] + resolver = "2" + + [workspace.package] +@@ -80,3 +85,10 @@ debug = false + debug-assertions = false + strip = "debuginfo" + incremental = false ++ ++# lance-core's SIMD_SUPPORT and lance-linalg's f32x8/f32x16/i32x8/f64x4/f64x8 ++# SIMD types compile only for x86_64/aarch64/loongarch64 upstream; ./lance is ++# the same v10.0.0 tag with a riscv64 scalar fallback added to both. ++[patch.crates-io] ++lance-core = { path = "lance/rust/lance-core" } ++lance-linalg = { path = "lance/rust/lance-linalg" } +-- +2.43.0 diff --git a/patches/lancedb/0.38.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch b/patches/lancedb/0.38.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch new file mode 100644 index 00000000000..0708da295a5 --- /dev/null +++ b/patches/lancedb/0.38.0/0002-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) + diff --git a/patches/lancedb/0.39.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch b/patches/lancedb/0.39.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch new file mode 100644 index 00000000000..4be13c59701 --- /dev/null +++ b/patches/lancedb/0.39.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 03 Sep 2026 00:00:00 +0000 +Subject: [PATCH] cargo: redirect lance-core/lance-linalg to a riscv64-patched + checkout + +lance-core's SIMD_SUPPORT static (rust/lance-core/src/utils/cpu.rs) and +lance-linalg's f32x8/f32x16/i32x8/f64x4/f64x8 SIMD types +(rust/lance-linalg/src/simd/{f32,i32,f64}.rs) are compiled for +x86_64/aarch64/loongarch64 only, with no fallback arm for any other +architecture: the SIMD_SUPPORT closure has no trailing expression on +riscv64 ("expected SimdSupport, found ()"), and the SIMD type structs are +defined only under those three `#[cfg(target_arch = ...)]` attributes +while every method implementing them is unconditional, so riscv64 hits +"cannot find type `f32x8` in this scope" as soon as lance-linalg is +built. + +Both crates are pulled in from crates.io at a pinned `=10.0.0`, so the +fix cannot be applied in place. Redirect them to a checkout of the same +v10.0.0 tag carrying a portable scalar fallback for both, see +0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch. `exclude` +keeps that checkout's own [workspace] from being silently absorbed into +this one, which would drop its `[workspace.package]` inheritance +(version, edition, ...) for lance-core and lance-linalg. + +Only the source line of the lance-core/lance-linalg entries changes in +Cargo.lock, so every other dependency stays pinned exactly as upstream +released it. + +Upstream-Status: Inappropriate [redirects two pinned dependencies to a local checkout; the fix itself belongs in lancedb/lance] + +Signed-off-by: Ludovic Henry +--- + Cargo.toml | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/Cargo.toml b/Cargo.toml +index a47e59e..ab6cf5c 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -1,5 +1,10 @@ + [workspace] + members = ["rust/lancedb", "nodejs", "python"] ++# ./lance (see [patch.crates-io] below) carries its own [workspace]; excluding ++# it keeps that workspace independent instead of being silently absorbed into ++# this one, which would drop its `[workspace.package]` inheritance (version, ++# edition, ...) for lance-core and lance-linalg. ++exclude = ["lance"] + resolver = "2" + + [workspace.package] +@@ -80,3 +85,10 @@ debug = false + debug-assertions = false + strip = "debuginfo" + incremental = false ++ ++# lance-core's SIMD_SUPPORT and lance-linalg's f32x8/f32x16/i32x8/f64x4/f64x8 ++# SIMD types compile only for x86_64/aarch64/loongarch64 upstream; ./lance is ++# the same v10.0.0 tag with a riscv64 scalar fallback added to both. ++[patch.crates-io] ++lance-core = { path = "lance/rust/lance-core" } ++lance-linalg = { path = "lance/rust/lance-linalg" } +-- +2.43.0 diff --git a/patches/lancedb/0.39.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch b/patches/lancedb/0.39.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch new file mode 100644 index 00000000000..0708da295a5 --- /dev/null +++ b/patches/lancedb/0.39.0/0002-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) +