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