Skip to content

Commit ffb12fd

Browse files
authored
Merge pull request #228 from AdaWorldAPI/claude/v3-substrate-migration-review-o0yoxv
feat(simd_soa): iter_i32x16 / iter_i64x8 typed lane iterators on MultiLaneColumn
2 parents de72d15 + ac59b1d commit ffb12fd

1 file changed

Lines changed: 91 additions & 1 deletion

File tree

src/simd_soa.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)