Skip to main content

binius_utils/
strided_array.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use std::{
4	marker::PhantomData,
5	ops::{Index, IndexMut, Range},
6	slice,
7};
8
9use crate::rayon::prelude::*;
10
11#[derive(Debug, thiserror::Error)]
12pub enum Error {
13	#[error("dimensions do not match data size")]
14	DimensionMismatch,
15}
16
17/// A mutable view of an 2D array in row-major order that allows for parallel processing of
18/// vertical slices.
19#[derive(Debug)]
20pub struct StridedArray2DViewMut<'a, T> {
21	data: *mut T,
22	data_width: usize,
23	height: usize,
24	cols: Range<usize>,
25	_marker: PhantomData<&'a mut [T]>,
26}
27
28// SAFETY: the view has exclusive access to its columns, matching `&mut [T]`.
29unsafe impl<T: Send> Send for StridedArray2DViewMut<'_, T> {}
30// SAFETY: a shared reference to the view yields only `&T`, matching `&mut [T]`.
31unsafe impl<T: Sync> Sync for StridedArray2DViewMut<'_, T> {}
32
33impl<'a, T> StridedArray2DViewMut<'a, T> {
34	/// Create a single-piece view of the data.
35	pub const fn without_stride(
36		data: &'a mut [T],
37		height: usize,
38		width: usize,
39	) -> Result<Self, Error> {
40		if width * height != data.len() {
41			return Err(Error::DimensionMismatch);
42		}
43		Ok(Self {
44			data: data.as_mut_ptr(),
45			data_width: width,
46			height,
47			cols: 0..width,
48			_marker: PhantomData,
49		})
50	}
51
52	/// Returns a reference to the data at the given indices without bounds checking.
53	/// # Safety
54	/// The caller must ensure that `i < self.height` and `j < self.width()`.
55	pub unsafe fn get_unchecked_ref(&self, i: usize, j: usize) -> &T {
56		debug_assert!(i < self.height);
57		debug_assert!(j < self.width());
58		// SAFETY:
59		// - Provenance: reborrowed from the exclusive borrow the view was built from.
60		// - Bounds: construction pairs the buffer length with the dimensions, and the caller
61		//   guarantees the indices are in range.
62		// - Non-overlap: sibling views hold disjoint column ranges.
63		// - Only the addressed element is ever referenced, never a span of the buffer.
64		unsafe { &*self.data.add(i * self.data_width + j + self.cols.start) }
65	}
66
67	/// Returns a mutable reference to the data at the given indices without bounds checking.
68	/// # Safety
69	/// The caller must ensure that `i < self.height` and `j < self.width()`.
70	pub unsafe fn get_unchecked_mut(&mut self, i: usize, j: usize) -> &mut T {
71		debug_assert!(i < self.height);
72		debug_assert!(j < self.width());
73		// SAFETY:
74		// - Provenance: reborrowed from the exclusive borrow the view was built from.
75		// - Bounds: construction pairs the buffer length with the dimensions, and the caller
76		//   guarantees the indices are in range.
77		// - Non-overlap: sibling views hold disjoint column ranges.
78		// - Only the addressed element is ever referenced, never a span of the buffer.
79		unsafe { &mut *self.data.add(i * self.data_width + j + self.cols.start) }
80	}
81
82	/// Returns this view's columns of row `i`, which are contiguous in memory.
83	///
84	/// # Panics
85	///
86	/// Panics if `i` is out of bounds.
87	pub fn row(&self, i: usize) -> &[T] {
88		assert!(i < self.height);
89		let start = i * self.data_width + self.cols.start;
90		// SAFETY:
91		// - Provenance: reborrowed from the exclusive borrow the view was built from.
92		// - Bounds: construction pairs the buffer length with the dimensions, `i` is checked above,
93		//   and the column range is a subrange of the row.
94		// - Non-overlap: the slice spans only this view's columns, which no sibling view holds.
95		unsafe { slice::from_raw_parts(self.data.add(start), self.width()) }
96	}
97
98	/// Returns the `mut_rows` rows mutably alongside the `shared_rows` rows.
99	///
100	/// # Panics
101	///
102	/// Panics if any index is out of bounds.
103	/// Panics if a mutable row repeats, or appears among the shared rows.
104	pub fn rows_mut<const M: usize, const S: usize>(
105		&mut self,
106		mut_rows: [usize; M],
107		shared_rows: [usize; S],
108	) -> ([&mut [T]; M], [&[T]; S]) {
109		for &shared in &shared_rows {
110			assert!(shared < self.height);
111		}
112		for (k, &row) in mut_rows.iter().enumerate() {
113			assert!(row < self.height);
114			assert!(!mut_rows[..k].contains(&row), "a mutable row repeats");
115			assert!(!shared_rows.contains(&row), "a mutable row is also a shared row");
116		}
117
118		let (data_width, start, width) = (self.data_width, self.cols.start, self.width());
119		let base = self.data;
120		// SAFETY:
121		// Row `i` occupies `i * data_width + start .. + width`, inside `data` because
122		// `i < height` and the column range is a subrange of the row.
123		// The asserts above make the mutable rows pairwise distinct and disjoint from the
124		// shared rows, so no two of the returned slices ever overlap.
125		// Dropping either assert would hand out two references to one element.
126		unsafe {
127			(
128				mut_rows
129					.map(|i| slice::from_raw_parts_mut(base.add(i * data_width + start), width)),
130				shared_rows.map(|i| slice::from_raw_parts(base.add(i * data_width + start), width)),
131			)
132		}
133	}
134
135	pub const fn height(&self) -> usize {
136		self.height
137	}
138
139	pub const fn width(&self) -> usize {
140		self.cols.end - self.cols.start
141	}
142
143	/// Iterate over the mutable references to the elements in the specified column.
144	pub fn iter_column_mut(&mut self, col: usize) -> impl Iterator<Item = &mut T> + '_ {
145		assert!(col < self.width());
146		let start = col + self.cols.start;
147		let data = self.data;
148		(0..self.height).map(move |i|
149				// SAFETY:
150				// - Provenance: reborrowed from the exclusive borrow the view was built from.
151				// - Bounds: the row is below the height and the column is checked above.
152				// - Non-overlap: one row per step, so no two yielded references coincide.
153				unsafe { &mut *data.add(i * self.data_width + start) })
154	}
155
156	/// Returns iterator over vertical slices of the data for the given stride.
157	pub fn into_strides(self, stride: usize) -> impl Iterator<Item = Self> + 'a {
158		let Self {
159			data,
160			data_width,
161			height,
162			cols,
163			..
164		} = self;
165
166		cols.clone().step_by(stride).map(move |start| {
167			let end = (start + stride).min(cols.end);
168			Self {
169				data,
170				data_width,
171				height,
172				cols: start..end,
173				_marker: PhantomData,
174			}
175		})
176	}
177
178	/// Returns parallel iterator over vertical slices of the data for the given stride.
179	pub fn into_par_strides(self, stride: usize) -> impl IndexedParallelIterator<Item = Self> + 'a
180	where
181		T: Send + Sync,
182	{
183		let Self {
184			data,
185			data_width,
186			height,
187			cols,
188			..
189		} = self;
190		let data = SendPtr(data);
191
192		cols.clone()
193			.into_par_iter()
194			.step_by(stride)
195			.map(move |start| {
196				let end = (start + stride).min(cols.end);
197				Self {
198					data: data.as_ptr(),
199					data_width,
200					height,
201					cols: start..end,
202					_marker: PhantomData,
203				}
204			})
205	}
206
207	/// Returns iterator over single-column mutable views of the data.
208	pub fn iter_cols(&mut self) -> impl Iterator<Item = StridedArray2DColMut<'_, T>> + '_ {
209		let data = self.data;
210		self.cols.clone().map(move |col| StridedArray2DColMut {
211			data,
212			data_width: self.data_width,
213			height: self.height,
214			col,
215			_marker: PhantomData,
216		})
217	}
218
219	/// Returns parallel iterator over single-column mutable views of the data.
220	pub fn par_iter_cols(
221		&mut self,
222	) -> impl IndexedParallelIterator<Item = StridedArray2DColMut<'_, T>> + '_
223	where
224		T: Send + Sync,
225	{
226		let data = SendPtr(self.data);
227		let data_width = self.data_width;
228		let height = self.height;
229		self.cols
230			.clone()
231			.into_par_iter()
232			.map(move |col| StridedArray2DColMut {
233				data: data.as_ptr(),
234				data_width,
235				height,
236				col,
237				_marker: PhantomData,
238			})
239	}
240}
241
242/// A mutable view of a single column (vertical slice) of a 2D array in row-major order.
243#[derive(Debug)]
244pub struct StridedArray2DColMut<'a, T> {
245	data: *mut T,
246	data_width: usize,
247	height: usize,
248	col: usize,
249	_marker: PhantomData<&'a mut [T]>,
250}
251
252// SAFETY: the view has exclusive access to its column, matching `&mut [T]`.
253unsafe impl<T: Send> Send for StridedArray2DColMut<'_, T> {}
254// SAFETY: a shared reference to the view yields only `&T`, matching `&mut [T]`.
255unsafe impl<T: Sync> Sync for StridedArray2DColMut<'_, T> {}
256
257impl<'a, T> StridedArray2DColMut<'a, T> {
258	pub const fn height(&self) -> usize {
259		self.height
260	}
261
262	/// Returns a reference to the data at the given row index without bounds checking.
263	/// # Safety
264	/// The caller must ensure that `i < self.height`.
265	pub unsafe fn get_unchecked_ref(&self, i: usize) -> &T {
266		debug_assert!(i < self.height);
267		// SAFETY:
268		// - Provenance: reborrowed from the exclusive borrow the parent view was built from.
269		// - Bounds: the parent's dimensions cover the buffer, and the caller guarantees the row is
270		//   below the height.
271		// - Non-overlap: the constructors give each view its own column.
272		// - Only the addressed element is ever referenced, never a span of the buffer.
273		unsafe { &*self.data.add(i * self.data_width + self.col) }
274	}
275
276	/// Returns a mutable reference to the data at the given row index without bounds checking.
277	/// # Safety
278	/// The caller must ensure that `i < self.height`.
279	pub unsafe fn get_unchecked_mut(&mut self, i: usize) -> &mut T {
280		debug_assert!(i < self.height);
281		// SAFETY:
282		// - Provenance: reborrowed from the exclusive borrow the parent view was built from.
283		// - Bounds: the parent's dimensions cover the buffer, and the caller guarantees the row is
284		//   below the height.
285		// - Non-overlap: the constructors give each view its own column.
286		// - Only the addressed element is ever referenced, never a span of the buffer.
287		unsafe { &mut *self.data.add(i * self.data_width + self.col) }
288	}
289}
290
291impl<T> Index<usize> for StridedArray2DColMut<'_, T> {
292	type Output = T;
293
294	fn index(&self, i: usize) -> &T {
295		assert!(i < self.height());
296		unsafe { self.get_unchecked_ref(i) }
297	}
298}
299
300impl<T> IndexMut<usize> for StridedArray2DColMut<'_, T> {
301	fn index_mut(&mut self, i: usize) -> &mut Self::Output {
302		assert!(i < self.height());
303		unsafe { self.get_unchecked_mut(i) }
304	}
305}
306
307impl<T> Index<(usize, usize)> for StridedArray2DViewMut<'_, T> {
308	type Output = T;
309
310	fn index(&self, (i, j): (usize, usize)) -> &T {
311		assert!(i < self.height());
312		assert!(j < self.width());
313		unsafe { self.get_unchecked_ref(i, j) }
314	}
315}
316
317impl<T> IndexMut<(usize, usize)> for StridedArray2DViewMut<'_, T> {
318	fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut Self::Output {
319		assert!(i < self.height());
320		assert!(j < self.width());
321		unsafe { self.get_unchecked_mut(i, j) }
322	}
323}
324
325/// A wrapper around a raw pointer that implements Send and Sync.
326///
327/// # Safety
328/// The caller must ensure that the pointer is valid and that concurrent access
329/// through multiple `SendPtr` instances does not cause data races.
330struct SendPtr<T>(*mut T);
331
332impl<T> SendPtr<T> {
333	const fn as_ptr(self) -> *mut T {
334		self.0
335	}
336}
337
338impl<T> Clone for SendPtr<T> {
339	fn clone(&self) -> Self {
340		*self
341	}
342}
343
344impl<T> Copy for SendPtr<T> {}
345
346// Safety: SendPtr is only used internally where we ensure non-overlapping access
347unsafe impl<T: Send> Send for SendPtr<T> {}
348unsafe impl<T: Sync> Sync for SendPtr<T> {}
349
350#[cfg(test)]
351mod tests {
352	use std::array;
353
354	use super::*;
355
356	#[test]
357	fn test_indexing() {
358		let mut data = array::from_fn::<_, 12, _>(|i| i);
359		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
360		assert_eq!(arr[(3, 1)], 10);
361		arr[(2, 2)] = 88;
362		assert_eq!(data[8], 88);
363	}
364
365	#[test]
366	fn test_strides() {
367		let mut data = array::from_fn::<_, 12, _>(|i| i);
368		let arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
369
370		{
371			let mut strides = arr.into_strides(2);
372			let mut stride0 = strides.next().unwrap();
373			let mut stride1 = strides.next().unwrap();
374			assert!(strides.next().is_none());
375
376			assert_eq!(stride0.width(), 2);
377			assert_eq!(stride1.width(), 1);
378
379			stride0[(0, 0)] = 88;
380			stride1[(1, 0)] = 99;
381		}
382
383		assert_eq!(data[0], 88);
384		assert_eq!(data[5], 99);
385	}
386
387	#[test]
388	fn test_parallel_strides() {
389		let mut data = array::from_fn::<_, 12, _>(|i| i);
390		let arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
391
392		{
393			let mut strides: Vec<_> = arr.into_par_strides(2).collect();
394			assert_eq!(strides.len(), 2);
395			assert_eq!(strides[0].width(), 2);
396			assert_eq!(strides[1].width(), 1);
397
398			strides[0][(0, 0)] = 88;
399			strides[1][(1, 0)] = 99;
400		}
401
402		assert_eq!(data[0], 88);
403		assert_eq!(data[5], 99);
404	}
405
406	#[test]
407	fn a_row_covers_only_the_columns_the_view_holds() {
408		let mut data = array::from_fn::<_, 12, _>(|i| i);
409		let arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
410
411		// Row 1 of the array is 3, 4, 5, and a full-width view sees all of it.
412		assert_eq!(arr.row(1), &[3, 4, 5]);
413
414		// The second stride of width 2 holds column 2 alone.
415		let stride = arr.into_strides(2).nth(1).unwrap();
416		assert_eq!(stride.row(1), &[5]);
417	}
418
419	#[test]
420	fn split_rows_alias_nothing_and_write_through() {
421		let mut data = array::from_fn::<_, 12, _>(|i| i);
422		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
423
424		let ([sum, diff], [x, y]) = arr.rows_mut([0, 1], [2, 3]);
425		assert_eq!(x, &[6, 7, 8]);
426		assert_eq!(y, &[9, 10, 11]);
427		for i in 0..3 {
428			sum[i] = x[i] + y[i];
429			diff[i] = y[i] - x[i];
430		}
431
432		assert_eq!(&data[0..3], &[15, 17, 19]);
433		assert_eq!(&data[3..6], &[3, 3, 3]);
434	}
435
436	#[test]
437	#[should_panic(expected = "a mutable row is also a shared row")]
438	fn a_mutable_row_may_not_be_a_shared_row() {
439		let mut data = array::from_fn::<_, 12, _>(|i| i);
440		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
441		arr.rows_mut([1], [0, 1]);
442	}
443
444	#[test]
445	#[should_panic(expected = "a mutable row repeats")]
446	fn two_mutable_rows_may_not_name_one_row() {
447		let mut data = array::from_fn::<_, 12, _>(|i| i);
448		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
449		arr.rows_mut([2, 2], [0]);
450	}
451
452	#[test]
453	fn test_iter_column_mut() {
454		let mut data = array::from_fn::<_, 12, _>(|i| i);
455		let data_clone = data;
456		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
457
458		let mut col_iter = arr.iter_column_mut(1);
459		assert_eq!(col_iter.next().copied(), Some(data_clone[1]));
460		assert_eq!(col_iter.next().copied(), Some(data_clone[4]));
461		assert_eq!(col_iter.next().copied(), Some(data_clone[7]));
462		assert_eq!(col_iter.next().copied(), Some(data_clone[10]));
463		assert_eq!(col_iter.next(), None);
464	}
465
466	#[test]
467	fn test_col_mut_indexing() {
468		let mut data = array::from_fn::<_, 12, _>(|i| i);
469		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
470
471		let mut cols: Vec<_> = arr.iter_cols().collect();
472		assert_eq!(cols.len(), 3);
473
474		// Test reading - column 1 contains elements at indices 1, 4, 7, 10
475		assert_eq!(cols[1][0], 1);
476		assert_eq!(cols[1][1], 4);
477		assert_eq!(cols[1][2], 7);
478		assert_eq!(cols[1][3], 10);
479
480		// Test writing
481		cols[0][2] = 88;
482		cols[2][1] = 99;
483
484		assert_eq!(data[6], 88); // row 2, col 0
485		assert_eq!(data[5], 99); // row 1, col 2
486	}
487
488	#[test]
489	fn test_iter_cols() {
490		let mut data = array::from_fn::<_, 12, _>(|i| i);
491		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
492
493		{
494			let mut cols = arr.iter_cols();
495			let mut col0 = cols.next().unwrap();
496			let mut col1 = cols.next().unwrap();
497			let mut col2 = cols.next().unwrap();
498			assert!(cols.next().is_none());
499
500			assert_eq!(col0.height(), 4);
501			assert_eq!(col1.height(), 4);
502			assert_eq!(col2.height(), 4);
503
504			col0[0] = 88;
505			col1[1] = 99;
506			col2[3] = 77;
507		}
508
509		assert_eq!(data[0], 88); // row 0, col 0
510		assert_eq!(data[4], 99); // row 1, col 1
511		assert_eq!(data[11], 77); // row 3, col 2
512	}
513
514	#[test]
515	fn test_views_are_send_and_sync() {
516		fn assert_send_sync<T: Send + Sync>() {}
517		assert_send_sync::<StridedArray2DViewMut<'_, usize>>();
518		assert_send_sync::<StridedArray2DColMut<'_, usize>>();
519	}
520
521	#[test]
522	fn test_par_iter_cols() {
523		let mut data = array::from_fn::<_, 12, _>(|i| i);
524		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
525
526		{
527			let mut cols: Vec<_> = arr.par_iter_cols().collect();
528			assert_eq!(cols.len(), 3);
529
530			cols[0][0] = 88;
531			cols[1][1] = 99;
532			cols[2][3] = 77;
533		}
534
535		assert_eq!(data[0], 88); // row 0, col 0
536		assert_eq!(data[4], 99); // row 1, col 1
537		assert_eq!(data[11], 77); // row 3, col 2
538	}
539}