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 ProductKernel, ReduceKernel, SumKernel, SumSquaresKernel,
19};
20
21trait ReduceWriter<T> {
22 fn offset(&self) -> isize;
23 unsafe fn ptr(&mut self) -> *mut T;
26 fn extent(&self) -> usize;
27 unsafe fn write_at(&mut self, offset: isize, value: T) {
30 debug_assert!(offset >= 0 && (offset as usize) < self.extent());
31 unsafe { self.ptr().offset(offset).write(value) }
33 }
34}
35
36struct RawReduceWriter<'a, T> {
37 ptr: *mut T,
38 extent: usize,
39 offset: isize,
40 _marker: core::marker::PhantomData<&'a mut [MaybeUninit<T>]>,
41}
42
43impl<'a, T> ReduceWriter<T> for RawReduceWriter<'a, T> {
44 fn offset(&self) -> isize {
45 self.offset
46 }
47 unsafe fn ptr(&mut self) -> *mut T {
48 self.ptr
49 }
50 fn extent(&self) -> usize {
51 self.extent
52 }
53}
54
55#[derive(Clone, Debug)]
57pub struct ErasedCopyPlan {
58 dtype: KernelDType,
59 plan: CopyPlan,
60}
61
62#[derive(Clone, Debug)]
64pub struct ErasedConcatenatePlan {
65 dtype: KernelDType,
66 plan: ConcatenatePlan,
67}
68
69impl ErasedCopyPlan {
70 pub fn compile(
72 dtype: KernelDType,
73 dims: &[usize],
74 dst_strides: &[isize],
75 src_strides: &[isize],
76 ) -> Result<Self> {
77 Ok(Self {
78 dtype,
79 plan: CopyPlan::compile(dims, dst_strides, src_strides)?,
80 })
81 }
82
83 #[inline]
84 pub fn dtype(&self) -> KernelDType {
85 self.dtype
86 }
87
88 pub fn execute(
90 &self,
91 ctx: &ExecContext,
92 dest: &mut ErasedRawStridedMut<'_>,
93 src: &ErasedRawStridedRef<'_>,
94 ) -> Result<()> {
95 self.check_dtype(dest.dtype())?;
96 self.check_dtype(src.dtype())?;
97
98 let result = ctx.run(|| match self.dtype {
99 KernelDType::F32 => execute_copy::<f32>(&self.plan, dest, src),
100 KernelDType::F64 => execute_copy::<f64>(&self.plan, dest, src),
101 KernelDType::I32 => execute_copy::<i32>(&self.plan, dest, src),
102 KernelDType::I64 => execute_copy::<i64>(&self.plan, dest, src),
103 KernelDType::Bool => execute_copy::<bool>(&self.plan, dest, src),
104 KernelDType::C32 => execute_copy::<Complex32>(&self.plan, dest, src),
105 KernelDType::C64 => execute_copy::<Complex64>(&self.plan, dest, src),
106 _ => Err(StridedError::UnsupportedDType {
107 dtype: self.dtype.label(),
108 }),
109 });
110 result
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 ctx: &ExecContext,
161 dest: &mut ErasedRawStridedMut<'_>,
162 inputs: &[ErasedRawStridedRef<'_>],
163 ) -> Result<()> {
164 check_dtype(self.dtype, dest.dtype())?;
165 for input in inputs {
166 check_dtype(self.dtype, input.dtype())?;
167 }
168
169 let result = ctx.run(|| match self.dtype {
170 KernelDType::F32 => execute_concatenate::<f32>(&self.plan, dest, inputs),
171 KernelDType::F64 => execute_concatenate::<f64>(&self.plan, dest, inputs),
172 KernelDType::I32 => execute_concatenate::<i32>(&self.plan, dest, inputs),
173 KernelDType::I64 => execute_concatenate::<i64>(&self.plan, dest, inputs),
174 KernelDType::Bool => execute_concatenate::<bool>(&self.plan, dest, inputs),
175 KernelDType::C32 => execute_concatenate::<Complex32>(&self.plan, dest, inputs),
176 KernelDType::C64 => execute_concatenate::<Complex64>(&self.plan, dest, inputs),
177 _ => Err(StridedError::UnsupportedDType {
178 dtype: self.dtype.label(),
179 }),
180 });
181 result
182 }
183
184 pub fn execute_uninit(
192 &self,
193 ctx: &ExecContext,
194 dest: &mut ErasedRawStridedUninitMut<'_>,
195 inputs: &[ErasedRawStridedPtr<'_>],
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 ctx.run(|| match self.dtype {
216 KernelDType::F32 => execute_concatenate_uninit::<f32>(&self.plan, dest, inputs),
217 KernelDType::F64 => execute_concatenate_uninit::<f64>(&self.plan, dest, inputs),
218 KernelDType::I32 => execute_concatenate_uninit::<i32>(&self.plan, dest, inputs),
219 KernelDType::I64 => execute_concatenate_uninit::<i64>(&self.plan, dest, inputs),
220 KernelDType::Bool => execute_concatenate_uninit::<bool>(&self.plan, dest, inputs),
221 KernelDType::C32 => execute_concatenate_uninit::<Complex32>(&self.plan, dest, inputs),
222 KernelDType::C64 => execute_concatenate_uninit::<Complex64>(&self.plan, dest, inputs),
223 _ => Err(StridedError::UnsupportedDType {
224 dtype: self.dtype.label(),
225 }),
226 })
227 }
228}
229
230#[non_exhaustive]
232#[derive(Clone, Copy, Debug, Eq, PartialEq)]
233pub enum ReduceOp {
234 Sum,
235 Product,
236 SumSquares,
241 Max,
250 Min,
259}
260
261#[derive(Clone, Debug)]
311pub struct ErasedReducePlan {
312 dtype: KernelDType,
313 op: ReduceOp,
314 layout: ReduceLayout,
315}
316
317#[derive(Clone, Debug)]
318enum ReduceLayout {
319 Full {
320 dims: Vec<usize>,
321 src_strides: Vec<isize>,
322 traversal: FullTraversal,
323 },
324 Axes {
325 src_dims: Vec<usize>,
326 src_strides: Vec<isize>,
327 dest_dims: Vec<usize>,
328 dest_strides: Vec<isize>,
329 outer_axes: Vec<ReduceOuterAxis>,
330 inner_axes: Vec<ReduceInnerAxis>,
331 dest_total: usize,
332 reduce_total: usize,
333 full: Option<FullTraversal>,
336 },
337}
338
339#[derive(Clone, Copy, Debug)]
340struct ReduceOuterAxis {
341 extent: usize,
342 source_step: isize,
343 source_reset: isize,
344 dest_step: isize,
345 dest_reset: isize,
346}
347#[derive(Clone, Copy, Debug)]
348struct ReduceInnerAxis {
349 extent: usize,
350 source_step: isize,
351 source_reset: isize,
352}
353impl ReduceLayout {
354 fn src_dims(&self) -> &[usize] {
355 match self {
356 Self::Full { dims, .. } => dims,
357 Self::Axes { src_dims, .. } => src_dims,
358 }
359 }
360
361 fn src_strides(&self) -> &[isize] {
362 match self {
363 Self::Full { src_strides, .. } | Self::Axes { src_strides, .. } => src_strides,
364 }
365 }
366
367 fn check_src_layout(&self, src: &ErasedRawStridedRef<'_>) -> Result<()> {
368 if src.dims() != self.src_dims() || src.strides() != self.src_strides() {
369 return Err(StridedError::PlanLayoutMismatch);
370 }
371 Ok(())
372 }
373}
374
375#[derive(Clone, Copy, Debug)]
376struct AxesLayout<'a> {
377 outer_axes: &'a [ReduceOuterAxis],
378 inner_axes: &'a [ReduceInnerAxis],
379 dest_total: usize,
380 reduce_total: usize,
381}
382
383impl ErasedReducePlan {
384 pub fn compile(
386 dtype: KernelDType,
387 op: ReduceOp,
388 dims: &[usize],
389 src_strides: &[isize],
390 ) -> Result<Self> {
391 check_reduce_op_dtype(dtype, op)?;
392 if dims.len() != src_strides.len() {
393 return Err(StridedError::StrideLengthMismatch);
394 }
395 checked_total_len(dims)?;
396 let traversal = FullTraversal::compile(dims, src_strides)?;
397 Ok(Self {
398 dtype,
399 op,
400 layout: ReduceLayout::Full {
401 dims: dims.to_vec(),
402 src_strides: src_strides.to_vec(),
403 traversal,
404 },
405 })
406 }
407
408 #[allow(clippy::too_many_arguments)]
414 pub fn compile_axes(
415 dtype: KernelDType,
416 op: ReduceOp,
417 src_dims: &[usize],
418 src_strides: &[isize],
419 dest_dims: &[usize],
420 dest_strides: &[isize],
421 axes: &[usize],
422 ) -> Result<Self> {
423 check_reduce_op_dtype(dtype, op)?;
424 if src_dims.len() != src_strides.len() || dest_dims.len() != dest_strides.len() {
425 return Err(StridedError::StrideLengthMismatch);
426 }
427 checked_total_len(src_dims)?;
428 check_reduce_layout_offset_arithmetic(src_dims, src_strides)?;
429 let dest_total = checked_total_len(dest_dims)?;
430 check_reduce_layout_offset_arithmetic(dest_dims, dest_strides)?;
431 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
432 return Err(StridedError::NonInjectiveOutputLayout);
433 }
434 validate_unique_axes(axes, src_dims.len())?;
435
436 let kept_axes: Vec<usize> = (0..src_dims.len())
437 .filter(|axis| !axes.contains(axis))
438 .collect();
439 let expected_dest_dims: Vec<usize> = kept_axes.iter().map(|&axis| src_dims[axis]).collect();
440 if expected_dest_dims.is_empty() {
441 if dest_total != 1 {
442 return Err(StridedError::ShapeMismatch(
443 dest_dims.to_vec(),
444 expected_dest_dims,
445 ));
446 }
447 } else if dest_dims != expected_dest_dims.as_slice() {
448 return Err(StridedError::ShapeMismatch(
449 dest_dims.to_vec(),
450 expected_dest_dims,
451 ));
452 }
453
454 let reduce_total = axes
455 .iter()
456 .try_fold(1usize, |total, &axis| total.checked_mul(src_dims[axis]))
457 .ok_or(StridedError::OffsetOverflow)?;
458 let outer_axes = compress_reduce_outer_axes(
459 kept_axes
460 .iter()
461 .enumerate()
462 .map(|(dest_axis, &src_axis)| {
463 let extent = src_dims[src_axis];
464 Ok(ReduceOuterAxis {
465 extent,
466 source_step: src_strides[src_axis],
467 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
468 dest_step: dest_strides[dest_axis],
469 dest_reset: checked_reduce_reset(extent, dest_strides[dest_axis])?,
470 })
471 })
472 .collect::<Result<Vec<_>>>()?,
473 )?;
474 let inner_axes = compress_reduce_inner_axes(
475 axes.iter()
476 .map(|&src_axis| {
477 let extent = src_dims[src_axis];
478 Ok(ReduceInnerAxis {
479 extent,
480 source_step: src_strides[src_axis],
481 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
482 })
483 })
484 .collect::<Result<Vec<_>>>()?,
485 )?;
486 let full = if kept_axes.is_empty() {
487 Some(FullTraversal::compile(src_dims, src_strides)?)
488 } else {
489 None
490 };
491 Ok(Self {
492 dtype,
493 op,
494 layout: ReduceLayout::Axes {
495 src_dims: src_dims.to_vec(),
496 src_strides: src_strides.to_vec(),
497 dest_dims: dest_dims.to_vec(),
498 dest_strides: dest_strides.to_vec(),
499 outer_axes,
500 inner_axes,
501 dest_total,
502 reduce_total,
503 full,
504 },
505 })
506 }
507
508 #[inline]
509 pub fn dtype(&self) -> KernelDType {
510 self.dtype
511 }
512
513 #[inline]
514 pub fn op(&self) -> ReduceOp {
515 self.op
516 }
517
518 pub fn execute(
520 &self,
521 ctx: &ExecContext,
522 dest: &mut ErasedRawStridedMut<'_>,
523 src: &ErasedRawStridedRef<'_>,
524 ) -> Result<()> {
525 check_dtype(self.dtype, dest.dtype())?;
526 check_dtype(self.dtype, src.dtype())?;
527 self.layout.check_src_layout(src)?;
528 match &self.layout {
529 ReduceLayout::Full { .. } => {
530 let dest_len = checked_total_len(dest.dims())?;
531 if dest_len != 1 {
532 return Err(StridedError::RankMismatch(dest_len, 1));
533 }
534 }
535 ReduceLayout::Axes {
536 dest_dims,
537 dest_strides,
538 ..
539 } => {
540 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
541 {
542 return Err(StridedError::PlanLayoutMismatch);
543 }
544 }
545 }
546
547 let result = match self.dtype {
548 KernelDType::F32 => {
549 let mut writer = reduce_writer::<f32>(dest)?;
550 dispatch_reduce::<f32, _>(self.op, &self.layout, ctx, &mut writer, src)
551 }
552 KernelDType::F64 => {
553 let mut writer = reduce_writer::<f64>(dest)?;
554 dispatch_reduce::<f64, _>(self.op, &self.layout, ctx, &mut writer, src)
555 }
556 KernelDType::I32 => {
557 let mut writer = reduce_writer::<i32>(dest)?;
558 dispatch_reduce::<i32, _>(self.op, &self.layout, ctx, &mut writer, src)
559 }
560 KernelDType::I64 => {
561 let mut writer = reduce_writer::<i64>(dest)?;
562 dispatch_reduce::<i64, _>(self.op, &self.layout, ctx, &mut writer, src)
563 }
564 KernelDType::C32 => {
565 let mut writer = reduce_writer::<Complex32>(dest)?;
566 dispatch_reduce::<Complex32, _>(self.op, &self.layout, ctx, &mut writer, src)
567 }
568 KernelDType::C64 => {
569 let mut writer = reduce_writer::<Complex64>(dest)?;
570 dispatch_reduce::<Complex64, _>(self.op, &self.layout, ctx, &mut writer, src)
571 }
572 _ => Err(StridedError::UnsupportedDType {
573 dtype: self.dtype.label(),
574 }),
575 };
576 result
577 }
578
579 pub fn execute_uninit(
586 &self,
587 ctx: &ExecContext,
588 dest: &mut ErasedRawStridedUninitMut<'_>,
589 src: &ErasedRawStridedPtr<'_>,
590 ) -> Result<()> {
591 check_dtype(self.dtype, dest.dtype())?;
592 check_dtype(self.dtype, src.dtype())?;
593 validate_uninit_no_overlap(dest, src, 0)?;
594 let src = unsafe { src.try_as_ref_after_no_overlap() }?;
596 self.layout.check_src_layout(&src)?;
597 match &self.layout {
598 ReduceLayout::Full { .. } => {
599 let total = checked_total_len(dest.dims())?;
600 if total != 1 {
601 return Err(StridedError::RankMismatch(total, 1));
602 }
603 }
604 ReduceLayout::Axes {
605 dest_dims,
606 dest_strides,
607 ..
608 } => {
609 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
610 {
611 return Err(StridedError::PlanLayoutMismatch);
612 }
613 }
614 }
615 macro_rules! run {
616 ($ty:ty) => {{
617 let mut writer = reduce_uninit_writer::<$ty>(dest)?;
618 dispatch_reduce::<$ty, _>(self.op, &self.layout, ctx, &mut writer, &src)
619 }};
620 }
621 match self.dtype {
622 KernelDType::F32 => run!(f32),
623 KernelDType::F64 => run!(f64),
624 KernelDType::I32 => run!(i32),
625 KernelDType::I64 => run!(i64),
626 KernelDType::C32 => run!(Complex32),
627 KernelDType::C64 => run!(Complex64),
628 _ => Err(StridedError::UnsupportedDType {
629 dtype: self.dtype.label(),
630 }),
631 }
632 }
633}
634
635fn reduce_writer<'a, T>(dest: &'a mut ErasedRawStridedMut<'_>) -> Result<RawReduceWriter<'a, T>>
636where
637 T: KernelStorageElement,
638{
639 let offset = dest.offset();
640 let data = dest.data_as_mut::<T>()?;
641 let ptr = data.as_mut_ptr();
642 let extent = data.len();
643 Ok(RawReduceWriter {
644 ptr,
645 extent,
646 offset,
647 _marker: core::marker::PhantomData,
648 })
649}
650
651fn reduce_uninit_writer<'a, T>(
652 dest: &'a mut ErasedRawStridedUninitMut<'_>,
653) -> Result<RawReduceWriter<'a, T>>
654where
655 T: KernelStorageElement,
656{
657 let offset = dest.offset();
658 let data = dest.data_as_uninit_mut::<T>()?;
659 let ptr = data.as_mut_ptr().cast::<T>();
660 let extent = data.len();
661 Ok(RawReduceWriter {
662 ptr,
663 extent,
664 offset,
665 _marker: core::marker::PhantomData,
666 })
667}
668
669fn check_reduce_dtype(dtype: KernelDType) -> Result<()> {
670 match dtype {
671 KernelDType::F32
672 | KernelDType::F64
673 | KernelDType::I32
674 | KernelDType::I64
675 | KernelDType::C32
676 | KernelDType::C64 => Ok(()),
677 _ => Err(StridedError::UnsupportedDType {
678 dtype: dtype.label(),
679 }),
680 }
681}
682
683fn check_reduce_op_dtype(dtype: KernelDType, op: ReduceOp) -> Result<()> {
684 if op == ReduceOp::SumSquares && !matches!(dtype, KernelDType::F32 | KernelDType::F64) {
685 return Err(StridedError::UnsupportedDType {
686 dtype: dtype.label(),
687 });
688 }
689 if matches!(op, ReduceOp::Max | ReduceOp::Min)
690 && !matches!(
691 dtype,
692 KernelDType::F32 | KernelDType::F64 | KernelDType::I32 | KernelDType::I64
693 )
694 {
695 return Err(StridedError::UnsupportedDType {
696 dtype: dtype.label(),
697 });
698 }
699 check_reduce_dtype(dtype)
700}
701
702fn checked_total_len(dims: &[usize]) -> Result<usize> {
703 if dims.is_empty() {
704 return Ok(1);
705 }
706 dims.iter()
707 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
708 .ok_or(StridedError::OffsetOverflow)
709}
710
711fn execute_copy<T>(
712 plan: &CopyPlan,
713 dest: &mut ErasedRawStridedMut<'_>,
714 src: &ErasedRawStridedRef<'_>,
715) -> Result<()>
716where
717 T: Copy + crate::MaybeSendSync + KernelStorageElement,
718{
719 let source_data = src.data_as::<T>()?;
720 let dest_dims = dest.dims();
721 let dest_strides = dest.strides();
722 let dest_offset = dest.offset();
723 let dest_data = dest.data_as_mut::<T>()?;
724 let source = unsafe {
725 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
726 };
727 let mut dest =
728 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
729 plan.execute(&mut dest, &source)
730}
731
732fn execute_concatenate<T>(
733 plan: &ConcatenatePlan,
734 dest: &mut ErasedRawStridedMut<'_>,
735 inputs: &[ErasedRawStridedRef<'_>],
736) -> Result<()>
737where
738 T: Copy + crate::MaybeSendSync + KernelStorageElement,
739{
740 if inputs.len() != plan.input_count() {
741 return Err(StridedError::RankMismatch(inputs.len(), plan.input_count()));
742 }
743 let dest_dims = dest.dims();
744 let dest_strides = dest.strides();
745 let dest_offset = dest.offset();
746 let dest_data = dest.data_as_mut::<T>()?;
747 let mut dest_ref =
748 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
749 plan.check_dest_layout(&dest_ref)?;
750
751 for (position, input) in inputs.iter().enumerate() {
752 let input_data = input.data_as::<T>()?;
753 let input_ref = unsafe {
754 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
755 };
756 plan.check_input_layout(position, &input_ref)?;
757 plan.segment_offset(position, dest_offset)?;
758 }
759 if plan.prefers_whole_plan() {
760 let input_refs = inputs
761 .iter()
762 .map(|input| {
763 let input_data = input.data_as::<T>()?;
764 Ok(unsafe {
765 RawStridedRef::new_unchecked(
766 input_data,
767 input.dims(),
768 input.strides(),
769 input.offset(),
770 )
771 })
772 })
773 .collect::<Result<Vec<_>>>()?;
774 return plan.execute(&mut dest_ref, &input_refs);
776 }
777 for (position, input) in inputs.iter().enumerate() {
778 let input_data = input.data_as::<T>()?;
779 let input_ref = unsafe {
780 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
781 };
782 plan.execute_segment(position, &mut dest_ref, &input_ref)?;
783 }
784 Ok(())
785}
786
787fn execute_concatenate_uninit<T>(
788 plan: &ConcatenatePlan,
789 dest: &mut ErasedRawStridedUninitMut<'_>,
790 inputs: &[ErasedRawStridedPtr<'_>],
791) -> Result<()>
792where
793 T: Copy + crate::MaybeSendSync + KernelStorageElement,
794{
795 let dest_dims = dest.dims();
796 let dest_strides = dest.strides();
797 let dest_offset = dest.offset();
798 let dest_data = dest.data_as_uninit_mut::<T>()?;
799 let mut dest_ref =
800 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
801 plan.check_dest_layout(&dest_ref)?;
802
803 for (position, input) in inputs.iter().enumerate() {
804 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
806 let input_data = input.data_as::<T>()?;
807 let input_ref = unsafe {
808 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
809 };
810 plan.check_input_layout(position, &input_ref)?;
811 plan.segment_offset(position, dest_offset)?;
812 }
813 if plan.prefers_whole_plan() {
814 let inputs = inputs
815 .iter()
816 .map(|input| unsafe { input.try_as_ref_after_no_overlap() })
818 .collect::<Result<Vec<_>>>()?;
819 let input_refs = inputs
820 .iter()
821 .map(|input| {
822 let input_data = input.data_as::<T>()?;
823 Ok(unsafe {
824 RawStridedRef::new_unchecked(
825 input_data,
826 input.dims(),
827 input.strides(),
828 input.offset(),
829 )
830 })
831 })
832 .collect::<Result<Vec<_>>>()?;
833 return plan.execute_uninit(&mut dest_ref, &input_refs);
835 }
836 for (position, input) in inputs.iter().enumerate() {
837 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
839 let input_data = input.data_as::<T>()?;
840 let input_ref = unsafe {
841 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
842 };
843 plan.execute_segment_uninit(position, &mut dest_ref, &input_ref)?;
844 }
845 Ok(())
846}
847
848fn reduce_context_is_serial(ctx: &ExecContext) -> bool {
851 ctx.is_serial()
852 || ctx
853 .max_threads_limit()
854 .is_some_and(|max_threads| max_threads.get() == 1)
855}
856
857fn execute_reduce_full<T, W, K>(
858 ctx: &ExecContext,
859 dest: &mut W,
860 src: &ErasedRawStridedRef<'_>,
861 traversal: &FullTraversal,
862) -> Result<()>
863where
864 T: ErasedReduceScalar,
865 W: ReduceWriter<T>,
866 K: ReduceKernel<T>,
867{
868 let source = src.data_as::<T>()?;
869 let total = traversal.total();
870 let value = if total == 0 {
871 K::identity()
872 } else if reduce_context_is_serial(ctx) {
873 unsafe { reduce_full_range::<T, K>(source.as_ptr(), src.offset(), traversal, 0..total)? }
879 } else {
880 ctx.run(|| reduce_full_policy::<T, K>(source, src.offset(), traversal))?
881 };
882
883 unsafe { dest.write_at(dest.offset(), value) };
885 Ok(())
886}
887
888fn reduce_full_policy<T, K>(
889 source: &[T],
890 source_base: isize,
891 traversal: &FullTraversal,
892) -> Result<T>
893where
894 T: ErasedReduceScalar,
895 K: ReduceKernel<T>,
896{
897 let total = traversal.total();
898 #[cfg(feature = "parallel")]
899 {
900 let nthreads = if K::reassociable() {
904 crate::threading::parallel_threads_for_len(total)
905 } else {
906 1
907 };
908 if nthreads > 1 {
909 let source_ptr = crate::threading::SendPtr(source.as_ptr() as *mut T);
910 return crate::threading::parallel_map_reduce(
911 0..total,
912 nthreads,
913 &|range| {
914 unsafe {
917 reduce_full_range::<T, K>(
918 source_ptr.as_const(),
919 source_base,
920 traversal,
921 range,
922 )
923 }
924 },
925 &|left, right| Ok(K::combine(left?, right?)),
926 );
927 }
928 }
929 unsafe { reduce_full_range::<T, K>(source.as_ptr(), source_base, traversal, 0..total) }
932}
933
934fn dispatch_reduce<T, W>(
935 op: ReduceOp,
936 layout: &ReduceLayout,
937 ctx: &ExecContext,
938 dest: &mut W,
939 src: &ErasedRawStridedRef<'_>,
940) -> Result<()>
941where
942 T: ErasedReduceScalar,
943 W: ReduceWriter<T>,
944{
945 match op {
948 ReduceOp::Sum => dispatch_reduce_kernel::<T, W, SumKernel>(layout, ctx, dest, src),
949 ReduceOp::Product => dispatch_reduce_kernel::<T, W, ProductKernel>(layout, ctx, dest, src),
950 ReduceOp::SumSquares => {
951 dispatch_reduce_kernel::<T, W, SumSquaresKernel>(layout, ctx, dest, src)
952 }
953 ReduceOp::Max => dispatch_reduce_kernel::<T, W, MaxKernel>(layout, ctx, dest, src),
954 ReduceOp::Min => dispatch_reduce_kernel::<T, W, MinKernel>(layout, ctx, dest, src),
955 }
956}
957
958fn dispatch_reduce_kernel<T, W, K>(
959 layout: &ReduceLayout,
960 ctx: &ExecContext,
961 dest: &mut W,
962 src: &ErasedRawStridedRef<'_>,
963) -> Result<()>
964where
965 T: ErasedReduceScalar,
966 W: ReduceWriter<T>,
967 K: ReduceKernel<T>,
968{
969 match layout {
970 ReduceLayout::Full { traversal, .. } => {
971 execute_reduce_full::<T, W, K>(ctx, dest, src, traversal)
972 }
973 ReduceLayout::Axes {
974 outer_axes,
975 inner_axes,
976 dest_total,
977 reduce_total,
978 full,
979 ..
980 } => {
981 if let Some(traversal) = full {
982 return execute_reduce_full::<T, W, K>(ctx, dest, src, traversal);
983 }
984 execute_reduce_axes::<T, W, K>(
985 ctx,
986 dest,
987 src,
988 AxesLayout {
989 outer_axes,
990 inner_axes,
991 dest_total: *dest_total,
992 reduce_total: *reduce_total,
993 },
994 )
995 }
996 }
997}
998
999fn execute_reduce_axes<T, W, K>(
1000 ctx: &ExecContext,
1001 dest: &mut W,
1002 src: &ErasedRawStridedRef<'_>,
1003 layout: AxesLayout<'_>,
1004) -> Result<()>
1005where
1006 T: ErasedReduceScalar,
1007 W: ReduceWriter<T>,
1008 K: ReduceKernel<T>,
1009{
1010 if layout.dest_total == 0 {
1011 return Ok(());
1012 }
1013
1014 if layout.reduce_total == 0 {
1015 if ctx.is_serial() {
1016 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1017 } else {
1018 ctx.run(|| execute_reduce_axes_identity_policy::<T, W, K>(dest, layout))
1019 }
1020 } else if reduce_context_is_serial(ctx) {
1021 execute_reduce_axes_policy::<T, W, K>(dest, src, layout, false)
1022 } else {
1023 ctx.run(|| execute_reduce_axes_policy::<T, W, K>(dest, src, layout, true))
1024 }
1025}
1026
1027fn execute_reduce_axes_policy<T, W, K>(
1028 dest: &mut W,
1029 src: &ErasedRawStridedRef<'_>,
1030 layout: AxesLayout<'_>,
1031 allow_parallel: bool,
1032) -> Result<()>
1033where
1034 T: ErasedReduceScalar,
1035 W: ReduceWriter<T>,
1036 K: ReduceKernel<T>,
1037{
1038 let source_data = src.data_as::<T>()?;
1039 let source_base = src.offset();
1040 let dest_base = dest.offset();
1041 let dest_ptr = unsafe { dest.ptr() };
1043 #[cfg(feature = "parallel")]
1044 if allow_parallel {
1045 let work = layout.dest_total.saturating_mul(layout.reduce_total);
1051 let nthreads = crate::threading::parallel_threads_for_len(work).min(layout.dest_total);
1052 if nthreads > 1 {
1053 let dest_ptr = crate::threading::SendPtr(dest_ptr);
1054 let source_ptr = crate::threading::SendPtr(source_data.as_ptr() as *mut T);
1055 return crate::threading::parallel_map_reduce(
1056 0..layout.dest_total,
1057 nthreads,
1058 &|range| {
1059 let parts = AxesRange {
1060 source: source_ptr.as_const(),
1061 source_base,
1062 dest: dest_ptr.as_ptr(),
1063 dest_base,
1064 outer_axes: layout.outer_axes,
1065 inner_axes: layout.inner_axes,
1066 reduce_total: layout.reduce_total,
1067 };
1068 unsafe { reduce_axes_range::<T, K>(parts, range) }
1070 },
1071 &|left, right| left.and(right),
1072 );
1073 }
1074 }
1075 #[cfg(not(feature = "parallel"))]
1076 let _ = allow_parallel;
1077
1078 let parts = AxesRange {
1079 source: source_data.as_ptr(),
1080 source_base,
1081 dest: dest_ptr,
1082 dest_base,
1083 outer_axes: layout.outer_axes,
1084 inner_axes: layout.inner_axes,
1085 reduce_total: layout.reduce_total,
1086 };
1087 unsafe { reduce_axes_range::<T, K>(parts, 0..layout.dest_total) }
1094}
1095
1096fn execute_reduce_axes_identity_serial<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1097where
1098 T: ErasedReduceScalar,
1099 W: ReduceWriter<T>,
1100 K: ReduceKernel<T>,
1101{
1102 let mut outer = ReduceOuterCursor::decode(0, 0, dest.offset(), layout.outer_axes)?;
1103 for output in 0..layout.dest_total {
1104 unsafe { dest.write_at(outer.dest_offset, K::identity()) };
1112 if output + 1 < layout.dest_total {
1113 outer.advance();
1114 }
1115 }
1116 Ok(())
1117}
1118fn execute_reduce_axes_identity_policy<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1119where
1120 T: ErasedReduceScalar,
1121 W: ReduceWriter<T>,
1122 K: ReduceKernel<T>,
1123{
1124 #[cfg(feature = "parallel")]
1125 {
1126 let nthreads = crate::threading::parallel_threads_for_len(layout.dest_total);
1127 if nthreads > 1 {
1128 return execute_reduce_axes_identity_parallel::<T, W, K>(dest, layout, nthreads);
1129 }
1130 }
1131 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1132}
1133#[cfg(feature = "parallel")]
1134fn execute_reduce_axes_identity_parallel<T, W, K>(
1135 dest: &mut W,
1136 layout: AxesLayout<'_>,
1137 nthreads: usize,
1138) -> Result<()>
1139where
1140 T: ErasedReduceScalar,
1141 W: ReduceWriter<T>,
1142 K: ReduceKernel<T>,
1143{
1144 let dest_ptr = crate::threading::SendPtr(unsafe { dest.ptr() });
1146 let dest_offset_base = dest.offset();
1147 crate::threading::parallel_map_reduce(
1148 0..layout.dest_total,
1149 nthreads,
1150 &|range| {
1151 let range_end = range.end;
1152 let mut outer =
1153 ReduceOuterCursor::decode(range.start, 0, dest_offset_base, layout.outer_axes)?;
1154 let dest_ptr = dest_ptr.as_ptr();
1155 for output in range {
1156 unsafe { dest_ptr.offset(outer.dest_offset).write(K::identity()) };
1164 if output + 1 < range_end {
1165 outer.advance();
1166 }
1167 }
1168 Ok(())
1169 },
1170 &|left, right| left.and(right),
1171 )
1172}
1173
1174trait ErasedReduceScalar:
1175 KernelStorageElement
1176 + Copy
1177 + One
1178 + Zero
1179 + crate::MaybeSendSync
1180 + crate::simd::MaybeSimdOps
1181 + crate::simd::MaybeSimdProduct
1182 + crate::simd::MaybeSimdSumSquares
1183 + crate::simd::MaybeSimdBlockFold
1184{
1185 fn reduce_sum(lhs: Self, rhs: Self) -> Self;
1186 fn reduce_product(lhs: Self, rhs: Self) -> Self;
1187 fn max_identity() -> Self;
1189 fn min_identity() -> Self;
1191 fn reduce_max(lhs: Self, rhs: Self) -> Self;
1192 fn reduce_min(lhs: Self, rhs: Self) -> Self;
1193}
1194
1195macro_rules! impl_float_erased_reduce_scalar {
1196 ($($ty:ty),* $(,)?) => {
1197 $(
1198 impl ErasedReduceScalar for $ty {
1199 #[inline(always)]
1200 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1201 lhs + rhs
1202 }
1203
1204 #[inline(always)]
1205 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1206 lhs * rhs
1207 }
1208
1209 #[inline(always)]
1210 fn max_identity() -> Self {
1211 <$ty>::NEG_INFINITY
1212 }
1213
1214 #[inline(always)]
1215 fn min_identity() -> Self {
1216 <$ty>::INFINITY
1217 }
1218
1219 #[inline(always)]
1220 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1221 if (lhs > rhs) | lhs.is_nan() {
1229 lhs
1230 } else {
1231 rhs
1232 }
1233 }
1234
1235 #[inline(always)]
1236 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1237 if (lhs < rhs) | lhs.is_nan() {
1245 lhs
1246 } else {
1247 rhs
1248 }
1249 }
1250 }
1251 )*
1252 };
1253}
1254
1255macro_rules! impl_complex_erased_reduce_scalar {
1256 ($($ty:ty),* $(,)?) => {
1257 $(
1258 impl ErasedReduceScalar for $ty {
1259 #[inline(always)]
1260 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1261 lhs + rhs
1262 }
1263
1264 #[inline(always)]
1265 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1266 lhs * rhs
1267 }
1268
1269 fn max_identity() -> Self {
1270 unreachable!("complex max reduction is rejected at plan compile")
1272 }
1273
1274 fn min_identity() -> Self {
1275 unreachable!("complex min reduction is rejected at plan compile")
1277 }
1278
1279 fn reduce_max(_lhs: Self, _rhs: Self) -> Self {
1280 unreachable!("complex max reduction is rejected at plan compile")
1282 }
1283
1284 fn reduce_min(_lhs: Self, _rhs: Self) -> Self {
1285 unreachable!("complex min reduction is rejected at plan compile")
1287 }
1288 }
1289 )*
1290 };
1291}
1292
1293macro_rules! impl_wrapping_erased_reduce_scalar {
1294 ($($ty:ty),* $(,)?) => {
1295 $(
1296 impl ErasedReduceScalar for $ty {
1297 #[inline(always)]
1298 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1299 lhs.wrapping_add(rhs)
1300 }
1301
1302 #[inline(always)]
1303 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1304 lhs.wrapping_mul(rhs)
1305 }
1306
1307 #[inline(always)]
1308 fn max_identity() -> Self {
1309 <$ty>::MIN
1310 }
1311
1312 #[inline(always)]
1313 fn min_identity() -> Self {
1314 <$ty>::MAX
1315 }
1316
1317 #[inline(always)]
1318 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1319 lhs.max(rhs)
1320 }
1321
1322 #[inline(always)]
1323 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1324 lhs.min(rhs)
1325 }
1326 }
1327 )*
1328 };
1329}
1330
1331impl_float_erased_reduce_scalar!(f32, f64);
1332
1333impl_complex_erased_reduce_scalar!(Complex32, Complex64);
1334
1335impl_wrapping_erased_reduce_scalar!(i32, i64);
1336
1337fn validate_unique_axes(axes: &[usize], rank: usize) -> Result<()> {
1338 let mut seen = vec![false; rank];
1339 for &axis in axes {
1340 if axis >= rank {
1341 return Err(StridedError::InvalidAxis { axis, rank });
1342 }
1343 if seen[axis] {
1344 return Err(StridedError::InvalidAxis { axis, rank });
1345 }
1346 seen[axis] = true;
1347 }
1348 Ok(())
1349}
1350
1351struct ReduceOuterCursor<'a> {
1352 axes: &'a [ReduceOuterAxis],
1353 coords: CoordScratch,
1354 source_offset: isize,
1355 dest_offset: isize,
1356}
1357impl<'a> ReduceOuterCursor<'a> {
1358 fn decode(
1359 mut linear: usize,
1360 source_base: isize,
1361 dest_base: isize,
1362 axes: &'a [ReduceOuterAxis],
1363 ) -> Result<Self> {
1364 let mut coords = CoordScratch::new(axes.len());
1365 let mut source_offset = source_base;
1366 let mut dest_offset = dest_base;
1367 for (coord, axis) in coords.as_mut_slice().iter_mut().zip(axes) {
1368 debug_assert!(axis.extent != 0);
1372 *coord = linear % axis.extent;
1373 linear /= axis.extent;
1374 source_offset = checked_offset_add(source_offset, axis.source_step, *coord)?;
1375 dest_offset = checked_offset_add(dest_offset, axis.dest_step, *coord)?;
1376 }
1377 Ok(Self {
1378 axes,
1379 coords,
1380 source_offset,
1381 dest_offset,
1382 })
1383 }
1384
1385 #[inline]
1387 fn leading_coord(&self) -> usize {
1388 self.coords.first()
1389 }
1390
1391 #[inline]
1392 fn advance(&mut self) {
1393 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1397 let next = *coord + 1;
1398 if next < axis.extent {
1399 *coord = next;
1400 self.source_offset += axis.source_step;
1401 self.dest_offset += axis.dest_step;
1402 return;
1403 }
1404 *coord = 0;
1405 self.source_offset += axis.source_reset;
1406 self.dest_offset += axis.dest_reset;
1407 }
1408 }
1409}
1410struct ReduceInnerCursor<'a> {
1411 axes: &'a [ReduceInnerAxis],
1412 coords: CoordScratch,
1413 source_offset: isize,
1414}
1415impl<'a> ReduceInnerCursor<'a> {
1416 fn new(source_base: isize, axes: &'a [ReduceInnerAxis]) -> Self {
1417 Self {
1418 axes,
1419 coords: CoordScratch::new(axes.len()),
1420 source_offset: source_base,
1421 }
1422 }
1423
1424 #[inline]
1425 fn reset(&mut self, source_base: isize) {
1426 self.coords.as_mut_slice().fill(0);
1427 self.source_offset = source_base;
1428 }
1429
1430 #[inline]
1431 fn advance(&mut self) {
1432 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1436 let next = *coord + 1;
1437 if next < axis.extent {
1438 *coord = next;
1439 self.source_offset += axis.source_step;
1440 return;
1441 }
1442 *coord = 0;
1443 self.source_offset += axis.source_reset;
1444 }
1445 }
1446}
1447fn check_reduce_layout_offset_arithmetic(dims: &[usize], strides: &[isize]) -> Result<()> {
1448 if dims.len() != strides.len() {
1449 return Err(StridedError::StrideLengthMismatch);
1450 }
1451 let mut min_offset = 0isize;
1452 let mut max_offset = 0isize;
1453 for (&dim, &stride) in dims.iter().zip(strides) {
1454 let last =
1455 isize::try_from(dim.saturating_sub(1)).map_err(|_| StridedError::OffsetOverflow)?;
1456 let extent = stride
1457 .checked_mul(last)
1458 .ok_or(StridedError::OffsetOverflow)?;
1459 if extent < 0 {
1460 min_offset = min_offset
1461 .checked_add(extent)
1462 .ok_or(StridedError::OffsetOverflow)?;
1463 } else {
1464 max_offset = max_offset
1465 .checked_add(extent)
1466 .ok_or(StridedError::OffsetOverflow)?;
1467 }
1468 }
1469 let _ = (min_offset, max_offset);
1470 Ok(())
1471}
1472fn compress_reduce_outer_axes(axes: Vec<ReduceOuterAxis>) -> Result<Vec<ReduceOuterAxis>> {
1473 let mut compressed: Vec<ReduceOuterAxis> = Vec::with_capacity(axes.len());
1474 for axis in axes {
1475 if let Some(previous) = compressed.last_mut() {
1476 let previous_extent =
1477 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1478 let expected_source = previous
1479 .source_step
1480 .checked_mul(previous_extent)
1481 .ok_or(StridedError::OffsetOverflow)?;
1482 let expected_dest = previous
1483 .dest_step
1484 .checked_mul(previous_extent)
1485 .ok_or(StridedError::OffsetOverflow)?;
1486 if axis.source_step == expected_source && axis.dest_step == expected_dest {
1487 let fused_extent = previous
1488 .extent
1489 .checked_mul(axis.extent)
1490 .ok_or(StridedError::OffsetOverflow)?;
1491 previous.extent = fused_extent;
1492 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1493 previous.dest_reset = checked_reduce_reset(fused_extent, previous.dest_step)?;
1494 continue;
1495 }
1496 }
1497 compressed.push(axis);
1498 }
1499 Ok(compressed)
1500}
1501fn compress_reduce_inner_axes(axes: Vec<ReduceInnerAxis>) -> Result<Vec<ReduceInnerAxis>> {
1502 let mut compressed: Vec<ReduceInnerAxis> = Vec::with_capacity(axes.len());
1503 for axis in axes {
1504 if let Some(previous) = compressed.last_mut() {
1505 let previous_extent =
1506 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1507 let expected_source = previous
1508 .source_step
1509 .checked_mul(previous_extent)
1510 .ok_or(StridedError::OffsetOverflow)?;
1511 if axis.source_step == expected_source {
1512 let fused_extent = previous
1513 .extent
1514 .checked_mul(axis.extent)
1515 .ok_or(StridedError::OffsetOverflow)?;
1516 previous.extent = fused_extent;
1517 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1518 continue;
1519 }
1520 }
1521 compressed.push(axis);
1522 }
1523 Ok(compressed)
1524}
1525fn checked_reduce_reset(extent: usize, stride: isize) -> Result<isize> {
1526 if extent == 0 {
1527 return Ok(0);
1528 }
1529 let last = isize::try_from(extent - 1).map_err(|_| StridedError::OffsetOverflow)?;
1530 stride
1531 .checked_mul(last)
1532 .and_then(isize::checked_neg)
1533 .ok_or(StridedError::OffsetOverflow)
1534}
1535fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1536 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1537 let scaled = stride
1538 .checked_mul(coord)
1539 .ok_or(StridedError::OffsetOverflow)?;
1540 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1541}
1542
1543struct CoordScratch {
1544 inline: [usize; RAW_FUSED_RANK_LIMIT],
1545 heap: Option<Vec<usize>>,
1546 len: usize,
1547}
1548
1549impl CoordScratch {
1550 fn new(len: usize) -> Self {
1551 if len <= RAW_FUSED_RANK_LIMIT {
1552 Self {
1553 inline: [0; RAW_FUSED_RANK_LIMIT],
1554 heap: None,
1555 len,
1556 }
1557 } else {
1558 Self {
1559 inline: [0; RAW_FUSED_RANK_LIMIT],
1560 heap: Some(vec![0; len]),
1561 len,
1562 }
1563 }
1564 }
1565
1566 #[inline]
1567 fn first(&self) -> usize {
1568 match &self.heap {
1569 Some(heap) => heap.first().copied().unwrap_or(0),
1570 None => self.inline[0],
1571 }
1572 }
1573
1574 fn as_mut_slice(&mut self) -> &mut [usize] {
1575 match &mut self.heap {
1576 Some(heap) => heap,
1577 None => &mut self.inline[..self.len],
1578 }
1579 }
1580}