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)]
53pub struct PadPlan {
54 operand_dims: AxisVec<usize>,
55 operand_strides: AxisVec<isize>,
56 dest_dims: AxisVec<usize>,
57 dest_strides: AxisVec<isize>,
58 edge_padding_low: AxisVec<i64>,
59 interior_step: AxisVec<i64>,
60 operand_total: usize,
61 dest_total: usize,
62 contiguous_dest_fill: bool,
63 contiguous_axis0_run: Option<ContiguousPadAxis0Run>,
64 contiguous_axis0_columns: Option<ContiguousPadAxis0Columns>,
65 generic_fill: PadFillCursor,
66 generic_copy: PadCopyCursor,
67}
68
69#[derive(Clone, Debug)]
70struct PadFillCursor {
71 steps: AxisVec<isize>,
72 resets: AxisVec<isize>,
73}
74
75#[derive(Clone, Debug)]
76struct PadCopyCursor {
77 shape: AxisVec<usize>,
78 source_base_delta: isize,
79 dest_base_delta: isize,
80 source_steps: AxisVec<isize>,
81 source_resets: AxisVec<isize>,
82 dest_steps: AxisVec<isize>,
83 dest_resets: AxisVec<isize>,
84 total: usize,
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88struct ContiguousPadAxis0Run {
89 operand_start: usize,
90 dest_start: usize,
91 len: usize,
92}
93
94#[derive(Clone, Debug)]
104struct ContiguousPadAxis0Columns {
105 column_total: usize,
107 dest_axis0_len: usize,
108 dest_axis0_step: isize,
109 low0: usize,
110 high0: usize,
111 run_len: usize,
112 operand_outer_dims: AxisVec<usize>,
113 operand_outer_strides: AxisVec<isize>,
114 dest_outer_dims: AxisVec<usize>,
115 edge_low_outer: AxisVec<usize>,
116}
117
118#[derive(Clone, Debug)]
123pub struct ConcatenatePlan {
124 input_dims: Vec<AxisVec<usize>>,
125 input_strides: Vec<AxisVec<isize>>,
126 dest_dims: AxisVec<usize>,
127 dest_strides: AxisVec<isize>,
128 dest_offset_deltas: Vec<isize>,
129 #[cfg_attr(not(feature = "parallel"), allow(dead_code))]
132 segment_starts: Vec<usize>,
133 copy_plans: Vec<CopyPlan>,
134}
135
136impl SlicePlan {
137 #[allow(clippy::too_many_arguments)]
139 pub fn compile(
140 operand_dims: &[usize],
141 operand_strides: &[isize],
142 dest_dims: &[usize],
143 dest_strides: &[isize],
144 starts: &[usize],
145 limits: &[usize],
146 slice_strides: &[usize],
147 ) -> Result<Self> {
148 let rank = operand_dims.len();
149 if operand_strides.len() != rank || dest_dims.len() != rank || dest_strides.len() != rank {
150 return Err(StridedError::StrideLengthMismatch);
151 }
152 if starts.len() != rank {
153 return Err(StridedError::RankMismatch(starts.len(), rank));
154 }
155 if limits.len() != rank {
156 return Err(StridedError::RankMismatch(limits.len(), rank));
157 }
158 if slice_strides.len() != rank {
159 return Err(StridedError::RankMismatch(slice_strides.len(), rank));
160 }
161 checked_total_len(operand_dims)?;
162 checked_total_len(dest_dims)?;
163
164 let mut expected_dest_dims: AxisVec<usize> = AxisVec::with_capacity(rank);
165 let mut source_strides: AxisVec<isize> = AxisVec::with_capacity(rank);
166 let mut source_offset_delta = 0isize;
167 for axis in 0..rank {
168 let start = starts[axis];
169 let limit = limits[axis];
170 let stride = slice_strides[axis];
171 if start > limit || limit > operand_dims[axis] || stride == 0 {
172 return Err(StridedError::InvalidAxis { axis, rank });
173 }
174 let span = limit - start;
175 expected_dest_dims.push(span.div_ceil(stride));
176 source_strides.push(checked_stride_mul(operand_strides[axis], stride)?);
177 source_offset_delta =
178 checked_offset_add(source_offset_delta, operand_strides[axis], start)?;
179 }
180 if dest_dims != &expected_dest_dims[..] {
181 return Err(StridedError::ShapeMismatch(
182 dest_dims.to_vec(),
183 expected_dest_dims.to_vec(),
184 ));
185 }
186 let copy_plan = CopyPlan::compile(dest_dims, dest_strides, &source_strides)?;
187
188 Ok(Self {
189 operand_dims: operand_dims.into(),
190 operand_strides: operand_strides.into(),
191 dest_dims: dest_dims.into(),
192 dest_strides: dest_strides.into(),
193 source_strides,
194 source_offset_delta,
195 copy_plan,
196 })
197 }
198
199 pub fn execute<T>(
201 &self,
202 dest: &mut RawStridedMut<'_, T>,
203 operand: &RawStridedRef<'_, T>,
204 ) -> Result<()>
205 where
206 T: Copy + MaybeSendSync,
207 {
208 self.check_call(dest, operand)?;
209 let source_offset = operand
210 .offset()
211 .checked_add(self.source_offset_delta)
212 .ok_or(StridedError::OffsetOverflow)?;
213 let source = unsafe {
214 RawStridedRef::new_unchecked(
215 operand.data(),
216 &self.dest_dims,
217 &self.source_strides,
218 source_offset,
219 )
220 };
221 self.copy_plan.execute(dest, &source)
222 }
223
224 pub fn execute_uninit<T>(
226 &self,
227 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
228 operand: &RawStridedRef<'_, T>,
229 ) -> Result<()>
230 where
231 T: Copy + MaybeSendSync,
232 {
233 self.check_call(dest, operand)?;
234 let source_offset = operand
235 .offset()
236 .checked_add(self.source_offset_delta)
237 .ok_or(StridedError::OffsetOverflow)?;
238 let source = unsafe {
239 RawStridedRef::new_unchecked(
240 operand.data(),
241 &self.dest_dims,
242 &self.source_strides,
243 source_offset,
244 )
245 };
246 self.copy_plan.execute_uninit(dest, &source)
247 }
248
249 fn check_call<D, T>(
250 &self,
251 dest: &RawStridedMut<'_, D>,
252 operand: &RawStridedRef<'_, T>,
253 ) -> Result<()> {
254 if operand.dims() != &self.operand_dims[..]
255 || operand.strides() != &self.operand_strides[..]
256 || dest.dims() != &self.dest_dims[..]
257 || dest.strides() != &self.dest_strides[..]
258 {
259 return Err(StridedError::PlanLayoutMismatch);
260 }
261 Ok(())
262 }
263}
264
265impl PadPlan {
266 #[allow(clippy::too_many_arguments)]
268 pub fn compile(
269 operand_dims: &[usize],
270 operand_strides: &[isize],
271 dest_dims: &[usize],
272 dest_strides: &[isize],
273 edge_padding_low: &[i64],
274 edge_padding_high: &[i64],
275 interior_padding: &[i64],
276 ) -> Result<Self> {
277 let rank = operand_dims.len();
278 if operand_strides.len() != rank || dest_dims.len() != rank || dest_strides.len() != rank {
279 return Err(StridedError::StrideLengthMismatch);
280 }
281 if edge_padding_low.len() != rank {
282 return Err(StridedError::RankMismatch(edge_padding_low.len(), rank));
283 }
284 if edge_padding_high.len() != rank {
285 return Err(StridedError::RankMismatch(edge_padding_high.len(), rank));
286 }
287 if interior_padding.len() != rank {
288 return Err(StridedError::RankMismatch(interior_padding.len(), rank));
289 }
290
291 let operand_total = checked_total_len(operand_dims)?;
292 let dest_total = checked_total_len(dest_dims)?;
293 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
294 return Err(StridedError::NonInjectiveOutputLayout);
295 }
296
297 let mut expected_dest_dims: AxisVec<usize> = AxisVec::with_capacity(rank);
298 let mut interior_step: AxisVec<i64> = AxisVec::with_capacity(rank);
299 for axis in 0..rank {
300 if interior_padding[axis] < 0 {
301 return Err(StridedError::InvalidAxis { axis, rank });
302 }
303 let step = interior_padding[axis]
304 .checked_add(1)
305 .ok_or(StridedError::OffsetOverflow)?;
306 interior_step.push(step);
307 expected_dest_dims.push(checked_pad_output_dim(
308 operand_dims[axis],
309 edge_padding_low[axis],
310 edge_padding_high[axis],
311 step,
312 axis,
313 rank,
314 )?);
315 }
316 if dest_dims != &expected_dest_dims[..] {
317 return Err(StridedError::ShapeMismatch(
318 dest_dims.to_vec(),
319 expected_dest_dims.to_vec(),
320 ));
321 }
322 let contiguous_dest_fill = is_dense_col_major(dest_dims, dest_strides);
323 let contiguous_axis0_run = compile_contiguous_pad_axis0_run(
324 operand_dims,
325 operand_strides,
326 dest_dims,
327 dest_strides,
328 edge_padding_low,
329 &interior_step,
330 );
331 let contiguous_axis0_columns = contiguous_axis0_run.and_then(|run| {
332 compile_contiguous_pad_axis0_columns(
333 operand_dims,
334 operand_strides,
335 dest_dims,
336 edge_padding_low,
337 edge_padding_high,
338 &interior_step,
339 contiguous_dest_fill,
340 run,
341 )
342 });
343 let generic_fill = compile_pad_fill_cursor(dest_dims, dest_strides)?;
344 let generic_copy = compile_pad_copy_cursor(
345 operand_dims,
346 operand_strides,
347 dest_dims,
348 dest_strides,
349 edge_padding_low,
350 &interior_step,
351 )?;
352
353 Ok(Self {
354 operand_dims: operand_dims.into(),
355 operand_strides: operand_strides.into(),
356 dest_dims: dest_dims.into(),
357 dest_strides: dest_strides.into(),
358 edge_padding_low: edge_padding_low.into(),
359 interior_step,
360 operand_total,
361 dest_total,
362 contiguous_dest_fill,
363 contiguous_axis0_run,
364 contiguous_axis0_columns,
365 generic_fill,
366 generic_copy,
367 })
368 }
369
370 pub fn execute<T>(
372 &self,
373 dest: &mut RawStridedMut<'_, T>,
374 operand: &RawStridedRef<'_, T>,
375 fill: T,
376 ) -> Result<()>
377 where
378 T: Copy + MaybeSendSync,
379 {
380 self.check_call(dest, operand)?;
381 if self.operand_total == 0 {
382 return self.fill_dest(dest, fill);
383 }
384 if let Some(columns) = &self.contiguous_axis0_columns {
385 return self.execute_axis0_columns(dest, operand, fill, columns);
386 }
387 self.fill_dest(dest, fill)?;
388 if let Some(run) = self.contiguous_axis0_run {
389 return self.copy_operand_axis0_runs(dest, operand, run);
390 }
391 self.copy_operand(dest, operand)
392 }
393
394 pub fn execute_uninit<T>(
396 &self,
397 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
398 operand: &RawStridedRef<'_, T>,
399 fill: T,
400 ) -> Result<()>
401 where
402 T: Copy + MaybeSendSync,
403 {
404 self.check_call(dest, operand)?;
405 if self.operand_total == 0 {
406 return self.fill_dest(dest, MaybeUninit::new(fill));
407 }
408 if let Some(columns) = &self.contiguous_axis0_columns {
409 return self.execute_axis0_columns_uninit(dest, operand, fill, columns);
410 }
411 self.fill_dest(dest, MaybeUninit::new(fill))?;
412 if let Some(run) = self.contiguous_axis0_run {
413 return self.copy_operand_axis0_runs_uninit(dest, operand, run);
414 }
415 self.copy_operand_uninit(dest, operand)
416 }
417
418 fn fill_dest<T>(&self, dest: &mut RawStridedMut<'_, T>, fill: T) -> Result<()>
419 where
420 T: Copy + MaybeSendSync,
421 {
422 if self.dest_total == 0 {
423 return Ok(());
424 }
425 if self.contiguous_dest_fill {
426 let dest_offset =
427 usize::try_from(dest.offset()).map_err(|_| StridedError::OffsetOverflow)?;
428 let dest_end = dest_offset
429 .checked_add(self.dest_total)
430 .ok_or(StridedError::OffsetOverflow)?;
431 let dest_data = dest.data_mut();
432 let dest_slice = dest_data
433 .get_mut(dest_offset..dest_end)
434 .ok_or(StridedError::OffsetOverflow)?;
435 crate::threading::fill_contiguous(dest_slice, fill);
436 return Ok(());
437 }
438 #[cfg(feature = "parallel")]
439 {
440 let nthreads = crate::threading::parallel_threads_for_len(self.dest_total);
441 if nthreads > 1 {
442 return self.fill_dest_parallel(dest, fill, nthreads);
443 }
444 }
445 self.fill_dest_serial(dest, fill)
446 }
447
448 fn copy_operand_axis0_runs<T>(
449 &self,
450 dest: &mut RawStridedMut<'_, T>,
451 operand: &RawStridedRef<'_, T>,
452 run: ContiguousPadAxis0Run,
453 ) -> Result<()>
454 where
455 T: Copy + MaybeSendSync,
456 {
457 let dest_offset = dest.offset();
458 let dest_ptr = dest.data_mut().as_mut_ptr();
459 unsafe { self.copy_axis0_runs_raw(dest_ptr, dest_offset, operand, run) }
463 }
464
465 fn copy_operand_axis0_runs_uninit<T>(
466 &self,
467 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
468 operand: &RawStridedRef<'_, T>,
469 run: ContiguousPadAxis0Run,
470 ) -> Result<()>
471 where
472 T: Copy + MaybeSendSync,
473 {
474 let dest_offset = dest.offset();
475 let dest_ptr = dest.data_mut().as_mut_ptr().cast::<T>();
476 unsafe { self.copy_axis0_runs_raw(dest_ptr, dest_offset, operand, run) }
479 }
480
481 fn execute_axis0_columns<T>(
482 &self,
483 dest: &mut RawStridedMut<'_, T>,
484 operand: &RawStridedRef<'_, T>,
485 fill: T,
486 columns: &ContiguousPadAxis0Columns,
487 ) -> Result<()>
488 where
489 T: Copy + MaybeSendSync,
490 {
491 let dest_offset = dest.offset();
492 let dest_ptr = dest.data_mut().as_mut_ptr();
493 let operand_offset = operand.offset();
494 let operand_ptr = operand.data().as_ptr();
495 unsafe {
499 self.write_axis0_columns_raw::<T, false>(
500 dest_ptr,
501 dest_offset,
502 operand_ptr,
503 operand_offset,
504 fill,
505 columns,
506 )
507 }
508 }
509
510 fn execute_axis0_columns_uninit<T>(
511 &self,
512 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
513 operand: &RawStridedRef<'_, T>,
514 fill: T,
515 columns: &ContiguousPadAxis0Columns,
516 ) -> Result<()>
517 where
518 T: Copy + MaybeSendSync,
519 {
520 let dest_offset = dest.offset();
521 let dest_ptr = dest.data_mut().as_mut_ptr().cast::<T>();
522 let operand_offset = operand.offset();
523 let operand_ptr = operand.data().as_ptr();
524 unsafe {
527 self.write_axis0_columns_raw::<T, true>(
528 dest_ptr,
529 dest_offset,
530 operand_ptr,
531 operand_offset,
532 fill,
533 columns,
534 )
535 }
536 }
537
538 unsafe fn write_axis0_columns_raw<T, const UNINIT: bool>(
556 &self,
557 dest_ptr: *mut T,
558 dest_offset: isize,
559 operand_ptr: *const T,
560 operand_offset: isize,
561 fill: T,
562 columns: &ContiguousPadAxis0Columns,
563 ) -> Result<()>
564 where
565 T: Copy + MaybeSendSync,
566 {
567 let column_total = columns.column_total;
568 if column_total == 0 {
569 return Ok(());
570 }
571 #[cfg(feature = "parallel")]
572 {
573 let nthreads = crate::threading::parallel_threads_for_len(self.dest_total);
574 if nthreads > 1 && column_total >= nthreads {
575 let dest_ptr = crate::threading::SendPtr(dest_ptr);
576 let operand_ptr = crate::threading::SendPtr(operand_ptr as *mut T);
577 return crate::threading::parallel_map_reduce(
578 0..column_total,
579 nthreads,
580 &|range| {
581 let mut outer_idx_storage =
582 CoordScratch::new(columns.dest_outer_dims.len());
583 let outer_idx = outer_idx_storage.as_mut_slice();
584 fill_col_major_index(range.start, &columns.dest_outer_dims, outer_idx);
585 unsafe {
591 self.write_axis0_columns_range::<T, UNINIT>(
592 dest_ptr.as_ptr(),
593 dest_offset,
594 operand_ptr.as_const(),
595 operand_offset,
596 fill,
597 columns,
598 outer_idx,
599 range.start,
600 range.len(),
601 )
602 }
603 },
604 &|left, right| left.and(right),
605 );
606 }
607 }
608 let mut outer_idx_storage = CoordScratch::new(columns.dest_outer_dims.len());
609 let outer_idx = outer_idx_storage.as_mut_slice();
610 unsafe {
612 self.write_axis0_columns_range::<T, UNINIT>(
613 dest_ptr,
614 dest_offset,
615 operand_ptr,
616 operand_offset,
617 fill,
618 columns,
619 outer_idx,
620 0,
621 column_total,
622 )
623 }
624 }
625
626 #[allow(clippy::too_many_arguments)]
634 unsafe fn write_axis0_columns_range<T, const UNINIT: bool>(
635 &self,
636 dest_ptr: *mut T,
637 dest_offset: isize,
638 operand_ptr: *const T,
639 operand_offset: isize,
640 fill: T,
641 columns: &ContiguousPadAxis0Columns,
642 outer_idx: &mut [usize],
643 column_start: usize,
644 count: usize,
645 ) -> Result<()>
646 where
647 T: Copy + MaybeSendSync,
648 {
649 if count == 0 {
650 return Ok(());
651 }
652 let mut dest_column_offset =
653 checked_offset_add(dest_offset, columns.dest_axis0_step, column_start)?;
654 let mut operand_column_offset = operand_offset;
659 let mut out_of_bounds = 0usize;
660 for (axis, &coord) in outer_idx.iter().enumerate() {
661 match in_bounds_outer_coord(columns, axis, coord) {
662 Some(q) => {
663 operand_column_offset = checked_offset_add(
664 operand_column_offset,
665 columns.operand_outer_strides[axis],
666 q,
667 )?;
668 }
669 None => out_of_bounds += 1,
670 }
671 }
672 for _ in 0..count {
673 if out_of_bounds == 0 {
674 let low_len = columns.low0;
675 let run_len = columns.run_len;
676 let high_len = columns.high0;
677 unsafe {
684 if UNINIT {
685 fill_raw_span_uninit(dest_ptr.offset(dest_column_offset), low_len, fill);
686 } else {
687 fill_raw_span(dest_ptr.offset(dest_column_offset), low_len, fill);
688 }
689 crate::threading::copy_contiguous(
690 operand_ptr.offset(operand_column_offset),
691 dest_ptr.offset(dest_column_offset + low_len as isize),
692 run_len,
693 );
694 if UNINIT {
695 fill_raw_span_uninit(
696 dest_ptr
697 .offset(dest_column_offset + low_len as isize + run_len as isize),
698 high_len,
699 fill,
700 );
701 } else {
702 fill_raw_span(
703 dest_ptr
704 .offset(dest_column_offset + low_len as isize + run_len as isize),
705 high_len,
706 fill,
707 );
708 }
709 }
710 } else {
711 unsafe {
714 if UNINIT {
715 fill_raw_span_uninit(
716 dest_ptr.offset(dest_column_offset),
717 columns.dest_axis0_len,
718 fill,
719 );
720 } else {
721 fill_raw_span(
722 dest_ptr.offset(dest_column_offset),
723 columns.dest_axis0_len,
724 fill,
725 );
726 }
727 }
728 }
729 dest_column_offset = dest_column_offset
732 .checked_add(columns.dest_axis0_step)
733 .ok_or(StridedError::OffsetOverflow)?;
734 for (axis, coord) in outer_idx.iter_mut().enumerate() {
735 let was_in_bounds = in_bounds_outer_coord(columns, axis, *coord);
736 if let Some(q) = was_in_bounds {
737 let q = isize::try_from(q).map_err(|_| StridedError::OffsetOverflow)?;
738 let scaled = columns.operand_outer_strides[axis]
739 .checked_mul(q)
740 .ok_or(StridedError::OffsetOverflow)?;
741 operand_column_offset = operand_column_offset
742 .checked_sub(scaled)
743 .ok_or(StridedError::OffsetOverflow)?;
744 }
745 *coord += 1;
746 let wrapped = *coord >= columns.dest_outer_dims[axis];
747 if wrapped {
748 *coord = 0;
749 }
750 let is_in_bounds = in_bounds_outer_coord(columns, axis, *coord);
751 if let Some(q) = is_in_bounds {
752 operand_column_offset = checked_offset_add(
753 operand_column_offset,
754 columns.operand_outer_strides[axis],
755 q,
756 )?;
757 }
758 match (was_in_bounds.is_some(), is_in_bounds.is_some()) {
759 (false, true) => out_of_bounds -= 1,
760 (true, false) => out_of_bounds += 1,
761 _ => {}
762 }
763 if !wrapped {
764 break;
765 }
766 }
767 }
768 Ok(())
769 }
770
771 unsafe fn copy_axis0_runs_raw<T>(
784 &self,
785 dest_ptr: *mut T,
786 dest_offset: isize,
787 operand: &RawStridedRef<'_, T>,
788 run: ContiguousPadAxis0Run,
789 ) -> Result<()>
790 where
791 T: Copy + MaybeSendSync,
792 {
793 if run.len == 0 {
794 return Ok(());
795 }
796 let outer_dims = &self.operand_dims[1..];
797 let outer_total = checked_total_len(outer_dims)?;
798 #[cfg(feature = "parallel")]
799 {
800 let copied = outer_total.saturating_mul(run.len);
801 let nthreads = crate::threading::parallel_threads_for_len(copied);
802 if nthreads > 1 && outer_total >= nthreads {
803 let dest_ptr = crate::threading::SendPtr(dest_ptr);
804 return crate::threading::parallel_map_reduce(
805 0..outer_total,
806 nthreads,
807 &|range| {
808 let mut outer_idx_storage = CoordScratch::new(outer_dims.len());
809 let outer_idx = outer_idx_storage.as_mut_slice();
810 fill_col_major_index(range.start, outer_dims, outer_idx);
811 unsafe {
815 self.copy_axis0_run_range(
816 dest_ptr.as_ptr(),
817 dest_offset,
818 operand,
819 run,
820 outer_idx,
821 range.len(),
822 )
823 }
824 },
825 &|left, right| left.and(right),
826 );
827 }
828 }
829 let mut outer_idx_storage = CoordScratch::new(outer_dims.len());
830 let outer_idx = outer_idx_storage.as_mut_slice();
831 unsafe {
833 self.copy_axis0_run_range(dest_ptr, dest_offset, operand, run, outer_idx, outer_total)
834 }
835 }
836
837 unsafe fn copy_axis0_run_range<T>(
845 &self,
846 dest_ptr: *mut T,
847 dest_offset: isize,
848 operand: &RawStridedRef<'_, T>,
849 run: ContiguousPadAxis0Run,
850 outer_idx: &mut [usize],
851 count: usize,
852 ) -> Result<()>
853 where
854 T: Copy + MaybeSendSync,
855 {
856 let outer_dims = &self.operand_dims[1..];
857 let operand_ptr = operand.data().as_ptr();
858 for _ in 0..count {
859 let mut operand_offset =
860 checked_offset_add(operand.offset(), self.operand_strides[0], run.operand_start)?;
861 let mut dest_run_offset =
862 checked_offset_add(dest_offset, self.dest_strides[0], run.dest_start)?;
863 let mut in_bounds = true;
864 for (outer_axis, &coord) in outer_idx.iter().enumerate() {
865 let axis = outer_axis + 1;
866 let out_pos = i128::from(self.edge_padding_low[axis])
867 + coord as i128 * i128::from(self.interior_step[axis]);
868 if out_pos < 0 || out_pos >= self.dest_dims[axis] as i128 {
869 in_bounds = false;
870 break;
871 }
872 operand_offset =
873 checked_offset_add(operand_offset, self.operand_strides[axis], coord)?;
874 dest_run_offset =
875 checked_offset_add(dest_run_offset, self.dest_strides[axis], out_pos as usize)?;
876 }
877 if in_bounds {
878 unsafe {
884 crate::threading::copy_contiguous(
885 operand_ptr.offset(operand_offset),
886 dest_ptr.offset(dest_run_offset),
887 run.len,
888 );
889 }
890 }
891 advance_col_major_index(outer_idx, outer_dims);
892 }
893 Ok(())
894 }
895
896 #[cfg(test)]
897 fn contiguous_axis0_run(&self) -> Option<(usize, usize, usize)> {
898 self.contiguous_axis0_run
899 .map(|run| (run.operand_start, run.dest_start, run.len))
900 }
901
902 #[cfg(test)]
903 fn has_contiguous_dest_fill(&self) -> bool {
904 self.contiguous_dest_fill
905 }
906
907 #[cfg(test)]
908 fn has_contiguous_axis0_columns(&self) -> bool {
909 self.contiguous_axis0_columns.is_some()
910 }
911
912 fn fill_dest_serial<T>(&self, dest: &mut RawStridedMut<'_, T>, fill: T) -> Result<()>
913 where
914 T: Copy,
915 {
916 let dest_ptr = dest.data_mut().as_mut_ptr();
917 let mut cursor = PadFillState::new(dest.offset(), &self.dest_dims, &self.generic_fill);
918 for _ in 0..self.dest_total {
919 unsafe {
920 *dest_ptr.offset(cursor.offset) = fill;
924 }
925 cursor.advance(&self.dest_dims, &self.generic_fill);
926 }
927 Ok(())
928 }
929
930 #[cfg(feature = "parallel")]
931 fn fill_dest_parallel<T>(
932 &self,
933 dest: &mut RawStridedMut<'_, T>,
934 fill: T,
935 nthreads: usize,
936 ) -> Result<()>
937 where
938 T: Copy + MaybeSendSync,
939 {
940 let dest_offset_base = dest.offset();
941 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
942 crate::threading::parallel_map_reduce(
943 0..self.dest_total,
944 nthreads,
945 &|range| {
946 let mut cursor = PadFillState::decode(
947 range.start,
948 dest_offset_base,
949 &self.dest_dims,
950 &self.generic_fill,
951 )?;
952 let dest_ptr = dest_ptr.as_ptr();
953 for _ in range {
954 unsafe {
955 *dest_ptr.offset(cursor.offset) = fill;
960 }
961 cursor.advance(&self.dest_dims, &self.generic_fill);
962 }
963 Ok(())
964 },
965 &|left, right| left.and(right),
966 )
967 }
968
969 fn copy_operand<T>(
970 &self,
971 dest: &mut RawStridedMut<'_, T>,
972 operand: &RawStridedRef<'_, T>,
973 ) -> Result<()>
974 where
975 T: Copy + MaybeSendSync,
976 {
977 #[cfg(feature = "parallel")]
978 {
979 let nthreads = crate::threading::parallel_threads_for_len(self.generic_copy.total);
980 if nthreads > 1 {
981 return self.copy_operand_parallel(dest, operand, nthreads);
982 }
983 }
984 self.copy_operand_serial(dest, operand)
985 }
986
987 fn copy_operand_uninit<T>(
988 &self,
989 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
990 operand: &RawStridedRef<'_, T>,
991 ) -> Result<()>
992 where
993 T: Copy + MaybeSendSync,
994 {
995 #[cfg(feature = "parallel")]
996 {
997 let nthreads = crate::threading::parallel_threads_for_len(self.generic_copy.total);
998 if nthreads > 1 {
999 return self.copy_operand_uninit_parallel(dest, operand, nthreads);
1000 }
1001 }
1002 self.copy_operand_uninit_serial(dest, operand)
1003 }
1004
1005 fn copy_operand_uninit_serial<T>(
1006 &self,
1007 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1008 operand: &RawStridedRef<'_, T>,
1009 ) -> Result<()>
1010 where
1011 T: Copy,
1012 {
1013 if self.generic_copy.total == 0 {
1014 return Ok(());
1015 }
1016 let operand_ptr = operand.data().as_ptr();
1017 let dest_ptr = dest.data_mut().as_mut_ptr();
1018 let source_base = operand
1019 .offset()
1020 .checked_add(self.generic_copy.source_base_delta)
1021 .ok_or(StridedError::OffsetOverflow)?;
1022 let dest_base = dest
1023 .offset()
1024 .checked_add(self.generic_copy.dest_base_delta)
1025 .ok_or(StridedError::OffsetOverflow)?;
1026 let mut cursor = PadCopyState::new(source_base, dest_base, &self.generic_copy);
1027 for _ in 0..self.generic_copy.total {
1028 unsafe {
1029 (*dest_ptr.offset(cursor.dest_offset))
1034 .write(*operand_ptr.offset(cursor.source_offset));
1035 }
1036 cursor.advance(&self.generic_copy);
1037 }
1038 Ok(())
1039 }
1040
1041 #[cfg(feature = "parallel")]
1042 fn copy_operand_uninit_parallel<T>(
1043 &self,
1044 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1045 operand: &RawStridedRef<'_, T>,
1046 nthreads: usize,
1047 ) -> Result<()>
1048 where
1049 T: Copy + MaybeSendSync,
1050 {
1051 let operand_base = operand
1052 .offset()
1053 .checked_add(self.generic_copy.source_base_delta)
1054 .ok_or(StridedError::OffsetOverflow)?;
1055 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
1056 let dest_base = dest
1057 .offset()
1058 .checked_add(self.generic_copy.dest_base_delta)
1059 .ok_or(StridedError::OffsetOverflow)?;
1060 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
1061 crate::threading::parallel_map_reduce(
1062 0..self.generic_copy.total,
1063 nthreads,
1064 &|range| {
1065 let mut cursor =
1066 PadCopyState::decode(range.start, operand_base, dest_base, &self.generic_copy)?;
1067 let operand_ptr = operand_ptr.as_const();
1068 let dest_ptr = dest_ptr.as_ptr();
1069
1070 for _ in range {
1071 unsafe {
1072 (*dest_ptr.offset(cursor.dest_offset))
1077 .write(*operand_ptr.offset(cursor.source_offset));
1078 }
1079 cursor.advance(&self.generic_copy);
1080 }
1081 Ok(())
1082 },
1083 &|left, right| left.and(right),
1084 )
1085 }
1086
1087 fn copy_operand_serial<T>(
1088 &self,
1089 dest: &mut RawStridedMut<'_, T>,
1090 operand: &RawStridedRef<'_, T>,
1091 ) -> Result<()>
1092 where
1093 T: Copy,
1094 {
1095 if self.generic_copy.total == 0 {
1096 return Ok(());
1097 }
1098 let operand_ptr = operand.data().as_ptr();
1099 let dest_ptr = dest.data_mut().as_mut_ptr();
1100 let source_base = operand
1101 .offset()
1102 .checked_add(self.generic_copy.source_base_delta)
1103 .ok_or(StridedError::OffsetOverflow)?;
1104 let dest_base = dest
1105 .offset()
1106 .checked_add(self.generic_copy.dest_base_delta)
1107 .ok_or(StridedError::OffsetOverflow)?;
1108 let mut cursor = PadCopyState::new(source_base, dest_base, &self.generic_copy);
1109 for _ in 0..self.generic_copy.total {
1110 unsafe {
1111 *dest_ptr.offset(cursor.dest_offset) = *operand_ptr.offset(cursor.source_offset);
1116 }
1117 cursor.advance(&self.generic_copy);
1118 }
1119 Ok(())
1120 }
1121
1122 #[cfg(feature = "parallel")]
1123 fn copy_operand_parallel<T>(
1124 &self,
1125 dest: &mut RawStridedMut<'_, T>,
1126 operand: &RawStridedRef<'_, T>,
1127 nthreads: usize,
1128 ) -> Result<()>
1129 where
1130 T: Copy + MaybeSendSync,
1131 {
1132 let operand_base = operand
1133 .offset()
1134 .checked_add(self.generic_copy.source_base_delta)
1135 .ok_or(StridedError::OffsetOverflow)?;
1136 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
1137 let dest_base = dest
1138 .offset()
1139 .checked_add(self.generic_copy.dest_base_delta)
1140 .ok_or(StridedError::OffsetOverflow)?;
1141 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
1142 crate::threading::parallel_map_reduce(
1143 0..self.generic_copy.total,
1144 nthreads,
1145 &|range| {
1146 let mut cursor =
1147 PadCopyState::decode(range.start, operand_base, dest_base, &self.generic_copy)?;
1148 let operand_ptr = operand_ptr.as_const();
1149 let dest_ptr = dest_ptr.as_ptr();
1150
1151 for _ in range {
1152 unsafe {
1153 *dest_ptr.offset(cursor.dest_offset) =
1158 *operand_ptr.offset(cursor.source_offset);
1159 }
1160 cursor.advance(&self.generic_copy);
1161 }
1162 Ok(())
1163 },
1164 &|left, right| left.and(right),
1165 )
1166 }
1167
1168 fn check_call<D, T>(
1169 &self,
1170 dest: &RawStridedMut<'_, D>,
1171 operand: &RawStridedRef<'_, T>,
1172 ) -> Result<()> {
1173 if operand.dims() != &self.operand_dims[..]
1174 || operand.strides() != &self.operand_strides[..]
1175 || dest.dims() != &self.dest_dims[..]
1176 || dest.strides() != &self.dest_strides[..]
1177 {
1178 return Err(StridedError::PlanLayoutMismatch);
1179 }
1180 Ok(())
1181 }
1182}
1183
1184#[cfg(feature = "parallel")]
1185struct ConcatSegment<'a, T> {
1186 layout: &'a crate::raw_ops::FusedPairLayout,
1187 dest_offset: isize,
1188 src_ptr: crate::threading::SendPtr<T>,
1189 src_offset: isize,
1190}
1191
1192impl ConcatenatePlan {
1193 pub fn compile(
1195 input_dims: &[&[usize]],
1196 input_strides: &[&[isize]],
1197 dest_dims: &[usize],
1198 dest_strides: &[isize],
1199 axis: usize,
1200 ) -> Result<Self> {
1201 if input_dims.is_empty() {
1202 return Err(StridedError::UnsupportedArity {
1203 arity: 0,
1204 max: usize::MAX,
1205 });
1206 }
1207 if input_dims.len() != input_strides.len() {
1208 return Err(StridedError::RankMismatch(
1209 input_strides.len(),
1210 input_dims.len(),
1211 ));
1212 }
1213
1214 let rank = input_dims[0].len();
1215 if dest_dims.len() != rank || dest_strides.len() != rank {
1216 return Err(StridedError::StrideLengthMismatch);
1217 }
1218 if axis >= rank {
1219 return Err(StridedError::InvalidAxis { axis, rank });
1220 }
1221 checked_total_len(dest_dims)?;
1222 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
1223 return Err(StridedError::NonInjectiveOutputLayout);
1224 }
1225
1226 let mut expected_dest_dims: AxisVec<usize> = input_dims[0].into();
1227 expected_dest_dims[axis] = 0;
1228 let mut stored_input_dims = Vec::with_capacity(input_dims.len());
1229 let mut stored_input_strides = Vec::with_capacity(input_dims.len());
1230 let mut dest_offset_deltas = Vec::with_capacity(input_dims.len());
1231 let mut copy_plans = Vec::with_capacity(input_dims.len());
1232 let mut segment_starts = Vec::with_capacity(input_dims.len() + 1);
1233 segment_starts.push(0usize);
1234 let mut axis_base = 0usize;
1235
1236 for (dims, strides) in input_dims.iter().zip(input_strides.iter()) {
1237 if dims.len() != rank {
1238 return Err(StridedError::RankMismatch(dims.len(), rank));
1239 }
1240 if strides.len() != rank {
1241 return Err(StridedError::StrideLengthMismatch);
1242 }
1243 let segment_total = checked_total_len(dims)?;
1244 let segment_end = segment_starts[segment_starts.len() - 1]
1245 .checked_add(segment_total)
1246 .ok_or(StridedError::OffsetOverflow)?;
1247 segment_starts.push(segment_end);
1248 for dim in 0..rank {
1249 if dim == axis {
1250 expected_dest_dims[axis] = expected_dest_dims[axis]
1251 .checked_add(dims[axis])
1252 .ok_or(StridedError::OffsetOverflow)?;
1253 } else if dims[dim] != input_dims[0][dim] {
1254 return Err(StridedError::ShapeMismatch(
1255 dims.to_vec(),
1256 input_dims[0].to_vec(),
1257 ));
1258 }
1259 }
1260 dest_offset_deltas.push(checked_offset_add(0, dest_strides[axis], axis_base)?);
1261 axis_base = axis_base
1262 .checked_add(dims[axis])
1263 .ok_or(StridedError::OffsetOverflow)?;
1264 copy_plans.push(CopyPlan::compile(dims, dest_strides, strides)?);
1265 stored_input_dims.push((*dims).into());
1266 stored_input_strides.push((*strides).into());
1267 }
1268
1269 if dest_dims != &expected_dest_dims[..] {
1270 return Err(StridedError::ShapeMismatch(
1271 dest_dims.to_vec(),
1272 expected_dest_dims.to_vec(),
1273 ));
1274 }
1275
1276 Ok(Self {
1277 input_dims: stored_input_dims,
1278 input_strides: stored_input_strides,
1279 dest_dims: dest_dims.into(),
1280 dest_strides: dest_strides.into(),
1281 dest_offset_deltas,
1282 segment_starts,
1283 copy_plans,
1284 })
1285 }
1286
1287 pub fn execute<T>(
1289 &self,
1290 dest: &mut RawStridedMut<'_, T>,
1291 inputs: &[RawStridedRef<'_, T>],
1292 ) -> Result<()>
1293 where
1294 T: Copy + MaybeSendSync,
1295 {
1296 self.check_dest_layout(dest)?;
1297 if inputs.len() != self.input_dims.len() {
1298 return Err(StridedError::RankMismatch(
1299 inputs.len(),
1300 self.input_dims.len(),
1301 ));
1302 }
1303 for (position, input) in inputs.iter().enumerate() {
1304 self.check_input_layout(position, input)?;
1305 self.segment_offset(position, dest.offset())?;
1306 }
1307 #[cfg(feature = "parallel")]
1308 if self.try_execute_parallel(dest, inputs, |dst: &mut T, value| *dst = value)? {
1309 return Ok(());
1310 }
1311 for (position, input) in inputs.iter().enumerate() {
1312 self.execute_segment(position, dest, input)?;
1313 }
1314 Ok(())
1315 }
1316
1317 pub fn execute_uninit<T>(
1319 &self,
1320 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1321 inputs: &[RawStridedRef<'_, T>],
1322 ) -> Result<()>
1323 where
1324 T: Copy + MaybeSendSync,
1325 {
1326 self.check_dest_layout(dest)?;
1327 if inputs.len() != self.input_dims.len() {
1328 return Err(StridedError::RankMismatch(
1329 inputs.len(),
1330 self.input_dims.len(),
1331 ));
1332 }
1333 for (position, input) in inputs.iter().enumerate() {
1334 self.check_input_layout(position, input)?;
1335 self.segment_offset(position, dest.offset())?;
1336 }
1337 #[cfg(feature = "parallel")]
1338 if self.try_execute_parallel(dest, inputs, |dst: &mut MaybeUninit<T>, value| {
1339 dst.write(value);
1340 })? {
1341 return Ok(());
1342 }
1343 for (position, input) in inputs.iter().enumerate() {
1344 self.execute_segment_uninit(position, dest, input)?;
1345 }
1346 Ok(())
1347 }
1348
1349 #[cfg(feature = "parallel")]
1360 fn try_execute_parallel<D, T, Apply>(
1361 &self,
1362 dest: &mut RawStridedMut<'_, D>,
1363 inputs: &[RawStridedRef<'_, T>],
1364 apply: Apply,
1365 ) -> Result<bool>
1366 where
1367 D: Copy + MaybeSendSync,
1368 T: Copy + MaybeSendSync,
1369 Apply: Fn(&mut D, T) + MaybeSendSync,
1370 {
1371 let total = self.segment_starts[self.segment_starts.len() - 1];
1372 let nthreads = crate::threading::parallel_threads_for_len(total);
1373 if nthreads <= 1 {
1374 return Ok(false);
1375 }
1376 let mut segments = Vec::with_capacity(inputs.len());
1377 for (position, input) in inputs.iter().enumerate() {
1378 let Some(layout) = self.copy_plans[position].fused_layout() else {
1379 return Ok(false);
1380 };
1381 segments.push(ConcatSegment {
1382 layout,
1383 dest_offset: self.segment_offset(position, dest.offset())?,
1384 src_ptr: crate::threading::SendPtr(input.data().as_ptr() as *mut T),
1385 src_offset: input.offset(),
1386 });
1387 }
1388 let dest_ptr = crate::threading::SendPtr(dest.data_mut().as_mut_ptr());
1389 let starts = &self.segment_starts;
1390 let segments = &segments;
1391 crate::threading::parallel_for_each(0..total, nthreads, &|range| {
1392 let mut position = starts[1..].partition_point(|&end| end <= range.start);
1394 while position < segments.len() && starts[position] < range.end {
1395 let segment = &segments[position];
1396 let local_start = range.start.max(starts[position]) - starts[position];
1397 let local_end = range.end.min(starts[position + 1]) - starts[position];
1398 unsafe {
1408 crate::raw_ops::apply_fused_range(
1409 dest_ptr.as_ptr(),
1410 segment.dest_offset,
1411 segment.src_ptr.as_const(),
1412 segment.src_offset,
1413 segment.layout,
1414 local_start,
1415 local_end - local_start,
1416 &apply,
1417 &|value| value,
1418 );
1419 }
1420 position += 1;
1421 }
1422 });
1423 Ok(true)
1424 }
1425
1426 pub(crate) fn check_dest_layout<T>(&self, dest: &RawStridedMut<'_, T>) -> Result<()> {
1427 if dest.dims() != &self.dest_dims[..] || dest.strides() != &self.dest_strides[..] {
1428 return Err(StridedError::PlanLayoutMismatch);
1429 }
1430 Ok(())
1431 }
1432
1433 pub(crate) fn check_input_layout<T>(
1434 &self,
1435 position: usize,
1436 input: &RawStridedRef<'_, T>,
1437 ) -> Result<()> {
1438 if position >= self.input_dims.len()
1439 || input.dims() != &self.input_dims[position][..]
1440 || input.strides() != &self.input_strides[position][..]
1441 {
1442 return Err(StridedError::PlanLayoutMismatch);
1443 }
1444 Ok(())
1445 }
1446
1447 pub(crate) fn input_count(&self) -> usize {
1448 self.input_dims.len()
1449 }
1450
1451 pub(crate) fn prefers_whole_plan(&self) -> bool {
1455 #[cfg(feature = "parallel")]
1456 {
1457 let total = self.segment_starts[self.segment_starts.len() - 1];
1458 crate::threading::parallel_threads_for_len(total) > 1
1459 }
1460 #[cfg(not(feature = "parallel"))]
1461 {
1462 false
1463 }
1464 }
1465
1466 pub(crate) fn segment_offset(&self, position: usize, dest_offset: isize) -> Result<isize> {
1467 dest_offset
1468 .checked_add(self.dest_offset_deltas[position])
1469 .ok_or(StridedError::OffsetOverflow)
1470 }
1471
1472 pub(crate) fn execute_segment<T>(
1473 &self,
1474 position: usize,
1475 dest: &mut RawStridedMut<'_, T>,
1476 input: &RawStridedRef<'_, T>,
1477 ) -> Result<()>
1478 where
1479 T: Copy + MaybeSendSync,
1480 {
1481 let segment_offset = self.segment_offset(position, dest.offset())?;
1482 let dest_data = dest.data_mut();
1483 let mut segment = unsafe {
1484 RawStridedMut::new_unchecked(
1485 dest_data,
1486 &self.input_dims[position],
1487 &self.dest_strides,
1488 segment_offset,
1489 )
1490 };
1491 self.copy_plans[position].execute(&mut segment, input)
1492 }
1493
1494 pub(crate) fn execute_segment_uninit<T>(
1495 &self,
1496 position: usize,
1497 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1498 input: &RawStridedRef<'_, T>,
1499 ) -> Result<()>
1500 where
1501 T: Copy + MaybeSendSync,
1502 {
1503 let segment_offset = self.segment_offset(position, dest.offset())?;
1504 let dest_data = dest.data_mut();
1505 let mut segment = unsafe {
1506 RawStridedMut::new_unchecked(
1507 dest_data,
1508 &self.input_dims[position],
1509 &self.dest_strides,
1510 segment_offset,
1511 )
1512 };
1513 self.copy_plans[position].execute_uninit(&mut segment, input)
1514 }
1515}
1516
1517impl ReversePlan {
1518 pub fn compile(
1520 operand_dims: &[usize],
1521 operand_strides: &[isize],
1522 dest_strides: &[isize],
1523 axes: &[usize],
1524 ) -> Result<Self> {
1525 let rank = operand_dims.len();
1526 if operand_strides.len() != rank || dest_strides.len() != rank {
1527 return Err(StridedError::StrideLengthMismatch);
1528 }
1529 checked_total_len(operand_dims)?;
1530
1531 let mut reverse_axis: AxisVec<bool> = (0..rank).map(|_| false).collect();
1532 for &axis in axes {
1533 if axis >= rank {
1534 return Err(StridedError::InvalidAxis { axis, rank });
1535 }
1536 reverse_axis[axis] = true;
1537 }
1538
1539 let mut source_strides: AxisVec<isize> = AxisVec::with_capacity(rank);
1540 let mut source_offset_delta = 0isize;
1541 for axis in 0..rank {
1542 if reverse_axis[axis] {
1543 source_strides.push(
1544 operand_strides[axis]
1545 .checked_neg()
1546 .ok_or(StridedError::OffsetOverflow)?,
1547 );
1548 if operand_dims[axis] > 0 {
1549 source_offset_delta = checked_offset_add(
1550 source_offset_delta,
1551 operand_strides[axis],
1552 operand_dims[axis] - 1,
1553 )?;
1554 }
1555 } else {
1556 source_strides.push(operand_strides[axis]);
1557 }
1558 }
1559 let copy_plan = CopyPlan::compile(operand_dims, dest_strides, &source_strides)?;
1560
1561 Ok(Self {
1562 operand_dims: operand_dims.into(),
1563 operand_strides: operand_strides.into(),
1564 dest_strides: dest_strides.into(),
1565 source_strides,
1566 source_offset_delta,
1567 copy_plan,
1568 })
1569 }
1570
1571 pub fn execute<T>(
1573 &self,
1574 dest: &mut RawStridedMut<'_, T>,
1575 operand: &RawStridedRef<'_, T>,
1576 ) -> Result<()>
1577 where
1578 T: Copy + MaybeSendSync,
1579 {
1580 self.check_call(dest, operand)?;
1581 let source_offset = operand
1582 .offset()
1583 .checked_add(self.source_offset_delta)
1584 .ok_or(StridedError::OffsetOverflow)?;
1585 let source = unsafe {
1586 RawStridedRef::new_unchecked(
1587 operand.data(),
1588 &self.operand_dims,
1589 &self.source_strides,
1590 source_offset,
1591 )
1592 };
1593 self.copy_plan.execute(dest, &source)
1594 }
1595
1596 pub fn execute_uninit<T>(
1598 &self,
1599 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1600 operand: &RawStridedRef<'_, T>,
1601 ) -> Result<()>
1602 where
1603 T: Copy + MaybeSendSync,
1604 {
1605 self.check_call(dest, operand)?;
1606 let source_offset = operand
1607 .offset()
1608 .checked_add(self.source_offset_delta)
1609 .ok_or(StridedError::OffsetOverflow)?;
1610 let source = unsafe {
1611 RawStridedRef::new_unchecked(
1612 operand.data(),
1613 &self.operand_dims,
1614 &self.source_strides,
1615 source_offset,
1616 )
1617 };
1618 self.copy_plan.execute_uninit(dest, &source)
1619 }
1620
1621 fn check_call<D, T>(
1622 &self,
1623 dest: &RawStridedMut<'_, D>,
1624 operand: &RawStridedRef<'_, T>,
1625 ) -> Result<()> {
1626 if operand.dims() != &self.operand_dims[..]
1627 || operand.strides() != &self.operand_strides[..]
1628 || dest.dims() != &self.operand_dims[..]
1629 || dest.strides() != &self.dest_strides[..]
1630 {
1631 return Err(StridedError::PlanLayoutMismatch);
1632 }
1633 Ok(())
1634 }
1635}
1636
1637fn checked_total_len(dims: &[usize]) -> Result<usize> {
1638 if dims.is_empty() {
1639 return Ok(1);
1640 }
1641 dims.iter()
1642 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
1643 .ok_or(StridedError::OffsetOverflow)
1644}
1645
1646fn checked_stride_mul(stride: isize, factor: usize) -> Result<isize> {
1647 let factor = isize::try_from(factor).map_err(|_| StridedError::OffsetOverflow)?;
1648 stride
1649 .checked_mul(factor)
1650 .ok_or(StridedError::OffsetOverflow)
1651}
1652
1653fn checked_pad_output_dim(
1654 input_extent: usize,
1655 edge_low: i64,
1656 edge_high: i64,
1657 interior_step: i64,
1658 axis: usize,
1659 rank: usize,
1660) -> Result<usize> {
1661 let base = if input_extent == 0 {
1662 0i128
1663 } else {
1664 (input_extent as i128 - 1)
1665 .checked_mul(i128::from(interior_step))
1666 .and_then(|value| value.checked_add(1))
1667 .ok_or(StridedError::OffsetOverflow)?
1668 };
1669 let dim = i128::from(edge_low)
1670 .checked_add(i128::from(edge_high))
1671 .and_then(|value| value.checked_add(base))
1672 .ok_or(StridedError::OffsetOverflow)?;
1673 usize::try_from(dim).map_err(|_| StridedError::InvalidAxis { axis, rank })
1674}
1675
1676fn compile_pad_fill_cursor(dims: &[usize], strides: &[isize]) -> Result<PadFillCursor> {
1677 let mut steps = AxisVec::with_capacity(dims.len());
1678 let mut resets = AxisVec::with_capacity(dims.len());
1679 for (&dim, &stride) in dims.iter().zip(strides) {
1680 steps.push(stride);
1681 resets.push(checked_cursor_reset(stride, dim)?);
1682 }
1683 check_offset_span(dims, &steps)?;
1684 Ok(PadFillCursor { steps, resets })
1685}
1686
1687fn compile_pad_copy_cursor(
1688 operand_dims: &[usize],
1689 operand_strides: &[isize],
1690 dest_dims: &[usize],
1691 dest_strides: &[isize],
1692 edge_padding_low: &[i64],
1693 interior_step: &[i64],
1694) -> Result<PadCopyCursor> {
1695 let mut shape = AxisVec::with_capacity(operand_dims.len());
1696 let mut source_steps = AxisVec::with_capacity(operand_dims.len());
1697 let mut source_resets = AxisVec::with_capacity(operand_dims.len());
1698 let mut dest_steps = AxisVec::with_capacity(operand_dims.len());
1699 let mut dest_resets = AxisVec::with_capacity(operand_dims.len());
1700 let mut source_base_delta = 0isize;
1701 let mut dest_base_delta = 0isize;
1702 let mut copy_empty = false;
1703
1704 for axis in 0..operand_dims.len() {
1705 let (start, end) = checked_pad_valid_interval(
1706 operand_dims[axis],
1707 dest_dims[axis],
1708 edge_padding_low[axis],
1709 interior_step[axis],
1710 )?;
1711 let extent = end - start;
1712 shape.push(extent);
1713 if !copy_empty && extent != 0 {
1714 source_base_delta =
1715 checked_offset_add(source_base_delta, operand_strides[axis], start)?;
1716 let output_start = i128::from(edge_padding_low[axis])
1717 .checked_add(
1718 i128::try_from(start)
1719 .map_err(|_| StridedError::OffsetOverflow)?
1720 .checked_mul(i128::from(interior_step[axis]))
1721 .ok_or(StridedError::OffsetOverflow)?,
1722 )
1723 .ok_or(StridedError::OffsetOverflow)?;
1724 let output_start =
1725 usize::try_from(output_start).map_err(|_| StridedError::OffsetOverflow)?;
1726 dest_base_delta =
1727 checked_offset_add(dest_base_delta, dest_strides[axis], output_start)?;
1728 }
1729 copy_empty |= extent == 0;
1730
1731 let source_step = operand_strides[axis];
1732 let dest_step = checked_stride_mul_i64(dest_strides[axis], interior_step[axis])?;
1733 source_steps.push(source_step);
1734 source_resets.push(checked_cursor_reset(source_step, extent)?);
1735 dest_steps.push(dest_step);
1736 dest_resets.push(checked_cursor_reset(dest_step, extent)?);
1737 }
1738
1739 check_offset_span(&shape, &source_steps)?;
1740 check_offset_span(&shape, &dest_steps)?;
1741 let total = if shape.iter().any(|&extent| extent == 0) {
1742 0
1743 } else {
1744 checked_total_len(&shape)?
1745 };
1746
1747 Ok(PadCopyCursor {
1748 shape,
1749 source_base_delta,
1750 dest_base_delta,
1751 source_steps,
1752 source_resets,
1753 dest_steps,
1754 dest_resets,
1755 total,
1756 })
1757}
1758
1759fn checked_pad_valid_interval(
1760 input_extent: usize,
1761 dest_extent: usize,
1762 edge_low: i64,
1763 step: i64,
1764) -> Result<(usize, usize)> {
1765 if input_extent == 0 || dest_extent == 0 {
1766 return Ok((0, 0));
1767 }
1768 let step = i128::from(step);
1769 let lower = ceil_div_positive(-i128::from(edge_low), step)?;
1770 let dest_last = i128::try_from(dest_extent)
1771 .map_err(|_| StridedError::OffsetOverflow)?
1772 .checked_sub(1)
1773 .ok_or(StridedError::OffsetOverflow)?;
1774 let upper = floor_div_positive(
1775 dest_last
1776 .checked_sub(i128::from(edge_low))
1777 .ok_or(StridedError::OffsetOverflow)?,
1778 step,
1779 )?
1780 .checked_add(1)
1781 .ok_or(StridedError::OffsetOverflow)?;
1782 let input_extent = i128::try_from(input_extent).map_err(|_| StridedError::OffsetOverflow)?;
1783 let lower = lower.clamp(0, input_extent);
1784 let upper = upper.clamp(0, input_extent);
1785 if lower >= upper {
1786 return Ok((0, 0));
1787 }
1788 Ok((
1789 usize::try_from(lower).map_err(|_| StridedError::OffsetOverflow)?,
1790 usize::try_from(upper).map_err(|_| StridedError::OffsetOverflow)?,
1791 ))
1792}
1793
1794fn ceil_div_positive(numerator: i128, denominator: i128) -> Result<i128> {
1795 if denominator <= 0 {
1796 return Err(StridedError::OffsetOverflow);
1797 }
1798 let quotient = numerator.div_euclid(denominator);
1799 let remainder = numerator.rem_euclid(denominator);
1800 quotient
1801 .checked_add(i128::from(remainder != 0))
1802 .ok_or(StridedError::OffsetOverflow)
1803}
1804
1805fn floor_div_positive(numerator: i128, denominator: i128) -> Result<i128> {
1806 if denominator <= 0 {
1807 return Err(StridedError::OffsetOverflow);
1808 }
1809 Ok(numerator.div_euclid(denominator))
1810}
1811
1812fn checked_stride_mul_i64(stride: isize, factor: i64) -> Result<isize> {
1813 let factor = isize::try_from(factor).map_err(|_| StridedError::OffsetOverflow)?;
1814 stride
1815 .checked_mul(factor)
1816 .ok_or(StridedError::OffsetOverflow)
1817}
1818
1819fn checked_cursor_reset(step: isize, extent: usize) -> Result<isize> {
1820 if extent == 0 {
1821 return Ok(0);
1822 }
1823 let last = isize::try_from(extent - 1).map_err(|_| StridedError::OffsetOverflow)?;
1824 step.checked_mul(last)
1825 .and_then(isize::checked_neg)
1826 .ok_or(StridedError::OffsetOverflow)
1827}
1828
1829fn check_offset_span(shape: &[usize], strides: &[isize]) -> Result<()> {
1830 let mut min = 0isize;
1831 let mut max = 0isize;
1832 for (&extent, &stride) in shape.iter().zip(strides) {
1833 if extent <= 1 {
1834 continue;
1835 }
1836 let last = isize::try_from(extent - 1).map_err(|_| StridedError::OffsetOverflow)?;
1837 let delta = stride
1838 .checked_mul(last)
1839 .ok_or(StridedError::OffsetOverflow)?;
1840 if delta < 0 {
1841 min = min.checked_add(delta).ok_or(StridedError::OffsetOverflow)?;
1842 } else {
1843 max = max.checked_add(delta).ok_or(StridedError::OffsetOverflow)?;
1844 }
1845 }
1846 let _ = (min, max);
1847 Ok(())
1848}
1849
1850fn is_dense_col_major(dims: &[usize], strides: &[isize]) -> bool {
1851 let mut expected = 1isize;
1852 for (&dim, &stride) in dims.iter().zip(strides.iter()) {
1853 if stride != expected {
1854 return false;
1855 }
1856 let Ok(dim) = isize::try_from(dim) else {
1857 return false;
1858 };
1859 let Some(next) = expected.checked_mul(dim) else {
1860 return false;
1861 };
1862 expected = next;
1863 }
1864 true
1865}
1866
1867fn compile_contiguous_pad_axis0_run(
1868 operand_dims: &[usize],
1869 operand_strides: &[isize],
1870 dest_dims: &[usize],
1871 dest_strides: &[isize],
1872 edge_padding_low: &[i64],
1873 interior_step: &[i64],
1874) -> Option<ContiguousPadAxis0Run> {
1875 if operand_dims.is_empty()
1876 || operand_strides[0] != 1
1877 || dest_strides[0] != 1
1878 || interior_step[0] != 1
1879 {
1880 return None;
1881 }
1882
1883 let operand_extent = operand_dims[0] as i128;
1884 let dest_extent = dest_dims[0] as i128;
1885 let edge_low = i128::from(edge_padding_low[0]);
1886 let operand_start = (-edge_low).clamp(0, operand_extent);
1887 let dest_start = edge_low.clamp(0, dest_extent);
1888 let len = (operand_extent - operand_start).min(dest_extent - dest_start);
1889 Some(ContiguousPadAxis0Run {
1890 operand_start: usize::try_from(operand_start).ok()?,
1891 dest_start: usize::try_from(dest_start).ok()?,
1892 len: usize::try_from(len).ok()?,
1893 })
1894}
1895
1896#[allow(clippy::too_many_arguments)]
1899fn compile_contiguous_pad_axis0_columns(
1900 operand_dims: &[usize],
1901 operand_strides: &[isize],
1902 dest_dims: &[usize],
1903 edge_padding_low: &[i64],
1904 edge_padding_high: &[i64],
1905 interior_step: &[i64],
1906 contiguous_dest_fill: bool,
1907 run: ContiguousPadAxis0Run,
1908) -> Option<ContiguousPadAxis0Columns> {
1909 if run.len == 0 || !contiguous_dest_fill || dest_dims.is_empty() {
1910 return None;
1911 }
1912 if edge_padding_low.iter().any(|&low| low < 0)
1913 || edge_padding_high.iter().any(|&high| high < 0)
1914 || interior_step.iter().any(|&step| step != 1)
1915 {
1916 return None;
1917 }
1918
1919 let rank = operand_dims.len();
1920 let dest_axis0_len = dest_dims[0];
1921 let dest_axis0_step = isize::try_from(dest_axis0_len).ok()?;
1922 let column_total = checked_total_len(&dest_dims[1..]).ok()?;
1923 let mut operand_outer_dims = AxisVec::with_capacity(rank - 1);
1924 let mut operand_outer_strides = AxisVec::with_capacity(rank - 1);
1925 let mut dest_outer_dims = AxisVec::with_capacity(rank - 1);
1926 let mut edge_low_outer = AxisVec::with_capacity(rank - 1);
1927 for axis in 1..rank {
1928 operand_outer_dims.push(operand_dims[axis]);
1929 operand_outer_strides.push(operand_strides[axis]);
1930 dest_outer_dims.push(dest_dims[axis]);
1931 edge_low_outer.push(usize::try_from(edge_padding_low[axis]).ok()?);
1932 }
1933
1934 let low0 = run.dest_start;
1935 let high0 = dest_axis0_len.checked_sub(low0)?.checked_sub(run.len)?;
1936 Some(ContiguousPadAxis0Columns {
1937 column_total,
1938 dest_axis0_len,
1939 dest_axis0_step,
1940 low0,
1941 high0,
1942 run_len: run.len,
1943 operand_outer_dims,
1944 operand_outer_strides,
1945 dest_outer_dims,
1946 edge_low_outer,
1947 })
1948}
1949
1950unsafe fn fill_raw_span<T: Copy + MaybeSendSync>(ptr: *mut T, len: usize, fill: T) {
1958 if len == 0 {
1959 return;
1960 }
1961 let span = unsafe { core::slice::from_raw_parts_mut(ptr, len) };
1964 crate::threading::fill_contiguous(span, fill);
1965}
1966
1967unsafe fn fill_raw_span_uninit<T: Copy + MaybeSendSync>(ptr: *mut T, len: usize, fill: T) {
1974 if len == 0 {
1975 return;
1976 }
1977 unsafe { crate::threading::fill_contiguous_uninit(ptr, len, fill) };
1980}
1981
1982fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1983 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1984 let scaled = stride
1985 .checked_mul(coord)
1986 .ok_or(StridedError::OffsetOverflow)?;
1987 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1988}
1989
1990fn in_bounds_outer_coord(
1993 columns: &ContiguousPadAxis0Columns,
1994 axis: usize,
1995 coord: usize,
1996) -> Option<usize> {
1997 let q = coord.checked_sub(columns.edge_low_outer[axis])?;
1998 (q < columns.operand_outer_dims[axis]).then_some(q)
1999}
2000
2001fn advance_col_major_index(index: &mut [usize], shape: &[usize]) {
2002 for axis in 0..index.len() {
2003 index[axis] += 1;
2004 if index[axis] < shape[axis] {
2005 return;
2006 }
2007 index[axis] = 0;
2008 }
2009}
2010
2011#[cfg(feature = "parallel")]
2012fn fill_col_major_index(mut linear: usize, shape: &[usize], out: &mut [usize]) {
2013 for (axis, coord) in out.iter_mut().enumerate() {
2014 let dim = shape[axis];
2015 *coord = linear % dim;
2016 linear /= dim;
2017 }
2018}
2019
2020struct PadFillState {
2021 coords: CoordScratch,
2022 offset: isize,
2023}
2024
2025impl PadFillState {
2026 fn new(base: isize, shape: &[usize], _cursor: &PadFillCursor) -> Self {
2027 Self {
2028 coords: CoordScratch::new(shape.len()),
2029 offset: base,
2030 }
2031 }
2032
2033 #[cfg(feature = "parallel")]
2034 fn decode(linear: usize, base: isize, shape: &[usize], cursor: &PadFillCursor) -> Result<Self> {
2035 let mut state = Self::new(base, shape, cursor);
2036 fill_col_major_index(linear, shape, state.coords.as_mut_slice());
2037 for (&coord, &step) in state.coords.as_mut_slice().iter().zip(&cursor.steps) {
2038 state.offset = checked_offset_add(state.offset, step, coord)?;
2039 }
2040 Ok(state)
2041 }
2042
2043 #[inline]
2044 fn advance(&mut self, shape: &[usize], cursor: &PadFillCursor) {
2045 for axis in 0..shape.len() {
2049 let next = self.coords.as_mut_slice()[axis] + 1;
2050 if next < shape[axis] {
2051 self.coords.as_mut_slice()[axis] = next;
2052 self.offset += cursor.steps[axis];
2053 return;
2054 }
2055 self.coords.as_mut_slice()[axis] = 0;
2056 self.offset += cursor.resets[axis];
2057 }
2058 }
2059}
2060
2061struct PadCopyState {
2062 coords: CoordScratch,
2063 source_offset: isize,
2064 dest_offset: isize,
2065}
2066
2067impl PadCopyState {
2068 fn new(source_base: isize, dest_base: isize, cursor: &PadCopyCursor) -> Self {
2069 Self {
2070 coords: CoordScratch::new(cursor.shape.len()),
2071 source_offset: source_base,
2072 dest_offset: dest_base,
2073 }
2074 }
2075
2076 #[cfg(feature = "parallel")]
2077 fn decode(
2078 linear: usize,
2079 source_base: isize,
2080 dest_base: isize,
2081 cursor: &PadCopyCursor,
2082 ) -> Result<Self> {
2083 let mut state = Self::new(source_base, dest_base, cursor);
2084 fill_col_major_index(linear, &cursor.shape, state.coords.as_mut_slice());
2085 for axis in 0..cursor.shape.len() {
2086 let coord = state.coords.as_mut_slice()[axis];
2087 state.source_offset =
2088 checked_offset_add(state.source_offset, cursor.source_steps[axis], coord)?;
2089 state.dest_offset =
2090 checked_offset_add(state.dest_offset, cursor.dest_steps[axis], coord)?;
2091 }
2092 Ok(state)
2093 }
2094
2095 #[inline]
2096 fn advance(&mut self, cursor: &PadCopyCursor) {
2097 for axis in 0..cursor.shape.len() {
2101 let next = self.coords.as_mut_slice()[axis] + 1;
2102 if next < cursor.shape[axis] {
2103 self.coords.as_mut_slice()[axis] = next;
2104 self.source_offset += cursor.source_steps[axis];
2105 self.dest_offset += cursor.dest_steps[axis];
2106 return;
2107 }
2108 self.coords.as_mut_slice()[axis] = 0;
2109 self.source_offset += cursor.source_resets[axis];
2110 self.dest_offset += cursor.dest_resets[axis];
2111 }
2112 }
2113}
2114
2115struct CoordScratch {
2116 inline: [usize; crate::RAW_FUSED_RANK_LIMIT],
2117 heap: Option<Vec<usize>>,
2118 len: usize,
2119}
2120
2121impl CoordScratch {
2122 fn new(len: usize) -> Self {
2123 if len <= crate::RAW_FUSED_RANK_LIMIT {
2124 Self {
2125 inline: [0; crate::RAW_FUSED_RANK_LIMIT],
2126 heap: None,
2127 len,
2128 }
2129 } else {
2130 Self {
2131 inline: [0; crate::RAW_FUSED_RANK_LIMIT],
2132 heap: Some(vec![0; len]),
2133 len,
2134 }
2135 }
2136 }
2137
2138 fn as_mut_slice(&mut self) -> &mut [usize] {
2139 match &mut self.heap {
2140 Some(heap) => heap,
2141 None => &mut self.inline[..self.len],
2142 }
2143 }
2144}
2145
2146#[cfg(test)]
2147#[path = "static_indexing_plan/tests/tests.rs"]
2148mod tests;
2149
2150#[cfg(all(test, feature = "parallel"))]
2151#[path = "static_indexing_plan/tests/parallel_tests.rs"]
2152mod parallel_tests;