Skip to content

Commit d64c5e0

Browse files
committed
refactor(pr-x1): split simd_soa (carriers) / simd_ops (slicing)
Per layering rule: slicing/ops helpers belong in simd_ops.rs, not simd_soa.rs. Moved `array_chunks` + `array_chunks_checked` + their tests from `src/simd_soa.rs` → `src/simd_ops.rs`. src/simd_soa.rs — MultiLaneColumn (Arc<[u8]> carrier) only src/simd_ops.rs — array_chunks + array_chunks_checked (alongside the existing add_f32 / sub_f32 / … slice elementwise ops) `src/simd.rs` re-exports now point at both source modules: pub use crate::simd_soa::MultiLaneColumn; pub use crate::simd_ops::{array_chunks, array_chunks_checked}; Also drops the stale `pub mod column; pub mod array_chunks;` from `src/hpc/mod.rs` (the two files were removed in 8483ae3; this commit fixes the dangling references that earlier Edits missed because the linter raced the writes).
1 parent 8483ae3 commit d64c5e0

4 files changed

Lines changed: 134 additions & 135 deletions

File tree

src/hpc/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ pub mod packed;
3939
#[allow(missing_docs)]
4040
pub mod fingerprint;
4141

42-
// PR-X1 — multi-lane typed column substrate + const-size array windows.
43-
// Re-exported from crate::simd::* per the W1a consumer contract.
44-
pub mod column;
45-
pub mod array_chunks;
42+
// PR-X1 primitives (MultiLaneColumn, array_chunks) live at the crate root
43+
// in `crate::simd_soa` + `crate::simd_ops` and are re-exported via
44+
// `crate::simd::*`. They are intentionally NOT under `hpc::*` — SIMD
45+
// substrate goes through the `simd_{type}.rs` family per the W1a layering
46+
// rule (carriers in simd_soa, slicing/ops in simd_ops).
4647
#[allow(missing_docs)]
4748
pub mod plane;
4849
#[allow(missing_docs)]

src/simd.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,12 +1718,11 @@ pub use crate::hpc::fingerprint::{
17181718
vector_config, Fingerprint, Fingerprint1K, Fingerprint2K, Fingerprint64K, VectorConfig, VectorWidth,
17191719
};
17201720

1721-
// PR-X1 — multi-lane typed column carrier + const-size slice window helpers.
1722-
// The W1a consumer contract requires all SIMD-staged primitives to surface
1723-
// through `crate::simd::*`; without these re-exports the cognitive-shader
1724-
// stack reaches for `crate::hpc::column::*` directly, breaking the contract.
1725-
pub use crate::hpc::column::MultiLaneColumn;
1726-
pub use crate::hpc::array_chunks::{array_chunks, array_chunks_checked};
1721+
// PR-X1 — SoA carrier + const-size slice helpers, dispatched from their
1722+
// respective `simd_{type}.rs` modules. The W1a consumer contract forbids
1723+
// reaching past `crate::simd::*` into the implementation modules directly.
1724+
pub use crate::simd_soa::MultiLaneColumn;
1725+
pub use crate::simd_ops::{array_chunks, array_chunks_checked};
17271726

17281727
pub use crate::hpc::quantized::{
17291728
dequantize_i2_to_f32, dequantize_i4_to_f32, dequantize_i8_to_f32, quantize_f32_to_i2, quantize_f32_to_i4,

src/simd_ops.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,119 @@ mod tests {
286286
assert_eq!(c.len(), 5);
287287
}
288288
}
289+
290+
// ════════════════════════════════════════════════════════════════════
291+
// PR-X1 — Const-size non-overlapping slice chunk helpers
292+
// ════════════════════════════════════════════════════════════════════
293+
//
294+
// Slicing primitive for SIMD-staged inner loops. Naming: `array_chunks`
295+
// (NOT `array_windows`) because `std::slice::array_windows::<N>()`
296+
// (nightly) is the **overlapping** iterator already referenced in
297+
// `src/simd.rs` comments. These helpers are the **non-overlapping**
298+
// variant, matching `std::slice::ArrayChunks` / stable `slice::as_chunks`.
299+
300+
/// Walk `data` as a sequence of non-overlapping const-size windows.
301+
///
302+
/// Returns an iterator over `&[T; N]` references into `data`. The tail
303+
/// (`data.len() % N` items) is discarded; use [`array_chunks_checked`] to
304+
/// fail-fast when the length is not a multiple of `N`.
305+
///
306+
/// Zero-cost: thin wrapper around [`slice::as_chunks`] that pins the
307+
/// chunk size at the call site for type inference.
308+
///
309+
/// # Examples
310+
///
311+
/// ```
312+
/// use ndarray::simd::array_chunks;
313+
/// let data: Vec<u8> = (0..16).collect();
314+
/// let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
315+
/// assert_eq!(windows.len(), 4);
316+
/// assert_eq!(windows[0], &[0, 1, 2, 3]);
317+
/// assert_eq!(windows[3], &[12, 13, 14, 15]);
318+
/// ```
319+
///
320+
/// # Examples — tail discarded
321+
///
322+
/// ```
323+
/// use ndarray::simd::array_chunks;
324+
/// let data: Vec<u8> = (0..7).collect();
325+
/// let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
326+
/// assert_eq!(windows.len(), 1);
327+
/// ```
328+
#[inline]
329+
pub fn array_chunks<T, const N: usize>(data: &[T]) -> impl Iterator<Item = &[T; N]> + '_ {
330+
data.as_chunks::<N>().0.iter()
331+
}
332+
333+
/// Walk `data` as `&[T; N]` windows, returning `Err(())` if `data.len()`
334+
/// is not a multiple of `N`.
335+
///
336+
/// Strict variant of [`array_chunks`]: the consumer asserts up front that
337+
/// the buffer is lane-aligned and wants the error surfaced rather than
338+
/// silently truncating.
339+
///
340+
/// # Examples
341+
///
342+
/// ```
343+
/// use ndarray::simd::array_chunks_checked;
344+
/// let data: Vec<u8> = (0..16).collect();
345+
/// let it = array_chunks_checked::<u8, 4>(&data).expect("16 is a multiple of 4");
346+
/// assert_eq!(it.count(), 4);
347+
///
348+
/// let bad: Vec<u8> = (0..7).collect();
349+
/// assert!(array_chunks_checked::<u8, 4>(&bad).is_err());
350+
/// ```
351+
#[inline]
352+
pub fn array_chunks_checked<T, const N: usize>(
353+
data: &[T],
354+
) -> Result<impl Iterator<Item = &[T; N]> + '_, ()> {
355+
if data.len() % N != 0 {
356+
return Err(());
357+
}
358+
Ok(array_chunks::<T, N>(data))
359+
}
360+
361+
#[cfg(test)]
362+
mod array_chunks_tests {
363+
use super::*;
364+
365+
#[test]
366+
fn array_chunks_4_over_16() {
367+
let data: Vec<u8> = (0u8..16).collect();
368+
let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
369+
assert_eq!(windows.len(), 4);
370+
assert_eq!(windows[0], &[0, 1, 2, 3]);
371+
assert_eq!(windows[1], &[4, 5, 6, 7]);
372+
assert_eq!(windows[2], &[8, 9, 10, 11]);
373+
assert_eq!(windows[3], &[12, 13, 14, 15]);
374+
}
375+
376+
#[test]
377+
fn array_chunks_drops_tail() {
378+
let data: Vec<u8> = (0u8..7).collect();
379+
let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
380+
assert_eq!(windows.len(), 1);
381+
assert_eq!(windows[0], &[0, 1, 2, 3]);
382+
}
383+
384+
#[test]
385+
fn array_chunks_checked_rejects_mismatch() {
386+
assert!(array_chunks_checked::<u8, 4>(&[0u8; 7]).is_err());
387+
assert!(array_chunks_checked::<u8, 4>(&[0u8; 5]).is_err());
388+
assert!(array_chunks_checked::<u8, 4>(&[0u8; 1]).is_err());
389+
}
390+
391+
#[test]
392+
fn array_chunks_checked_accepts_aligned() {
393+
let data = [0u8; 16];
394+
let it = array_chunks_checked::<u8, 4>(&data).expect("16 is a multiple of 4");
395+
assert_eq!(it.count(), 4);
396+
}
397+
398+
#[test]
399+
fn array_chunks_empty_buffer() {
400+
assert_eq!(array_chunks::<u8, 4>(&[]).count(), 0);
401+
let it = array_chunks_checked::<u8, 4>(&[]).expect("0 % 4 == 0, should be Ok");
402+
assert_eq!(it.count(), 0);
403+
}
404+
}

src/simd_soa.rs

Lines changed: 8 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
//! SoA-shaped SIMD substrate primitives (PR-X1).
1+
//! SoA-shaped SIMD substrate carriers (PR-X1).
22
//!
33
//! Lives at the crate root under `crate::simd_soa::*` and is re-exported
44
//! through `crate::simd::*` per the W1a consumer contract. This is the
5-
//! canonical home for SoA-of-bytes / multi-lane column carriers and the
6-
//! const-size chunk-walking helpers that SIMD-staged inner loops use.
5+
//! canonical home for SoA-of-bytes / multi-lane column carriers.
6+
//!
7+
//! Generic slice / chunk helpers (`array_chunks`, `array_chunks_checked`)
8+
//! live in `crate::simd_ops` — they're operations, not carriers.
79
//!
810
//! # What lives here
911
//!
1012
//! - [`MultiLaneColumn`] — `Arc<[u8]>` carrier with typed-width chunk iters
11-
//! - [`array_chunks`] / [`array_chunks_checked`] — generic const-size
12-
//! non-overlapping `&[T] → impl Iterator<Item = &[T; N]>` helpers
1313
//!
1414
//! # Layering
1515
//!
1616
//! This module is **layout-only**. No `#[target_feature]`, no per-arch
1717
//! imports, no raw intrinsics. The SIMD register load happens inside the
1818
//! consumer's loop using `crate::simd::F32x16::from_array` etc. The
19-
//! `simd.rs` dispatcher re-exports these primitives via `pub use
19+
//! `simd.rs` dispatcher re-exports these carriers via `pub use
2020
//! crate::simd_soa::{…};` so consumers always go through
2121
//! `use ndarray::simd::*;`.
2222
//!
@@ -25,13 +25,9 @@
2525
//! These types are layout-only. No distance-aware API. See
2626
//! `.claude/knowledge/cognitive-distance-typing.md` (no-umbrella rule).
2727
//!
28-
//! # Design references
28+
//! # Design reference
2929
//!
30-
//! - `.claude/knowledge/pr-x1-design.md` § "1. `MultiLaneColumn`"
31-
//! - `.claude/knowledge/pr-x1-design.md` § "3. `array_window`" (the
32-
//! singular-window sketch superseded by the iterator form here; the
33-
//! name landed as `array_chunks` to avoid collision with std's
34-
//! nightly overlapping `slice::array_windows`).
30+
//! `.claude/knowledge/pr-x1-design.md` § "1. `MultiLaneColumn`".
3531
3632
use std::sync::Arc;
3733

@@ -174,77 +170,6 @@ impl MultiLaneColumn {
174170
}
175171
}
176172

177-
// ════════════════════════════════════════════════════════════════════
178-
// array_chunks — generic non-overlapping const-size chunk helpers
179-
// ════════════════════════════════════════════════════════════════════
180-
//
181-
// Naming: `array_chunks` (NOT `array_windows`) because:
182-
// - `std::slice::array_windows::<N>()` (nightly) is the **overlapping**
183-
// variant, already referenced in src/simd.rs comments.
184-
// - These helpers are the **non-overlapping** variant, matching
185-
// `std::slice::ArrayChunks` / stable `slice::as_chunks`.
186-
187-
/// Walk `data` as a sequence of non-overlapping const-size windows.
188-
///
189-
/// Returns an iterator over `&[T; N]` references into `data`. The tail
190-
/// (`data.len() % N` items) is discarded; use [`array_chunks_checked`] to
191-
/// fail-fast when the length is not a multiple of `N`.
192-
///
193-
/// Zero-cost: this is a thin wrapper around [`slice::as_chunks`] that pins
194-
/// the chunk size at the call site for type inference.
195-
///
196-
/// # Examples
197-
///
198-
/// ```
199-
/// use ndarray::simd::array_chunks;
200-
/// let data: Vec<u8> = (0..16).collect();
201-
/// let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
202-
/// assert_eq!(windows.len(), 4);
203-
/// assert_eq!(windows[0], &[0, 1, 2, 3]);
204-
/// assert_eq!(windows[3], &[12, 13, 14, 15]);
205-
/// ```
206-
///
207-
/// # Examples — tail discarded
208-
///
209-
/// ```
210-
/// use ndarray::simd::array_chunks;
211-
/// let data: Vec<u8> = (0..7).collect();
212-
/// let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
213-
/// assert_eq!(windows.len(), 1);
214-
/// ```
215-
#[inline]
216-
pub fn array_chunks<T, const N: usize>(data: &[T]) -> impl Iterator<Item = &[T; N]> + '_ {
217-
data.as_chunks::<N>().0.iter()
218-
}
219-
220-
/// Walk `data` as `&[T; N]` windows, returning `Err(())` if `data.len()`
221-
/// is not a multiple of `N`.
222-
///
223-
/// Strict variant of [`array_chunks`]: the consumer asserts up front that
224-
/// the buffer is lane-aligned and wants the error surfaced rather than
225-
/// silently truncating.
226-
///
227-
/// # Examples
228-
///
229-
/// ```
230-
/// use ndarray::simd::array_chunks_checked;
231-
/// let data: Vec<u8> = (0..16).collect();
232-
/// let it = array_chunks_checked::<u8, 4>(&data).expect("16 is a multiple of 4");
233-
/// assert_eq!(it.count(), 4);
234-
///
235-
/// let bad: Vec<u8> = (0..7).collect();
236-
/// assert!(array_chunks_checked::<u8, 4>(&bad).is_err());
237-
/// ```
238-
#[inline]
239-
pub fn array_chunks_checked<T, const N: usize>(
240-
data: &[T],
241-
) -> Result<impl Iterator<Item = &[T; N]> + '_, ()> {
242-
if data.len() % N != 0 {
243-
return Err(());
244-
}
245-
Ok(array_chunks::<T, N>(data))
246-
}
247-
248173
// ════════════════════════════════════════════════════════════════════
249174
// Tests
250175
// ════════════════════════════════════════════════════════════════════
@@ -352,46 +277,4 @@ mod tests {
352277
fn assert_send_sync<T: Send + Sync>() {}
353278
assert_send_sync::<MultiLaneColumn>();
354279
}
355-
356-
// ---- array_chunks ----
357-
358-
#[test]
359-
fn array_chunks_4_over_16() {
360-
let data: Vec<u8> = (0u8..16).collect();
361-
let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
362-
assert_eq!(windows.len(), 4);
363-
assert_eq!(windows[0], &[0, 1, 2, 3]);
364-
assert_eq!(windows[1], &[4, 5, 6, 7]);
365-
assert_eq!(windows[2], &[8, 9, 10, 11]);
366-
assert_eq!(windows[3], &[12, 13, 14, 15]);
367-
}
368-
369-
#[test]
370-
fn array_chunks_drops_tail() {
371-
let data: Vec<u8> = (0u8..7).collect();
372-
let windows: Vec<&[u8; 4]> = array_chunks::<u8, 4>(&data).collect();
373-
assert_eq!(windows.len(), 1);
374-
assert_eq!(windows[0], &[0, 1, 2, 3]);
375-
}
376-
377-
#[test]
378-
fn array_chunks_checked_rejects_mismatch() {
379-
assert!(array_chunks_checked::<u8, 4>(&[0u8; 7]).is_err());
380-
assert!(array_chunks_checked::<u8, 4>(&[0u8; 5]).is_err());
381-
assert!(array_chunks_checked::<u8, 4>(&[0u8; 1]).is_err());
382-
}
383-
384-
#[test]
385-
fn array_chunks_checked_accepts_aligned() {
386-
let data = [0u8; 16];
387-
let it = array_chunks_checked::<u8, 4>(&data).expect("16 is a multiple of 4");
388-
assert_eq!(it.count(), 4);
389-
}
390-
391-
#[test]
392-
fn array_chunks_empty_buffer() {
393-
assert_eq!(array_chunks::<u8, 4>(&[]).count(), 0);
394-
let it = array_chunks_checked::<u8, 4>(&[]).expect("0 % 4 == 0, should be Ok");
395-
assert_eq!(it.count(), 0);
396-
}
397280
}

0 commit comments

Comments
 (0)