diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a1ff027dceee..edc8f43c9038 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -93,8 +93,12 @@ jobs: git switch --force-create "$branch" "$base" || return 1 fi + # The base copy of the package YAML tells --declare which versions main + # already has, so it does not copy patches for those. + git show "$base:docs/packages/$slug.yaml" > "$RUNNER_TEMP/base-$slug.yaml" 2>/dev/null \ + || : > "$RUNNER_TEMP/base-$slug.yaml" uv run --with-requirements ci_scripts/requirements.txt python3 \ - ci_scripts/check_versions.py --declare "$package" || return 1 + ci_scripts/check_versions.py --declare --base-file "$RUNNER_TEMP/base-$slug.yaml" "$package" || return 1 if git diff --quiet -- docs/packages patches ci_scripts/packages.txt ci_scripts/deprecated.txt; then echo "$package: nothing to declare" diff --git a/ci_scripts/check_versions.py b/ci_scripts/check_versions.py index bfab4db004e9..f02b9b51a46f 100755 --- a/ci_scripts/check_versions.py +++ b/ci_scripts/check_versions.py @@ -26,6 +26,7 @@ import requests import yaml +from pending_versions import base_versions from update_doc import normalize_name, version_blocks @@ -430,6 +431,7 @@ def check_package( package: str, since: Optional[datetime] = None, declare: bool = False, + base_file: Optional[Path] = None, ) -> Dict[str, any]: """ Check a single package against PyPI and optionally apply the outcome to @@ -465,7 +467,7 @@ def check_package( marked = mark_yanked(package_yaml(package), yanked) print(f" [+] marked {', '.join(f'v{v}' for v in marked)} yanked in {package_yaml(package).relative_to(ROOT_DIR)}") - result = check_upgrade(package, package_data, pypi_info, registry_version, since, declare) + result = check_upgrade(package, package_data, pypi_info, registry_version, since, declare, base_file) result["yanked"] = yanked return result @@ -477,6 +479,7 @@ def check_upgrade( registry_version: str, since: Optional[datetime], declare: bool, + base_file: Optional[Path] = None, ) -> Dict[str, any]: """Classify the package as up to date, deprecatable or upgradable, and act on it.""" pypi_version = get_pypi_latest_version(pypi_info) @@ -514,7 +517,16 @@ def check_upgrade( if declare: added = declare_versions(package_yaml(package), new_versions) print(f" [+] declared {', '.join(f'v{v}' for v in added)} in {package_yaml(package).relative_to(ROOT_DIR)}") - for created in carry_over_patches(package, added, declared): + # The package branch can be stale (it is not rebased while its PR is + # open), so a version it adds may already be on the base branch, where + # it was built with or without patches. Only a version the base does + # not declare is new enough to get the previous version's patches. + on_base = base_versions(base_file) if base_file else set() + fresh = [v for v in added if v not in on_base] + if len(fresh) < len(added): + print(f" [=] {package}: {', '.join(f'v{v}' for v in added if v not in fresh)} " + "already declared on the base branch, not copying patches") + for created in carry_over_patches(package, fresh, declared): print(f" [+] copied the patches of the previous version to patches/{created}") return { "status": "need_upgrade", @@ -804,6 +816,9 @@ def main(): parser.add_argument("--declare", action="store_true", help="Apply the outcome to the working tree: declare new versions and mark yanked ones in " "docs/packages/.yaml, deprecate packages in the YAML, packages.txt and deprecated.txt") + parser.add_argument("--base-file", type=Path, metavar="FILE", + help="Base branch copy of the package YAML (as for pending_versions.py): with --declare, " + "only versions it does not declare get the previous version's patches") parser.add_argument("--report", type=Path, metavar="FILE", help="Write a Markdown summary of the whole run") parser.add_argument("--report-dir", type=Path, metavar="DIR", help="Write one Markdown PR body per changed package, plus a packages.json manifest, " @@ -828,7 +843,7 @@ def main(): results = [] for package in packages: try: - results.append(check_package(package, since=args.since, declare=args.declare)) + results.append(check_package(package, since=args.since, declare=args.declare, base_file=args.base_file)) except KeyboardInterrupt: print("\n[!] Interrupted by user") sys.exit(1) diff --git a/patches/lancedb/0.40.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch b/patches/lancedb/0.40.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch deleted file mode 100644 index 4be13c597012..000000000000 --- a/patches/lancedb/0.40.0/0001-cargo-redirect-lance-core-lance-linalg-to-a-riscv64.patch +++ /dev/null @@ -1,64 +0,0 @@ -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.40.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch b/patches/lancedb/0.40.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch deleted file mode 100644 index 0708da295a51..000000000000 --- a/patches/lancedb/0.40.0/0002-lance-linalg-add-a-portable-riscv64-simd-fallback.patch +++ /dev/null @@ -1,1292 +0,0 @@ -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) -