Skip to content

Commit 1420f13

Browse files
committed
feat(simd): Tier 3 — U16x32 lane type + movemask_epi8
Completes the seismon rasterizer wishlist (all 3 tiers shipped). U16x32 (32 × u16 in one __m512i): - splat, zero, from_slice, from_array, to_array, copy_to_slice - Add, Sub, AddAssign operators - from_u8x64_lo / from_u8x64_hi — widen u8→u16 (zero-extend) - pack_saturate_u8 — narrow u16→u8 (unsigned saturation) - shr / shl — immediate shift per 16-bit lane - mullo — wrapping multiply, keep low 16 bits - reduce_sum → u32 - AVX-512 native: _mm512_set1_epi16, _mm512_cvtepu8_epi16, _mm512_packus_epi16, _mm512_srli/slli_epi16, _mm512_mullo_epi16, _mm512_add/sub_epi16 - AVX2 + scalar: matching loop fallbacks U8x64::movemask() → u64: - Extract MSB of each byte as 64-bit mask - AVX-512: _mm512_movepi8_mask (single instruction) - Scalar: (byte & 0x80) != 0 loop - Empty-tile skip: if movemask(row) == 0 → skip entire 64-pixel row Tests: 12 new tier3_tests (movemask ×3, U16x32 splat/add/widen_lo/ widen_hi/pack_saturate ×2/mullo/shift_roundtrip/reduce_sum). All pass. All three SIMD backends (simd_avx512.rs, simd_avx2.rs, simd.rs) updated. Consumer writes crate::simd::U16x32 / crate::simd::U8x64::movemask(). https://claude.ai/code/session_01SbYsmmbPf9YQuYbHZN52Zh
1 parent d4da568 commit 1420f13

3 files changed

Lines changed: 340 additions & 3 deletions

File tree

src/simd.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub use crate::simd_avx512::{
118118
// 256-bit (AVX2 baseline, __m256/__m256d)
119119
F32x8, F64x4, f32x8, f64x4,
120120
// 512-bit (native AVX-512, __m512/__m512d/__m512i)
121-
F32x16, F64x8, U8x64, I32x16, I64x8, U32x16, U64x8,
121+
F32x16, F64x8, U8x64, I32x16, I64x8, U16x32, U32x16, U64x8,
122122
F32Mask16, F64Mask8,
123123
f32x16, f64x8, u8x64, i32x16, i64x8, u32x16, u64x8,
124124
};
@@ -152,7 +152,7 @@ pub use crate::simd_avx512::{F32x8, F64x4, f32x8, f64x4};
152152

153153
#[cfg(all(target_arch = "x86_64", not(target_feature = "avx512f")))]
154154
pub use crate::simd_avx2::{
155-
F32x16, F64x8, U8x64, I32x16, I64x8, U32x16, U64x8,
155+
F32x16, F64x8, U8x64, I32x16, I64x8, U16x32, U32x16, U64x8,
156156
F32Mask16, F64Mask8,
157157
f32x16, f64x8, u8x64, i32x16, i64x8, u32x16, u64x8,
158158
};
@@ -551,9 +551,41 @@ mod scalar {
551551
impl_int_type!(U8x64, u8, 64, 0u8);
552552
impl_int_type!(I32x16, i32, 16, 0i32);
553553
impl_int_type!(I64x8, i64, 8, 0i64);
554+
impl_int_type!(U16x32, u16, 32, 0u16);
554555
impl_int_type!(U32x16, u32, 16, 0u32);
555556
impl_int_type!(U64x8, u64, 8, 0u64);
556557

558+
// Extra methods for U16x32 (widen/narrow, shift, multiply)
559+
impl U16x32 {
560+
#[inline(always)]
561+
pub fn from_u8x64_lo(v: U8x64) -> Self {
562+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = v.0[i] as u16; } Self(out)
563+
}
564+
#[inline(always)]
565+
pub fn from_u8x64_hi(v: U8x64) -> Self {
566+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = v.0[32 + i] as u16; } Self(out)
567+
}
568+
#[inline(always)]
569+
pub fn pack_saturate_u8(self, other: Self) -> U8x64 {
570+
let mut out = [0u8; 64];
571+
for i in 0..32 { out[i] = self.0[i].min(255) as u8; }
572+
for i in 0..32 { out[32 + i] = other.0[i].min(255) as u8; }
573+
U8x64(out)
574+
}
575+
#[inline(always)]
576+
pub fn shr(self, imm: u32) -> Self {
577+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = if imm < 16 { self.0[i] >> imm } else { 0 }; } Self(out)
578+
}
579+
#[inline(always)]
580+
pub fn shl(self, imm: u32) -> Self {
581+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = if imm < 16 { self.0[i] << imm } else { 0 }; } Self(out)
582+
}
583+
#[inline(always)]
584+
pub fn mullo(self, other: Self) -> Self {
585+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = self.0[i].wrapping_mul(other.0[i]); } Self(out)
586+
}
587+
}
588+
557589
// Extra methods for I32x16 that float types have via the macro
558590
impl I32x16 {
559591
#[inline(always)]
@@ -842,6 +874,10 @@ mod scalar {
842874
let mut out = [0u8; 64]; for i in 0..64 { out[i] = self.0[(idx.0[i] & 63) as usize]; } Self(out)
843875
}
844876
#[inline(always)]
877+
pub fn movemask(self) -> u64 {
878+
let mut m: u64 = 0; for i in 0..64 { if self.0[i] & 0x80 != 0 { m |= 1 << i; } } m
879+
}
880+
#[inline(always)]
845881
pub fn unpack_lo_epi8(self, other: Self) -> Self {
846882
let mut out = [0u8; 64];
847883
for lane in 0..4 { let b = lane * 16; for i in 0..8 { out[b+i*2] = self.0[b+i]; out[b+i*2+1] = other.0[b+i]; } }
@@ -905,7 +941,7 @@ mod scalar {
905941

906942
#[cfg(not(target_arch = "x86_64"))]
907943
pub use scalar::{
908-
F32x16, F64x8, U8x64, I32x16, I64x8, U32x16, U64x8,
944+
F32x16, F64x8, U8x64, I32x16, I64x8, U16x32, U32x16, U64x8,
909945
F32x8, F64x4,
910946
F32Mask16, F64Mask8,
911947
f32x16, f64x8, u8x64, i32x16, i64x8, u32x16, u64x8,

src/simd_avx2.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,10 @@ impl U8x64 {
842842
pub fn permute_bytes(self, idx: Self) -> Self {
843843
let mut out = [0u8; 64]; for i in 0..64 { out[i] = self.0[(idx.0[i] & 63) as usize]; } Self(out)
844844
}
845+
#[inline(always)]
846+
pub fn movemask(self) -> u64 {
847+
let mut m: u64 = 0; for i in 0..64 { if self.0[i] & 0x80 != 0 { m |= 1 << i; } } m
848+
}
845849

846850
/// Interleave low bytes within each 128-bit lane.
847851
#[inline(always)]
@@ -909,9 +913,41 @@ impl U8x64 {
909913

910914
avx2_int_type!(I32x16, i32, 16, 0i32);
911915
avx2_int_type!(I64x8, i64, 8, 0i64);
916+
avx2_int_type!(U16x32, u16, 32, 0u16);
912917
avx2_int_type!(U32x16, u32, 16, 0u32);
913918
avx2_int_type!(U64x8, u64, 8, 0u64);
914919

920+
// Extra methods for U16x32 (widen/narrow, shift, multiply) — AVX2 scalar fallback.
921+
impl U16x32 {
922+
#[inline(always)]
923+
pub fn from_u8x64_lo(v: U8x64) -> Self {
924+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = v.0[i] as u16; } Self(out)
925+
}
926+
#[inline(always)]
927+
pub fn from_u8x64_hi(v: U8x64) -> Self {
928+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = v.0[32 + i] as u16; } Self(out)
929+
}
930+
#[inline(always)]
931+
pub fn pack_saturate_u8(self, other: Self) -> U8x64 {
932+
let mut out = [0u8; 64];
933+
for i in 0..32 { out[i] = self.0[i].min(255) as u8; }
934+
for i in 0..32 { out[32 + i] = other.0[i].min(255) as u8; }
935+
U8x64(out)
936+
}
937+
#[inline(always)]
938+
pub fn shr(self, imm: u32) -> Self {
939+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = if imm < 16 { self.0[i] >> imm } else { 0 }; } Self(out)
940+
}
941+
#[inline(always)]
942+
pub fn shl(self, imm: u32) -> Self {
943+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = if imm < 16 { self.0[i] << imm } else { 0 }; } Self(out)
944+
}
945+
#[inline(always)]
946+
pub fn mullo(self, other: Self) -> Self {
947+
let mut out = [0u16; 32]; for i in 0..32 { out[i] = self.0[i].wrapping_mul(other.0[i]); } Self(out)
948+
}
949+
}
950+
915951
impl I32x16 {
916952
#[inline(always)] pub fn reduce_min(self) -> i32 { *self.0.iter().min().unwrap() }
917953
#[inline(always)] pub fn reduce_max(self) -> i32 { *self.0.iter().max().unwrap() }

0 commit comments

Comments
 (0)