1use crate::erased_common::*;
2use crate::*;
3use core::mem::MaybeUninit;
4use num_complex::{Complex32, Complex64};
5use num_traits::{One, Zero};
6mod arg_reduce;
7mod line;
8mod norm;
9mod reduce_kernel;
10mod scan;
11
12pub use arg_reduce::{ArgReduceOp, ErasedArgReducePlan};
13pub use norm::{ErasedNormPlan, NormKind, NormSpec};
14pub use scan::{ErasedScanPlan, ScanOp, ScanOptions};
15
16use reduce_kernel::{
17 reduce_axes_range, reduce_full_range, AxesRange, FullTraversal, MaxKernel, MinKernel,
18 ReassociatedProductKernel, ReduceKernel, StableProductKernel, StableProductScalar, SumKernel,
19 SumSquaresKernel,
20};
21
22trait ReduceWriter<T> {
23 fn offset(&self) -> isize;
24 unsafe fn ptr(&mut self) -> *mut T;
27 fn extent(&self) -> usize;
28 unsafe fn write_at(&mut self, offset: isize, value: T) {
31 debug_assert!(offset >= 0 && (offset as usize) < self.extent());
32 unsafe { self.ptr().offset(offset).write(value) }
34 }
35}
36
37struct RawReduceWriter<'a, T> {
38 ptr: *mut T,
39 extent: usize,
40 offset: isize,
41 _marker: core::marker::PhantomData<&'a mut [MaybeUninit<T>]>,
42}
43
44impl<'a, T> ReduceWriter<T> for RawReduceWriter<'a, T> {
45 fn offset(&self) -> isize {
46 self.offset
47 }
48 unsafe fn ptr(&mut self) -> *mut T {
49 self.ptr
50 }
51 fn extent(&self) -> usize {
52 self.extent
53 }
54}
55
56#[derive(Clone, Debug)]
58pub struct ErasedCopyPlan {
59 dtype: KernelDType,
60 plan: CopyPlan,
61}
62
63#[derive(Clone, Debug)]
65pub struct ErasedConcatenatePlan {
66 dtype: KernelDType,
67 plan: ConcatenatePlan,
68}
69
70impl ErasedCopyPlan {
71 pub fn compile(
73 dtype: KernelDType,
74 dims: &[usize],
75 dst_strides: &[isize],
76 src_strides: &[isize],
77 ) -> Result<Self> {
78 Ok(Self {
79 dtype,
80 plan: CopyPlan::compile(dims, dst_strides, src_strides)?,
81 })
82 }
83
84 #[inline]
85 pub fn dtype(&self) -> KernelDType {
86 self.dtype
87 }
88
89 pub fn execute(
91 &self,
92 dest: &mut ErasedRawStridedMut<'_>,
93 src: &ErasedRawStridedRef<'_>,
94 exec: Exec<'_>,
95 ) -> Result<()> {
96 self.check_dtype(dest.dtype())?;
97 self.check_dtype(src.dtype())?;
98
99 match self.dtype {
100 KernelDType::F32 => execute_copy::<f32>(&self.plan, dest, src, exec),
101 KernelDType::F64 => execute_copy::<f64>(&self.plan, dest, src, exec),
102 KernelDType::I32 => execute_copy::<i32>(&self.plan, dest, src, exec),
103 KernelDType::I64 => execute_copy::<i64>(&self.plan, dest, src, exec),
104 KernelDType::Bool => execute_copy::<bool>(&self.plan, dest, src, exec),
105 KernelDType::C32 => execute_copy::<Complex32>(&self.plan, dest, src, exec),
106 KernelDType::C64 => execute_copy::<Complex64>(&self.plan, dest, src, exec),
107 _ => Err(StridedError::UnsupportedDType {
108 dtype: self.dtype.label(),
109 }),
110 }
111 }
112
113 fn check_dtype(&self, actual: KernelDType) -> Result<()> {
114 if actual != self.dtype {
115 return Err(StridedError::DTypeMismatch {
116 expected: self.dtype.label(),
117 actual: actual.label(),
118 });
119 }
120 Ok(())
121 }
122}
123
124impl ErasedConcatenatePlan {
125 pub fn compile(
127 dtype: KernelDType,
128 input_dims: &[&[usize]],
129 input_strides: &[&[isize]],
130 dest_dims: &[usize],
131 dest_strides: &[isize],
132 axis: usize,
133 ) -> Result<Self> {
134 check_static_indexing_dtype(dtype)?;
135 Ok(Self {
136 dtype,
137 plan: ConcatenatePlan::compile(
138 input_dims,
139 input_strides,
140 dest_dims,
141 dest_strides,
142 axis,
143 )?,
144 })
145 }
146
147 #[inline]
148 pub fn dtype(&self) -> KernelDType {
149 self.dtype
150 }
151
152 #[inline]
153 pub fn plan(&self) -> &ConcatenatePlan {
154 &self.plan
155 }
156
157 pub fn execute(
159 &self,
160 dest: &mut ErasedRawStridedMut<'_>,
161 inputs: &[ErasedRawStridedRef<'_>],
162 exec: Exec<'_>,
163 ) -> Result<()> {
164 check_dtype(self.dtype, dest.dtype())?;
165 for input in inputs {
166 check_dtype(self.dtype, input.dtype())?;
167 }
168
169 match self.dtype {
170 KernelDType::F32 => execute_concatenate::<f32>(&self.plan, dest, inputs, exec),
171 KernelDType::F64 => execute_concatenate::<f64>(&self.plan, dest, inputs, exec),
172 KernelDType::I32 => execute_concatenate::<i32>(&self.plan, dest, inputs, exec),
173 KernelDType::I64 => execute_concatenate::<i64>(&self.plan, dest, inputs, exec),
174 KernelDType::Bool => execute_concatenate::<bool>(&self.plan, dest, inputs, exec),
175 KernelDType::C32 => execute_concatenate::<Complex32>(&self.plan, dest, inputs, exec),
176 KernelDType::C64 => execute_concatenate::<Complex64>(&self.plan, dest, inputs, exec),
177 _ => Err(StridedError::UnsupportedDType {
178 dtype: self.dtype.label(),
179 }),
180 }
181 }
182
183 pub fn execute_uninit(
191 &self,
192 dest: &mut ErasedRawStridedUninitMut<'_>,
193 inputs: &[ErasedRawStridedPtr<'_>],
194 exec: Exec<'_>,
195 ) -> Result<()> {
196 check_dtype(self.dtype, dest.dtype())?;
197 if inputs.len() != self.plan.input_count() {
198 return Err(StridedError::RankMismatch(
199 inputs.len(),
200 self.plan.input_count(),
201 ));
202 }
203 for input in inputs {
204 check_dtype(self.dtype, input.dtype())?;
205 }
206 for (position, input) in inputs.iter().enumerate() {
207 validate_uninit_no_overlap(dest, input, position)?;
208 }
209 for input in inputs {
210 unsafe { input.try_as_ref_after_no_overlap() }?;
212 }
213
214 match self.dtype {
215 KernelDType::F32 => execute_concatenate_uninit::<f32>(&self.plan, dest, inputs, exec),
216 KernelDType::F64 => execute_concatenate_uninit::<f64>(&self.plan, dest, inputs, exec),
217 KernelDType::I32 => execute_concatenate_uninit::<i32>(&self.plan, dest, inputs, exec),
218 KernelDType::I64 => execute_concatenate_uninit::<i64>(&self.plan, dest, inputs, exec),
219 KernelDType::Bool => execute_concatenate_uninit::<bool>(&self.plan, dest, inputs, exec),
220 KernelDType::C32 => {
221 execute_concatenate_uninit::<Complex32>(&self.plan, dest, inputs, exec)
222 }
223 KernelDType::C64 => {
224 execute_concatenate_uninit::<Complex64>(&self.plan, dest, inputs, exec)
225 }
226 _ => Err(StridedError::UnsupportedDType {
227 dtype: self.dtype.label(),
228 }),
229 }
230 }
231}
232
233#[non_exhaustive]
235#[derive(Clone, Copy, Debug, Eq, PartialEq)]
236pub enum ReduceOp {
237 Sum,
238 Product,
239 StableProduct,
252 SumSquares,
257 Max,
266 Min,
275}
276
277#[derive(Clone, Debug)]
336pub struct ErasedReducePlan {
337 dtype: KernelDType,
338 op: ReduceOp,
339 layout: ReduceLayout,
340}
341
342#[derive(Clone, Debug)]
343enum ReduceLayout {
344 Full {
345 dims: crate::inline::InlineVec<usize>,
346 src_strides: crate::inline::InlineVec<isize>,
347 traversal: FullTraversal,
348 },
349 Axes {
350 src_dims: crate::inline::InlineVec<usize>,
351 src_strides: crate::inline::InlineVec<isize>,
352 dest_dims: crate::inline::InlineVec<usize>,
353 dest_strides: crate::inline::InlineVec<isize>,
354 outer_axes: crate::inline::InlineVec<ReduceOuterAxis>,
355 inner_axes: crate::inline::InlineVec<ReduceInnerAxis>,
356 dest_total: usize,
357 reduce_total: usize,
358 full: Option<FullTraversal>,
361 },
362}
363
364#[derive(Clone, Copy, Debug, Default)]
365struct ReduceOuterAxis {
366 extent: usize,
367 source_step: isize,
368 source_reset: isize,
369 dest_step: isize,
370 dest_reset: isize,
371}
372#[derive(Clone, Copy, Debug, Default)]
373struct ReduceInnerAxis {
374 extent: usize,
375 source_step: isize,
376 source_reset: isize,
377}
378impl ReduceLayout {
379 fn src_dims(&self) -> &[usize] {
380 match self {
381 Self::Full { dims, .. } => dims,
382 Self::Axes { src_dims, .. } => src_dims,
383 }
384 }
385
386 fn src_strides(&self) -> &[isize] {
387 match self {
388 Self::Full { src_strides, .. } | Self::Axes { src_strides, .. } => src_strides,
389 }
390 }
391
392 fn check_src_layout(&self, src: &ErasedRawStridedRef<'_>) -> Result<()> {
393 if src.dims() != self.src_dims() || src.strides() != self.src_strides() {
394 return Err(StridedError::PlanLayoutMismatch);
395 }
396 Ok(())
397 }
398}
399
400#[derive(Clone, Copy, Debug)]
401struct AxesLayout<'a> {
402 outer_axes: &'a [ReduceOuterAxis],
403 inner_axes: &'a [ReduceInnerAxis],
404 dest_total: usize,
405 reduce_total: usize,
406}
407
408impl ErasedReducePlan {
409 pub fn compile(
411 dtype: KernelDType,
412 op: ReduceOp,
413 dims: &[usize],
414 src_strides: &[isize],
415 ) -> Result<Self> {
416 check_reduce_op_dtype(dtype, op)?;
417 if dims.len() != src_strides.len() {
418 return Err(StridedError::StrideLengthMismatch);
419 }
420 checked_total_len(dims)?;
421 let traversal = FullTraversal::compile(dims, src_strides)?;
422 Ok(Self {
423 dtype,
424 op,
425 layout: ReduceLayout::Full {
426 dims: dims.into(),
427 src_strides: src_strides.into(),
428 traversal,
429 },
430 })
431 }
432
433 #[allow(clippy::too_many_arguments)]
439 pub fn compile_axes(
440 dtype: KernelDType,
441 op: ReduceOp,
442 src_dims: &[usize],
443 src_strides: &[isize],
444 dest_dims: &[usize],
445 dest_strides: &[isize],
446 axes: &[usize],
447 ) -> Result<Self> {
448 check_reduce_op_dtype(dtype, op)?;
449 if src_dims.len() != src_strides.len() || dest_dims.len() != dest_strides.len() {
450 return Err(StridedError::StrideLengthMismatch);
451 }
452 checked_total_len(src_dims)?;
453 check_reduce_layout_offset_arithmetic(src_dims, src_strides)?;
454 let dest_total = checked_total_len(dest_dims)?;
455 check_reduce_layout_offset_arithmetic(dest_dims, dest_strides)?;
456 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
457 return Err(StridedError::NonInjectiveOutputLayout);
458 }
459 validate_unique_axes(axes, src_dims.len())?;
460
461 let kept_axes: crate::inline::InlineVec<usize> = (0..src_dims.len())
462 .filter(|axis| !axes.contains(axis))
463 .collect();
464 let expected_dest_dims: crate::inline::InlineVec<usize> =
465 kept_axes.iter().map(|&axis| src_dims[axis]).collect();
466 if expected_dest_dims.is_empty() {
467 if dest_total != 1 {
468 return Err(StridedError::ShapeMismatch(
469 dest_dims.to_vec(),
470 expected_dest_dims.to_vec(),
471 ));
472 }
473 } else if dest_dims != expected_dest_dims.as_slice() {
474 return Err(StridedError::ShapeMismatch(
475 dest_dims.to_vec(),
476 expected_dest_dims.to_vec(),
477 ));
478 }
479
480 let reduce_total = axes
481 .iter()
482 .try_fold(1usize, |total, &axis| total.checked_mul(src_dims[axis]))
483 .ok_or(StridedError::OffsetOverflow)?;
484 let outer_axes = compress_reduce_outer_axes(
485 kept_axes
486 .iter()
487 .enumerate()
488 .map(|(dest_axis, &src_axis)| {
489 let extent = src_dims[src_axis];
490 Ok(ReduceOuterAxis {
491 extent,
492 source_step: src_strides[src_axis],
493 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
494 dest_step: dest_strides[dest_axis],
495 dest_reset: checked_reduce_reset(extent, dest_strides[dest_axis])?,
496 })
497 })
498 .collect::<Result<crate::inline::InlineVec<_>>>()?,
499 )?;
500 let inner_axes = compress_reduce_inner_axes(
501 axes.iter()
502 .map(|&src_axis| {
503 let extent = src_dims[src_axis];
504 Ok(ReduceInnerAxis {
505 extent,
506 source_step: src_strides[src_axis],
507 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
508 })
509 })
510 .collect::<Result<crate::inline::InlineVec<_>>>()?,
511 )?;
512 let full = if kept_axes.is_empty() {
513 Some(FullTraversal::compile(src_dims, src_strides)?)
514 } else {
515 None
516 };
517 Ok(Self {
518 dtype,
519 op,
520 layout: ReduceLayout::Axes {
521 src_dims: src_dims.into(),
522 src_strides: src_strides.into(),
523 dest_dims: dest_dims.into(),
524 dest_strides: dest_strides.into(),
525 outer_axes,
526 inner_axes,
527 dest_total,
528 reduce_total,
529 full,
530 },
531 })
532 }
533
534 #[inline]
535 pub fn dtype(&self) -> KernelDType {
536 self.dtype
537 }
538
539 #[inline]
540 pub fn op(&self) -> ReduceOp {
541 self.op
542 }
543
544 pub fn execute(
546 &self,
547 dest: &mut ErasedRawStridedMut<'_>,
548 src: &ErasedRawStridedRef<'_>,
549 exec: Exec<'_>,
550 ) -> Result<()> {
551 check_dtype(self.dtype, dest.dtype())?;
552 check_dtype(self.dtype, src.dtype())?;
553 self.layout.check_src_layout(src)?;
554 match &self.layout {
555 ReduceLayout::Full { .. } => {
556 let dest_len = checked_total_len(dest.dims())?;
557 if dest_len != 1 {
558 return Err(StridedError::RankMismatch(dest_len, 1));
559 }
560 }
561 ReduceLayout::Axes {
562 dest_dims,
563 dest_strides,
564 ..
565 } => {
566 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
567 {
568 return Err(StridedError::PlanLayoutMismatch);
569 }
570 }
571 }
572
573 let result = match self.dtype {
574 KernelDType::F32 => {
575 let mut writer = reduce_writer::<f32>(dest)?;
576 dispatch_reduce::<f32, _>(self.op, &self.layout, &mut writer, src, exec)
577 }
578 KernelDType::F64 => {
579 let mut writer = reduce_writer::<f64>(dest)?;
580 dispatch_reduce::<f64, _>(self.op, &self.layout, &mut writer, src, exec)
581 }
582 KernelDType::I32 => {
583 let mut writer = reduce_writer::<i32>(dest)?;
584 dispatch_reduce::<i32, _>(self.op, &self.layout, &mut writer, src, exec)
585 }
586 KernelDType::I64 => {
587 let mut writer = reduce_writer::<i64>(dest)?;
588 dispatch_reduce::<i64, _>(self.op, &self.layout, &mut writer, src, exec)
589 }
590 KernelDType::C32 => {
591 let mut writer = reduce_writer::<Complex32>(dest)?;
592 dispatch_reduce::<Complex32, _>(self.op, &self.layout, &mut writer, src, exec)
593 }
594 KernelDType::C64 => {
595 let mut writer = reduce_writer::<Complex64>(dest)?;
596 dispatch_reduce::<Complex64, _>(self.op, &self.layout, &mut writer, src, exec)
597 }
598 _ => Err(StridedError::UnsupportedDType {
599 dtype: self.dtype.label(),
600 }),
601 };
602 result
603 }
604
605 pub fn execute_uninit(
612 &self,
613 dest: &mut ErasedRawStridedUninitMut<'_>,
614 src: &ErasedRawStridedPtr<'_>,
615 exec: Exec<'_>,
616 ) -> Result<()> {
617 check_dtype(self.dtype, dest.dtype())?;
618 check_dtype(self.dtype, src.dtype())?;
619 validate_uninit_no_overlap(dest, src, 0)?;
620 let src = unsafe { src.try_as_ref_after_no_overlap() }?;
622 self.layout.check_src_layout(&src)?;
623 match &self.layout {
624 ReduceLayout::Full { .. } => {
625 let total = checked_total_len(dest.dims())?;
626 if total != 1 {
627 return Err(StridedError::RankMismatch(total, 1));
628 }
629 }
630 ReduceLayout::Axes {
631 dest_dims,
632 dest_strides,
633 ..
634 } => {
635 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
636 {
637 return Err(StridedError::PlanLayoutMismatch);
638 }
639 }
640 }
641 macro_rules! run {
642 ($ty:ty) => {{
643 let mut writer = reduce_uninit_writer::<$ty>(dest)?;
644 dispatch_reduce::<$ty, _>(self.op, &self.layout, &mut writer, &src, exec)
645 }};
646 }
647 match self.dtype {
648 KernelDType::F32 => run!(f32),
649 KernelDType::F64 => run!(f64),
650 KernelDType::I32 => run!(i32),
651 KernelDType::I64 => run!(i64),
652 KernelDType::C32 => run!(Complex32),
653 KernelDType::C64 => run!(Complex64),
654 _ => Err(StridedError::UnsupportedDType {
655 dtype: self.dtype.label(),
656 }),
657 }
658 }
659}
660
661fn reduce_writer<'a, T>(dest: &'a mut ErasedRawStridedMut<'_>) -> Result<RawReduceWriter<'a, T>>
662where
663 T: KernelStorageElement,
664{
665 let offset = dest.offset();
666 let data = dest.data_as_mut::<T>()?;
667 let ptr = data.as_mut_ptr();
668 let extent = data.len();
669 Ok(RawReduceWriter {
670 ptr,
671 extent,
672 offset,
673 _marker: core::marker::PhantomData,
674 })
675}
676
677fn reduce_uninit_writer<'a, T>(
678 dest: &'a mut ErasedRawStridedUninitMut<'_>,
679) -> Result<RawReduceWriter<'a, T>>
680where
681 T: KernelStorageElement,
682{
683 let offset = dest.offset();
684 let data = dest.data_as_uninit_mut::<T>()?;
685 let ptr = data.as_mut_ptr().cast::<T>();
686 let extent = data.len();
687 Ok(RawReduceWriter {
688 ptr,
689 extent,
690 offset,
691 _marker: core::marker::PhantomData,
692 })
693}
694
695fn check_reduce_dtype(dtype: KernelDType) -> Result<()> {
696 match dtype {
697 KernelDType::F32
698 | KernelDType::F64
699 | KernelDType::I32
700 | KernelDType::I64
701 | KernelDType::C32
702 | KernelDType::C64 => Ok(()),
703 _ => Err(StridedError::UnsupportedDType {
704 dtype: dtype.label(),
705 }),
706 }
707}
708
709fn check_reduce_op_dtype(dtype: KernelDType, op: ReduceOp) -> Result<()> {
710 if op == ReduceOp::SumSquares && !matches!(dtype, KernelDType::F32 | KernelDType::F64) {
711 return Err(StridedError::UnsupportedDType {
712 dtype: dtype.label(),
713 });
714 }
715 if matches!(op, ReduceOp::Max | ReduceOp::Min)
716 && !matches!(
717 dtype,
718 KernelDType::F32 | KernelDType::F64 | KernelDType::I32 | KernelDType::I64
719 )
720 {
721 return Err(StridedError::UnsupportedDType {
722 dtype: dtype.label(),
723 });
724 }
725 check_reduce_dtype(dtype)
726}
727
728fn checked_total_len(dims: &[usize]) -> Result<usize> {
729 if dims.is_empty() {
730 return Ok(1);
731 }
732 dims.iter()
733 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
734 .ok_or(StridedError::OffsetOverflow)
735}
736
737fn execute_copy<T>(
738 plan: &CopyPlan,
739 dest: &mut ErasedRawStridedMut<'_>,
740 src: &ErasedRawStridedRef<'_>,
741 exec: Exec<'_>,
742) -> Result<()>
743where
744 T: Copy + Send + Sync + KernelStorageElement,
745{
746 let source_data = src.data_as::<T>()?;
747 let dest_dims = dest.dims();
748 let dest_strides = dest.strides();
749 let dest_offset = dest.offset();
750 let dest_data = dest.data_as_mut::<T>()?;
751 let source = unsafe {
752 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
753 };
754 let mut dest =
755 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
756 plan.execute(&mut dest, &source, exec)
757}
758
759fn execute_concatenate<T>(
760 plan: &ConcatenatePlan,
761 dest: &mut ErasedRawStridedMut<'_>,
762 inputs: &[ErasedRawStridedRef<'_>],
763 mut exec: Exec<'_>,
764) -> Result<()>
765where
766 T: Copy + Send + Sync + KernelStorageElement,
767{
768 if inputs.len() != plan.input_count() {
769 return Err(StridedError::RankMismatch(inputs.len(), plan.input_count()));
770 }
771 let dest_dims = dest.dims();
772 let dest_strides = dest.strides();
773 let dest_offset = dest.offset();
774 let dest_data = dest.data_as_mut::<T>()?;
775 let mut dest_ref =
776 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
777 plan.check_dest_layout(&dest_ref)?;
778
779 for (position, input) in inputs.iter().enumerate() {
780 let input_data = input.data_as::<T>()?;
781 let input_ref = unsafe {
782 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
783 };
784 plan.check_input_layout(position, &input_ref)?;
785 plan.segment_offset(position, dest_offset)?;
786 }
787 if plan.prefers_whole_plan(&exec)
788 && !inputs.is_empty()
789 && inputs.len() <= crate::inline::OPERAND_CAP
790 {
791 let first_data = inputs[0].data_as::<T>()?;
796 let first = unsafe {
797 RawStridedRef::new_unchecked(
798 first_data,
799 inputs[0].dims(),
800 inputs[0].strides(),
801 inputs[0].offset(),
802 )
803 };
804 let mut input_refs = [first; crate::inline::OPERAND_CAP];
805 for (slot, input) in input_refs.iter_mut().zip(inputs) {
806 let input_data = input.data_as::<T>()?;
807 *slot = unsafe {
808 RawStridedRef::new_unchecked(
809 input_data,
810 input.dims(),
811 input.strides(),
812 input.offset(),
813 )
814 };
815 }
816 return plan.execute(&mut dest_ref, &input_refs[..inputs.len()], exec);
817 }
818 for (position, input) in inputs.iter().enumerate() {
819 let input_data = input.data_as::<T>()?;
820 let input_ref = unsafe {
821 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
822 };
823 plan.execute_segment(position, &mut dest_ref, &input_ref, exec.reborrow())?;
824 }
825 Ok(())
826}
827
828fn execute_concatenate_uninit<T>(
829 plan: &ConcatenatePlan,
830 dest: &mut ErasedRawStridedUninitMut<'_>,
831 inputs: &[ErasedRawStridedPtr<'_>],
832 mut exec: Exec<'_>,
833) -> Result<()>
834where
835 T: Copy + Send + Sync + KernelStorageElement,
836{
837 let dest_dims = dest.dims();
838 let dest_strides = dest.strides();
839 let dest_offset = dest.offset();
840 let dest_data = dest.data_as_uninit_mut::<T>()?;
841 let mut dest_ref =
842 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
843 plan.check_dest_layout(&dest_ref)?;
844
845 for (position, input) in inputs.iter().enumerate() {
846 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
848 let input_data = input.data_as::<T>()?;
849 let input_ref = unsafe {
850 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
851 };
852 plan.check_input_layout(position, &input_ref)?;
853 plan.segment_offset(position, dest_offset)?;
854 }
855 if plan.prefers_whole_plan(&exec)
856 && !inputs.is_empty()
857 && inputs.len() <= crate::inline::OPERAND_CAP
858 {
859 let first_erased = unsafe { inputs[0].try_as_ref_after_no_overlap() }?;
861 let mut erased_refs = [first_erased; crate::inline::OPERAND_CAP];
862 for (slot, input) in erased_refs.iter_mut().zip(inputs) {
863 *slot = unsafe { input.try_as_ref_after_no_overlap() }?;
865 }
866 let first_data = erased_refs[0].data_as::<T>()?;
867 let first = unsafe {
868 RawStridedRef::new_unchecked(
869 first_data,
870 erased_refs[0].dims(),
871 erased_refs[0].strides(),
872 erased_refs[0].offset(),
873 )
874 };
875 let mut input_refs = [first; crate::inline::OPERAND_CAP];
876 for (slot, erased) in input_refs.iter_mut().zip(&erased_refs) {
877 let input_data = erased.data_as::<T>()?;
878 *slot = unsafe {
879 RawStridedRef::new_unchecked(
880 input_data,
881 erased.dims(),
882 erased.strides(),
883 erased.offset(),
884 )
885 };
886 }
887 return plan.execute_uninit(&mut dest_ref, &input_refs[..inputs.len()], exec);
888 }
889 for (position, input) in inputs.iter().enumerate() {
890 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
892 let input_data = input.data_as::<T>()?;
893 let input_ref = unsafe {
894 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
895 };
896 plan.execute_segment_uninit(position, &mut dest_ref, &input_ref, exec.reborrow())?;
897 }
898 Ok(())
899}
900
901#[non_exhaustive]
906#[derive(Clone, Copy, Debug, Eq, PartialEq)]
907pub enum UpdateOp {
908 Identity,
910 Conj,
912}
913
914#[derive(Clone, Debug)]
920pub struct ErasedUpdatePlan {
921 dtype: KernelDType,
922 plan: UpdatePlan,
923}
924
925impl ErasedUpdatePlan {
926 pub fn compile(
928 dtype: KernelDType,
929 dims: &[usize],
930 dst_strides: &[isize],
931 src_strides: &[isize],
932 ) -> Result<Self> {
933 check_static_indexing_dtype(dtype)?;
934 Ok(Self {
935 dtype,
936 plan: UpdatePlan::compile(dims, dst_strides, src_strides)?,
937 })
938 }
939
940 #[inline]
941 pub fn dtype(&self) -> KernelDType {
942 self.dtype
943 }
944
945 #[inline]
947 pub fn plan(&self) -> &UpdatePlan {
948 &self.plan
949 }
950
951 pub fn execute<T>(
976 &self,
977 dest: &mut ErasedRawStridedMut<'_>,
978 src: &ErasedRawStridedRef<'_>,
979 alpha: T,
980 beta: T,
981 op: UpdateOp,
982 exec: Exec<'_>,
983 ) -> Result<()>
984 where
985 T: Copy
986 + Send
987 + Sync
988 + KernelStorageElement
989 + core::ops::Add<Output = T>
990 + core::ops::Mul<Output = T>
991 + Zero
992 + One
993 + PartialEq
994 + ElementOpApply,
995 {
996 check_dtype(self.dtype, dest.dtype())?;
997 check_dtype(self.dtype, src.dtype())?;
998 let source_data = src.data_as::<T>()?;
999 let dest_dims = dest.dims();
1000 let dest_strides = dest.strides();
1001 let dest_offset = dest.offset();
1002 let dest_data = dest.data_as_mut::<T>()?;
1003 let source = unsafe {
1004 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
1005 };
1006 let mut dest_ref = unsafe {
1007 RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset)
1008 };
1009 match op {
1010 UpdateOp::Identity => {
1011 self.plan
1012 .execute(&mut dest_ref, &source, alpha, beta, |x| x, exec)
1013 }
1014 UpdateOp::Conj => {
1015 self.plan
1016 .execute(&mut dest_ref, &source, alpha, beta, |x| x.conj(), exec)
1017 }
1018 }
1019 }
1020
1021 pub fn execute_uninit<T>(
1060 &self,
1061 dest: &mut ErasedRawStridedUninitMut<'_>,
1062 src: &ErasedRawStridedPtr<'_>,
1063 alpha: T,
1064 op: UpdateOp,
1065 exec: Exec<'_>,
1066 ) -> Result<()>
1067 where
1068 T: Copy
1069 + Send
1070 + Sync
1071 + KernelStorageElement
1072 + core::ops::Mul<Output = T>
1073 + One
1074 + PartialEq
1075 + ElementOpApply,
1076 {
1077 check_dtype(self.dtype, dest.dtype())?;
1078 check_dtype(self.dtype, src.dtype())?;
1079 validate_uninit_no_overlap(dest, src, 0)?;
1080 let src = unsafe { src.try_as_ref_after_no_overlap() }?;
1082 let source_data = src.data_as::<T>()?;
1083 let dest_dims = dest.dims();
1084 let dest_strides = dest.strides();
1085 let dest_offset = dest.offset();
1086 let dest_data = dest.data_as_uninit_mut::<T>()?;
1087 let source = unsafe {
1088 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
1089 };
1090 let mut dest_ref = unsafe {
1091 RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset)
1092 };
1093 match op {
1094 UpdateOp::Identity => {
1095 self.plan
1096 .execute_uninit(&mut dest_ref, &source, alpha, |x| x, exec)
1097 }
1098 UpdateOp::Conj => {
1099 self.plan
1100 .execute_uninit(&mut dest_ref, &source, alpha, |x| x.conj(), exec)
1101 }
1102 }
1103 }
1104}
1105
1106fn execute_reduce_full<T, W, K>(
1107 dest: &mut W,
1108 src: &ErasedRawStridedRef<'_>,
1109 traversal: &FullTraversal,
1110 exec: Exec<'_>,
1111) -> Result<bool>
1112where
1113 T: ErasedReduceScalar,
1114 W: ReduceWriter<T>,
1115 K: ReduceKernel<T>,
1116{
1117 let source = src.data_as::<T>()?;
1118 let total = traversal.total();
1119 if total == 0 {
1120 unsafe { dest.write_at(dest.offset(), K::finish(K::identity())) };
1122 return Ok(false);
1123 }
1124 let acc = reduce_full_split::<T, K>(source, src.offset(), traversal, exec)?;
1125
1126 if K::needs_ordered_fallback(&acc) {
1127 return Ok(true);
1130 }
1131 unsafe { dest.write_at(dest.offset(), K::finish(acc)) };
1133 Ok(false)
1134}
1135
1136fn reduce_full_split<T, K>(
1144 source: &[T],
1145 source_base: isize,
1146 traversal: &FullTraversal,
1147 exec: Exec<'_>,
1148) -> Result<K::Acc>
1149where
1150 T: ErasedReduceScalar,
1151 K: ReduceKernel<T>,
1152{
1153 let total = traversal.total();
1154 if !K::reassociable() {
1158 return unsafe {
1161 reduce_full_range::<T, K>(source.as_ptr(), source_base, traversal, 0..total)
1162 };
1163 }
1164 let source_ptr = crate::threading::SendPtr(source.as_ptr() as *mut T);
1165 crate::threading::parallel_map_reduce(
1166 0..total,
1167 crate::threading::for_len(exec, total),
1168 &|range| {
1169 unsafe {
1172 reduce_full_range::<T, K>(source_ptr.as_const(), source_base, traversal, range)
1173 }
1174 },
1175 &|left, right| Ok(K::combine(left?, right?)),
1176 )
1177}
1178
1179fn dispatch_reduce<T, W>(
1180 op: ReduceOp,
1181 layout: &ReduceLayout,
1182 dest: &mut W,
1183 src: &ErasedRawStridedRef<'_>,
1184 exec: Exec<'_>,
1185) -> Result<()>
1186where
1187 T: ErasedReduceScalar,
1188 W: ReduceWriter<T>,
1189{
1190 match op {
1193 ReduceOp::Sum => dispatch_reduce_kernel::<T, W, SumKernel>(layout, dest, src, exec),
1194 ReduceOp::Product => {
1195 dispatch_reduce_kernel::<T, W, ReassociatedProductKernel>(layout, dest, src, exec)
1196 }
1197 ReduceOp::StableProduct => {
1198 dispatch_reduce_kernel::<T, W, StableProductKernel>(layout, dest, src, exec)
1199 }
1200 ReduceOp::SumSquares => {
1201 dispatch_reduce_kernel::<T, W, SumSquaresKernel>(layout, dest, src, exec)
1202 }
1203 ReduceOp::Max => dispatch_reduce_kernel::<T, W, MaxKernel>(layout, dest, src, exec),
1204 ReduceOp::Min => dispatch_reduce_kernel::<T, W, MinKernel>(layout, dest, src, exec),
1205 }
1206}
1207
1208fn dispatch_reduce_kernel<T, W, K>(
1209 layout: &ReduceLayout,
1210 dest: &mut W,
1211 src: &ErasedRawStridedRef<'_>,
1212 mut exec: Exec<'_>,
1213) -> Result<()>
1214where
1215 T: ErasedReduceScalar,
1216 W: ReduceWriter<T>,
1217 K: ReduceKernel<T>,
1218{
1219 let fallback = match layout {
1220 ReduceLayout::Full { traversal, .. } => {
1221 execute_reduce_full::<T, W, K>(dest, src, traversal, exec.reborrow())?
1222 }
1223 ReduceLayout::Axes {
1224 outer_axes,
1225 inner_axes,
1226 dest_total,
1227 reduce_total,
1228 full,
1229 ..
1230 } => {
1231 let axes = AxesLayout {
1232 outer_axes,
1233 inner_axes,
1234 dest_total: *dest_total,
1235 reduce_total: *reduce_total,
1236 };
1237 match full {
1238 Some(traversal) => {
1239 execute_reduce_full::<T, W, K>(dest, src, traversal, exec.reborrow())?
1240 }
1241 None => {
1242 execute_reduce_axes::<T, W, K>(dest, src, axes, exec.reborrow())?;
1246 false
1247 }
1248 }
1249 }
1250 };
1251 if fallback {
1252 return dispatch_reduce_kernel::<T, W, K::Fallback>(layout, dest, src, exec);
1257 }
1258 Ok(())
1259}
1260
1261fn execute_reduce_axes<T, W, K>(
1262 dest: &mut W,
1263 src: &ErasedRawStridedRef<'_>,
1264 layout: AxesLayout<'_>,
1265 exec: Exec<'_>,
1266) -> Result<()>
1267where
1268 T: ErasedReduceScalar,
1269 W: ReduceWriter<T>,
1270 K: ReduceKernel<T>,
1271{
1272 if layout.dest_total == 0 {
1273 return Ok(());
1274 }
1275
1276 if layout.reduce_total == 0 {
1277 execute_reduce_axes_identity::<T, W, K>(dest, layout, exec)
1278 } else {
1279 execute_reduce_axes_split::<T, W, K>(dest, src, layout, exec)
1280 }
1281}
1282
1283fn execute_reduce_axes_split<T, W, K>(
1284 dest: &mut W,
1285 src: &ErasedRawStridedRef<'_>,
1286 layout: AxesLayout<'_>,
1287 exec: Exec<'_>,
1288) -> Result<()>
1289where
1290 T: ErasedReduceScalar,
1291 W: ReduceWriter<T>,
1292 K: ReduceKernel<T>,
1293{
1294 let source_data = src.data_as::<T>()?;
1295 let source_base = src.offset();
1296 let dest_base = dest.offset();
1297 let dest_ptr = unsafe { dest.ptr() };
1299 let work = layout.dest_total.saturating_mul(layout.reduce_total);
1305 let exec = crate::threading::for_len(exec, work).limit(
1306 core::num::NonZeroUsize::new(layout.dest_total).unwrap_or(core::num::NonZeroUsize::MIN),
1307 );
1308 if !exec.is_serial() {
1309 let dest_ptr = crate::threading::SendPtr(dest_ptr);
1310 let source_ptr = crate::threading::SendPtr(source_data.as_ptr() as *mut T);
1311 return crate::threading::parallel_map_reduce(
1312 0..layout.dest_total,
1313 exec,
1314 &|range| {
1315 let parts = AxesRange {
1316 source: source_ptr.as_const(),
1317 source_base,
1318 dest: dest_ptr.as_ptr(),
1319 dest_base,
1320 outer_axes: layout.outer_axes,
1321 inner_axes: layout.inner_axes,
1322 reduce_total: layout.reduce_total,
1323 };
1324 unsafe { reduce_axes_range::<T, K>(parts, range) }
1326 },
1327 &|left, right| left.and(right),
1328 );
1329 }
1330
1331 let parts = AxesRange {
1332 source: source_data.as_ptr(),
1333 source_base,
1334 dest: dest_ptr,
1335 dest_base,
1336 outer_axes: layout.outer_axes,
1337 inner_axes: layout.inner_axes,
1338 reduce_total: layout.reduce_total,
1339 };
1340 unsafe { reduce_axes_range::<T, K>(parts, 0..layout.dest_total) }
1347}
1348
1349fn execute_reduce_axes_identity_serial<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1350where
1351 T: ErasedReduceScalar,
1352 W: ReduceWriter<T>,
1353 K: ReduceKernel<T>,
1354{
1355 let mut outer = ReduceOuterCursor::decode(0, 0, dest.offset(), layout.outer_axes)?;
1356 for output in 0..layout.dest_total {
1357 unsafe { dest.write_at(outer.dest_offset, K::finish(K::identity())) };
1365 if output + 1 < layout.dest_total {
1366 outer.advance();
1367 }
1368 }
1369 Ok(())
1370}
1371fn execute_reduce_axes_identity<T, W, K>(
1372 dest: &mut W,
1373 layout: AxesLayout<'_>,
1374 exec: Exec<'_>,
1375) -> Result<()>
1376where
1377 T: ErasedReduceScalar,
1378 W: ReduceWriter<T>,
1379 K: ReduceKernel<T>,
1380{
1381 let exec = crate::threading::for_len(exec, layout.dest_total);
1382 if !exec.is_serial() {
1383 return execute_reduce_axes_identity_parallel::<T, W, K>(dest, layout, exec);
1384 }
1385 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1386}
1387
1388fn execute_reduce_axes_identity_parallel<T, W, K>(
1389 dest: &mut W,
1390 layout: AxesLayout<'_>,
1391 exec: Exec<'_>,
1392) -> Result<()>
1393where
1394 T: ErasedReduceScalar,
1395 W: ReduceWriter<T>,
1396 K: ReduceKernel<T>,
1397{
1398 let dest_ptr = crate::threading::SendPtr(unsafe { dest.ptr() });
1400 let dest_offset_base = dest.offset();
1401 crate::threading::parallel_map_reduce(
1402 0..layout.dest_total,
1403 exec,
1404 &|range| {
1405 let range_end = range.end;
1406 let mut outer =
1407 ReduceOuterCursor::decode(range.start, 0, dest_offset_base, layout.outer_axes)?;
1408 let dest_ptr = dest_ptr.as_ptr();
1409 for output in range {
1410 unsafe {
1418 dest_ptr
1419 .offset(outer.dest_offset)
1420 .write(K::finish(K::identity()))
1421 };
1422 if output + 1 < range_end {
1423 outer.advance();
1424 }
1425 }
1426 Ok(())
1427 },
1428 &|left, right| left.and(right),
1429 )
1430}
1431
1432trait ErasedReduceScalar:
1433 KernelStorageElement
1434 + Copy
1435 + One
1436 + Zero
1437 + Send
1438 + Sync
1439 + crate::simd::MaybeSimdOps
1440 + crate::simd::MaybeSimdProduct
1441 + crate::simd::MaybeSimdSumSquares
1442 + crate::simd::MaybeSimdBlockFold
1443 + StableProductScalar
1444{
1445 fn reduce_sum(lhs: Self, rhs: Self) -> Self;
1446 fn reduce_product(lhs: Self, rhs: Self) -> Self;
1447 fn max_identity() -> Self;
1449 fn min_identity() -> Self;
1451 fn reduce_max(lhs: Self, rhs: Self) -> Self;
1452 fn reduce_min(lhs: Self, rhs: Self) -> Self;
1453}
1454
1455macro_rules! impl_float_erased_reduce_scalar {
1456 ($($ty:ty),* $(,)?) => {
1457 $(
1458 impl ErasedReduceScalar for $ty {
1459 #[inline(always)]
1460 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1461 lhs + rhs
1462 }
1463
1464 #[inline(always)]
1465 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1466 lhs * rhs
1467 }
1468
1469 #[inline(always)]
1470 fn max_identity() -> Self {
1471 <$ty>::NEG_INFINITY
1472 }
1473
1474 #[inline(always)]
1475 fn min_identity() -> Self {
1476 <$ty>::INFINITY
1477 }
1478
1479 #[inline(always)]
1480 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1481 if (lhs > rhs) | lhs.is_nan() {
1489 lhs
1490 } else {
1491 rhs
1492 }
1493 }
1494
1495 #[inline(always)]
1496 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1497 if (lhs < rhs) | lhs.is_nan() {
1505 lhs
1506 } else {
1507 rhs
1508 }
1509 }
1510 }
1511 )*
1512 };
1513}
1514
1515macro_rules! impl_complex_erased_reduce_scalar {
1516 ($($ty:ty),* $(,)?) => {
1517 $(
1518 impl ErasedReduceScalar for $ty {
1519 #[inline(always)]
1520 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1521 lhs + rhs
1522 }
1523
1524 #[inline(always)]
1525 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1526 lhs * rhs
1527 }
1528
1529 fn max_identity() -> Self {
1530 unreachable!("complex max reduction is rejected at plan compile")
1532 }
1533
1534 fn min_identity() -> Self {
1535 unreachable!("complex min reduction is rejected at plan compile")
1537 }
1538
1539 fn reduce_max(_lhs: Self, _rhs: Self) -> Self {
1540 unreachable!("complex max reduction is rejected at plan compile")
1542 }
1543
1544 fn reduce_min(_lhs: Self, _rhs: Self) -> Self {
1545 unreachable!("complex min reduction is rejected at plan compile")
1547 }
1548 }
1549 )*
1550 };
1551}
1552
1553macro_rules! impl_wrapping_erased_reduce_scalar {
1554 ($($ty:ty),* $(,)?) => {
1555 $(
1556 impl ErasedReduceScalar for $ty {
1557 #[inline(always)]
1558 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1559 lhs.wrapping_add(rhs)
1560 }
1561
1562 #[inline(always)]
1563 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1564 lhs.wrapping_mul(rhs)
1565 }
1566
1567 #[inline(always)]
1568 fn max_identity() -> Self {
1569 <$ty>::MIN
1570 }
1571
1572 #[inline(always)]
1573 fn min_identity() -> Self {
1574 <$ty>::MAX
1575 }
1576
1577 #[inline(always)]
1578 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1579 lhs.max(rhs)
1580 }
1581
1582 #[inline(always)]
1583 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1584 lhs.min(rhs)
1585 }
1586 }
1587 )*
1588 };
1589}
1590
1591impl_float_erased_reduce_scalar!(f32, f64);
1592
1593impl_complex_erased_reduce_scalar!(Complex32, Complex64);
1594
1595impl_wrapping_erased_reduce_scalar!(i32, i64);
1596
1597fn validate_unique_axes(axes: &[usize], rank: usize) -> Result<()> {
1598 let mut inline = [false; strided_view::INLINE_RANK];
1600 let mut spill: Vec<bool> = Vec::new();
1601 let seen: &mut [bool] = if rank <= strided_view::INLINE_RANK {
1602 &mut inline[..rank]
1603 } else {
1604 spill.resize(rank, false);
1605 &mut spill
1606 };
1607 for &axis in axes {
1608 if axis >= rank {
1609 return Err(StridedError::InvalidAxis { axis, rank });
1610 }
1611 if seen[axis] {
1612 return Err(StridedError::InvalidAxis { axis, rank });
1613 }
1614 seen[axis] = true;
1615 }
1616 Ok(())
1617}
1618
1619struct ReduceOuterCursor<'a> {
1620 axes: &'a [ReduceOuterAxis],
1621 coords: CoordScratch,
1622 source_offset: isize,
1623 dest_offset: isize,
1624}
1625impl<'a> ReduceOuterCursor<'a> {
1626 fn decode(
1627 mut linear: usize,
1628 source_base: isize,
1629 dest_base: isize,
1630 axes: &'a [ReduceOuterAxis],
1631 ) -> Result<Self> {
1632 let mut coords = CoordScratch::new(axes.len());
1633 let mut source_offset = source_base;
1634 let mut dest_offset = dest_base;
1635 for (coord, axis) in coords.as_mut_slice().iter_mut().zip(axes) {
1636 debug_assert!(axis.extent != 0);
1640 *coord = linear % axis.extent;
1641 linear /= axis.extent;
1642 source_offset = checked_offset_add(source_offset, axis.source_step, *coord)?;
1643 dest_offset = checked_offset_add(dest_offset, axis.dest_step, *coord)?;
1644 }
1645 Ok(Self {
1646 axes,
1647 coords,
1648 source_offset,
1649 dest_offset,
1650 })
1651 }
1652
1653 #[inline]
1655 fn leading_coord(&self) -> usize {
1656 self.coords.first()
1657 }
1658
1659 #[inline]
1660 fn advance(&mut self) {
1661 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1665 let next = *coord + 1;
1666 if next < axis.extent {
1667 *coord = next;
1668 self.source_offset += axis.source_step;
1669 self.dest_offset += axis.dest_step;
1670 return;
1671 }
1672 *coord = 0;
1673 self.source_offset += axis.source_reset;
1674 self.dest_offset += axis.dest_reset;
1675 }
1676 }
1677}
1678struct ReduceInnerCursor<'a> {
1679 axes: &'a [ReduceInnerAxis],
1680 coords: CoordScratch,
1681 source_offset: isize,
1682}
1683impl<'a> ReduceInnerCursor<'a> {
1684 fn new(source_base: isize, axes: &'a [ReduceInnerAxis]) -> Self {
1685 Self {
1686 axes,
1687 coords: CoordScratch::new(axes.len()),
1688 source_offset: source_base,
1689 }
1690 }
1691
1692 #[inline]
1693 fn reset(&mut self, source_base: isize) {
1694 self.coords.as_mut_slice().fill(0);
1695 self.source_offset = source_base;
1696 }
1697
1698 #[inline]
1699 fn advance(&mut self) {
1700 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1704 let next = *coord + 1;
1705 if next < axis.extent {
1706 *coord = next;
1707 self.source_offset += axis.source_step;
1708 return;
1709 }
1710 *coord = 0;
1711 self.source_offset += axis.source_reset;
1712 }
1713 }
1714}
1715fn check_reduce_layout_offset_arithmetic(dims: &[usize], strides: &[isize]) -> Result<()> {
1716 if dims.len() != strides.len() {
1717 return Err(StridedError::StrideLengthMismatch);
1718 }
1719 let mut min_offset = 0isize;
1720 let mut max_offset = 0isize;
1721 for (&dim, &stride) in dims.iter().zip(strides) {
1722 let last =
1723 isize::try_from(dim.saturating_sub(1)).map_err(|_| StridedError::OffsetOverflow)?;
1724 let extent = stride
1725 .checked_mul(last)
1726 .ok_or(StridedError::OffsetOverflow)?;
1727 if extent < 0 {
1728 min_offset = min_offset
1729 .checked_add(extent)
1730 .ok_or(StridedError::OffsetOverflow)?;
1731 } else {
1732 max_offset = max_offset
1733 .checked_add(extent)
1734 .ok_or(StridedError::OffsetOverflow)?;
1735 }
1736 }
1737 let _ = (min_offset, max_offset);
1738 Ok(())
1739}
1740fn compress_reduce_outer_axes(
1741 axes: crate::inline::InlineVec<ReduceOuterAxis>,
1742) -> Result<crate::inline::InlineVec<ReduceOuterAxis>> {
1743 let mut compressed: crate::inline::InlineVec<ReduceOuterAxis> =
1744 crate::inline::InlineVec::with_capacity(axes.len());
1745 for axis in axes {
1746 if let Some(previous) = compressed.last_mut() {
1747 let previous_extent =
1748 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1749 let expected_source = previous
1750 .source_step
1751 .checked_mul(previous_extent)
1752 .ok_or(StridedError::OffsetOverflow)?;
1753 let expected_dest = previous
1754 .dest_step
1755 .checked_mul(previous_extent)
1756 .ok_or(StridedError::OffsetOverflow)?;
1757 if axis.source_step == expected_source && axis.dest_step == expected_dest {
1758 let fused_extent = previous
1759 .extent
1760 .checked_mul(axis.extent)
1761 .ok_or(StridedError::OffsetOverflow)?;
1762 previous.extent = fused_extent;
1763 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1764 previous.dest_reset = checked_reduce_reset(fused_extent, previous.dest_step)?;
1765 continue;
1766 }
1767 }
1768 compressed.push(axis);
1769 }
1770 Ok(compressed)
1771}
1772fn compress_reduce_inner_axes(
1773 axes: crate::inline::InlineVec<ReduceInnerAxis>,
1774) -> Result<crate::inline::InlineVec<ReduceInnerAxis>> {
1775 let mut compressed: crate::inline::InlineVec<ReduceInnerAxis> =
1776 crate::inline::InlineVec::with_capacity(axes.len());
1777 for axis in axes {
1778 if let Some(previous) = compressed.last_mut() {
1779 let previous_extent =
1780 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1781 let expected_source = previous
1782 .source_step
1783 .checked_mul(previous_extent)
1784 .ok_or(StridedError::OffsetOverflow)?;
1785 if axis.source_step == expected_source {
1786 let fused_extent = previous
1787 .extent
1788 .checked_mul(axis.extent)
1789 .ok_or(StridedError::OffsetOverflow)?;
1790 previous.extent = fused_extent;
1791 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1792 continue;
1793 }
1794 }
1795 compressed.push(axis);
1796 }
1797 Ok(compressed)
1798}
1799fn checked_reduce_reset(extent: usize, stride: isize) -> Result<isize> {
1800 if extent == 0 {
1801 return Ok(0);
1802 }
1803 let last = isize::try_from(extent - 1).map_err(|_| StridedError::OffsetOverflow)?;
1804 stride
1805 .checked_mul(last)
1806 .and_then(isize::checked_neg)
1807 .ok_or(StridedError::OffsetOverflow)
1808}
1809fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1810 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1811 let scaled = stride
1812 .checked_mul(coord)
1813 .ok_or(StridedError::OffsetOverflow)?;
1814 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1815}
1816
1817struct CoordScratch {
1818 inline: [usize; RAW_FUSED_RANK_LIMIT],
1819 heap: Option<Vec<usize>>,
1820 len: usize,
1821}
1822
1823impl CoordScratch {
1824 fn new(len: usize) -> Self {
1825 if len <= RAW_FUSED_RANK_LIMIT {
1826 Self {
1827 inline: [0; RAW_FUSED_RANK_LIMIT],
1828 heap: None,
1829 len,
1830 }
1831 } else {
1832 Self {
1833 inline: [0; RAW_FUSED_RANK_LIMIT],
1834 heap: Some(vec![0; len]),
1835 len,
1836 }
1837 }
1838 }
1839
1840 #[inline]
1841 fn first(&self) -> usize {
1842 match &self.heap {
1843 Some(heap) => heap.first().copied().unwrap_or(0),
1844 None => self.inline[0],
1845 }
1846 }
1847
1848 fn as_mut_slice(&mut self) -> &mut [usize] {
1849 match &mut self.heap {
1850 Some(heap) => heap,
1851 None => &mut self.inline[..self.len],
1852 }
1853 }
1854}