1010//!
1111//! # Layering
1212//!
13- //! Lives in `hpc::column`, re-exported from `crate::simd::*` per the
14- //! W1a consumer contract at
15- //! `.claude/knowledge/vertical-simd-consumer-contract.md`.
13+ //! Lives in `hpc::column`; the `crate::simd::*` re-export lands in the PR-X1
14+ //! re-export sweep (see `.claude/knowledge/pr-x1-design.md` § 4). Doctests in
15+ //! this file therefore use the canonical `ndarray::hpc::column` path until
16+ //! the sweep ships.
1617//!
1718//! # Distance typing
1819//!
2122//!
2223//! # Design reference
2324//!
24- //! `.claude/knowledge/pr-x1-design.md` § "1. `MultiLaneColumn`" — verbatim
25- //! API surface; this file is the commented-out final form (preflight
26- //! skeleton) for the PR-X1 sprint.
25+ //! `.claude/knowledge/pr-x1-design.md` § "1. `MultiLaneColumn`". The
26+ //! `iter_*_bytes` family deliberately returns `&[u8; 64]` "shape" iterators
27+ //! (the consumer applies the typed reinterpret at the call site) — this is
28+ //! the maintainer-blessed deviation from the design doc's typed-iterator
29+ //! sketch, centralising the one allowed `unsafe` cast at the consumer rather
30+ //! than per-iterator here.
2731
28- extern crate alloc;
29- use alloc:: sync:: Arc ;
32+ use std:: sync:: Arc ;
3033
3134/// Multi-lane (N-wide) typed column view over a shared `Arc<[u8]>` buffer.
3235///
@@ -42,10 +45,10 @@ use alloc::sync::Arc;
4245/// # Examples
4346///
4447/// ```
45- /// use ndarray::simd ::MultiLaneColumn;
46- /// use alloc ::sync::Arc;
48+ /// use ndarray::hpc::column ::MultiLaneColumn;
49+ /// use std ::sync::Arc;
4750///
48- /// let data: Arc<[u8]> = vec![0u8; 128].into( );
51+ /// let data: Arc<[u8]> = Arc::from( vec![0u8; 128]);
4952/// let col = MultiLaneColumn::new(data).unwrap();
5053/// assert_eq!(col.len_bytes(), 128);
5154/// assert_eq!(col.len_u8x64(), 2);
@@ -67,52 +70,55 @@ impl MultiLaneColumn {
6770 /// # Examples
6871 ///
6972 /// ```
70- /// use ndarray::simd ::MultiLaneColumn;
71- /// use alloc ::sync::Arc;
73+ /// use ndarray::hpc::column ::MultiLaneColumn;
74+ /// use std ::sync::Arc;
7275 ///
73- /// let ok: Arc<[u8]> = vec![1u8; 64].into( );
76+ /// let ok: Arc<[u8]> = Arc::from( vec![1u8; 64]);
7477 /// assert!(MultiLaneColumn::new(ok).is_ok());
7578 ///
76- /// let bad: Arc<[u8]> = vec![0u8; 100].into( );
79+ /// let bad: Arc<[u8]> = Arc::from( vec![0u8; 100]);
7780 /// assert!(MultiLaneColumn::new(bad).is_err());
7881 /// ```
79- pub fn new ( _data : Arc < [ u8 ] > ) -> Result < Self , ( ) > {
80- unimplemented ! ( "PR-X1: MultiLaneColumn::new — multiple-of-64 check + Arc wrap" )
82+ pub fn new ( data : Arc < [ u8 ] > ) -> Result < Self , ( ) > {
83+ if data. len ( ) % 64 != 0 {
84+ return Err ( ( ) ) ;
85+ }
86+ Ok ( Self { data } )
8187 }
8288
8389 /// Total byte length of the backing store.
8490 pub fn len_bytes ( & self ) -> usize {
85- unimplemented ! ( "PR-X1: MultiLaneColumn::len_bytes — returns self.data.len()" )
91+ self . data . len ( )
8692 }
8793
8894 /// Returns `true` if the column has zero bytes.
8995 pub fn is_empty ( & self ) -> bool {
90- unimplemented ! ( "PR-X1: MultiLaneColumn::is_empty — returns self.data.is_empty()" )
96+ self . data . is_empty ( )
9197 }
9298
9399 /// Number of 64-byte (`U8x64`) chunks in this column.
94100 pub fn len_u8x64 ( & self ) -> usize {
95- unimplemented ! ( "PR-X1: MultiLaneColumn::len_u8x64 — returns self.data.len() / 64" )
101+ self . data . len ( ) / 64
96102 }
97103
98104 /// Number of `F32x16`-shaped (16 × f32 = 64-byte) chunks.
99105 pub fn len_f32x16 ( & self ) -> usize {
100- unimplemented ! ( "PR-X1: MultiLaneColumn::len_f32x16 — returns self.data.len() / 64" )
106+ self . data . len ( ) / 64
101107 }
102108
103109 /// Number of `F64x8`-shaped (8 × f64 = 64-byte) chunks.
104110 pub fn len_f64x8 ( & self ) -> usize {
105- unimplemented ! ( "PR-X1: MultiLaneColumn::len_f64x8 — returns self.data.len() / 64" )
111+ self . data . len ( ) / 64
106112 }
107113
108114 /// Number of `U64x8`-shaped (8 × u64 = 64-byte) chunks.
109115 pub fn len_u64x8 ( & self ) -> usize {
110- unimplemented ! ( "PR-X1: MultiLaneColumn::len_u64x8 — returns self.data.len() / 64" )
116+ self . data . len ( ) / 64
111117 }
112118
113119 /// View the backing store as a raw byte slice.
114120 pub fn as_bytes ( & self ) -> & [ u8 ] {
115- unimplemented ! ( "PR-X1: MultiLaneColumn::as_bytes — returns &self.data" )
121+ & self . data
116122 }
117123
118124 /// Iterate the column as contiguous `&[u8; 64]` windows (`U8x64` shape).
@@ -126,20 +132,18 @@ impl MultiLaneColumn {
126132 /// # Examples
127133 ///
128134 /// ```
129- /// use ndarray::simd ::MultiLaneColumn;
130- /// use alloc ::sync::Arc;
135+ /// use ndarray::hpc::column ::MultiLaneColumn;
136+ /// use std ::sync::Arc;
131137 ///
132- /// let data: Arc<[u8]> = ( 0u8..128).collect::<Vec<_>>().into( );
138+ /// let data: Arc<[u8]> = Arc::from(( 0u8..128).collect::<Vec<_>>());
133139 /// let col = MultiLaneColumn::new(data).unwrap();
134140 /// let windows: Vec<&[u8; 64]> = col.iter_u8x64().collect();
135141 /// assert_eq!(windows.len(), 2);
136142 /// assert_eq!(windows[0][0], 0u8);
137143 /// assert_eq!(windows[1][0], 64u8);
138144 /// ```
139145 pub fn iter_u8x64 ( & self ) -> impl Iterator < Item = & [ u8 ; 64 ] > + ' _ {
140- // Skeleton: as_chunks::<64>() over &self.data, yielding &[u8;64].
141- // Implementation lands in the uncomment sprint.
142- core:: iter:: empty :: < & [ u8 ; 64 ] > ( )
146+ self . data . as_chunks :: < 64 > ( ) . 0 . iter ( )
143147 }
144148
145149 /// Iterate the column as `&[u8; 64]` windows reinterpreted as `[f32; 16]`-shape.
@@ -148,17 +152,17 @@ impl MultiLaneColumn {
148152 /// Consumer is responsible for using `F32x16::from_array(bytemuck::cast(*win))`
149153 /// or equivalent typed reinterpretation.
150154 pub fn iter_f32x16_bytes ( & self ) -> impl Iterator < Item = & [ u8 ; 64 ] > + ' _ {
151- core :: iter :: empty :: < & [ u8 ; 64 ] > ( )
155+ self . data . as_chunks :: < 64 > ( ) . 0 . iter ( )
152156 }
153157
154158 /// Iterate the column as `&[u8; 64]` windows reinterpreted as `[f64; 8]`-shape.
155159 pub fn iter_f64x8_bytes ( & self ) -> impl Iterator < Item = & [ u8 ; 64 ] > + ' _ {
156- core :: iter :: empty :: < & [ u8 ; 64 ] > ( )
160+ self . data . as_chunks :: < 64 > ( ) . 0 . iter ( )
157161 }
158162
159163 /// Iterate the column as `&[u8; 64]` windows reinterpreted as `[u64; 8]`-shape.
160164 pub fn iter_u64x8_bytes ( & self ) -> impl Iterator < Item = & [ u8 ; 64 ] > + ' _ {
161- core :: iter :: empty :: < & [ u8 ; 64 ] > ( )
165+ self . data . as_chunks :: < 64 > ( ) . 0 . iter ( )
162166 }
163167}
164168
@@ -173,30 +177,113 @@ mod tests {
173177 /// Construction with a 64-byte buffer succeeds; len_bytes round-trips.
174178 #[ test]
175179 fn new_64byte_buffer_succeeds ( ) {
176- unimplemented ! ( "PR-X1 test: assert_eq!(MultiLaneColumn::new(Arc::from(vec![0u8;64])).unwrap().len_bytes(), 64)" )
180+ let col = MultiLaneColumn :: new ( Arc :: from ( vec ! [ 0u8 ; 64 ] ) ) . unwrap ( ) ;
181+ assert_eq ! ( col. len_bytes( ) , 64 ) ;
182+ assert_eq ! ( col. len_u8x64( ) , 1 ) ;
183+ assert_eq ! ( col. len_f32x16( ) , 1 ) ;
184+ assert_eq ! ( col. len_f64x8( ) , 1 ) ;
185+ assert_eq ! ( col. len_u64x8( ) , 1 ) ;
177186 }
178187
179188 /// Construction with a non-multiple-of-64 buffer returns Err.
180189 #[ test]
181190 fn new_non_multiple_of_64_errors ( ) {
182- unimplemented ! ( "PR-X1 test: assert!(MultiLaneColumn::new(Arc::from(vec![0u8;100])).is_err())" )
191+ assert ! ( MultiLaneColumn :: new( Arc :: from( vec![ 0u8 ; 100 ] ) ) . is_err( ) ) ;
192+ assert ! ( MultiLaneColumn :: new( Arc :: from( vec![ 0u8 ; 63 ] ) ) . is_err( ) ) ;
193+ assert ! ( MultiLaneColumn :: new( Arc :: from( vec![ 0u8 ; 65 ] ) ) . is_err( ) ) ;
183194 }
184195
185196 /// Empty buffer is accepted; is_empty == true; iterators yield 0 windows.
186197 #[ test]
187198 fn empty_buffer_yields_zero_windows ( ) {
188- unimplemented ! ( "PR-X1 test: empty Arc → is_empty true + iter_u8x64.count() == 0" )
199+ let col = MultiLaneColumn :: new ( Arc :: from ( vec ! [ 0u8 ; 0 ] ) ) . unwrap ( ) ;
200+ assert ! ( col. is_empty( ) ) ;
201+ assert_eq ! ( col. len_bytes( ) , 0 ) ;
202+ assert_eq ! ( col. iter_u8x64( ) . count( ) , 0 ) ;
203+ assert_eq ! ( col. iter_f32x16_bytes( ) . count( ) , 0 ) ;
204+ assert_eq ! ( col. iter_f64x8_bytes( ) . count( ) , 0 ) ;
205+ assert_eq ! ( col. iter_u64x8_bytes( ) . count( ) , 0 ) ;
189206 }
190207
191208 /// Two-chunk buffer yields exactly 2 windows of 64 bytes each.
192209 #[ test]
193210 fn iter_u8x64_two_chunks ( ) {
194- unimplemented ! ( "PR-X1 test: 128-byte Arc → iter_u8x64 yields 2 windows starting at byte 0 + byte 64" )
211+ let mut v = vec ! [ 0u8 ; 128 ] ;
212+ for i in 0 ..128 {
213+ v[ i] = i as u8 ;
214+ }
215+ let col = MultiLaneColumn :: new ( Arc :: from ( v) ) . unwrap ( ) ;
216+ let windows: Vec < & [ u8 ; 64 ] > = col. iter_u8x64 ( ) . collect ( ) ;
217+ assert_eq ! ( windows. len( ) , 2 ) ;
218+ assert_eq ! ( windows[ 0 ] [ 0 ] , 0u8 ) ;
219+ assert_eq ! ( windows[ 0 ] [ 63 ] , 63u8 ) ;
220+ assert_eq ! ( windows[ 1 ] [ 0 ] , 64u8 ) ;
221+ assert_eq ! ( windows[ 1 ] [ 63 ] , 127u8 ) ;
195222 }
196223
197224 /// Clone shares the same backing Arc (no copy).
198225 #[ test]
199226 fn clone_shares_backing ( ) {
200- unimplemented ! ( "PR-X1 test: Arc::strong_count after clone == 2" )
227+ let col = MultiLaneColumn :: new ( Arc :: from ( vec ! [ 0u8 ; 64 ] ) ) . unwrap ( ) ;
228+ let col2 = col. clone ( ) ;
229+ // Both columns reference the same underlying allocation: pointer equality
230+ // is the observable contract without accessing private Arc internals.
231+ assert_eq ! (
232+ col. as_bytes( ) . as_ptr( ) ,
233+ col2. as_bytes( ) . as_ptr( ) ,
234+ "clone must share the same Arc backing, not copy"
235+ ) ;
236+ }
237+
238+ /// Bytes-shape iterators all yield the same chunk count and content as
239+ /// `iter_u8x64` — they are pure aliasing views, not separate buffers.
240+ #[ test]
241+ fn bytes_shape_iterators_alias_u8x64 ( ) {
242+ let v: Vec < u8 > = ( 0u8 ..192 ) . collect ( ) ;
243+ let col = MultiLaneColumn :: new ( Arc :: from ( v) ) . unwrap ( ) ;
244+
245+ let u8_wins: Vec < & [ u8 ; 64 ] > = col. iter_u8x64 ( ) . collect ( ) ;
246+ let f32_wins: Vec < & [ u8 ; 64 ] > = col. iter_f32x16_bytes ( ) . collect ( ) ;
247+ let f64_wins: Vec < & [ u8 ; 64 ] > = col. iter_f64x8_bytes ( ) . collect ( ) ;
248+ let u64_wins: Vec < & [ u8 ; 64 ] > = col. iter_u64x8_bytes ( ) . collect ( ) ;
249+
250+ assert_eq ! ( u8_wins. len( ) , 3 ) ;
251+ assert_eq ! ( f32_wins. len( ) , 3 ) ;
252+ assert_eq ! ( f64_wins. len( ) , 3 ) ;
253+ assert_eq ! ( u64_wins. len( ) , 3 ) ;
254+
255+ // Each shape iterator yields references into the same backing bytes:
256+ // pointer equality across the four iterators on every chunk.
257+ for i in 0 ..3 {
258+ assert_eq ! ( u8_wins[ i] . as_ptr( ) , f32_wins[ i] . as_ptr( ) ) ;
259+ assert_eq ! ( u8_wins[ i] . as_ptr( ) , f64_wins[ i] . as_ptr( ) ) ;
260+ assert_eq ! ( u8_wins[ i] . as_ptr( ) , u64_wins[ i] . as_ptr( ) ) ;
261+ assert_eq ! ( u8_wins[ i] [ 0 ] , ( i as u8 ) * 64 ) ;
262+ assert_eq ! ( u8_wins[ i] [ 63 ] , ( i as u8 ) * 64 + 63 ) ;
263+ }
264+ }
265+
266+ /// `as_bytes()` returns the full backing slice and aliases the Arc storage.
267+ #[ test]
268+ fn as_bytes_returns_full_backing_slice ( ) {
269+ let v: Vec < u8 > = ( 0u8 ..64 ) . collect ( ) ;
270+ let arc: Arc < [ u8 ] > = Arc :: from ( v) ;
271+ let arc_ptr = arc. as_ptr ( ) ;
272+ let col = MultiLaneColumn :: new ( arc) . unwrap ( ) ;
273+ let bytes = col. as_bytes ( ) ;
274+ assert_eq ! ( bytes. len( ) , 64 ) ;
275+ assert_eq ! ( bytes. as_ptr( ) , arc_ptr, "as_bytes must alias the Arc backing, not copy" ) ;
276+ for ( i, & b) in bytes. iter ( ) . enumerate ( ) {
277+ assert_eq ! ( b, i as u8 ) ;
278+ }
279+ }
280+
281+ /// Static assertion: `MultiLaneColumn` is `Send + Sync`, so it can cross
282+ /// thread boundaries — required for cognitive-shader-stack multi-consumer
283+ /// access patterns.
284+ #[ test]
285+ fn multilane_column_is_send_sync ( ) {
286+ fn assert_send_sync < T : Send + Sync > ( ) { }
287+ assert_send_sync :: < MultiLaneColumn > ( ) ;
201288 }
202289}
0 commit comments