Files
ruvnet--RuView/vendor/ruvector/crates/ruvector-delta-wasm/src/simd.rs
T

365 lines
9.2 KiB
Rust

//! SIMD-accelerated operations for WASM
//!
//! Provides SIMD-optimized vector operations when wasm32-simd128 is available.
/// Check if SIMD is available at runtime
pub fn simd_available() -> bool {
#[cfg(target_feature = "simd128")]
{
true
}
#[cfg(not(target_feature = "simd128"))]
{
false
}
}
/// SIMD-accelerated vector addition (a += b)
#[cfg(target_feature = "simd128")]
pub fn simd_add_assign(a: &mut [f32], b: &[f32]) {
use core::arch::wasm32::*;
assert_eq!(a.len(), b.len());
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_ptr = a.as_mut_ptr().add(offset);
let b_ptr = b.as_ptr().add(offset);
let a_vec = v128_load(a_ptr as *const v128);
let b_vec = v128_load(b_ptr as *const v128);
let result = f32x4_add(a_vec, b_vec);
v128_store(a_ptr as *mut v128, result);
}
}
// Handle remainder
for i in (chunks * 4)..a.len() {
a[i] += b[i];
}
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_add_assign(a: &mut [f32], b: &[f32]) {
for (av, bv) in a.iter_mut().zip(b.iter()) {
*av += *bv;
}
}
/// SIMD-accelerated vector subtraction (a -= b)
#[cfg(target_feature = "simd128")]
pub fn simd_sub_assign(a: &mut [f32], b: &[f32]) {
use core::arch::wasm32::*;
assert_eq!(a.len(), b.len());
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_ptr = a.as_mut_ptr().add(offset);
let b_ptr = b.as_ptr().add(offset);
let a_vec = v128_load(a_ptr as *const v128);
let b_vec = v128_load(b_ptr as *const v128);
let result = f32x4_sub(a_vec, b_vec);
v128_store(a_ptr as *mut v128, result);
}
}
for i in (chunks * 4)..a.len() {
a[i] -= b[i];
}
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_sub_assign(a: &mut [f32], b: &[f32]) {
for (av, bv) in a.iter_mut().zip(b.iter()) {
*av -= *bv;
}
}
/// SIMD-accelerated vector scaling (a *= scalar)
#[cfg(target_feature = "simd128")]
pub fn simd_scale(a: &mut [f32], scalar: f32) {
use core::arch::wasm32::*;
let scalar_vec = f32x4_splat(scalar);
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_ptr = a.as_mut_ptr().add(offset);
let a_vec = v128_load(a_ptr as *const v128);
let result = f32x4_mul(a_vec, scalar_vec);
v128_store(a_ptr as *mut v128, result);
}
}
for i in (chunks * 4)..a.len() {
a[i] *= scalar;
}
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_scale(a: &mut [f32], scalar: f32) {
for v in a.iter_mut() {
*v *= scalar;
}
}
/// SIMD-accelerated dot product
#[cfg(target_feature = "simd128")]
pub fn simd_dot(a: &[f32], b: &[f32]) -> f32 {
use core::arch::wasm32::*;
assert_eq!(a.len(), b.len());
let chunks = a.len() / 4;
let mut sum_vec = f32x4_splat(0.0);
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_vec = v128_load(a.as_ptr().add(offset) as *const v128);
let b_vec = v128_load(b.as_ptr().add(offset) as *const v128);
let prod = f32x4_mul(a_vec, b_vec);
sum_vec = f32x4_add(sum_vec, prod);
}
}
// Horizontal sum
let sum_array: [f32; 4] = unsafe { core::mem::transmute(sum_vec) };
let mut sum = sum_array[0] + sum_array[1] + sum_array[2] + sum_array[3];
// Handle remainder
for i in (chunks * 4)..a.len() {
sum += a[i] * b[i];
}
sum
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_dot(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
/// SIMD-accelerated L2 norm squared
#[cfg(target_feature = "simd128")]
pub fn simd_l2_norm_squared(a: &[f32]) -> f32 {
use core::arch::wasm32::*;
let chunks = a.len() / 4;
let mut sum_vec = f32x4_splat(0.0);
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_vec = v128_load(a.as_ptr().add(offset) as *const v128);
let sq = f32x4_mul(a_vec, a_vec);
sum_vec = f32x4_add(sum_vec, sq);
}
}
let sum_array: [f32; 4] = unsafe { core::mem::transmute(sum_vec) };
let mut sum = sum_array[0] + sum_array[1] + sum_array[2] + sum_array[3];
for i in (chunks * 4)..a.len() {
sum += a[i] * a[i];
}
sum
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_l2_norm_squared(a: &[f32]) -> f32 {
a.iter().map(|x| x * x).sum()
}
/// SIMD-accelerated element-wise difference (result = a - b)
#[cfg(target_feature = "simd128")]
pub fn simd_diff(a: &[f32], b: &[f32], result: &mut [f32]) {
use core::arch::wasm32::*;
assert_eq!(a.len(), b.len());
assert_eq!(a.len(), result.len());
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_vec = v128_load(a.as_ptr().add(offset) as *const v128);
let b_vec = v128_load(b.as_ptr().add(offset) as *const v128);
let diff = f32x4_sub(a_vec, b_vec);
v128_store(result.as_mut_ptr().add(offset) as *mut v128, diff);
}
}
for i in (chunks * 4)..a.len() {
result[i] = a[i] - b[i];
}
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_diff(a: &[f32], b: &[f32], result: &mut [f32]) {
for i in 0..a.len() {
result[i] = a[i] - b[i];
}
}
/// SIMD-accelerated element-wise absolute value
#[cfg(target_feature = "simd128")]
pub fn simd_abs(a: &mut [f32]) {
use core::arch::wasm32::*;
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_ptr = a.as_mut_ptr().add(offset);
let a_vec = v128_load(a_ptr as *const v128);
let result = f32x4_abs(a_vec);
v128_store(a_ptr as *mut v128, result);
}
}
for i in (chunks * 4)..a.len() {
a[i] = a[i].abs();
}
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_abs(a: &mut [f32]) {
for v in a.iter_mut() {
*v = v.abs();
}
}
/// SIMD-accelerated clamp
#[cfg(target_feature = "simd128")]
pub fn simd_clamp(a: &mut [f32], min: f32, max: f32) {
use core::arch::wasm32::*;
let min_vec = f32x4_splat(min);
let max_vec = f32x4_splat(max);
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_ptr = a.as_mut_ptr().add(offset);
let a_vec = v128_load(a_ptr as *const v128);
let clamped = f32x4_max(f32x4_min(a_vec, max_vec), min_vec);
v128_store(a_ptr as *mut v128, clamped);
}
}
for i in (chunks * 4)..a.len() {
a[i] = a[i].clamp(min, max);
}
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_clamp(a: &mut [f32], min: f32, max: f32) {
for v in a.iter_mut() {
*v = v.clamp(min, max);
}
}
/// Count non-zero elements with SIMD acceleration
#[cfg(target_feature = "simd128")]
pub fn simd_count_nonzero(a: &[f32], epsilon: f32) -> usize {
use core::arch::wasm32::*;
let eps_vec = f32x4_splat(epsilon);
let neg_eps_vec = f32x4_splat(-epsilon);
let chunks = a.len() / 4;
let mut count = 0usize;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let a_vec = v128_load(a.as_ptr().add(offset) as *const v128);
// Check if |a| > epsilon
let gt_eps = f32x4_gt(a_vec, eps_vec);
let lt_neg_eps = f32x4_lt(a_vec, neg_eps_vec);
let nonzero = v128_or(gt_eps, lt_neg_eps);
// Convert to bitmask and count
let mask = i32x4_bitmask(nonzero) as u8;
count += mask.count_ones() as usize;
}
}
// Handle remainder
for i in (chunks * 4)..a.len() {
if a[i].abs() > epsilon {
count += 1;
}
}
count
}
#[cfg(not(target_feature = "simd128"))]
pub fn simd_count_nonzero(a: &[f32], epsilon: f32) -> usize {
a.iter().filter(|v| v.abs() > epsilon).count()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_assign() {
let mut a = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let b = vec![1.0f32, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
simd_add_assign(&mut a, &b);
assert!((a[0] - 2.0).abs() < 1e-6);
assert!((a[7] - 9.0).abs() < 1e-6);
}
#[test]
fn test_dot() {
let a = vec![1.0f32, 2.0, 3.0, 4.0];
let b = vec![1.0f32, 1.0, 1.0, 1.0];
let result = simd_dot(&a, &b);
assert!((result - 10.0).abs() < 1e-6);
}
#[test]
fn test_l2_norm_squared() {
let a = vec![3.0f32, 4.0];
let result = simd_l2_norm_squared(&a);
assert!((result - 25.0).abs() < 1e-6);
}
#[test]
fn test_scale() {
let mut a = vec![1.0f32, 2.0, 3.0, 4.0];
simd_scale(&mut a, 2.0);
assert!((a[0] - 2.0).abs() < 1e-6);
assert!((a[3] - 8.0).abs() < 1e-6);
}
#[test]
fn test_count_nonzero() {
let a = vec![1.0f32, 0.0, 2.0, 0.0, 3.0, 0.0, 4.0, 0.0];
let count = simd_count_nonzero(&a, 1e-7);
assert_eq!(count, 4);
}
}