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 ctx: &ExecContext,
93 dest: &mut ErasedRawStridedMut<'_>,
94 src: &ErasedRawStridedRef<'_>,
95 ) -> Result<()> {
96 self.check_dtype(dest.dtype())?;
97 self.check_dtype(src.dtype())?;
98
99 let result = ctx.run(|| match self.dtype {
100 KernelDType::F32 => execute_copy::<f32>(&self.plan, dest, src),
101 KernelDType::F64 => execute_copy::<f64>(&self.plan, dest, src),
102 KernelDType::I32 => execute_copy::<i32>(&self.plan, dest, src),
103 KernelDType::I64 => execute_copy::<i64>(&self.plan, dest, src),
104 KernelDType::Bool => execute_copy::<bool>(&self.plan, dest, src),
105 KernelDType::C32 => execute_copy::<Complex32>(&self.plan, dest, src),
106 KernelDType::C64 => execute_copy::<Complex64>(&self.plan, dest, src),
107 _ => Err(StridedError::UnsupportedDType {
108 dtype: self.dtype.label(),
109 }),
110 });
111 result
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 ctx: &ExecContext,
162 dest: &mut ErasedRawStridedMut<'_>,
163 inputs: &[ErasedRawStridedRef<'_>],
164 ) -> Result<()> {
165 check_dtype(self.dtype, dest.dtype())?;
166 for input in inputs {
167 check_dtype(self.dtype, input.dtype())?;
168 }
169
170 let result = ctx.run(|| match self.dtype {
171 KernelDType::F32 => execute_concatenate::<f32>(&self.plan, dest, inputs),
172 KernelDType::F64 => execute_concatenate::<f64>(&self.plan, dest, inputs),
173 KernelDType::I32 => execute_concatenate::<i32>(&self.plan, dest, inputs),
174 KernelDType::I64 => execute_concatenate::<i64>(&self.plan, dest, inputs),
175 KernelDType::Bool => execute_concatenate::<bool>(&self.plan, dest, inputs),
176 KernelDType::C32 => execute_concatenate::<Complex32>(&self.plan, dest, inputs),
177 KernelDType::C64 => execute_concatenate::<Complex64>(&self.plan, dest, inputs),
178 _ => Err(StridedError::UnsupportedDType {
179 dtype: self.dtype.label(),
180 }),
181 });
182 result
183 }
184
185 pub fn execute_uninit(
193 &self,
194 ctx: &ExecContext,
195 dest: &mut ErasedRawStridedUninitMut<'_>,
196 inputs: &[ErasedRawStridedPtr<'_>],
197 ) -> Result<()> {
198 check_dtype(self.dtype, dest.dtype())?;
199 if inputs.len() != self.plan.input_count() {
200 return Err(StridedError::RankMismatch(
201 inputs.len(),
202 self.plan.input_count(),
203 ));
204 }
205 for input in inputs {
206 check_dtype(self.dtype, input.dtype())?;
207 }
208 for (position, input) in inputs.iter().enumerate() {
209 validate_uninit_no_overlap(dest, input, position)?;
210 }
211 for input in inputs {
212 unsafe { input.try_as_ref_after_no_overlap() }?;
214 }
215
216 ctx.run(|| match self.dtype {
217 KernelDType::F32 => execute_concatenate_uninit::<f32>(&self.plan, dest, inputs),
218 KernelDType::F64 => execute_concatenate_uninit::<f64>(&self.plan, dest, inputs),
219 KernelDType::I32 => execute_concatenate_uninit::<i32>(&self.plan, dest, inputs),
220 KernelDType::I64 => execute_concatenate_uninit::<i64>(&self.plan, dest, inputs),
221 KernelDType::Bool => execute_concatenate_uninit::<bool>(&self.plan, dest, inputs),
222 KernelDType::C32 => execute_concatenate_uninit::<Complex32>(&self.plan, dest, inputs),
223 KernelDType::C64 => execute_concatenate_uninit::<Complex64>(&self.plan, dest, inputs),
224 _ => Err(StridedError::UnsupportedDType {
225 dtype: self.dtype.label(),
226 }),
227 })
228 }
229}
230
231#[non_exhaustive]
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub enum ReduceOp {
235 Sum,
236 Product,
237 StableProduct,
250 SumSquares,
255 Max,
264 Min,
273}
274
275#[derive(Clone, Debug)]
334pub struct ErasedReducePlan {
335 dtype: KernelDType,
336 op: ReduceOp,
337 layout: ReduceLayout,
338}
339
340#[derive(Clone, Debug)]
341enum ReduceLayout {
342 Full {
343 dims: crate::inline::InlineVec<usize>,
344 src_strides: crate::inline::InlineVec<isize>,
345 traversal: FullTraversal,
346 },
347 Axes {
348 src_dims: crate::inline::InlineVec<usize>,
349 src_strides: crate::inline::InlineVec<isize>,
350 dest_dims: crate::inline::InlineVec<usize>,
351 dest_strides: crate::inline::InlineVec<isize>,
352 outer_axes: crate::inline::InlineVec<ReduceOuterAxis>,
353 inner_axes: crate::inline::InlineVec<ReduceInnerAxis>,
354 dest_total: usize,
355 reduce_total: usize,
356 full: Option<FullTraversal>,
359 },
360}
361
362#[derive(Clone, Copy, Debug, Default)]
363struct ReduceOuterAxis {
364 extent: usize,
365 source_step: isize,
366 source_reset: isize,
367 dest_step: isize,
368 dest_reset: isize,
369}
370#[derive(Clone, Copy, Debug, Default)]
371struct ReduceInnerAxis {
372 extent: usize,
373 source_step: isize,
374 source_reset: isize,
375}
376impl ReduceLayout {
377 fn src_dims(&self) -> &[usize] {
378 match self {
379 Self::Full { dims, .. } => dims,
380 Self::Axes { src_dims, .. } => src_dims,
381 }
382 }
383
384 fn src_strides(&self) -> &[isize] {
385 match self {
386 Self::Full { src_strides, .. } | Self::Axes { src_strides, .. } => src_strides,
387 }
388 }
389
390 fn check_src_layout(&self, src: &ErasedRawStridedRef<'_>) -> Result<()> {
391 if src.dims() != self.src_dims() || src.strides() != self.src_strides() {
392 return Err(StridedError::PlanLayoutMismatch);
393 }
394 Ok(())
395 }
396}
397
398#[derive(Clone, Copy, Debug)]
399struct AxesLayout<'a> {
400 outer_axes: &'a [ReduceOuterAxis],
401 inner_axes: &'a [ReduceInnerAxis],
402 dest_total: usize,
403 reduce_total: usize,
404}
405
406impl ErasedReducePlan {
407 pub fn compile(
409 dtype: KernelDType,
410 op: ReduceOp,
411 dims: &[usize],
412 src_strides: &[isize],
413 ) -> Result<Self> {
414 check_reduce_op_dtype(dtype, op)?;
415 if dims.len() != src_strides.len() {
416 return Err(StridedError::StrideLengthMismatch);
417 }
418 checked_total_len(dims)?;
419 let traversal = FullTraversal::compile(dims, src_strides)?;
420 Ok(Self {
421 dtype,
422 op,
423 layout: ReduceLayout::Full {
424 dims: dims.into(),
425 src_strides: src_strides.into(),
426 traversal,
427 },
428 })
429 }
430
431 #[allow(clippy::too_many_arguments)]
437 pub fn compile_axes(
438 dtype: KernelDType,
439 op: ReduceOp,
440 src_dims: &[usize],
441 src_strides: &[isize],
442 dest_dims: &[usize],
443 dest_strides: &[isize],
444 axes: &[usize],
445 ) -> Result<Self> {
446 check_reduce_op_dtype(dtype, op)?;
447 if src_dims.len() != src_strides.len() || dest_dims.len() != dest_strides.len() {
448 return Err(StridedError::StrideLengthMismatch);
449 }
450 checked_total_len(src_dims)?;
451 check_reduce_layout_offset_arithmetic(src_dims, src_strides)?;
452 let dest_total = checked_total_len(dest_dims)?;
453 check_reduce_layout_offset_arithmetic(dest_dims, dest_strides)?;
454 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
455 return Err(StridedError::NonInjectiveOutputLayout);
456 }
457 validate_unique_axes(axes, src_dims.len())?;
458
459 let kept_axes: crate::inline::InlineVec<usize> = (0..src_dims.len())
460 .filter(|axis| !axes.contains(axis))
461 .collect();
462 let expected_dest_dims: crate::inline::InlineVec<usize> =
463 kept_axes.iter().map(|&axis| src_dims[axis]).collect();
464 if expected_dest_dims.is_empty() {
465 if dest_total != 1 {
466 return Err(StridedError::ShapeMismatch(
467 dest_dims.to_vec(),
468 expected_dest_dims.to_vec(),
469 ));
470 }
471 } else if dest_dims != expected_dest_dims.as_slice() {
472 return Err(StridedError::ShapeMismatch(
473 dest_dims.to_vec(),
474 expected_dest_dims.to_vec(),
475 ));
476 }
477
478 let reduce_total = axes
479 .iter()
480 .try_fold(1usize, |total, &axis| total.checked_mul(src_dims[axis]))
481 .ok_or(StridedError::OffsetOverflow)?;
482 let outer_axes = compress_reduce_outer_axes(
483 kept_axes
484 .iter()
485 .enumerate()
486 .map(|(dest_axis, &src_axis)| {
487 let extent = src_dims[src_axis];
488 Ok(ReduceOuterAxis {
489 extent,
490 source_step: src_strides[src_axis],
491 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
492 dest_step: dest_strides[dest_axis],
493 dest_reset: checked_reduce_reset(extent, dest_strides[dest_axis])?,
494 })
495 })
496 .collect::<Result<crate::inline::InlineVec<_>>>()?,
497 )?;
498 let inner_axes = compress_reduce_inner_axes(
499 axes.iter()
500 .map(|&src_axis| {
501 let extent = src_dims[src_axis];
502 Ok(ReduceInnerAxis {
503 extent,
504 source_step: src_strides[src_axis],
505 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
506 })
507 })
508 .collect::<Result<crate::inline::InlineVec<_>>>()?,
509 )?;
510 let full = if kept_axes.is_empty() {
511 Some(FullTraversal::compile(src_dims, src_strides)?)
512 } else {
513 None
514 };
515 Ok(Self {
516 dtype,
517 op,
518 layout: ReduceLayout::Axes {
519 src_dims: src_dims.into(),
520 src_strides: src_strides.into(),
521 dest_dims: dest_dims.into(),
522 dest_strides: dest_strides.into(),
523 outer_axes,
524 inner_axes,
525 dest_total,
526 reduce_total,
527 full,
528 },
529 })
530 }
531
532 #[inline]
533 pub fn dtype(&self) -> KernelDType {
534 self.dtype
535 }
536
537 #[inline]
538 pub fn op(&self) -> ReduceOp {
539 self.op
540 }
541
542 pub fn execute(
544 &self,
545 ctx: &ExecContext,
546 dest: &mut ErasedRawStridedMut<'_>,
547 src: &ErasedRawStridedRef<'_>,
548 ) -> Result<()> {
549 check_dtype(self.dtype, dest.dtype())?;
550 check_dtype(self.dtype, src.dtype())?;
551 self.layout.check_src_layout(src)?;
552 match &self.layout {
553 ReduceLayout::Full { .. } => {
554 let dest_len = checked_total_len(dest.dims())?;
555 if dest_len != 1 {
556 return Err(StridedError::RankMismatch(dest_len, 1));
557 }
558 }
559 ReduceLayout::Axes {
560 dest_dims,
561 dest_strides,
562 ..
563 } => {
564 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
565 {
566 return Err(StridedError::PlanLayoutMismatch);
567 }
568 }
569 }
570
571 let result = match self.dtype {
572 KernelDType::F32 => {
573 let mut writer = reduce_writer::<f32>(dest)?;
574 dispatch_reduce::<f32, _>(self.op, &self.layout, ctx, &mut writer, src)
575 }
576 KernelDType::F64 => {
577 let mut writer = reduce_writer::<f64>(dest)?;
578 dispatch_reduce::<f64, _>(self.op, &self.layout, ctx, &mut writer, src)
579 }
580 KernelDType::I32 => {
581 let mut writer = reduce_writer::<i32>(dest)?;
582 dispatch_reduce::<i32, _>(self.op, &self.layout, ctx, &mut writer, src)
583 }
584 KernelDType::I64 => {
585 let mut writer = reduce_writer::<i64>(dest)?;
586 dispatch_reduce::<i64, _>(self.op, &self.layout, ctx, &mut writer, src)
587 }
588 KernelDType::C32 => {
589 let mut writer = reduce_writer::<Complex32>(dest)?;
590 dispatch_reduce::<Complex32, _>(self.op, &self.layout, ctx, &mut writer, src)
591 }
592 KernelDType::C64 => {
593 let mut writer = reduce_writer::<Complex64>(dest)?;
594 dispatch_reduce::<Complex64, _>(self.op, &self.layout, ctx, &mut writer, src)
595 }
596 _ => Err(StridedError::UnsupportedDType {
597 dtype: self.dtype.label(),
598 }),
599 };
600 result
601 }
602
603 pub fn execute_uninit(
610 &self,
611 ctx: &ExecContext,
612 dest: &mut ErasedRawStridedUninitMut<'_>,
613 src: &ErasedRawStridedPtr<'_>,
614 ) -> Result<()> {
615 check_dtype(self.dtype, dest.dtype())?;
616 check_dtype(self.dtype, src.dtype())?;
617 validate_uninit_no_overlap(dest, src, 0)?;
618 let src = unsafe { src.try_as_ref_after_no_overlap() }?;
620 self.layout.check_src_layout(&src)?;
621 match &self.layout {
622 ReduceLayout::Full { .. } => {
623 let total = checked_total_len(dest.dims())?;
624 if total != 1 {
625 return Err(StridedError::RankMismatch(total, 1));
626 }
627 }
628 ReduceLayout::Axes {
629 dest_dims,
630 dest_strides,
631 ..
632 } => {
633 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
634 {
635 return Err(StridedError::PlanLayoutMismatch);
636 }
637 }
638 }
639 macro_rules! run {
640 ($ty:ty) => {{
641 let mut writer = reduce_uninit_writer::<$ty>(dest)?;
642 dispatch_reduce::<$ty, _>(self.op, &self.layout, ctx, &mut writer, &src)
643 }};
644 }
645 match self.dtype {
646 KernelDType::F32 => run!(f32),
647 KernelDType::F64 => run!(f64),
648 KernelDType::I32 => run!(i32),
649 KernelDType::I64 => run!(i64),
650 KernelDType::C32 => run!(Complex32),
651 KernelDType::C64 => run!(Complex64),
652 _ => Err(StridedError::UnsupportedDType {
653 dtype: self.dtype.label(),
654 }),
655 }
656 }
657}
658
659fn reduce_writer<'a, T>(dest: &'a mut ErasedRawStridedMut<'_>) -> Result<RawReduceWriter<'a, T>>
660where
661 T: KernelStorageElement,
662{
663 let offset = dest.offset();
664 let data = dest.data_as_mut::<T>()?;
665 let ptr = data.as_mut_ptr();
666 let extent = data.len();
667 Ok(RawReduceWriter {
668 ptr,
669 extent,
670 offset,
671 _marker: core::marker::PhantomData,
672 })
673}
674
675fn reduce_uninit_writer<'a, T>(
676 dest: &'a mut ErasedRawStridedUninitMut<'_>,
677) -> Result<RawReduceWriter<'a, T>>
678where
679 T: KernelStorageElement,
680{
681 let offset = dest.offset();
682 let data = dest.data_as_uninit_mut::<T>()?;
683 let ptr = data.as_mut_ptr().cast::<T>();
684 let extent = data.len();
685 Ok(RawReduceWriter {
686 ptr,
687 extent,
688 offset,
689 _marker: core::marker::PhantomData,
690 })
691}
692
693fn check_reduce_dtype(dtype: KernelDType) -> Result<()> {
694 match dtype {
695 KernelDType::F32
696 | KernelDType::F64
697 | KernelDType::I32
698 | KernelDType::I64
699 | KernelDType::C32
700 | KernelDType::C64 => Ok(()),
701 _ => Err(StridedError::UnsupportedDType {
702 dtype: dtype.label(),
703 }),
704 }
705}
706
707fn check_reduce_op_dtype(dtype: KernelDType, op: ReduceOp) -> Result<()> {
708 if op == ReduceOp::SumSquares && !matches!(dtype, KernelDType::F32 | KernelDType::F64) {
709 return Err(StridedError::UnsupportedDType {
710 dtype: dtype.label(),
711 });
712 }
713 if matches!(op, ReduceOp::Max | ReduceOp::Min)
714 && !matches!(
715 dtype,
716 KernelDType::F32 | KernelDType::F64 | KernelDType::I32 | KernelDType::I64
717 )
718 {
719 return Err(StridedError::UnsupportedDType {
720 dtype: dtype.label(),
721 });
722 }
723 check_reduce_dtype(dtype)
724}
725
726fn checked_total_len(dims: &[usize]) -> Result<usize> {
727 if dims.is_empty() {
728 return Ok(1);
729 }
730 dims.iter()
731 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
732 .ok_or(StridedError::OffsetOverflow)
733}
734
735fn execute_copy<T>(
736 plan: &CopyPlan,
737 dest: &mut ErasedRawStridedMut<'_>,
738 src: &ErasedRawStridedRef<'_>,
739) -> Result<()>
740where
741 T: Copy + crate::MaybeSendSync + KernelStorageElement,
742{
743 let source_data = src.data_as::<T>()?;
744 let dest_dims = dest.dims();
745 let dest_strides = dest.strides();
746 let dest_offset = dest.offset();
747 let dest_data = dest.data_as_mut::<T>()?;
748 let source = unsafe {
749 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
750 };
751 let mut dest =
752 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
753 plan.execute(&mut dest, &source)
754}
755
756fn execute_concatenate<T>(
757 plan: &ConcatenatePlan,
758 dest: &mut ErasedRawStridedMut<'_>,
759 inputs: &[ErasedRawStridedRef<'_>],
760) -> Result<()>
761where
762 T: Copy + crate::MaybeSendSync + KernelStorageElement,
763{
764 if inputs.len() != plan.input_count() {
765 return Err(StridedError::RankMismatch(inputs.len(), plan.input_count()));
766 }
767 let dest_dims = dest.dims();
768 let dest_strides = dest.strides();
769 let dest_offset = dest.offset();
770 let dest_data = dest.data_as_mut::<T>()?;
771 let mut dest_ref =
772 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
773 plan.check_dest_layout(&dest_ref)?;
774
775 for (position, input) in inputs.iter().enumerate() {
776 let input_data = input.data_as::<T>()?;
777 let input_ref = unsafe {
778 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
779 };
780 plan.check_input_layout(position, &input_ref)?;
781 plan.segment_offset(position, dest_offset)?;
782 }
783 if plan.prefers_whole_plan() && !inputs.is_empty() && inputs.len() <= crate::inline::OPERAND_CAP
784 {
785 let first_data = inputs[0].data_as::<T>()?;
790 let first = unsafe {
791 RawStridedRef::new_unchecked(
792 first_data,
793 inputs[0].dims(),
794 inputs[0].strides(),
795 inputs[0].offset(),
796 )
797 };
798 let mut input_refs = [first; crate::inline::OPERAND_CAP];
799 for (slot, input) in input_refs.iter_mut().zip(inputs) {
800 let input_data = input.data_as::<T>()?;
801 *slot = unsafe {
802 RawStridedRef::new_unchecked(
803 input_data,
804 input.dims(),
805 input.strides(),
806 input.offset(),
807 )
808 };
809 }
810 return plan.execute(&mut dest_ref, &input_refs[..inputs.len()]);
811 }
812 for (position, input) in inputs.iter().enumerate() {
813 let input_data = input.data_as::<T>()?;
814 let input_ref = unsafe {
815 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
816 };
817 plan.execute_segment(position, &mut dest_ref, &input_ref)?;
818 }
819 Ok(())
820}
821
822fn execute_concatenate_uninit<T>(
823 plan: &ConcatenatePlan,
824 dest: &mut ErasedRawStridedUninitMut<'_>,
825 inputs: &[ErasedRawStridedPtr<'_>],
826) -> Result<()>
827where
828 T: Copy + crate::MaybeSendSync + KernelStorageElement,
829{
830 let dest_dims = dest.dims();
831 let dest_strides = dest.strides();
832 let dest_offset = dest.offset();
833 let dest_data = dest.data_as_uninit_mut::<T>()?;
834 let mut dest_ref =
835 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
836 plan.check_dest_layout(&dest_ref)?;
837
838 for (position, input) in inputs.iter().enumerate() {
839 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
841 let input_data = input.data_as::<T>()?;
842 let input_ref = unsafe {
843 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
844 };
845 plan.check_input_layout(position, &input_ref)?;
846 plan.segment_offset(position, dest_offset)?;
847 }
848 if plan.prefers_whole_plan() && !inputs.is_empty() && inputs.len() <= crate::inline::OPERAND_CAP
849 {
850 let first_erased = unsafe { inputs[0].try_as_ref_after_no_overlap() }?;
852 let mut erased_refs = [first_erased; crate::inline::OPERAND_CAP];
853 for (slot, input) in erased_refs.iter_mut().zip(inputs) {
854 *slot = unsafe { input.try_as_ref_after_no_overlap() }?;
856 }
857 let first_data = erased_refs[0].data_as::<T>()?;
858 let first = unsafe {
859 RawStridedRef::new_unchecked(
860 first_data,
861 erased_refs[0].dims(),
862 erased_refs[0].strides(),
863 erased_refs[0].offset(),
864 )
865 };
866 let mut input_refs = [first; crate::inline::OPERAND_CAP];
867 for (slot, erased) in input_refs.iter_mut().zip(&erased_refs) {
868 let input_data = erased.data_as::<T>()?;
869 *slot = unsafe {
870 RawStridedRef::new_unchecked(
871 input_data,
872 erased.dims(),
873 erased.strides(),
874 erased.offset(),
875 )
876 };
877 }
878 return plan.execute_uninit(&mut dest_ref, &input_refs[..inputs.len()]);
879 }
880 for (position, input) in inputs.iter().enumerate() {
881 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
883 let input_data = input.data_as::<T>()?;
884 let input_ref = unsafe {
885 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
886 };
887 plan.execute_segment_uninit(position, &mut dest_ref, &input_ref)?;
888 }
889 Ok(())
890}
891
892#[non_exhaustive]
897#[derive(Clone, Copy, Debug, Eq, PartialEq)]
898pub enum UpdateOp {
899 Identity,
901 Conj,
903}
904
905#[derive(Clone, Debug)]
911pub struct ErasedUpdatePlan {
912 dtype: KernelDType,
913 plan: UpdatePlan,
914}
915
916impl ErasedUpdatePlan {
917 pub fn compile(
919 dtype: KernelDType,
920 dims: &[usize],
921 dst_strides: &[isize],
922 src_strides: &[isize],
923 ) -> Result<Self> {
924 check_static_indexing_dtype(dtype)?;
925 Ok(Self {
926 dtype,
927 plan: UpdatePlan::compile(dims, dst_strides, src_strides)?,
928 })
929 }
930
931 #[inline]
932 pub fn dtype(&self) -> KernelDType {
933 self.dtype
934 }
935
936 #[inline]
938 pub fn plan(&self) -> &UpdatePlan {
939 &self.plan
940 }
941
942 pub fn execute<T>(
967 &self,
968 ctx: &ExecContext,
969 dest: &mut ErasedRawStridedMut<'_>,
970 src: &ErasedRawStridedRef<'_>,
971 alpha: T,
972 beta: T,
973 op: UpdateOp,
974 ) -> Result<()>
975 where
976 T: Copy
977 + crate::MaybeSendSync
978 + KernelStorageElement
979 + core::ops::Add<Output = T>
980 + core::ops::Mul<Output = T>
981 + Zero
982 + One
983 + PartialEq
984 + ElementOpApply,
985 {
986 check_dtype(self.dtype, dest.dtype())?;
987 check_dtype(self.dtype, src.dtype())?;
988 let source_data = src.data_as::<T>()?;
989 let dest_dims = dest.dims();
990 let dest_strides = dest.strides();
991 let dest_offset = dest.offset();
992 ctx.run(|| {
993 let dest_data = dest.data_as_mut::<T>()?;
994 let source = unsafe {
995 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
996 };
997 let mut dest_ref = unsafe {
998 RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset)
999 };
1000 match op {
1001 UpdateOp::Identity => self
1002 .plan
1003 .execute(&mut dest_ref, &source, alpha, beta, |x| x),
1004 UpdateOp::Conj => self
1005 .plan
1006 .execute(&mut dest_ref, &source, alpha, beta, |x| x.conj()),
1007 }
1008 })
1009 }
1010
1011 pub fn execute_uninit<T>(
1050 &self,
1051 ctx: &ExecContext,
1052 dest: &mut ErasedRawStridedUninitMut<'_>,
1053 src: &ErasedRawStridedPtr<'_>,
1054 alpha: T,
1055 op: UpdateOp,
1056 ) -> Result<()>
1057 where
1058 T: Copy
1059 + crate::MaybeSendSync
1060 + KernelStorageElement
1061 + core::ops::Mul<Output = T>
1062 + One
1063 + PartialEq
1064 + ElementOpApply,
1065 {
1066 check_dtype(self.dtype, dest.dtype())?;
1067 check_dtype(self.dtype, src.dtype())?;
1068 validate_uninit_no_overlap(dest, src, 0)?;
1069 let src = unsafe { src.try_as_ref_after_no_overlap() }?;
1071 let source_data = src.data_as::<T>()?;
1072 let dest_dims = dest.dims();
1073 let dest_strides = dest.strides();
1074 let dest_offset = dest.offset();
1075 ctx.run(|| {
1076 let dest_data = dest.data_as_uninit_mut::<T>()?;
1077 let source = unsafe {
1078 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
1079 };
1080 let mut dest_ref = unsafe {
1081 RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset)
1082 };
1083 match op {
1084 UpdateOp::Identity => {
1085 self.plan
1086 .execute_uninit(&mut dest_ref, &source, alpha, |x| x)
1087 }
1088 UpdateOp::Conj => self
1089 .plan
1090 .execute_uninit(&mut dest_ref, &source, alpha, |x| x.conj()),
1091 }
1092 })
1093 }
1094}
1095
1096fn reduce_context_is_serial(ctx: &ExecContext) -> bool {
1099 ctx.is_serial()
1100 || ctx
1101 .max_threads_limit()
1102 .is_some_and(|max_threads| max_threads.get() == 1)
1103}
1104
1105fn execute_reduce_full<T, W, K>(
1106 ctx: &ExecContext,
1107 dest: &mut W,
1108 src: &ErasedRawStridedRef<'_>,
1109 traversal: &FullTraversal,
1110) -> Result<bool>
1111where
1112 T: ErasedReduceScalar,
1113 W: ReduceWriter<T>,
1114 K: ReduceKernel<T>,
1115{
1116 let source = src.data_as::<T>()?;
1117 let total = traversal.total();
1118 if total == 0 {
1119 unsafe { dest.write_at(dest.offset(), K::finish(K::identity())) };
1121 return Ok(false);
1122 }
1123 let acc = if reduce_context_is_serial(ctx) {
1124 unsafe { reduce_full_range::<T, K>(source.as_ptr(), src.offset(), traversal, 0..total)? }
1130 } else {
1131 ctx.run(|| reduce_full_policy::<T, K>(source, src.offset(), traversal))?
1132 };
1133
1134 if K::needs_ordered_fallback(&acc) {
1135 return Ok(true);
1138 }
1139 unsafe { dest.write_at(dest.offset(), K::finish(acc)) };
1141 Ok(false)
1142}
1143
1144fn reduce_full_policy<T, K>(
1145 source: &[T],
1146 source_base: isize,
1147 traversal: &FullTraversal,
1148) -> Result<K::Acc>
1149where
1150 T: ErasedReduceScalar,
1151 K: ReduceKernel<T>,
1152{
1153 let total = traversal.total();
1154 #[cfg(feature = "parallel")]
1155 {
1156 let nthreads = if K::reassociable() {
1162 crate::threading::parallel_threads_for_len(total)
1163 } else {
1164 1
1165 };
1166 if nthreads > 1 {
1167 let source_ptr = crate::threading::SendPtr(source.as_ptr() as *mut T);
1168 return crate::threading::parallel_map_reduce(
1169 0..total,
1170 nthreads,
1171 &|range| {
1172 unsafe {
1175 reduce_full_range::<T, K>(
1176 source_ptr.as_const(),
1177 source_base,
1178 traversal,
1179 range,
1180 )
1181 }
1182 },
1183 &|left, right| Ok(K::combine(left?, right?)),
1184 );
1185 }
1186 }
1187 unsafe { reduce_full_range::<T, K>(source.as_ptr(), source_base, traversal, 0..total) }
1190}
1191
1192fn dispatch_reduce<T, W>(
1193 op: ReduceOp,
1194 layout: &ReduceLayout,
1195 ctx: &ExecContext,
1196 dest: &mut W,
1197 src: &ErasedRawStridedRef<'_>,
1198) -> Result<()>
1199where
1200 T: ErasedReduceScalar,
1201 W: ReduceWriter<T>,
1202{
1203 match op {
1206 ReduceOp::Sum => dispatch_reduce_kernel::<T, W, SumKernel>(layout, ctx, dest, src),
1207 ReduceOp::Product => {
1208 dispatch_reduce_kernel::<T, W, ReassociatedProductKernel>(layout, ctx, dest, src)
1209 }
1210 ReduceOp::StableProduct => {
1211 dispatch_reduce_kernel::<T, W, StableProductKernel>(layout, ctx, dest, src)
1212 }
1213 ReduceOp::SumSquares => {
1214 dispatch_reduce_kernel::<T, W, SumSquaresKernel>(layout, ctx, dest, src)
1215 }
1216 ReduceOp::Max => dispatch_reduce_kernel::<T, W, MaxKernel>(layout, ctx, dest, src),
1217 ReduceOp::Min => dispatch_reduce_kernel::<T, W, MinKernel>(layout, ctx, dest, src),
1218 }
1219}
1220
1221fn dispatch_reduce_kernel<T, W, K>(
1222 layout: &ReduceLayout,
1223 ctx: &ExecContext,
1224 dest: &mut W,
1225 src: &ErasedRawStridedRef<'_>,
1226) -> Result<()>
1227where
1228 T: ErasedReduceScalar,
1229 W: ReduceWriter<T>,
1230 K: ReduceKernel<T>,
1231{
1232 let fallback = match layout {
1233 ReduceLayout::Full { traversal, .. } => {
1234 execute_reduce_full::<T, W, K>(ctx, dest, src, traversal)?
1235 }
1236 ReduceLayout::Axes {
1237 outer_axes,
1238 inner_axes,
1239 dest_total,
1240 reduce_total,
1241 full,
1242 ..
1243 } => {
1244 let axes = AxesLayout {
1245 outer_axes,
1246 inner_axes,
1247 dest_total: *dest_total,
1248 reduce_total: *reduce_total,
1249 };
1250 match full {
1251 Some(traversal) => execute_reduce_full::<T, W, K>(ctx, dest, src, traversal)?,
1252 None => {
1253 execute_reduce_axes::<T, W, K>(ctx, dest, src, axes)?;
1257 false
1258 }
1259 }
1260 }
1261 };
1262 if fallback {
1263 return dispatch_reduce_kernel::<T, W, K::Fallback>(layout, ctx, dest, src);
1268 }
1269 Ok(())
1270}
1271
1272fn execute_reduce_axes<T, W, K>(
1273 ctx: &ExecContext,
1274 dest: &mut W,
1275 src: &ErasedRawStridedRef<'_>,
1276 layout: AxesLayout<'_>,
1277) -> Result<()>
1278where
1279 T: ErasedReduceScalar,
1280 W: ReduceWriter<T>,
1281 K: ReduceKernel<T>,
1282{
1283 if layout.dest_total == 0 {
1284 return Ok(());
1285 }
1286
1287 if layout.reduce_total == 0 {
1288 if ctx.is_serial() {
1289 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1290 } else {
1291 ctx.run(|| execute_reduce_axes_identity_policy::<T, W, K>(dest, layout))
1292 }
1293 } else if reduce_context_is_serial(ctx) {
1294 execute_reduce_axes_policy::<T, W, K>(dest, src, layout, false)
1295 } else {
1296 ctx.run(|| execute_reduce_axes_policy::<T, W, K>(dest, src, layout, true))
1297 }
1298}
1299
1300fn execute_reduce_axes_policy<T, W, K>(
1301 dest: &mut W,
1302 src: &ErasedRawStridedRef<'_>,
1303 layout: AxesLayout<'_>,
1304 allow_parallel: bool,
1305) -> Result<()>
1306where
1307 T: ErasedReduceScalar,
1308 W: ReduceWriter<T>,
1309 K: ReduceKernel<T>,
1310{
1311 let source_data = src.data_as::<T>()?;
1312 let source_base = src.offset();
1313 let dest_base = dest.offset();
1314 let dest_ptr = unsafe { dest.ptr() };
1316 #[cfg(feature = "parallel")]
1317 if allow_parallel {
1318 let work = layout.dest_total.saturating_mul(layout.reduce_total);
1324 let nthreads = crate::threading::parallel_threads_for_len(work).min(layout.dest_total);
1325 if nthreads > 1 {
1326 let dest_ptr = crate::threading::SendPtr(dest_ptr);
1327 let source_ptr = crate::threading::SendPtr(source_data.as_ptr() as *mut T);
1328 return crate::threading::parallel_map_reduce(
1329 0..layout.dest_total,
1330 nthreads,
1331 &|range| {
1332 let parts = AxesRange {
1333 source: source_ptr.as_const(),
1334 source_base,
1335 dest: dest_ptr.as_ptr(),
1336 dest_base,
1337 outer_axes: layout.outer_axes,
1338 inner_axes: layout.inner_axes,
1339 reduce_total: layout.reduce_total,
1340 };
1341 unsafe { reduce_axes_range::<T, K>(parts, range) }
1343 },
1344 &|left, right| left.and(right),
1345 );
1346 }
1347 }
1348 #[cfg(not(feature = "parallel"))]
1349 let _ = allow_parallel;
1350
1351 let parts = AxesRange {
1352 source: source_data.as_ptr(),
1353 source_base,
1354 dest: dest_ptr,
1355 dest_base,
1356 outer_axes: layout.outer_axes,
1357 inner_axes: layout.inner_axes,
1358 reduce_total: layout.reduce_total,
1359 };
1360 unsafe { reduce_axes_range::<T, K>(parts, 0..layout.dest_total) }
1367}
1368
1369fn execute_reduce_axes_identity_serial<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1370where
1371 T: ErasedReduceScalar,
1372 W: ReduceWriter<T>,
1373 K: ReduceKernel<T>,
1374{
1375 let mut outer = ReduceOuterCursor::decode(0, 0, dest.offset(), layout.outer_axes)?;
1376 for output in 0..layout.dest_total {
1377 unsafe { dest.write_at(outer.dest_offset, K::finish(K::identity())) };
1385 if output + 1 < layout.dest_total {
1386 outer.advance();
1387 }
1388 }
1389 Ok(())
1390}
1391fn execute_reduce_axes_identity_policy<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1392where
1393 T: ErasedReduceScalar,
1394 W: ReduceWriter<T>,
1395 K: ReduceKernel<T>,
1396{
1397 #[cfg(feature = "parallel")]
1398 {
1399 let nthreads = crate::threading::parallel_threads_for_len(layout.dest_total);
1400 if nthreads > 1 {
1401 return execute_reduce_axes_identity_parallel::<T, W, K>(dest, layout, nthreads);
1402 }
1403 }
1404 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1405}
1406#[cfg(feature = "parallel")]
1407fn execute_reduce_axes_identity_parallel<T, W, K>(
1408 dest: &mut W,
1409 layout: AxesLayout<'_>,
1410 nthreads: usize,
1411) -> Result<()>
1412where
1413 T: ErasedReduceScalar,
1414 W: ReduceWriter<T>,
1415 K: ReduceKernel<T>,
1416{
1417 let dest_ptr = crate::threading::SendPtr(unsafe { dest.ptr() });
1419 let dest_offset_base = dest.offset();
1420 crate::threading::parallel_map_reduce(
1421 0..layout.dest_total,
1422 nthreads,
1423 &|range| {
1424 let range_end = range.end;
1425 let mut outer =
1426 ReduceOuterCursor::decode(range.start, 0, dest_offset_base, layout.outer_axes)?;
1427 let dest_ptr = dest_ptr.as_ptr();
1428 for output in range {
1429 unsafe {
1437 dest_ptr
1438 .offset(outer.dest_offset)
1439 .write(K::finish(K::identity()))
1440 };
1441 if output + 1 < range_end {
1442 outer.advance();
1443 }
1444 }
1445 Ok(())
1446 },
1447 &|left, right| left.and(right),
1448 )
1449}
1450
1451trait ErasedReduceScalar:
1452 KernelStorageElement
1453 + Copy
1454 + One
1455 + Zero
1456 + crate::MaybeSendSync
1457 + crate::simd::MaybeSimdOps
1458 + crate::simd::MaybeSimdProduct
1459 + crate::simd::MaybeSimdSumSquares
1460 + crate::simd::MaybeSimdBlockFold
1461 + StableProductScalar
1462{
1463 fn reduce_sum(lhs: Self, rhs: Self) -> Self;
1464 fn reduce_product(lhs: Self, rhs: Self) -> Self;
1465 fn max_identity() -> Self;
1467 fn min_identity() -> Self;
1469 fn reduce_max(lhs: Self, rhs: Self) -> Self;
1470 fn reduce_min(lhs: Self, rhs: Self) -> Self;
1471}
1472
1473macro_rules! impl_float_erased_reduce_scalar {
1474 ($($ty:ty),* $(,)?) => {
1475 $(
1476 impl ErasedReduceScalar for $ty {
1477 #[inline(always)]
1478 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1479 lhs + rhs
1480 }
1481
1482 #[inline(always)]
1483 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1484 lhs * rhs
1485 }
1486
1487 #[inline(always)]
1488 fn max_identity() -> Self {
1489 <$ty>::NEG_INFINITY
1490 }
1491
1492 #[inline(always)]
1493 fn min_identity() -> Self {
1494 <$ty>::INFINITY
1495 }
1496
1497 #[inline(always)]
1498 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1499 if (lhs > rhs) | lhs.is_nan() {
1507 lhs
1508 } else {
1509 rhs
1510 }
1511 }
1512
1513 #[inline(always)]
1514 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1515 if (lhs < rhs) | lhs.is_nan() {
1523 lhs
1524 } else {
1525 rhs
1526 }
1527 }
1528 }
1529 )*
1530 };
1531}
1532
1533macro_rules! impl_complex_erased_reduce_scalar {
1534 ($($ty:ty),* $(,)?) => {
1535 $(
1536 impl ErasedReduceScalar for $ty {
1537 #[inline(always)]
1538 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1539 lhs + rhs
1540 }
1541
1542 #[inline(always)]
1543 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1544 lhs * rhs
1545 }
1546
1547 fn max_identity() -> Self {
1548 unreachable!("complex max reduction is rejected at plan compile")
1550 }
1551
1552 fn min_identity() -> Self {
1553 unreachable!("complex min reduction is rejected at plan compile")
1555 }
1556
1557 fn reduce_max(_lhs: Self, _rhs: Self) -> Self {
1558 unreachable!("complex max reduction is rejected at plan compile")
1560 }
1561
1562 fn reduce_min(_lhs: Self, _rhs: Self) -> Self {
1563 unreachable!("complex min reduction is rejected at plan compile")
1565 }
1566 }
1567 )*
1568 };
1569}
1570
1571macro_rules! impl_wrapping_erased_reduce_scalar {
1572 ($($ty:ty),* $(,)?) => {
1573 $(
1574 impl ErasedReduceScalar for $ty {
1575 #[inline(always)]
1576 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1577 lhs.wrapping_add(rhs)
1578 }
1579
1580 #[inline(always)]
1581 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1582 lhs.wrapping_mul(rhs)
1583 }
1584
1585 #[inline(always)]
1586 fn max_identity() -> Self {
1587 <$ty>::MIN
1588 }
1589
1590 #[inline(always)]
1591 fn min_identity() -> Self {
1592 <$ty>::MAX
1593 }
1594
1595 #[inline(always)]
1596 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1597 lhs.max(rhs)
1598 }
1599
1600 #[inline(always)]
1601 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1602 lhs.min(rhs)
1603 }
1604 }
1605 )*
1606 };
1607}
1608
1609impl_float_erased_reduce_scalar!(f32, f64);
1610
1611impl_complex_erased_reduce_scalar!(Complex32, Complex64);
1612
1613impl_wrapping_erased_reduce_scalar!(i32, i64);
1614
1615fn validate_unique_axes(axes: &[usize], rank: usize) -> Result<()> {
1616 let mut inline = [false; strided_view::INLINE_RANK];
1618 let mut spill: Vec<bool> = Vec::new();
1619 let seen: &mut [bool] = if rank <= strided_view::INLINE_RANK {
1620 &mut inline[..rank]
1621 } else {
1622 spill.resize(rank, false);
1623 &mut spill
1624 };
1625 for &axis in axes {
1626 if axis >= rank {
1627 return Err(StridedError::InvalidAxis { axis, rank });
1628 }
1629 if seen[axis] {
1630 return Err(StridedError::InvalidAxis { axis, rank });
1631 }
1632 seen[axis] = true;
1633 }
1634 Ok(())
1635}
1636
1637struct ReduceOuterCursor<'a> {
1638 axes: &'a [ReduceOuterAxis],
1639 coords: CoordScratch,
1640 source_offset: isize,
1641 dest_offset: isize,
1642}
1643impl<'a> ReduceOuterCursor<'a> {
1644 fn decode(
1645 mut linear: usize,
1646 source_base: isize,
1647 dest_base: isize,
1648 axes: &'a [ReduceOuterAxis],
1649 ) -> Result<Self> {
1650 let mut coords = CoordScratch::new(axes.len());
1651 let mut source_offset = source_base;
1652 let mut dest_offset = dest_base;
1653 for (coord, axis) in coords.as_mut_slice().iter_mut().zip(axes) {
1654 debug_assert!(axis.extent != 0);
1658 *coord = linear % axis.extent;
1659 linear /= axis.extent;
1660 source_offset = checked_offset_add(source_offset, axis.source_step, *coord)?;
1661 dest_offset = checked_offset_add(dest_offset, axis.dest_step, *coord)?;
1662 }
1663 Ok(Self {
1664 axes,
1665 coords,
1666 source_offset,
1667 dest_offset,
1668 })
1669 }
1670
1671 #[inline]
1673 fn leading_coord(&self) -> usize {
1674 self.coords.first()
1675 }
1676
1677 #[inline]
1678 fn advance(&mut self) {
1679 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1683 let next = *coord + 1;
1684 if next < axis.extent {
1685 *coord = next;
1686 self.source_offset += axis.source_step;
1687 self.dest_offset += axis.dest_step;
1688 return;
1689 }
1690 *coord = 0;
1691 self.source_offset += axis.source_reset;
1692 self.dest_offset += axis.dest_reset;
1693 }
1694 }
1695}
1696struct ReduceInnerCursor<'a> {
1697 axes: &'a [ReduceInnerAxis],
1698 coords: CoordScratch,
1699 source_offset: isize,
1700}
1701impl<'a> ReduceInnerCursor<'a> {
1702 fn new(source_base: isize, axes: &'a [ReduceInnerAxis]) -> Self {
1703 Self {
1704 axes,
1705 coords: CoordScratch::new(axes.len()),
1706 source_offset: source_base,
1707 }
1708 }
1709
1710 #[inline]
1711 fn reset(&mut self, source_base: isize) {
1712 self.coords.as_mut_slice().fill(0);
1713 self.source_offset = source_base;
1714 }
1715
1716 #[inline]
1717 fn advance(&mut self) {
1718 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1722 let next = *coord + 1;
1723 if next < axis.extent {
1724 *coord = next;
1725 self.source_offset += axis.source_step;
1726 return;
1727 }
1728 *coord = 0;
1729 self.source_offset += axis.source_reset;
1730 }
1731 }
1732}
1733fn check_reduce_layout_offset_arithmetic(dims: &[usize], strides: &[isize]) -> Result<()> {
1734 if dims.len() != strides.len() {
1735 return Err(StridedError::StrideLengthMismatch);
1736 }
1737 let mut min_offset = 0isize;
1738 let mut max_offset = 0isize;
1739 for (&dim, &stride) in dims.iter().zip(strides) {
1740 let last =
1741 isize::try_from(dim.saturating_sub(1)).map_err(|_| StridedError::OffsetOverflow)?;
1742 let extent = stride
1743 .checked_mul(last)
1744 .ok_or(StridedError::OffsetOverflow)?;
1745 if extent < 0 {
1746 min_offset = min_offset
1747 .checked_add(extent)
1748 .ok_or(StridedError::OffsetOverflow)?;
1749 } else {
1750 max_offset = max_offset
1751 .checked_add(extent)
1752 .ok_or(StridedError::OffsetOverflow)?;
1753 }
1754 }
1755 let _ = (min_offset, max_offset);
1756 Ok(())
1757}
1758fn compress_reduce_outer_axes(
1759 axes: crate::inline::InlineVec<ReduceOuterAxis>,
1760) -> Result<crate::inline::InlineVec<ReduceOuterAxis>> {
1761 let mut compressed: crate::inline::InlineVec<ReduceOuterAxis> =
1762 crate::inline::InlineVec::with_capacity(axes.len());
1763 for axis in axes {
1764 if let Some(previous) = compressed.last_mut() {
1765 let previous_extent =
1766 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1767 let expected_source = previous
1768 .source_step
1769 .checked_mul(previous_extent)
1770 .ok_or(StridedError::OffsetOverflow)?;
1771 let expected_dest = previous
1772 .dest_step
1773 .checked_mul(previous_extent)
1774 .ok_or(StridedError::OffsetOverflow)?;
1775 if axis.source_step == expected_source && axis.dest_step == expected_dest {
1776 let fused_extent = previous
1777 .extent
1778 .checked_mul(axis.extent)
1779 .ok_or(StridedError::OffsetOverflow)?;
1780 previous.extent = fused_extent;
1781 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1782 previous.dest_reset = checked_reduce_reset(fused_extent, previous.dest_step)?;
1783 continue;
1784 }
1785 }
1786 compressed.push(axis);
1787 }
1788 Ok(compressed)
1789}
1790fn compress_reduce_inner_axes(
1791 axes: crate::inline::InlineVec<ReduceInnerAxis>,
1792) -> Result<crate::inline::InlineVec<ReduceInnerAxis>> {
1793 let mut compressed: crate::inline::InlineVec<ReduceInnerAxis> =
1794 crate::inline::InlineVec::with_capacity(axes.len());
1795 for axis in axes {
1796 if let Some(previous) = compressed.last_mut() {
1797 let previous_extent =
1798 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1799 let expected_source = previous
1800 .source_step
1801 .checked_mul(previous_extent)
1802 .ok_or(StridedError::OffsetOverflow)?;
1803 if axis.source_step == expected_source {
1804 let fused_extent = previous
1805 .extent
1806 .checked_mul(axis.extent)
1807 .ok_or(StridedError::OffsetOverflow)?;
1808 previous.extent = fused_extent;
1809 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1810 continue;
1811 }
1812 }
1813 compressed.push(axis);
1814 }
1815 Ok(compressed)
1816}
1817fn checked_reduce_reset(extent: usize, stride: isize) -> Result<isize> {
1818 if extent == 0 {
1819 return Ok(0);
1820 }
1821 let last = isize::try_from(extent - 1).map_err(|_| StridedError::OffsetOverflow)?;
1822 stride
1823 .checked_mul(last)
1824 .and_then(isize::checked_neg)
1825 .ok_or(StridedError::OffsetOverflow)
1826}
1827fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1828 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1829 let scaled = stride
1830 .checked_mul(coord)
1831 .ok_or(StridedError::OffsetOverflow)?;
1832 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1833}
1834
1835struct CoordScratch {
1836 inline: [usize; RAW_FUSED_RANK_LIMIT],
1837 heap: Option<Vec<usize>>,
1838 len: usize,
1839}
1840
1841impl CoordScratch {
1842 fn new(len: usize) -> Self {
1843 if len <= RAW_FUSED_RANK_LIMIT {
1844 Self {
1845 inline: [0; RAW_FUSED_RANK_LIMIT],
1846 heap: None,
1847 len,
1848 }
1849 } else {
1850 Self {
1851 inline: [0; RAW_FUSED_RANK_LIMIT],
1852 heap: Some(vec![0; len]),
1853 len,
1854 }
1855 }
1856 }
1857
1858 #[inline]
1859 fn first(&self) -> usize {
1860 match &self.heap {
1861 Some(heap) => heap.first().copied().unwrap_or(0),
1862 None => self.inline[0],
1863 }
1864 }
1865
1866 fn as_mut_slice(&mut self) -> &mut [usize] {
1867 match &mut self.heap {
1868 Some(heap) => heap,
1869 None => &mut self.inline[..self.len],
1870 }
1871 }
1872}