1use crate::fuse::FusionPlanError;
4use crate::hptt::{build_permute_plan, execute_permute_blocked};
5use strided_view::{Result, StridedError, StridedView, StridedViewMut};
6use threadteam::Exec;
7
8fn plan_error(err: FusionPlanError) -> StridedError {
15 match err {
16 FusionPlanError::LengthMismatch { .. } => StridedError::StrideLengthMismatch,
17 FusionPlanError::DimensionOverflow => StridedError::OffsetOverflow,
18 }
19}
20
21fn total_len(dims: &[usize]) -> usize {
22 dims.iter().product()
23}
24
25fn is_both_contiguous(dims: &[usize], dst_strides: &[isize], src_strides: &[isize]) -> bool {
27 if dims.is_empty() {
28 return true;
29 }
30
31 let mut expected = 1isize;
33 let mut col_ok = true;
34 for (&d, (&ds, &ss)) in dims.iter().zip(dst_strides.iter().zip(src_strides.iter())) {
35 if d <= 1 {
36 continue;
37 }
38 if ds != expected || ss != expected {
39 col_ok = false;
40 break;
41 }
42 expected = expected.saturating_mul(d as isize);
43 }
44 if col_ok {
45 return true;
46 }
47
48 let mut expected = 1isize;
50 let mut row_ok = true;
51 for (&d, (&ds, &ss)) in dims
52 .iter()
53 .rev()
54 .zip(dst_strides.iter().rev().zip(src_strides.iter().rev()))
55 {
56 if d <= 1 {
57 continue;
58 }
59 if ds != expected || ss != expected {
60 row_ok = false;
61 break;
62 }
63 expected = expected.saturating_mul(d as isize);
64 }
65 row_ok
66}
67
68struct RawCopy<'a, T, U> {
72 dims: &'a [usize],
73 dst_strides: &'a [isize],
74 src_strides: &'a [isize],
75 dst: *mut U,
76 src: *const T,
77}
78
79fn prepare<'a, T, U>(
86 dest: &'a mut StridedViewMut<U>,
87 src: &'a StridedView<T>,
88) -> Result<Option<RawCopy<'a, T, U>>> {
89 let dst_dims = dest.dims();
90 let src_dims = src.dims();
91 if dst_dims.len() != src_dims.len() {
92 return Err(StridedError::RankMismatch(dst_dims.len(), src_dims.len()));
93 }
94 if dst_dims != src_dims {
95 return Err(StridedError::ShapeMismatch(
96 dst_dims.to_vec(),
97 src_dims.to_vec(),
98 ));
99 }
100 if dst_dims.contains(&0) {
101 return Ok(None);
102 }
103 Ok(Some(RawCopy {
104 dims: dst_dims,
105 dst_strides: dest.strides(),
106 src_strides: src.strides(),
107 dst: dest.as_mut_ptr(),
108 src: src.ptr(),
109 }))
110}
111
112unsafe fn copy_raw<T: Copy + Send + Sync + 'static, U>(
121 raw: RawCopy<'_, T, U>,
122 exec: Exec<'_>,
123) -> Result<()> {
124 let dst_ptr = raw.dst.cast::<T>();
125 let exec = match raw.dims.iter().try_fold(1usize, |n, &d| n.checked_mul(d)) {
128 Some(n) => {
129 let estimate =
130 std::time::Duration::from_secs_f64(n as f64 * crate::copy_ns_per_element() * 1e-9);
131 let width = exec.width_for(estimate, 1);
132 exec.limit(width)
133 }
134 None => exec.limit(std::num::NonZeroUsize::MIN),
136 };
137 if is_both_contiguous(raw.dims, raw.dst_strides, raw.src_strides) {
140 let len = total_len(raw.dims);
141 if exec.is_serial() {
142 unsafe { std::ptr::copy_nonoverlapping(raw.src, dst_ptr, len) };
144 } else {
145 let src_addr = raw.src as usize;
146 let dst_addr = dst_ptr as usize;
147 exec.for_ranges(len, std::num::NonZeroUsize::MIN, |range| {
148 unsafe {
151 std::ptr::copy_nonoverlapping(
152 (src_addr as *const T).add(range.start),
153 (dst_addr as *mut T).add(range.start),
154 range.len(),
155 );
156 }
157 });
158 }
159 return Ok(());
160 }
161
162 let elem_size = std::mem::size_of::<T>();
167 let plan = build_permute_plan(raw.dims, raw.src_strides, raw.dst_strides, elem_size)
168 .map_err(plan_error)?;
169 if !strided_view::is_injective_layout(raw.dims, raw.dst_strides) {
176 return Err(StridedError::NonInjectiveOutputLayout);
177 }
178 execute_permute_blocked(raw.src, dst_ptr, &plan, exec);
179 Ok(())
180}
181
182pub fn copy_into<T: Copy + Send + Sync + 'static>(
209 dest: &mut StridedViewMut<T>,
210 src: &StridedView<T>,
211 exec: Exec<'_>,
212) -> Result<()> {
213 match prepare(dest, src)? {
214 Some(raw) => unsafe { copy_raw(raw, exec) },
217 None => Ok(()),
218 }
219}
220
221pub fn copy_into_uninit<T: Copy + Send + Sync + 'static>(
231 dest: &mut StridedViewMut<core::mem::MaybeUninit<T>>,
232 src: &StridedView<T>,
233 exec: Exec<'_>,
234) -> Result<()> {
235 match prepare(dest, src)? {
236 Some(raw) => unsafe { copy_raw(raw, exec) },
239 None => Ok(()),
240 }
241}
242
243pub fn copy_into_col_major<T: Copy + Send + Sync + 'static>(
249 dst: &mut StridedViewMut<T>,
250 src: &StridedView<T>,
251 exec: Exec<'_>,
252) -> Result<()> {
253 copy_into(dst, src, exec)
254}
255
256#[cfg(test)]
257fn try_fuse_group(dims: &[usize], strides: &[isize]) -> Option<(usize, isize)> {
258 match dims.len() {
259 0 => Some((1, 0)),
260 1 => Some((dims[0], strides[0])),
261 _ => {
262 if dims.len() != strides.len() {
263 return None;
264 }
265 for (&d, &s) in dims.iter().zip(strides.iter()) {
266 if d > 1 && s == 0 {
267 return None;
268 }
269 }
270 let mut base_idx: Option<usize> = None;
271 let mut base_abs = usize::MAX;
272 for (i, (&d, &s)) in dims.iter().zip(strides.iter()).enumerate() {
273 if d <= 1 {
274 continue;
275 }
276 let abs = s.unsigned_abs();
277 if abs < base_abs {
278 base_abs = abs;
279 base_idx = Some(i);
280 }
281 }
282
283 let Some(base) = base_idx else {
284 let stride = *strides
285 .iter()
286 .min_by_key(|s| s.unsigned_abs())
287 .unwrap_or(&0);
288 return Some((dims.iter().product(), stride));
289 };
290
291 let mut used = vec![false; dims.len()];
292 used[base] = true;
293 let mut expected_abs = base_abs.checked_mul(dims[base])?;
294
295 let non_singleton = dims.iter().filter(|&&d| d > 1).count();
296 for _ in 1..non_singleton {
297 let mut next = None;
298 for i in 0..dims.len() {
299 if used[i] || dims[i] <= 1 {
300 continue;
301 }
302 if strides[i].unsigned_abs() == expected_abs {
303 next = Some(i);
304 break;
305 }
306 }
307 let i = next?;
308 used[i] = true;
309 expected_abs = expected_abs.checked_mul(dims[i])?;
310 }
311
312 Some((dims.iter().product(), strides[base]))
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use strided_view::StridedArray;
321
322 #[test]
323 fn test_copy_into_contiguous() {
324 let src =
325 StridedArray::<f64>::from_fn_col_major(&[2, 3], |idx| (idx[0] * 10 + idx[1]) as f64);
326 let mut dst = StridedArray::<f64>::col_major(&[2, 3]);
327 copy_into(&mut dst.view_mut(), &src.view(), Exec::serial()).unwrap();
328 assert_eq!(dst.get(&[0, 0]), 0.0);
329 assert_eq!(dst.get(&[1, 2]), 12.0);
330 }
331
332 #[test]
333 fn test_copy_into_transposed() {
334 let src = StridedArray::<f64>::from_parts(
336 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
337 &[3, 2],
338 &[2, 1], 0,
340 )
341 .unwrap();
342 let mut dst = StridedArray::<f64>::col_major(&[3, 2]);
343 copy_into(&mut dst.view_mut(), &src.view(), Exec::serial()).unwrap();
344 assert_eq!(dst.get(&[0, 0]), 0.0);
345 assert_eq!(dst.get(&[0, 1]), 1.0);
346 assert_eq!(dst.get(&[1, 0]), 2.0);
347 assert_eq!(dst.get(&[2, 1]), 5.0);
348 }
349
350 #[test]
351 fn test_copy_into_col_major_basic() {
352 let src =
353 StridedArray::<f64>::from_fn_col_major(&[4, 3], |idx| (idx[0] * 10 + idx[1]) as f64);
354 let mut dst = StridedArray::<f64>::col_major(&[4, 3]);
355 copy_into_col_major(&mut dst.view_mut(), &src.view(), Exec::serial()).unwrap();
356 assert_eq!(dst.get(&[0, 0]), 0.0);
357 assert_eq!(dst.get(&[3, 2]), 32.0);
358 }
359
360 #[test]
361 fn test_copy_into_col_major_permuted_src() {
362 let data: Vec<f64> = (0..24).map(|i| i as f64).collect();
364 let src = StridedArray::<f64>::from_parts(
365 data,
366 &[2, 3, 4],
367 &[12, 4, 1], 0,
369 )
370 .unwrap();
371 let mut dst = StridedArray::<f64>::col_major(&[2, 3, 4]);
372 copy_into_col_major(&mut dst.view_mut(), &src.view(), Exec::serial()).unwrap();
373 for i in 0..2 {
375 for j in 0..3 {
376 for k in 0..4 {
377 assert_eq!(
378 dst.get(&[i, j, k]),
379 src.get(&[i, j, k]),
380 "mismatch at [{},{},{}]",
381 i,
382 j,
383 k
384 );
385 }
386 }
387 }
388 }
389
390 #[test]
391 fn test_copy_into_grouped_high_rank_binary_permutation() {
392 let dims = vec![2usize; 12];
393 let total: usize = dims.iter().product();
394 let src = StridedArray::<u32>::from_fn_col_major(&dims, |idx| {
395 idx.iter()
396 .enumerate()
397 .map(|(axis, &coord)| (coord as u32) << axis)
398 .sum::<u32>()
399 });
400 let perm = vec![2, 4, 5, 6, 0, 3, 7, 1, 8, 9, 10, 11];
401 let src_view = src.view().permute(&perm).unwrap();
402 let mut dst = StridedArray::<u32>::col_major(&dims);
403
404 copy_into(&mut dst.view_mut(), &src_view, Exec::serial()).unwrap();
405
406 for flat in 0..total {
407 let mut rem = flat;
408 let mut src_offset = 0usize;
409 for &stride in src_view.strides() {
410 let coord = rem & 1;
411 rem >>= 1;
412 src_offset += coord * stride as usize;
413 }
414 assert_eq!(dst.data()[flat], src.data()[src_offset], "flat={flat}");
415 }
416 }
417
418 #[test]
419 fn test_try_fuse_group_empty() {
420 assert_eq!(try_fuse_group(&[], &[]), Some((1, 0)));
421 }
422
423 #[test]
424 fn test_try_fuse_group_single() {
425 assert_eq!(try_fuse_group(&[5], &[2]), Some((5, 2)));
426 }
427
428 #[test]
429 fn test_try_fuse_group_contiguous_row_major() {
430 assert_eq!(try_fuse_group(&[3, 4], &[4, 1]), Some((12, 1)));
431 }
432
433 #[test]
434 fn test_try_fuse_group_contiguous_col_major() {
435 assert_eq!(try_fuse_group(&[3, 4], &[1, 3]), Some((12, 1)));
436 }
437
438 #[test]
439 fn test_try_fuse_group_non_contiguous() {
440 assert_eq!(try_fuse_group(&[3, 4], &[8, 1]), None);
441 }
442
443 #[test]
444 fn parallel_transpose_splits_the_full_tile_grid_for_a_short_row_extent() {
445 let rows = 20000usize;
451 let cols = 3usize;
452 let data: Vec<f64> = (0..rows * cols).map(|i| i as f64).collect();
453 let src =
454 StridedArray::<f64>::from_parts(data, &[rows, cols], &[cols as isize, 1], 0).unwrap();
455 let pool = threadteam::Pool::builder().threads(2).build().unwrap();
456 let mut dst = StridedArray::<f64>::col_major(&[rows, cols]);
457 let exec = Exec::new(&pool, 2).unwrap();
458 copy_into(&mut dst.view_mut(), &src.view(), exec).unwrap();
459 for r in 0..rows {
460 for c in 0..cols {
461 assert_eq!(dst.get(&[r, c]), (r * cols + c) as f64);
462 }
463 }
464 }
465
466 #[test]
467 fn test_copy_shape_mismatch() {
468 let src = StridedArray::<f64>::col_major(&[2, 3]);
469 let mut dst = StridedArray::<f64>::col_major(&[3, 2]);
470 let result = copy_into(&mut dst.view_mut(), &src.view(), Exec::serial());
471 assert!(result.is_err());
472 }
473}