@@ -35,7 +35,7 @@ use std::sync::Arc;
3535// re-exports the right backend (AVX-512 / NEON / scalar) per `cfg`. Per
3636// the W1a layering rule, `simd_soa.rs` MUST go through `crate::simd::`
3737// rather than dipping into `simd_avx512` / `simd_neon` / `scalar` directly.
38- use crate :: simd:: { F32x16 , F64x8 , U64x8 , U8x64 } ;
38+ use crate :: simd:: { F32x16 , F64x8 , I32x16 , I64x8 , U64x8 , U8x64 } ;
3939
4040// Endian-correct `&[u8; 4]` → `f32` / `&[u8; 8]` → `f64`/`u64` helpers.
4141// `f32::from_le_bytes` is intrinsically optimised to a single load on
@@ -89,6 +89,33 @@ fn u64x8_from_chunk(chunk: &[u8; 64]) -> U64x8 {
8989 U64x8 :: from_array ( arr)
9090}
9191
92+ #[ inline( always) ]
93+ fn i32x16_from_chunk ( chunk : & [ u8 ; 64 ] ) -> I32x16 {
94+ let arr: [ i32 ; 16 ] = core:: array:: from_fn ( |i| {
95+ let off = i * 4 ;
96+ i32:: from_le_bytes ( [ chunk[ off] , chunk[ off + 1 ] , chunk[ off + 2 ] , chunk[ off + 3 ] ] )
97+ } ) ;
98+ I32x16 :: from_array ( arr)
99+ }
100+
101+ #[ inline( always) ]
102+ fn i64x8_from_chunk ( chunk : & [ u8 ; 64 ] ) -> I64x8 {
103+ let arr: [ i64 ; 8 ] = core:: array:: from_fn ( |i| {
104+ let off = i * 8 ;
105+ i64:: from_le_bytes ( [
106+ chunk[ off] ,
107+ chunk[ off + 1 ] ,
108+ chunk[ off + 2 ] ,
109+ chunk[ off + 3 ] ,
110+ chunk[ off + 4 ] ,
111+ chunk[ off + 5 ] ,
112+ chunk[ off + 6 ] ,
113+ chunk[ off + 7 ] ,
114+ ] )
115+ } ) ;
116+ I64x8 :: from_array ( arr)
117+ }
118+
92119// ════════════════════════════════════════════════════════════════════
93120// MultiLaneColumn — Arc<[u8]> carrier with typed lane-width chunk iters
94121// ════════════════════════════════════════════════════════════════════
@@ -179,6 +206,16 @@ impl MultiLaneColumn {
179206 self . data . len ( ) / 64
180207 }
181208
209+ /// Number of `I32x16`-shaped (16 × i32 = 64-byte) chunks.
210+ pub fn len_i32x16 ( & self ) -> usize {
211+ self . data . len ( ) / 64
212+ }
213+
214+ /// Number of `I64x8`-shaped (8 × i64 = 64-byte) chunks.
215+ pub fn len_i64x8 ( & self ) -> usize {
216+ self . data . len ( ) / 64
217+ }
218+
182219 /// View the backing store as a raw byte slice.
183220 pub fn as_bytes ( & self ) -> & [ u8 ] {
184221 & self . data
@@ -235,6 +272,27 @@ impl MultiLaneColumn {
235272 pub fn iter_u64x8 ( & self ) -> impl Iterator < Item = U64x8 > + ' _ {
236273 self . data . as_chunks :: < 64 > ( ) . 0 . iter ( ) . map ( u64x8_from_chunk)
237274 }
275+
276+ /// Iterate the column as typed [`I32x16`] values dispatched via
277+ /// `crate::simd::*`.
278+ ///
279+ /// Bytes are decoded little-endian (`i32::from_le_bytes`), the signed
280+ /// sibling of [`iter_f32x16`](Self::iter_f32x16) — the lane width the
281+ /// gridlake batch SoA needs for integer min/max/sum tile columns (the
282+ /// consumer that could previously only view f32 min/max columns).
283+ pub fn iter_i32x16 ( & self ) -> impl Iterator < Item = I32x16 > + ' _ {
284+ self . data . as_chunks :: < 64 > ( ) . 0 . iter ( ) . map ( i32x16_from_chunk)
285+ }
286+
287+ /// Iterate the column as typed [`I64x8`] values dispatched via
288+ /// `crate::simd::*`.
289+ ///
290+ /// Bytes are decoded little-endian (`i64::from_le_bytes`), the signed
291+ /// sibling of [`iter_u64x8`](Self::iter_u64x8) — the lane width for
292+ /// 64-bit integer accumulator columns (running sums).
293+ pub fn iter_i64x8 ( & self ) -> impl Iterator < Item = I64x8 > + ' _ {
294+ self . data . as_chunks :: < 64 > ( ) . 0 . iter ( ) . map ( i64x8_from_chunk)
295+ }
238296}
239297
240298// ════════════════════════════════════════════════════════════════════
@@ -255,6 +313,8 @@ mod tests {
255313 assert_eq ! ( col. len_f32x16( ) , 1 ) ;
256314 assert_eq ! ( col. len_f64x8( ) , 1 ) ;
257315 assert_eq ! ( col. len_u64x8( ) , 1 ) ;
316+ assert_eq ! ( col. len_i32x16( ) , 1 ) ;
317+ assert_eq ! ( col. len_i64x8( ) , 1 ) ;
258318 }
259319
260320 #[ test]
@@ -273,6 +333,8 @@ mod tests {
273333 assert_eq ! ( col. iter_f32x16( ) . count( ) , 0 ) ;
274334 assert_eq ! ( col. iter_f64x8( ) . count( ) , 0 ) ;
275335 assert_eq ! ( col. iter_u64x8( ) . count( ) , 0 ) ;
336+ assert_eq ! ( col. iter_i32x16( ) . count( ) , 0 ) ;
337+ assert_eq ! ( col. iter_i64x8( ) . count( ) , 0 ) ;
276338 }
277339
278340 #[ test]
@@ -341,6 +403,32 @@ mod tests {
341403 assert_eq ! ( lane. to_array( ) , src) ;
342404 }
343405
406+ #[ test]
407+ fn iter_i32x16_le_round_trip ( ) {
408+ // Signed values incl. negatives, to prove sign-extension is
409+ // preserved by the LE decode (the point of the i32 lane).
410+ let src: [ i32 ; 16 ] = core:: array:: from_fn ( |i| ( i as i32 - 8 ) * 0x0011_2233 ) ;
411+ let mut bytes = vec ! [ 0u8 ; 64 ] ;
412+ for ( i, & v) in src. iter ( ) . enumerate ( ) {
413+ bytes[ i * 4 ..i * 4 + 4 ] . copy_from_slice ( & v. to_le_bytes ( ) ) ;
414+ }
415+ let col = MultiLaneColumn :: new ( Arc :: from ( bytes) ) . unwrap ( ) ;
416+ let lane = col. iter_i32x16 ( ) . next ( ) . expect ( "one lane" ) ;
417+ assert_eq ! ( lane. to_array( ) , src) ;
418+ }
419+
420+ #[ test]
421+ fn iter_i64x8_le_round_trip ( ) {
422+ let src: [ i64 ; 8 ] = core:: array:: from_fn ( |i| ( i as i64 - 4 ) * 0x0123_4567_89AB_CDEF ) ;
423+ let mut bytes = vec ! [ 0u8 ; 64 ] ;
424+ for ( i, & v) in src. iter ( ) . enumerate ( ) {
425+ bytes[ i * 8 ..i * 8 + 8 ] . copy_from_slice ( & v. to_le_bytes ( ) ) ;
426+ }
427+ let col = MultiLaneColumn :: new ( Arc :: from ( bytes) ) . unwrap ( ) ;
428+ let lane = col. iter_i64x8 ( ) . next ( ) . expect ( "one lane" ) ;
429+ assert_eq ! ( lane. to_array( ) , src) ;
430+ }
431+
344432 #[ test]
345433 fn typed_iters_yield_three_lanes_over_192_bytes ( ) {
346434 let v: Vec < u8 > = ( 0u8 ..192 ) . collect ( ) ;
@@ -349,6 +437,8 @@ mod tests {
349437 assert_eq ! ( col. iter_f32x16( ) . count( ) , 3 ) ;
350438 assert_eq ! ( col. iter_f64x8( ) . count( ) , 3 ) ;
351439 assert_eq ! ( col. iter_u64x8( ) . count( ) , 3 ) ;
440+ assert_eq ! ( col. iter_i32x16( ) . count( ) , 3 ) ;
441+ assert_eq ! ( col. iter_i64x8( ) . count( ) , 3 ) ;
352442 }
353443
354444 #[ test]
0 commit comments