1use crate::fuse::FusionPlanError;
4#[cfg(feature = "parallel")]
5use crate::hptt::execute_permute_blocked_par;
6use crate::hptt::{build_permute_plan, execute_permute_blocked};
7use strided_view::{Result, StridedError, StridedView, StridedViewMut};
8
9fn plan_error(err: FusionPlanError) -> StridedError {
16 match err {
17 FusionPlanError::LengthMismatch { .. } => StridedError::StrideLengthMismatch,
18 FusionPlanError::DimensionOverflow => StridedError::OffsetOverflow,
19 }
20}
21
22fn total_len(dims: &[usize]) -> usize {
23 dims.iter().product()
24}
25
26fn is_both_contiguous(dims: &[usize], dst_strides: &[isize], src_strides: &[isize]) -> bool {
28 if dims.is_empty() {
29 return true;
30 }
31
32 let mut expected = 1isize;
34 let mut col_ok = true;
35 for (&d, (&ds, &ss)) in dims.iter().zip(dst_strides.iter().zip(src_strides.iter())) {
36 if d <= 1 {
37 continue;
38 }
39 if ds != expected || ss != expected {
40 col_ok = false;
41 break;
42 }
43 expected = expected.saturating_mul(d as isize);
44 }
45 if col_ok {
46 return true;
47 }
48
49 let mut expected = 1isize;
51 let mut row_ok = true;
52 for (&d, (&ds, &ss)) in dims
53 .iter()
54 .rev()
55 .zip(dst_strides.iter().rev().zip(src_strides.iter().rev()))
56 {
57 if d <= 1 {
58 continue;
59 }
60 if ds != expected || ss != expected {
61 row_ok = false;
62 break;
63 }
64 expected = expected.saturating_mul(d as isize);
65 }
66 row_ok
67}
68
69pub fn copy_into<T: Copy>(dest: &mut StridedViewMut<T>, src: &StridedView<T>) -> Result<()> {
77 let dst_dims = dest.dims();
78 let src_dims = src.dims();
79 if dst_dims.len() != src_dims.len() {
80 return Err(StridedError::RankMismatch(dst_dims.len(), src_dims.len()));
81 }
82 if dst_dims != src_dims {
83 return Err(StridedError::ShapeMismatch(
84 dst_dims.to_vec(),
85 src_dims.to_vec(),
86 ));
87 }
88 if dst_dims.contains(&0) {
92 return Ok(());
93 }
94
95 let dst_ptr = dest.as_mut_ptr();
96 let src_ptr = src.ptr();
97 let dst_strides = dest.strides();
98 let src_strides = src.strides();
99
100 if is_both_contiguous(dst_dims, dst_strides, src_strides) {
102 let len = total_len(dst_dims);
103 unsafe { std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len) };
104 return Ok(());
105 }
106
107 let elem_size = std::mem::size_of::<T>();
109 let plan =
110 build_permute_plan(dst_dims, src_strides, dst_strides, elem_size).map_err(plan_error)?;
111 unsafe {
112 execute_permute_blocked(src_ptr, dst_ptr, &plan);
113 }
114 Ok(())
115}
116
117pub fn copy_into_col_major<T: Copy>(
123 dst: &mut StridedViewMut<T>,
124 src: &StridedView<T>,
125) -> Result<()> {
126 copy_into(dst, src)
127}
128
129fn destination_offsets_are_separated(dims: &[usize], strides: &[isize]) -> bool {
140 let mut axes: Vec<(isize, usize)> = dims
141 .iter()
142 .copied()
143 .zip(strides.iter().copied())
144 .filter(|&(dim, _)| dim > 1)
145 .map(|(dim, stride)| (stride, dim))
146 .collect();
147 axes.sort_by_key(|&(stride, _)| stride.unsigned_abs());
148 let mut span: u128 = 0;
149 for (stride, dim) in axes {
150 if stride <= 0 || stride as u128 <= span {
151 return false;
152 }
153 span = span.saturating_add((dim as u128 - 1).saturating_mul(stride as u128));
154 }
155 true
156}
157
158#[cfg(feature = "parallel")]
163pub fn copy_into_par<T: Copy + Send + Sync>(
164 dest: &mut StridedViewMut<T>,
165 src: &StridedView<T>,
166) -> Result<()> {
167 let dst_dims = dest.dims();
168 let src_dims = src.dims();
169 if dst_dims.len() != src_dims.len() {
170 return Err(StridedError::RankMismatch(dst_dims.len(), src_dims.len()));
171 }
172 if dst_dims != src_dims {
173 return Err(StridedError::ShapeMismatch(
174 dst_dims.to_vec(),
175 src_dims.to_vec(),
176 ));
177 }
178 if dst_dims.contains(&0) {
182 return Ok(());
183 }
184
185 let dst_ptr = dest.as_mut_ptr();
186 let src_ptr = src.ptr();
187 let dst_strides = dest.strides();
188 let src_strides = src.strides();
189
190 if !destination_offsets_are_separated(dst_dims, dst_strides) {
191 return copy_into(dest, src);
195 }
196
197 if is_both_contiguous(dst_dims, dst_strides, src_strides) {
199 let len = total_len(dst_dims);
200 unsafe { std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len) };
201 return Ok(());
202 }
203
204 let elem_size = std::mem::size_of::<T>();
205 let plan =
206 build_permute_plan(dst_dims, src_strides, dst_strides, elem_size).map_err(plan_error)?;
207 unsafe {
208 execute_permute_blocked_par(src_ptr, dst_ptr, &plan);
209 }
210 Ok(())
211}
212
213#[cfg(feature = "parallel")]
215pub fn copy_into_col_major_par<T: Copy + Send + Sync>(
216 dst: &mut StridedViewMut<T>,
217 src: &StridedView<T>,
218) -> Result<()> {
219 copy_into_par(dst, src)
220}
221
222#[cfg(test)]
227fn try_fuse_group(dims: &[usize], strides: &[isize]) -> Option<(usize, isize)> {
228 match dims.len() {
229 0 => Some((1, 0)),
230 1 => Some((dims[0], strides[0])),
231 _ => {
232 if dims.len() != strides.len() {
233 return None;
234 }
235 for (&d, &s) in dims.iter().zip(strides.iter()) {
236 if d > 1 && s == 0 {
237 return None;
238 }
239 }
240 let mut base_idx: Option<usize> = None;
241 let mut base_abs = usize::MAX;
242 for (i, (&d, &s)) in dims.iter().zip(strides.iter()).enumerate() {
243 if d <= 1 {
244 continue;
245 }
246 let abs = s.unsigned_abs();
247 if abs < base_abs {
248 base_abs = abs;
249 base_idx = Some(i);
250 }
251 }
252
253 let Some(base) = base_idx else {
254 let stride = *strides
255 .iter()
256 .min_by_key(|s| s.unsigned_abs())
257 .unwrap_or(&0);
258 return Some((dims.iter().product(), stride));
259 };
260
261 let mut used = vec![false; dims.len()];
262 used[base] = true;
263 let mut expected_abs = base_abs.checked_mul(dims[base])?;
264
265 let non_singleton = dims.iter().filter(|&&d| d > 1).count();
266 for _ in 1..non_singleton {
267 let mut next = None;
268 for i in 0..dims.len() {
269 if used[i] || dims[i] <= 1 {
270 continue;
271 }
272 if strides[i].unsigned_abs() == expected_abs {
273 next = Some(i);
274 break;
275 }
276 }
277 let i = next?;
278 used[i] = true;
279 expected_abs = expected_abs.checked_mul(dims[i])?;
280 }
281
282 Some((dims.iter().product(), strides[base]))
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use strided_view::StridedArray;
291
292 #[test]
293 fn test_copy_into_contiguous() {
294 let src =
295 StridedArray::<f64>::from_fn_col_major(&[2, 3], |idx| (idx[0] * 10 + idx[1]) as f64);
296 let mut dst = StridedArray::<f64>::col_major(&[2, 3]);
297 copy_into(&mut dst.view_mut(), &src.view()).unwrap();
298 assert_eq!(dst.get(&[0, 0]), 0.0);
299 assert_eq!(dst.get(&[1, 2]), 12.0);
300 }
301
302 #[test]
303 fn test_copy_into_transposed() {
304 let src = StridedArray::<f64>::from_parts(
306 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
307 &[3, 2],
308 &[2, 1], 0,
310 )
311 .unwrap();
312 let mut dst = StridedArray::<f64>::col_major(&[3, 2]);
313 copy_into(&mut dst.view_mut(), &src.view()).unwrap();
314 assert_eq!(dst.get(&[0, 0]), 0.0);
315 assert_eq!(dst.get(&[0, 1]), 1.0);
316 assert_eq!(dst.get(&[1, 0]), 2.0);
317 assert_eq!(dst.get(&[2, 1]), 5.0);
318 }
319
320 #[test]
321 fn test_copy_into_col_major_basic() {
322 let src =
323 StridedArray::<f64>::from_fn_col_major(&[4, 3], |idx| (idx[0] * 10 + idx[1]) as f64);
324 let mut dst = StridedArray::<f64>::col_major(&[4, 3]);
325 copy_into_col_major(&mut dst.view_mut(), &src.view()).unwrap();
326 assert_eq!(dst.get(&[0, 0]), 0.0);
327 assert_eq!(dst.get(&[3, 2]), 32.0);
328 }
329
330 #[test]
331 fn test_copy_into_col_major_permuted_src() {
332 let data: Vec<f64> = (0..24).map(|i| i as f64).collect();
334 let src = StridedArray::<f64>::from_parts(
335 data,
336 &[2, 3, 4],
337 &[12, 4, 1], 0,
339 )
340 .unwrap();
341 let mut dst = StridedArray::<f64>::col_major(&[2, 3, 4]);
342 copy_into_col_major(&mut dst.view_mut(), &src.view()).unwrap();
343 for i in 0..2 {
345 for j in 0..3 {
346 for k in 0..4 {
347 assert_eq!(
348 dst.get(&[i, j, k]),
349 src.get(&[i, j, k]),
350 "mismatch at [{},{},{}]",
351 i,
352 j,
353 k
354 );
355 }
356 }
357 }
358 }
359
360 #[test]
361 fn test_copy_into_grouped_high_rank_binary_permutation() {
362 let dims = vec![2usize; 12];
363 let total: usize = dims.iter().product();
364 let src = StridedArray::<u32>::from_fn_col_major(&dims, |idx| {
365 idx.iter()
366 .enumerate()
367 .map(|(axis, &coord)| (coord as u32) << axis)
368 .sum::<u32>()
369 });
370 let perm = vec![2, 4, 5, 6, 0, 3, 7, 1, 8, 9, 10, 11];
371 let src_view = src.view().permute(&perm).unwrap();
372 let mut dst = StridedArray::<u32>::col_major(&dims);
373
374 copy_into(&mut dst.view_mut(), &src_view).unwrap();
375
376 for flat in 0..total {
377 let mut rem = flat;
378 let mut src_offset = 0usize;
379 for &stride in src_view.strides() {
380 let coord = rem & 1;
381 rem >>= 1;
382 src_offset += coord * stride as usize;
383 }
384 assert_eq!(dst.data()[flat], src.data()[src_offset], "flat={flat}");
385 }
386 }
387
388 #[test]
389 fn test_try_fuse_group_empty() {
390 assert_eq!(try_fuse_group(&[], &[]), Some((1, 0)));
391 }
392
393 #[test]
394 fn test_try_fuse_group_single() {
395 assert_eq!(try_fuse_group(&[5], &[2]), Some((5, 2)));
396 }
397
398 #[test]
399 fn test_try_fuse_group_contiguous_row_major() {
400 assert_eq!(try_fuse_group(&[3, 4], &[4, 1]), Some((12, 1)));
401 }
402
403 #[test]
404 fn test_try_fuse_group_contiguous_col_major() {
405 assert_eq!(try_fuse_group(&[3, 4], &[1, 3]), Some((12, 1)));
406 }
407
408 #[test]
409 fn test_try_fuse_group_non_contiguous() {
410 assert_eq!(try_fuse_group(&[3, 4], &[8, 1]), None);
411 }
412
413 #[test]
414 fn destination_separation_rejects_overlapping_strides() {
415 assert!(destination_offsets_are_separated(&[3, 16384], &[1, 3]));
416 assert!(!destination_offsets_are_separated(&[3, 16384], &[2, 1]));
419 assert!(!destination_offsets_are_separated(&[4], &[-1]));
420 assert!(destination_offsets_are_separated(&[1, 1], &[0, 1]));
421 }
422
423 #[cfg(feature = "parallel")]
424 #[test]
425 fn parallel_transpose_splits_the_full_tile_grid_for_a_short_row_extent() {
426 let rows = 20000usize;
432 let cols = 3usize;
433 let data: Vec<f64> = (0..rows * cols).map(|i| i as f64).collect();
434 let src =
435 StridedArray::<f64>::from_parts(data, &[rows, cols], &[cols as isize, 1], 0).unwrap();
436 let pool = rayon::ThreadPoolBuilder::new()
437 .num_threads(2)
438 .build()
439 .unwrap();
440 let mut dst = StridedArray::<f64>::col_major(&[rows, cols]);
441 pool.install(|| copy_into_par(&mut dst.view_mut(), &src.view()).unwrap());
442 for r in 0..rows {
443 for c in 0..cols {
444 assert_eq!(dst.get(&[r, c]), (r * cols + c) as f64);
445 }
446 }
447 }
448
449 #[test]
450 fn test_copy_shape_mismatch() {
451 let src = StridedArray::<f64>::col_major(&[2, 3]);
452 let mut dst = StridedArray::<f64>::col_major(&[3, 2]);
453 let result = copy_into(&mut dst.view_mut(), &src.view());
454 assert!(result.is_err());
455 }
456}