1use core::mem::MaybeUninit;
9
10use crate::{CopyPlan, MaybeSendSync, RawStridedMut, RawStridedRef, Result, StridedError};
11
12#[cfg(feature = "parallel")]
13type AxisVec<T> = smallvec::SmallVec<[T; crate::RAW_FUSED_RANK_LIMIT]>;
14#[cfg(not(feature = "parallel"))]
15type AxisVec<T> = Vec<T>;
16
17#[derive(Clone, Debug)]
22pub struct SlicePlan {
23 operand_dims: AxisVec<usize>,
24 operand_strides: AxisVec<isize>,
25 dest_dims: AxisVec<usize>,
26 dest_strides: AxisVec<isize>,
27 source_strides: AxisVec<isize>,
28 source_offset_delta: isize,
29 copy_plan: CopyPlan,
30}
31
32#[derive(Clone, Debug)]
36pub struct ReversePlan {
37 operand_dims: AxisVec<usize>,
38 operand_strides: AxisVec<isize>,
39 dest_strides: AxisVec<isize>,
40 source_strides: AxisVec<isize>,
41 source_offset_delta: isize,
42 copy_plan: CopyPlan,
43}
44
45#[derive(Clone, Debug)]
50pub struct PadPlan {
51 operand_dims: AxisVec<usize>,
52 operand_strides: AxisVec<isize>,
53 dest_dims: AxisVec<usize>,
54 dest_strides: AxisVec<isize>,
55 edge_padding_low: AxisVec<i64>,
56 interior_step: AxisVec<i64>,
57 operand_total: usize,
58 dest_total: usize,
59 contiguous_dest_fill: bool,
60 contiguous_axis0_run: Option<ContiguousPadAxis0Run>,
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64struct ContiguousPadAxis0Run {
65 operand_start: usize,
66 dest_start: usize,
67 len: usize,
68}
69
70#[derive(Clone, Debug)]
75pub struct ConcatenatePlan {
76 input_dims: Vec<AxisVec<usize>>,
77 input_strides: Vec<AxisVec<isize>>,
78 dest_dims: AxisVec<usize>,
79 dest_strides: AxisVec<isize>,
80 dest_offset_deltas: Vec<isize>,
81 copy_plans: Vec<CopyPlan>,
82}
83
84impl SlicePlan {
85 #[allow(clippy::too_many_arguments)]
87 pub fn compile(
88 operand_dims: &[usize],
89 operand_strides: &[isize],
90 dest_dims: &[usize],
91 dest_strides: &[isize],
92 starts: &[usize],
93 limits: &[usize],
94 slice_strides: &[usize],
95 ) -> Result<Self> {
96 let rank = operand_dims.len();
97 if operand_strides.len() != rank || dest_dims.len() != rank || dest_strides.len() != rank {
98 return Err(StridedError::StrideLengthMismatch);
99 }
100 if starts.len() != rank {
101 return Err(StridedError::RankMismatch(starts.len(), rank));
102 }
103 if limits.len() != rank {
104 return Err(StridedError::RankMismatch(limits.len(), rank));
105 }
106 if slice_strides.len() != rank {
107 return Err(StridedError::RankMismatch(slice_strides.len(), rank));
108 }
109 checked_total_len(operand_dims)?;
110 checked_total_len(dest_dims)?;
111
112 let mut expected_dest_dims: AxisVec<usize> = AxisVec::with_capacity(rank);
113 let mut source_strides: AxisVec<isize> = AxisVec::with_capacity(rank);
114 let mut source_offset_delta = 0isize;
115 for axis in 0..rank {
116 let start = starts[axis];
117 let limit = limits[axis];
118 let stride = slice_strides[axis];
119 if start > limit || limit > operand_dims[axis] || stride == 0 {
120 return Err(StridedError::InvalidAxis { axis, rank });
121 }
122 let span = limit - start;
123 expected_dest_dims.push(span.div_ceil(stride));
124 source_strides.push(checked_stride_mul(operand_strides[axis], stride)?);
125 source_offset_delta =
126 checked_offset_add(source_offset_delta, operand_strides[axis], start)?;
127 }
128 if dest_dims != &expected_dest_dims[..] {
129 return Err(StridedError::ShapeMismatch(
130 dest_dims.to_vec(),
131 expected_dest_dims.to_vec(),
132 ));
133 }
134 let copy_plan = CopyPlan::compile(dest_dims, dest_strides, &source_strides)?;
135
136 Ok(Self {
137 operand_dims: operand_dims.into(),
138 operand_strides: operand_strides.into(),
139 dest_dims: dest_dims.into(),
140 dest_strides: dest_strides.into(),
141 source_strides,
142 source_offset_delta,
143 copy_plan,
144 })
145 }
146
147 pub fn execute<T>(
149 &self,
150 dest: &mut RawStridedMut<'_, T>,
151 operand: &RawStridedRef<'_, T>,
152 ) -> Result<()>
153 where
154 T: Copy + MaybeSendSync,
155 {
156 self.check_call(dest, operand)?;
157 let source_offset = operand
158 .offset()
159 .checked_add(self.source_offset_delta)
160 .ok_or(StridedError::OffsetOverflow)?;
161 let source = unsafe {
162 RawStridedRef::new_unchecked(
163 operand.data(),
164 &self.dest_dims,
165 &self.source_strides,
166 source_offset,
167 )
168 };
169 self.copy_plan.execute(dest, &source)
170 }
171
172 pub fn execute_uninit<T>(
174 &self,
175 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
176 operand: &RawStridedRef<'_, T>,
177 ) -> Result<()>
178 where
179 T: Copy + MaybeSendSync,
180 {
181 self.check_call(dest, operand)?;
182 let source_offset = operand
183 .offset()
184 .checked_add(self.source_offset_delta)
185 .ok_or(StridedError::OffsetOverflow)?;
186 let source = unsafe {
187 RawStridedRef::new_unchecked(
188 operand.data(),
189 &self.dest_dims,
190 &self.source_strides,
191 source_offset,
192 )
193 };
194 self.copy_plan.execute_uninit(dest, &source)
195 }
196
197 fn check_call<D, T>(
198 &self,
199 dest: &RawStridedMut<'_, D>,
200 operand: &RawStridedRef<'_, T>,
201 ) -> Result<()> {
202 if operand.dims() != &self.operand_dims[..]
203 || operand.strides() != &self.operand_strides[..]
204 || dest.dims() != &self.dest_dims[..]
205 || dest.strides() != &self.dest_strides[..]
206 {
207 return Err(StridedError::PlanLayoutMismatch);
208 }
209 Ok(())
210 }
211}
212
213impl PadPlan {
214 #[allow(clippy::too_many_arguments)]
216 pub fn compile(
217 operand_dims: &[usize],
218 operand_strides: &[isize],
219 dest_dims: &[usize],
220 dest_strides: &[isize],
221 edge_padding_low: &[i64],
222 edge_padding_high: &[i64],
223 interior_padding: &[i64],
224 ) -> Result<Self> {
225 let rank = operand_dims.len();
226 if operand_strides.len() != rank || dest_dims.len() != rank || dest_strides.len() != rank {
227 return Err(StridedError::StrideLengthMismatch);
228 }
229 if edge_padding_low.len() != rank {
230 return Err(StridedError::RankMismatch(edge_padding_low.len(), rank));
231 }
232 if edge_padding_high.len() != rank {
233 return Err(StridedError::RankMismatch(edge_padding_high.len(), rank));
234 }
235 if interior_padding.len() != rank {
236 return Err(StridedError::RankMismatch(interior_padding.len(), rank));
237 }
238
239 let operand_total = checked_total_len(operand_dims)?;
240 let dest_total = checked_total_len(dest_dims)?;
241 if !crate::fused::is_injective_layout(dest_dims, dest_strides) {
242 return Err(StridedError::NonInjectiveOutputLayout);
243 }
244
245 let mut expected_dest_dims: AxisVec<usize> = AxisVec::with_capacity(rank);
246 let mut interior_step: AxisVec<i64> = AxisVec::with_capacity(rank);
247 for axis in 0..rank {
248 if interior_padding[axis] < 0 {
249 return Err(StridedError::InvalidAxis { axis, rank });
250 }
251 let step = interior_padding[axis]
252 .checked_add(1)
253 .ok_or(StridedError::OffsetOverflow)?;
254 interior_step.push(step);
255 expected_dest_dims.push(checked_pad_output_dim(
256 operand_dims[axis],
257 edge_padding_low[axis],
258 edge_padding_high[axis],
259 step,
260 axis,
261 rank,
262 )?);
263 }
264 if dest_dims != &expected_dest_dims[..] {
265 return Err(StridedError::ShapeMismatch(
266 dest_dims.to_vec(),
267 expected_dest_dims.to_vec(),
268 ));
269 }
270 let contiguous_dest_fill = is_dense_col_major(dest_dims, dest_strides);
271 let contiguous_axis0_run = compile_contiguous_pad_axis0_run(
272 operand_dims,
273 operand_strides,
274 dest_dims,
275 dest_strides,
276 edge_padding_low,
277 &interior_step,
278 );
279
280 Ok(Self {
281 operand_dims: operand_dims.into(),
282 operand_strides: operand_strides.into(),
283 dest_dims: dest_dims.into(),
284 dest_strides: dest_strides.into(),
285 edge_padding_low: edge_padding_low.into(),
286 interior_step,
287 operand_total,
288 dest_total,
289 contiguous_dest_fill,
290 contiguous_axis0_run,
291 })
292 }
293
294 pub fn execute<T>(
296 &self,
297 dest: &mut RawStridedMut<'_, T>,
298 operand: &RawStridedRef<'_, T>,
299 fill: T,
300 ) -> Result<()>
301 where
302 T: Copy + MaybeSendSync,
303 {
304 self.check_call(dest, operand)?;
305 self.fill_dest(dest, fill)?;
306
307 if self.operand_total == 0 {
308 return Ok(());
309 }
310 if let Some(run) = self.contiguous_axis0_run {
311 return self.copy_operand_axis0_runs(dest, operand, run);
312 }
313 self.copy_operand(dest, operand)
314 }
315
316 pub fn execute_uninit<T>(
318 &self,
319 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
320 operand: &RawStridedRef<'_, T>,
321 fill: T,
322 ) -> Result<()>
323 where
324 T: Copy + MaybeSendSync,
325 {
326 self.check_call(dest, operand)?;
327 self.fill_dest(dest, MaybeUninit::new(fill))?;
328
329 if self.operand_total == 0 {
330 return Ok(());
331 }
332 if let Some(run) = self.contiguous_axis0_run {
333 return self.copy_operand_axis0_runs_uninit(dest, operand, run);
334 }
335 self.copy_operand_uninit(dest, operand)
336 }
337
338 fn fill_dest<T>(&self, dest: &mut RawStridedMut<'_, T>, fill: T) -> Result<()>
339 where
340 T: Copy + MaybeSendSync,
341 {
342 if self.dest_total == 0 {
343 return Ok(());
344 }
345 if self.contiguous_dest_fill {
346 let dest_offset =
347 usize::try_from(dest.offset()).map_err(|_| StridedError::OffsetOverflow)?;
348 let dest_end = dest_offset
349 .checked_add(self.dest_total)
350 .ok_or(StridedError::OffsetOverflow)?;
351 let dest_data = dest.data_mut();
352 let dest_slice = dest_data
353 .get_mut(dest_offset..dest_end)
354 .ok_or(StridedError::OffsetOverflow)?;
355 dest_slice.fill(fill);
356 return Ok(());
357 }
358 #[cfg(feature = "parallel")]
359 {
360 let nthreads = crate::threading::parallel_threads_for_len(self.dest_total);
361 if nthreads > 1 {
362 return self.fill_dest_parallel(dest, fill, nthreads);
363 }
364 }
365 self.fill_dest_serial(dest, fill)
366 }
367
368 fn copy_operand_axis0_runs<T>(
369 &self,
370 dest: &mut RawStridedMut<'_, T>,
371 operand: &RawStridedRef<'_, T>,
372 run: ContiguousPadAxis0Run,
373 ) -> Result<()>
374 where
375 T: Copy,
376 {
377 if run.len == 0 {
378 return Ok(());
379 }
380 let outer_dims = &self.operand_dims[1..];
381 let outer_total = checked_total_len(outer_dims)?;
382 let mut outer_idx_storage = CoordScratch::new(outer_dims.len());
383 let outer_idx = outer_idx_storage.as_mut_slice();
384 let operand_ptr = operand.data().as_ptr();
385 let dest_ptr = dest.data_mut().as_mut_ptr();
386
387 for _ in 0..outer_total {
388 let mut operand_offset =
389 checked_offset_add(operand.offset(), self.operand_strides[0], run.operand_start)?;
390 let mut dest_offset =
391 checked_offset_add(dest.offset(), self.dest_strides[0], run.dest_start)?;
392 let mut in_bounds = true;
393 for (outer_axis, &coord) in outer_idx.iter().enumerate() {
394 let axis = outer_axis + 1;
395 let out_pos = i128::from(self.edge_padding_low[axis])
396 + coord as i128 * i128::from(self.interior_step[axis]);
397 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
398 in_bounds = false;
399 break;
400 }
401 operand_offset =
402 checked_offset_add(operand_offset, self.operand_strides[axis], coord)?;
403 dest_offset =
404 checked_offset_add(dest_offset, self.dest_strides[axis], out_pos as usize)?;
405 }
406 if in_bounds {
407 unsafe {
408 core::ptr::copy_nonoverlapping(
413 operand_ptr.offset(operand_offset),
414 dest_ptr.offset(dest_offset),
415 run.len,
416 );
417 }
418 }
419 advance_col_major_index(outer_idx, outer_dims);
420 }
421 Ok(())
422 }
423
424 fn copy_operand_axis0_runs_uninit<T>(
425 &self,
426 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
427 operand: &RawStridedRef<'_, T>,
428 run: ContiguousPadAxis0Run,
429 ) -> Result<()>
430 where
431 T: Copy,
432 {
433 if run.len == 0 {
434 return Ok(());
435 }
436 let outer_dims = &self.operand_dims[1..];
437 let outer_total = checked_total_len(outer_dims)?;
438 let mut outer_idx_storage = CoordScratch::new(outer_dims.len());
439 let outer_idx = outer_idx_storage.as_mut_slice();
440 let operand_ptr = operand.data().as_ptr();
441 let dest_ptr = dest.data_mut().as_mut_ptr();
442
443 for _ in 0..outer_total {
444 let mut operand_offset =
445 checked_offset_add(operand.offset(), self.operand_strides[0], run.operand_start)?;
446 let mut dest_offset =
447 checked_offset_add(dest.offset(), self.dest_strides[0], run.dest_start)?;
448 let mut in_bounds = true;
449 for (outer_axis, &coord) in outer_idx.iter().enumerate() {
450 let axis = outer_axis + 1;
451 let out_pos = i128::from(self.edge_padding_low[axis])
452 + coord as i128 * i128::from(self.interior_step[axis]);
453 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
454 in_bounds = false;
455 break;
456 }
457 operand_offset =
458 checked_offset_add(operand_offset, self.operand_strides[axis], coord)?;
459 dest_offset =
460 checked_offset_add(dest_offset, self.dest_strides[axis], out_pos as usize)?;
461 }
462 if in_bounds {
463 unsafe {
464 core::ptr::copy_nonoverlapping(
465 operand_ptr.offset(operand_offset),
466 dest_ptr.offset(dest_offset).cast::<T>(),
467 run.len,
468 );
469 }
470 }
471 advance_col_major_index(outer_idx, outer_dims);
472 }
473 Ok(())
474 }
475
476 #[cfg(test)]
477 fn contiguous_axis0_run(&self) -> Option<(usize, usize, usize)> {
478 self.contiguous_axis0_run
479 .map(|run| (run.operand_start, run.dest_start, run.len))
480 }
481
482 #[cfg(test)]
483 fn has_contiguous_dest_fill(&self) -> bool {
484 self.contiguous_dest_fill
485 }
486
487 fn fill_dest_serial<T>(&self, dest: &mut RawStridedMut<'_, T>, fill: T) -> Result<()>
488 where
489 T: Copy,
490 {
491 let dest_offset_base = dest.offset();
492 let dest_strides = dest.strides();
493 let dest_data = dest.data_mut();
494 let mut dest_idx_storage = CoordScratch::new(self.dest_dims.len());
495 let dest_idx = dest_idx_storage.as_mut_slice();
496 for _ in 0..self.dest_total {
497 let dest_offset = checked_strided_offset(dest_offset_base, dest_strides, dest_idx)?;
498 unsafe {
499 *dest_data.as_mut_ptr().offset(dest_offset) = fill;
500 }
501 advance_col_major_index(dest_idx, &self.dest_dims);
502 }
503 Ok(())
504 }
505
506 #[cfg(feature = "parallel")]
507 fn fill_dest_parallel<T>(
508 &self,
509 dest: &mut RawStridedMut<'_, T>,
510 fill: T,
511 nthreads: usize,
512 ) -> Result<()>
513 where
514 T: Copy + MaybeSendSync,
515 {
516 let dest_offset_base = dest.offset();
517 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
518 crate::threading::parallel_map_reduce(
519 0..self.dest_total,
520 nthreads,
521 &|range| {
522 let mut dest_idx_storage = CoordScratch::new(self.dest_dims.len());
523 let dest_idx = dest_idx_storage.as_mut_slice();
524 fill_col_major_index(range.start, &self.dest_dims, dest_idx);
525 let dest_ptr = dest_ptr.as_ptr();
526 for _ in range {
527 let dest_offset =
528 checked_strided_offset(dest_offset_base, &self.dest_strides, dest_idx)?;
529 unsafe {
530 *dest_ptr.offset(dest_offset) = fill;
534 }
535 advance_col_major_index(dest_idx, &self.dest_dims);
536 }
537 Ok(())
538 },
539 &|left, right| left.and(right),
540 )
541 }
542
543 fn copy_operand<T>(
544 &self,
545 dest: &mut RawStridedMut<'_, T>,
546 operand: &RawStridedRef<'_, T>,
547 ) -> Result<()>
548 where
549 T: Copy + MaybeSendSync,
550 {
551 #[cfg(feature = "parallel")]
552 {
553 let nthreads = crate::threading::parallel_threads_for_len(self.operand_total);
554 if nthreads > 1 {
555 return self.copy_operand_parallel(dest, operand, nthreads);
556 }
557 }
558 self.copy_operand_serial(dest, operand)
559 }
560
561 fn copy_operand_uninit<T>(
562 &self,
563 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
564 operand: &RawStridedRef<'_, T>,
565 ) -> Result<()>
566 where
567 T: Copy + MaybeSendSync,
568 {
569 #[cfg(feature = "parallel")]
570 {
571 let nthreads = crate::threading::parallel_threads_for_len(self.operand_total);
572 if nthreads > 1 {
573 return self.copy_operand_uninit_parallel(dest, operand, nthreads);
574 }
575 }
576 self.copy_operand_uninit_serial(dest, operand)
577 }
578
579 fn copy_operand_uninit_serial<T>(
580 &self,
581 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
582 operand: &RawStridedRef<'_, T>,
583 ) -> Result<()>
584 where
585 T: Copy,
586 {
587 let operand_offset_base = operand.offset();
588 let operand_strides = operand.strides();
589 let operand_data = operand.data();
590 let dest_offset_base = dest.offset();
591 let dest_strides = dest.strides();
592 let dest_data = dest.data_mut();
593 let mut input_idx_storage = CoordScratch::new(self.operand_dims.len());
594 let mut out_idx_storage = CoordScratch::new(self.dest_dims.len());
595 let input_idx = input_idx_storage.as_mut_slice();
596 let out_idx = out_idx_storage.as_mut_slice();
597
598 for _ in 0..self.operand_total {
599 let mut in_bounds = true;
600 for axis in 0..self.operand_dims.len() {
601 let out_pos = i128::from(self.edge_padding_low[axis])
602 + input_idx[axis] as i128 * i128::from(self.interior_step[axis]);
603 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
604 in_bounds = false;
605 break;
606 }
607 out_idx[axis] = out_pos as usize;
608 }
609 if in_bounds {
610 let operand_offset =
611 checked_strided_offset(operand_offset_base, operand_strides, input_idx)?;
612 let dest_offset = checked_strided_offset(dest_offset_base, dest_strides, out_idx)?;
613 unsafe {
614 (*dest_data.as_mut_ptr().offset(dest_offset))
615 .write(*operand_data.as_ptr().offset(operand_offset));
616 }
617 }
618 advance_col_major_index(input_idx, &self.operand_dims);
619 }
620 Ok(())
621 }
622
623 #[cfg(feature = "parallel")]
624 fn copy_operand_uninit_parallel<T>(
625 &self,
626 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
627 operand: &RawStridedRef<'_, T>,
628 nthreads: usize,
629 ) -> Result<()>
630 where
631 T: Copy + MaybeSendSync,
632 {
633 let operand_offset_base = operand.offset();
634 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
635 let dest_offset_base = dest.offset();
636 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
637 crate::threading::parallel_map_reduce(
638 0..self.operand_total,
639 nthreads,
640 &|range| {
641 let mut input_idx_storage = CoordScratch::new(self.operand_dims.len());
642 let mut out_idx_storage = CoordScratch::new(self.dest_dims.len());
643 let input_idx = input_idx_storage.as_mut_slice();
644 let out_idx = out_idx_storage.as_mut_slice();
645 fill_col_major_index(range.start, &self.operand_dims, input_idx);
646 let operand_ptr = operand_ptr.as_const();
647 let dest_ptr = dest_ptr.as_ptr();
648
649 for _ in range {
650 let mut in_bounds = true;
651 for axis in 0..self.operand_dims.len() {
652 let out_pos = i128::from(self.edge_padding_low[axis])
653 + input_idx[axis] as i128 * i128::from(self.interior_step[axis]);
654 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
655 in_bounds = false;
656 break;
657 }
658 out_idx[axis] = out_pos as usize;
659 }
660 if in_bounds {
661 let operand_offset = checked_strided_offset(
662 operand_offset_base,
663 &self.operand_strides,
664 input_idx,
665 )?;
666 let dest_offset =
667 checked_strided_offset(dest_offset_base, &self.dest_strides, out_idx)?;
668 unsafe {
669 (*dest_ptr.offset(dest_offset))
670 .write(*operand_ptr.offset(operand_offset));
671 }
672 }
673 advance_col_major_index(input_idx, &self.operand_dims);
674 }
675 Ok(())
676 },
677 &|left, right| left.and(right),
678 )
679 }
680
681 fn copy_operand_serial<T>(
682 &self,
683 dest: &mut RawStridedMut<'_, T>,
684 operand: &RawStridedRef<'_, T>,
685 ) -> Result<()>
686 where
687 T: Copy,
688 {
689 let operand_offset_base = operand.offset();
690 let operand_strides = operand.strides();
691 let operand_data = operand.data();
692 let dest_offset_base = dest.offset();
693 let dest_strides = dest.strides();
694 let dest_data = dest.data_mut();
695 let mut input_idx_storage = CoordScratch::new(self.operand_dims.len());
696 let mut out_idx_storage = CoordScratch::new(self.dest_dims.len());
697 let input_idx = input_idx_storage.as_mut_slice();
698 let out_idx = out_idx_storage.as_mut_slice();
699
700 for _ in 0..self.operand_total {
701 let mut in_bounds = true;
702 for axis in 0..self.operand_dims.len() {
703 let out_pos = i128::from(self.edge_padding_low[axis])
704 + input_idx[axis] as i128 * i128::from(self.interior_step[axis]);
705 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
706 in_bounds = false;
707 break;
708 }
709 out_idx[axis] = out_pos as usize;
710 }
711 if in_bounds {
712 let operand_offset =
713 checked_strided_offset(operand_offset_base, operand_strides, input_idx)?;
714 let dest_offset = checked_strided_offset(dest_offset_base, dest_strides, out_idx)?;
715 unsafe {
716 *dest_data.as_mut_ptr().offset(dest_offset) =
717 *operand_data.as_ptr().offset(operand_offset);
718 }
719 }
720 advance_col_major_index(input_idx, &self.operand_dims);
721 }
722 Ok(())
723 }
724
725 #[cfg(feature = "parallel")]
726 fn copy_operand_parallel<T>(
727 &self,
728 dest: &mut RawStridedMut<'_, T>,
729 operand: &RawStridedRef<'_, T>,
730 nthreads: usize,
731 ) -> Result<()>
732 where
733 T: Copy + MaybeSendSync,
734 {
735 let operand_offset_base = operand.offset();
736 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
737 let dest_offset_base = dest.offset();
738 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
739 crate::threading::parallel_map_reduce(
740 0..self.operand_total,
741 nthreads,
742 &|range| {
743 let mut input_idx_storage = CoordScratch::new(self.operand_dims.len());
744 let mut out_idx_storage = CoordScratch::new(self.dest_dims.len());
745 let input_idx = input_idx_storage.as_mut_slice();
746 let out_idx = out_idx_storage.as_mut_slice();
747 fill_col_major_index(range.start, &self.operand_dims, input_idx);
748 let operand_ptr = operand_ptr.as_const();
749 let dest_ptr = dest_ptr.as_ptr();
750
751 for _ in range {
752 let mut in_bounds = true;
753 for axis in 0..self.operand_dims.len() {
754 let out_pos = i128::from(self.edge_padding_low[axis])
755 + input_idx[axis] as i128 * i128::from(self.interior_step[axis]);
756 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
757 in_bounds = false;
758 break;
759 }
760 out_idx[axis] = out_pos as usize;
761 }
762 if in_bounds {
763 let operand_offset = checked_strided_offset(
764 operand_offset_base,
765 &self.operand_strides,
766 input_idx,
767 )?;
768 let dest_offset =
769 checked_strided_offset(dest_offset_base, &self.dest_strides, out_idx)?;
770 unsafe {
771 *dest_ptr.offset(dest_offset) = *operand_ptr.offset(operand_offset);
776 }
777 }
778 advance_col_major_index(input_idx, &self.operand_dims);
779 }
780 Ok(())
781 },
782 &|left, right| left.and(right),
783 )
784 }
785
786 fn check_call<D, T>(
787 &self,
788 dest: &RawStridedMut<'_, D>,
789 operand: &RawStridedRef<'_, T>,
790 ) -> Result<()> {
791 if operand.dims() != &self.operand_dims[..]
792 || operand.strides() != &self.operand_strides[..]
793 || dest.dims() != &self.dest_dims[..]
794 || dest.strides() != &self.dest_strides[..]
795 {
796 return Err(StridedError::PlanLayoutMismatch);
797 }
798 Ok(())
799 }
800}
801
802impl ConcatenatePlan {
803 pub fn compile(
805 input_dims: &[&[usize]],
806 input_strides: &[&[isize]],
807 dest_dims: &[usize],
808 dest_strides: &[isize],
809 axis: usize,
810 ) -> Result<Self> {
811 if input_dims.is_empty() {
812 return Err(StridedError::UnsupportedArity {
813 arity: 0,
814 max: usize::MAX,
815 });
816 }
817 if input_dims.len() != input_strides.len() {
818 return Err(StridedError::RankMismatch(
819 input_strides.len(),
820 input_dims.len(),
821 ));
822 }
823
824 let rank = input_dims[0].len();
825 if dest_dims.len() != rank || dest_strides.len() != rank {
826 return Err(StridedError::StrideLengthMismatch);
827 }
828 if axis >= rank {
829 return Err(StridedError::InvalidAxis { axis, rank });
830 }
831 checked_total_len(dest_dims)?;
832 if !crate::fused::is_injective_layout(dest_dims, dest_strides) {
833 return Err(StridedError::NonInjectiveOutputLayout);
834 }
835
836 let mut expected_dest_dims: AxisVec<usize> = input_dims[0].into();
837 expected_dest_dims[axis] = 0;
838 let mut stored_input_dims = Vec::with_capacity(input_dims.len());
839 let mut stored_input_strides = Vec::with_capacity(input_dims.len());
840 let mut dest_offset_deltas = Vec::with_capacity(input_dims.len());
841 let mut copy_plans = Vec::with_capacity(input_dims.len());
842 let mut axis_base = 0usize;
843
844 for (dims, strides) in input_dims.iter().zip(input_strides.iter()) {
845 if dims.len() != rank {
846 return Err(StridedError::RankMismatch(dims.len(), rank));
847 }
848 if strides.len() != rank {
849 return Err(StridedError::StrideLengthMismatch);
850 }
851 checked_total_len(dims)?;
852 for dim in 0..rank {
853 if dim == axis {
854 expected_dest_dims[axis] = expected_dest_dims[axis]
855 .checked_add(dims[axis])
856 .ok_or(StridedError::OffsetOverflow)?;
857 } else if dims[dim] != input_dims[0][dim] {
858 return Err(StridedError::ShapeMismatch(
859 dims.to_vec(),
860 input_dims[0].to_vec(),
861 ));
862 }
863 }
864 dest_offset_deltas.push(checked_offset_add(0, dest_strides[axis], axis_base)?);
865 axis_base = axis_base
866 .checked_add(dims[axis])
867 .ok_or(StridedError::OffsetOverflow)?;
868 copy_plans.push(CopyPlan::compile(dims, dest_strides, strides)?);
869 stored_input_dims.push((*dims).into());
870 stored_input_strides.push((*strides).into());
871 }
872
873 if dest_dims != &expected_dest_dims[..] {
874 return Err(StridedError::ShapeMismatch(
875 dest_dims.to_vec(),
876 expected_dest_dims.to_vec(),
877 ));
878 }
879
880 Ok(Self {
881 input_dims: stored_input_dims,
882 input_strides: stored_input_strides,
883 dest_dims: dest_dims.into(),
884 dest_strides: dest_strides.into(),
885 dest_offset_deltas,
886 copy_plans,
887 })
888 }
889
890 pub fn execute<T>(
892 &self,
893 dest: &mut RawStridedMut<'_, T>,
894 inputs: &[RawStridedRef<'_, T>],
895 ) -> Result<()>
896 where
897 T: Copy + MaybeSendSync,
898 {
899 self.check_dest_layout(dest)?;
900 if inputs.len() != self.input_dims.len() {
901 return Err(StridedError::RankMismatch(
902 inputs.len(),
903 self.input_dims.len(),
904 ));
905 }
906 for (position, input) in inputs.iter().enumerate() {
907 self.check_input_layout(position, input)?;
908 self.segment_offset(position, dest.offset())?;
909 }
910 for (position, input) in inputs.iter().enumerate() {
911 self.execute_segment(position, dest, input)?;
912 }
913 Ok(())
914 }
915
916 pub fn execute_uninit<T>(
918 &self,
919 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
920 inputs: &[RawStridedRef<'_, T>],
921 ) -> Result<()>
922 where
923 T: Copy + MaybeSendSync,
924 {
925 self.check_dest_layout(dest)?;
926 if inputs.len() != self.input_dims.len() {
927 return Err(StridedError::RankMismatch(
928 inputs.len(),
929 self.input_dims.len(),
930 ));
931 }
932 for (position, input) in inputs.iter().enumerate() {
933 self.check_input_layout(position, input)?;
934 self.segment_offset(position, dest.offset())?;
935 }
936 for (position, input) in inputs.iter().enumerate() {
937 self.execute_segment_uninit(position, dest, input)?;
938 }
939 Ok(())
940 }
941
942 pub(crate) fn check_dest_layout<T>(&self, dest: &RawStridedMut<'_, T>) -> Result<()> {
943 if dest.dims() != &self.dest_dims[..] || dest.strides() != &self.dest_strides[..] {
944 return Err(StridedError::PlanLayoutMismatch);
945 }
946 Ok(())
947 }
948
949 pub(crate) fn check_input_layout<T>(
950 &self,
951 position: usize,
952 input: &RawStridedRef<'_, T>,
953 ) -> Result<()> {
954 if position >= self.input_dims.len()
955 || input.dims() != &self.input_dims[position][..]
956 || input.strides() != &self.input_strides[position][..]
957 {
958 return Err(StridedError::PlanLayoutMismatch);
959 }
960 Ok(())
961 }
962
963 pub(crate) fn input_count(&self) -> usize {
964 self.input_dims.len()
965 }
966
967 pub(crate) fn segment_offset(&self, position: usize, dest_offset: isize) -> Result<isize> {
968 dest_offset
969 .checked_add(self.dest_offset_deltas[position])
970 .ok_or(StridedError::OffsetOverflow)
971 }
972
973 pub(crate) fn execute_segment<T>(
974 &self,
975 position: usize,
976 dest: &mut RawStridedMut<'_, T>,
977 input: &RawStridedRef<'_, T>,
978 ) -> Result<()>
979 where
980 T: Copy + MaybeSendSync,
981 {
982 let segment_offset = self.segment_offset(position, dest.offset())?;
983 let dest_data = dest.data_mut();
984 let mut segment = unsafe {
985 RawStridedMut::new_unchecked(
986 dest_data,
987 &self.input_dims[position],
988 &self.dest_strides,
989 segment_offset,
990 )
991 };
992 self.copy_plans[position].execute(&mut segment, input)
993 }
994
995 pub(crate) fn execute_segment_uninit<T>(
996 &self,
997 position: usize,
998 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
999 input: &RawStridedRef<'_, T>,
1000 ) -> Result<()>
1001 where
1002 T: Copy + MaybeSendSync,
1003 {
1004 let segment_offset = self.segment_offset(position, dest.offset())?;
1005 let dest_data = dest.data_mut();
1006 let mut segment = unsafe {
1007 RawStridedMut::new_unchecked(
1008 dest_data,
1009 &self.input_dims[position],
1010 &self.dest_strides,
1011 segment_offset,
1012 )
1013 };
1014 self.copy_plans[position].execute_uninit(&mut segment, input)
1015 }
1016}
1017
1018impl ReversePlan {
1019 pub fn compile(
1021 operand_dims: &[usize],
1022 operand_strides: &[isize],
1023 dest_strides: &[isize],
1024 axes: &[usize],
1025 ) -> Result<Self> {
1026 let rank = operand_dims.len();
1027 if operand_strides.len() != rank || dest_strides.len() != rank {
1028 return Err(StridedError::StrideLengthMismatch);
1029 }
1030 checked_total_len(operand_dims)?;
1031
1032 let mut reverse_axis: AxisVec<bool> = (0..rank).map(|_| false).collect();
1033 for &axis in axes {
1034 if axis >= rank {
1035 return Err(StridedError::InvalidAxis { axis, rank });
1036 }
1037 reverse_axis[axis] = true;
1038 }
1039
1040 let mut source_strides: AxisVec<isize> = AxisVec::with_capacity(rank);
1041 let mut source_offset_delta = 0isize;
1042 for axis in 0..rank {
1043 if reverse_axis[axis] {
1044 source_strides.push(
1045 operand_strides[axis]
1046 .checked_neg()
1047 .ok_or(StridedError::OffsetOverflow)?,
1048 );
1049 if operand_dims[axis] > 0 {
1050 source_offset_delta = checked_offset_add(
1051 source_offset_delta,
1052 operand_strides[axis],
1053 operand_dims[axis] - 1,
1054 )?;
1055 }
1056 } else {
1057 source_strides.push(operand_strides[axis]);
1058 }
1059 }
1060 let copy_plan = CopyPlan::compile(operand_dims, dest_strides, &source_strides)?;
1061
1062 Ok(Self {
1063 operand_dims: operand_dims.into(),
1064 operand_strides: operand_strides.into(),
1065 dest_strides: dest_strides.into(),
1066 source_strides,
1067 source_offset_delta,
1068 copy_plan,
1069 })
1070 }
1071
1072 pub fn execute<T>(
1074 &self,
1075 dest: &mut RawStridedMut<'_, T>,
1076 operand: &RawStridedRef<'_, T>,
1077 ) -> Result<()>
1078 where
1079 T: Copy + MaybeSendSync,
1080 {
1081 self.check_call(dest, operand)?;
1082 let source_offset = operand
1083 .offset()
1084 .checked_add(self.source_offset_delta)
1085 .ok_or(StridedError::OffsetOverflow)?;
1086 let source = unsafe {
1087 RawStridedRef::new_unchecked(
1088 operand.data(),
1089 &self.operand_dims,
1090 &self.source_strides,
1091 source_offset,
1092 )
1093 };
1094 self.copy_plan.execute(dest, &source)
1095 }
1096
1097 pub fn execute_uninit<T>(
1099 &self,
1100 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1101 operand: &RawStridedRef<'_, T>,
1102 ) -> Result<()>
1103 where
1104 T: Copy + MaybeSendSync,
1105 {
1106 self.check_call(dest, operand)?;
1107 let source_offset = operand
1108 .offset()
1109 .checked_add(self.source_offset_delta)
1110 .ok_or(StridedError::OffsetOverflow)?;
1111 let source = unsafe {
1112 RawStridedRef::new_unchecked(
1113 operand.data(),
1114 &self.operand_dims,
1115 &self.source_strides,
1116 source_offset,
1117 )
1118 };
1119 self.copy_plan.execute_uninit(dest, &source)
1120 }
1121
1122 fn check_call<D, T>(
1123 &self,
1124 dest: &RawStridedMut<'_, D>,
1125 operand: &RawStridedRef<'_, T>,
1126 ) -> Result<()> {
1127 if operand.dims() != &self.operand_dims[..]
1128 || operand.strides() != &self.operand_strides[..]
1129 || dest.dims() != &self.operand_dims[..]
1130 || dest.strides() != &self.dest_strides[..]
1131 {
1132 return Err(StridedError::PlanLayoutMismatch);
1133 }
1134 Ok(())
1135 }
1136}
1137
1138fn checked_total_len(dims: &[usize]) -> Result<usize> {
1139 if dims.is_empty() {
1140 return Ok(1);
1141 }
1142 dims.iter()
1143 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
1144 .ok_or(StridedError::OffsetOverflow)
1145}
1146
1147fn checked_stride_mul(stride: isize, factor: usize) -> Result<isize> {
1148 let factor = isize::try_from(factor).map_err(|_| StridedError::OffsetOverflow)?;
1149 stride
1150 .checked_mul(factor)
1151 .ok_or(StridedError::OffsetOverflow)
1152}
1153
1154fn checked_pad_output_dim(
1155 input_extent: usize,
1156 edge_low: i64,
1157 edge_high: i64,
1158 interior_step: i64,
1159 axis: usize,
1160 rank: usize,
1161) -> Result<usize> {
1162 let base = if input_extent == 0 {
1163 0i128
1164 } else {
1165 (input_extent as i128 - 1)
1166 .checked_mul(i128::from(interior_step))
1167 .and_then(|value| value.checked_add(1))
1168 .ok_or(StridedError::OffsetOverflow)?
1169 };
1170 let dim = i128::from(edge_low)
1171 .checked_add(i128::from(edge_high))
1172 .and_then(|value| value.checked_add(base))
1173 .ok_or(StridedError::OffsetOverflow)?;
1174 usize::try_from(dim).map_err(|_| StridedError::InvalidAxis { axis, rank })
1175}
1176
1177fn is_dense_col_major(dims: &[usize], strides: &[isize]) -> bool {
1178 let mut expected = 1isize;
1179 for (&dim, &stride) in dims.iter().zip(strides.iter()) {
1180 if stride != expected {
1181 return false;
1182 }
1183 let Ok(dim) = isize::try_from(dim) else {
1184 return false;
1185 };
1186 let Some(next) = expected.checked_mul(dim) else {
1187 return false;
1188 };
1189 expected = next;
1190 }
1191 true
1192}
1193
1194fn compile_contiguous_pad_axis0_run(
1195 operand_dims: &[usize],
1196 operand_strides: &[isize],
1197 dest_dims: &[usize],
1198 dest_strides: &[isize],
1199 edge_padding_low: &[i64],
1200 interior_step: &[i64],
1201) -> Option<ContiguousPadAxis0Run> {
1202 if operand_dims.is_empty()
1203 || operand_strides[0] != 1
1204 || dest_strides[0] != 1
1205 || interior_step[0] != 1
1206 {
1207 return None;
1208 }
1209
1210 let operand_extent = operand_dims[0] as i128;
1211 let dest_extent = dest_dims[0] as i128;
1212 let edge_low = i128::from(edge_padding_low[0]);
1213 let operand_start = (-edge_low).clamp(0, operand_extent);
1214 let dest_start = edge_low.clamp(0, dest_extent);
1215 let len = (operand_extent - operand_start).min(dest_extent - dest_start);
1216 Some(ContiguousPadAxis0Run {
1217 operand_start: usize::try_from(operand_start).ok()?,
1218 dest_start: usize::try_from(dest_start).ok()?,
1219 len: usize::try_from(len).ok()?,
1220 })
1221}
1222
1223fn checked_strided_offset(base: isize, strides: &[isize], index: &[usize]) -> Result<isize> {
1224 let mut offset = base;
1225 for (&stride, &coord) in strides.iter().zip(index.iter()) {
1226 offset = checked_offset_add(offset, stride, coord)?;
1227 }
1228 Ok(offset)
1229}
1230
1231fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1232 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1233 let scaled = stride
1234 .checked_mul(coord)
1235 .ok_or(StridedError::OffsetOverflow)?;
1236 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1237}
1238
1239fn advance_col_major_index(index: &mut [usize], shape: &[usize]) {
1240 for axis in 0..index.len() {
1241 index[axis] += 1;
1242 if index[axis] < shape[axis] {
1243 return;
1244 }
1245 index[axis] = 0;
1246 }
1247}
1248
1249#[cfg(feature = "parallel")]
1250fn fill_col_major_index(mut linear: usize, shape: &[usize], out: &mut [usize]) {
1251 for (axis, coord) in out.iter_mut().enumerate() {
1252 let dim = shape[axis];
1253 *coord = linear % dim;
1254 linear /= dim;
1255 }
1256}
1257
1258struct CoordScratch {
1259 inline: [usize; crate::RAW_FUSED_RANK_LIMIT],
1260 heap: Option<Vec<usize>>,
1261 len: usize,
1262}
1263
1264impl CoordScratch {
1265 fn new(len: usize) -> Self {
1266 if len <= crate::RAW_FUSED_RANK_LIMIT {
1267 Self {
1268 inline: [0; crate::RAW_FUSED_RANK_LIMIT],
1269 heap: None,
1270 len,
1271 }
1272 } else {
1273 Self {
1274 inline: [0; crate::RAW_FUSED_RANK_LIMIT],
1275 heap: Some(vec![0; len]),
1276 len,
1277 }
1278 }
1279 }
1280
1281 fn as_mut_slice(&mut self) -> &mut [usize] {
1282 match &mut self.heap {
1283 Some(heap) => heap,
1284 None => &mut self.inline[..self.len],
1285 }
1286 }
1287}
1288
1289#[cfg(test)]
1290mod tests {
1291 use core::fmt::Debug;
1292
1293 use num_complex::{Complex32, Complex64};
1294
1295 use super::{PadPlan, RawStridedMut, RawStridedRef};
1296
1297 fn assert_contiguous_pad_matches_scalar<T>(operand_data: &[T], fill: T)
1298 where
1299 T: Copy + Debug + PartialEq + super::MaybeSendSync,
1300 {
1301 let operand_dims = [3usize, 2];
1302 let operand_strides = [1isize, -3];
1303 let operand_offset = 3isize;
1304 let dest_dims = [4usize, 4];
1305 let dest_strides = [1isize, 4];
1306 let dest_offset = 2isize;
1307 let edge_low = [-1i64, 1];
1308 let edge_high = [2i64, 0];
1309 let interior = [0i64, 1];
1310 let plan = PadPlan::compile(
1311 &operand_dims,
1312 &operand_strides,
1313 &dest_dims,
1314 &dest_strides,
1315 &edge_low,
1316 &edge_high,
1317 &interior,
1318 )
1319 .unwrap();
1320 assert!(plan.contiguous_axis0_run.is_some());
1321
1322 let mut scalar_plan = plan.clone();
1323 scalar_plan.contiguous_dest_fill = false;
1324 scalar_plan.contiguous_axis0_run = None;
1325 let mut fast_dest = vec![fill; 20];
1326 let mut scalar_dest = fast_dest.clone();
1327 let operand = RawStridedRef::new(
1328 operand_data,
1329 &operand_dims,
1330 &operand_strides,
1331 operand_offset,
1332 )
1333 .unwrap();
1334 {
1335 let mut dest =
1336 RawStridedMut::new(&mut fast_dest, &dest_dims, &dest_strides, dest_offset).unwrap();
1337 plan.execute(&mut dest, &operand, fill).unwrap();
1338 }
1339 {
1340 let mut dest =
1341 RawStridedMut::new(&mut scalar_dest, &dest_dims, &dest_strides, dest_offset)
1342 .unwrap();
1343 scalar_plan.execute(&mut dest, &operand, fill).unwrap();
1344 }
1345 assert_eq!(fast_dest, scalar_dest);
1346 }
1347
1348 #[test]
1349 fn pad_plan_selects_contiguous_axis0_run_for_dense_edge_padding() {
1350 let plan =
1351 PadPlan::compile(&[2_097_152], &[1], &[2_097_408], &[1], &[128], &[128], &[0]).unwrap();
1352
1353 assert_eq!(plan.contiguous_axis0_run(), Some((0, 128, 2_097_152)));
1354 assert!(plan.has_contiguous_dest_fill());
1355 }
1356
1357 #[test]
1358 fn contiguous_pad_matches_scalar_for_every_erased_scalar_type() {
1359 assert_contiguous_pad_matches_scalar(&[1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], -1.0);
1360 assert_contiguous_pad_matches_scalar(&[1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0], -1.0);
1361 assert_contiguous_pad_matches_scalar(&[1i32, 2, 3, 4, 5, 6], -1);
1362 assert_contiguous_pad_matches_scalar(&[1i64, 2, 3, 4, 5, 6], -1);
1363 assert_contiguous_pad_matches_scalar(&[true, false, true, false, true, false], false);
1364 assert_contiguous_pad_matches_scalar(
1365 &[
1366 Complex32::new(1.0, -1.0),
1367 Complex32::new(2.0, -2.0),
1368 Complex32::new(3.0, -3.0),
1369 Complex32::new(4.0, -4.0),
1370 Complex32::new(5.0, -5.0),
1371 Complex32::new(6.0, -6.0),
1372 ],
1373 Complex32::new(-1.0, 0.0),
1374 );
1375 assert_contiguous_pad_matches_scalar(
1376 &[
1377 Complex64::new(1.0, -1.0),
1378 Complex64::new(2.0, -2.0),
1379 Complex64::new(3.0, -3.0),
1380 Complex64::new(4.0, -4.0),
1381 Complex64::new(5.0, -5.0),
1382 Complex64::new(6.0, -6.0),
1383 ],
1384 Complex64::new(-1.0, 0.0),
1385 );
1386 }
1387}