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: Vec<usize>,
344 src_strides: Vec<isize>,
345 traversal: FullTraversal,
346 },
347 Axes {
348 src_dims: Vec<usize>,
349 src_strides: Vec<isize>,
350 dest_dims: Vec<usize>,
351 dest_strides: Vec<isize>,
352 outer_axes: Vec<ReduceOuterAxis>,
353 inner_axes: Vec<ReduceInnerAxis>,
354 dest_total: usize,
355 reduce_total: usize,
356 full: Option<FullTraversal>,
359 },
360}
361
362#[derive(Clone, Copy, Debug)]
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)]
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.to_vec(),
425 src_strides: src_strides.to_vec(),
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: Vec<usize> = (0..src_dims.len())
460 .filter(|axis| !axes.contains(axis))
461 .collect();
462 let expected_dest_dims: Vec<usize> = kept_axes.iter().map(|&axis| src_dims[axis]).collect();
463 if expected_dest_dims.is_empty() {
464 if dest_total != 1 {
465 return Err(StridedError::ShapeMismatch(
466 dest_dims.to_vec(),
467 expected_dest_dims,
468 ));
469 }
470 } else if dest_dims != expected_dest_dims.as_slice() {
471 return Err(StridedError::ShapeMismatch(
472 dest_dims.to_vec(),
473 expected_dest_dims,
474 ));
475 }
476
477 let reduce_total = axes
478 .iter()
479 .try_fold(1usize, |total, &axis| total.checked_mul(src_dims[axis]))
480 .ok_or(StridedError::OffsetOverflow)?;
481 let outer_axes = compress_reduce_outer_axes(
482 kept_axes
483 .iter()
484 .enumerate()
485 .map(|(dest_axis, &src_axis)| {
486 let extent = src_dims[src_axis];
487 Ok(ReduceOuterAxis {
488 extent,
489 source_step: src_strides[src_axis],
490 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
491 dest_step: dest_strides[dest_axis],
492 dest_reset: checked_reduce_reset(extent, dest_strides[dest_axis])?,
493 })
494 })
495 .collect::<Result<Vec<_>>>()?,
496 )?;
497 let inner_axes = compress_reduce_inner_axes(
498 axes.iter()
499 .map(|&src_axis| {
500 let extent = src_dims[src_axis];
501 Ok(ReduceInnerAxis {
502 extent,
503 source_step: src_strides[src_axis],
504 source_reset: checked_reduce_reset(extent, src_strides[src_axis])?,
505 })
506 })
507 .collect::<Result<Vec<_>>>()?,
508 )?;
509 let full = if kept_axes.is_empty() {
510 Some(FullTraversal::compile(src_dims, src_strides)?)
511 } else {
512 None
513 };
514 Ok(Self {
515 dtype,
516 op,
517 layout: ReduceLayout::Axes {
518 src_dims: src_dims.to_vec(),
519 src_strides: src_strides.to_vec(),
520 dest_dims: dest_dims.to_vec(),
521 dest_strides: dest_strides.to_vec(),
522 outer_axes,
523 inner_axes,
524 dest_total,
525 reduce_total,
526 full,
527 },
528 })
529 }
530
531 #[inline]
532 pub fn dtype(&self) -> KernelDType {
533 self.dtype
534 }
535
536 #[inline]
537 pub fn op(&self) -> ReduceOp {
538 self.op
539 }
540
541 pub fn execute(
543 &self,
544 ctx: &ExecContext,
545 dest: &mut ErasedRawStridedMut<'_>,
546 src: &ErasedRawStridedRef<'_>,
547 ) -> Result<()> {
548 check_dtype(self.dtype, dest.dtype())?;
549 check_dtype(self.dtype, src.dtype())?;
550 self.layout.check_src_layout(src)?;
551 match &self.layout {
552 ReduceLayout::Full { .. } => {
553 let dest_len = checked_total_len(dest.dims())?;
554 if dest_len != 1 {
555 return Err(StridedError::RankMismatch(dest_len, 1));
556 }
557 }
558 ReduceLayout::Axes {
559 dest_dims,
560 dest_strides,
561 ..
562 } => {
563 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
564 {
565 return Err(StridedError::PlanLayoutMismatch);
566 }
567 }
568 }
569
570 let result = match self.dtype {
571 KernelDType::F32 => {
572 let mut writer = reduce_writer::<f32>(dest)?;
573 dispatch_reduce::<f32, _>(self.op, &self.layout, ctx, &mut writer, src)
574 }
575 KernelDType::F64 => {
576 let mut writer = reduce_writer::<f64>(dest)?;
577 dispatch_reduce::<f64, _>(self.op, &self.layout, ctx, &mut writer, src)
578 }
579 KernelDType::I32 => {
580 let mut writer = reduce_writer::<i32>(dest)?;
581 dispatch_reduce::<i32, _>(self.op, &self.layout, ctx, &mut writer, src)
582 }
583 KernelDType::I64 => {
584 let mut writer = reduce_writer::<i64>(dest)?;
585 dispatch_reduce::<i64, _>(self.op, &self.layout, ctx, &mut writer, src)
586 }
587 KernelDType::C32 => {
588 let mut writer = reduce_writer::<Complex32>(dest)?;
589 dispatch_reduce::<Complex32, _>(self.op, &self.layout, ctx, &mut writer, src)
590 }
591 KernelDType::C64 => {
592 let mut writer = reduce_writer::<Complex64>(dest)?;
593 dispatch_reduce::<Complex64, _>(self.op, &self.layout, ctx, &mut writer, src)
594 }
595 _ => Err(StridedError::UnsupportedDType {
596 dtype: self.dtype.label(),
597 }),
598 };
599 result
600 }
601
602 pub fn execute_uninit(
609 &self,
610 ctx: &ExecContext,
611 dest: &mut ErasedRawStridedUninitMut<'_>,
612 src: &ErasedRawStridedPtr<'_>,
613 ) -> Result<()> {
614 check_dtype(self.dtype, dest.dtype())?;
615 check_dtype(self.dtype, src.dtype())?;
616 validate_uninit_no_overlap(dest, src, 0)?;
617 let src = unsafe { src.try_as_ref_after_no_overlap() }?;
619 self.layout.check_src_layout(&src)?;
620 match &self.layout {
621 ReduceLayout::Full { .. } => {
622 let total = checked_total_len(dest.dims())?;
623 if total != 1 {
624 return Err(StridedError::RankMismatch(total, 1));
625 }
626 }
627 ReduceLayout::Axes {
628 dest_dims,
629 dest_strides,
630 ..
631 } => {
632 if dest.dims() != dest_dims.as_slice() || dest.strides() != dest_strides.as_slice()
633 {
634 return Err(StridedError::PlanLayoutMismatch);
635 }
636 }
637 }
638 macro_rules! run {
639 ($ty:ty) => {{
640 let mut writer = reduce_uninit_writer::<$ty>(dest)?;
641 dispatch_reduce::<$ty, _>(self.op, &self.layout, ctx, &mut writer, &src)
642 }};
643 }
644 match self.dtype {
645 KernelDType::F32 => run!(f32),
646 KernelDType::F64 => run!(f64),
647 KernelDType::I32 => run!(i32),
648 KernelDType::I64 => run!(i64),
649 KernelDType::C32 => run!(Complex32),
650 KernelDType::C64 => run!(Complex64),
651 _ => Err(StridedError::UnsupportedDType {
652 dtype: self.dtype.label(),
653 }),
654 }
655 }
656}
657
658fn reduce_writer<'a, T>(dest: &'a mut ErasedRawStridedMut<'_>) -> Result<RawReduceWriter<'a, T>>
659where
660 T: KernelStorageElement,
661{
662 let offset = dest.offset();
663 let data = dest.data_as_mut::<T>()?;
664 let ptr = data.as_mut_ptr();
665 let extent = data.len();
666 Ok(RawReduceWriter {
667 ptr,
668 extent,
669 offset,
670 _marker: core::marker::PhantomData,
671 })
672}
673
674fn reduce_uninit_writer<'a, T>(
675 dest: &'a mut ErasedRawStridedUninitMut<'_>,
676) -> Result<RawReduceWriter<'a, T>>
677where
678 T: KernelStorageElement,
679{
680 let offset = dest.offset();
681 let data = dest.data_as_uninit_mut::<T>()?;
682 let ptr = data.as_mut_ptr().cast::<T>();
683 let extent = data.len();
684 Ok(RawReduceWriter {
685 ptr,
686 extent,
687 offset,
688 _marker: core::marker::PhantomData,
689 })
690}
691
692fn check_reduce_dtype(dtype: KernelDType) -> Result<()> {
693 match dtype {
694 KernelDType::F32
695 | KernelDType::F64
696 | KernelDType::I32
697 | KernelDType::I64
698 | KernelDType::C32
699 | KernelDType::C64 => Ok(()),
700 _ => Err(StridedError::UnsupportedDType {
701 dtype: dtype.label(),
702 }),
703 }
704}
705
706fn check_reduce_op_dtype(dtype: KernelDType, op: ReduceOp) -> Result<()> {
707 if op == ReduceOp::SumSquares && !matches!(dtype, KernelDType::F32 | KernelDType::F64) {
708 return Err(StridedError::UnsupportedDType {
709 dtype: dtype.label(),
710 });
711 }
712 if matches!(op, ReduceOp::Max | ReduceOp::Min)
713 && !matches!(
714 dtype,
715 KernelDType::F32 | KernelDType::F64 | KernelDType::I32 | KernelDType::I64
716 )
717 {
718 return Err(StridedError::UnsupportedDType {
719 dtype: dtype.label(),
720 });
721 }
722 check_reduce_dtype(dtype)
723}
724
725fn checked_total_len(dims: &[usize]) -> Result<usize> {
726 if dims.is_empty() {
727 return Ok(1);
728 }
729 dims.iter()
730 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
731 .ok_or(StridedError::OffsetOverflow)
732}
733
734fn execute_copy<T>(
735 plan: &CopyPlan,
736 dest: &mut ErasedRawStridedMut<'_>,
737 src: &ErasedRawStridedRef<'_>,
738) -> Result<()>
739where
740 T: Copy + crate::MaybeSendSync + KernelStorageElement,
741{
742 let source_data = src.data_as::<T>()?;
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 source = unsafe {
748 RawStridedRef::new_unchecked(source_data, src.dims(), src.strides(), src.offset())
749 };
750 let mut dest =
751 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
752 plan.execute(&mut dest, &source)
753}
754
755fn execute_concatenate<T>(
756 plan: &ConcatenatePlan,
757 dest: &mut ErasedRawStridedMut<'_>,
758 inputs: &[ErasedRawStridedRef<'_>],
759) -> Result<()>
760where
761 T: Copy + crate::MaybeSendSync + KernelStorageElement,
762{
763 if inputs.len() != plan.input_count() {
764 return Err(StridedError::RankMismatch(inputs.len(), plan.input_count()));
765 }
766 let dest_dims = dest.dims();
767 let dest_strides = dest.strides();
768 let dest_offset = dest.offset();
769 let dest_data = dest.data_as_mut::<T>()?;
770 let mut dest_ref =
771 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
772 plan.check_dest_layout(&dest_ref)?;
773
774 for (position, input) in inputs.iter().enumerate() {
775 let input_data = input.data_as::<T>()?;
776 let input_ref = unsafe {
777 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
778 };
779 plan.check_input_layout(position, &input_ref)?;
780 plan.segment_offset(position, dest_offset)?;
781 }
782 if plan.prefers_whole_plan() {
783 let input_refs = inputs
784 .iter()
785 .map(|input| {
786 let input_data = input.data_as::<T>()?;
787 Ok(unsafe {
788 RawStridedRef::new_unchecked(
789 input_data,
790 input.dims(),
791 input.strides(),
792 input.offset(),
793 )
794 })
795 })
796 .collect::<Result<Vec<_>>>()?;
797 return plan.execute(&mut dest_ref, &input_refs);
799 }
800 for (position, input) in inputs.iter().enumerate() {
801 let input_data = input.data_as::<T>()?;
802 let input_ref = unsafe {
803 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
804 };
805 plan.execute_segment(position, &mut dest_ref, &input_ref)?;
806 }
807 Ok(())
808}
809
810fn execute_concatenate_uninit<T>(
811 plan: &ConcatenatePlan,
812 dest: &mut ErasedRawStridedUninitMut<'_>,
813 inputs: &[ErasedRawStridedPtr<'_>],
814) -> Result<()>
815where
816 T: Copy + crate::MaybeSendSync + KernelStorageElement,
817{
818 let dest_dims = dest.dims();
819 let dest_strides = dest.strides();
820 let dest_offset = dest.offset();
821 let dest_data = dest.data_as_uninit_mut::<T>()?;
822 let mut dest_ref =
823 unsafe { RawStridedMut::new_unchecked(dest_data, dest_dims, dest_strides, dest_offset) };
824 plan.check_dest_layout(&dest_ref)?;
825
826 for (position, input) in inputs.iter().enumerate() {
827 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
829 let input_data = input.data_as::<T>()?;
830 let input_ref = unsafe {
831 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
832 };
833 plan.check_input_layout(position, &input_ref)?;
834 plan.segment_offset(position, dest_offset)?;
835 }
836 if plan.prefers_whole_plan() {
837 let inputs = inputs
838 .iter()
839 .map(|input| unsafe { input.try_as_ref_after_no_overlap() })
841 .collect::<Result<Vec<_>>>()?;
842 let input_refs = inputs
843 .iter()
844 .map(|input| {
845 let input_data = input.data_as::<T>()?;
846 Ok(unsafe {
847 RawStridedRef::new_unchecked(
848 input_data,
849 input.dims(),
850 input.strides(),
851 input.offset(),
852 )
853 })
854 })
855 .collect::<Result<Vec<_>>>()?;
856 return plan.execute_uninit(&mut dest_ref, &input_refs);
858 }
859 for (position, input) in inputs.iter().enumerate() {
860 let input = unsafe { input.try_as_ref_after_no_overlap() }?;
862 let input_data = input.data_as::<T>()?;
863 let input_ref = unsafe {
864 RawStridedRef::new_unchecked(input_data, input.dims(), input.strides(), input.offset())
865 };
866 plan.execute_segment_uninit(position, &mut dest_ref, &input_ref)?;
867 }
868 Ok(())
869}
870
871fn reduce_context_is_serial(ctx: &ExecContext) -> bool {
874 ctx.is_serial()
875 || ctx
876 .max_threads_limit()
877 .is_some_and(|max_threads| max_threads.get() == 1)
878}
879
880fn execute_reduce_full<T, W, K>(
881 ctx: &ExecContext,
882 dest: &mut W,
883 src: &ErasedRawStridedRef<'_>,
884 traversal: &FullTraversal,
885) -> Result<bool>
886where
887 T: ErasedReduceScalar,
888 W: ReduceWriter<T>,
889 K: ReduceKernel<T>,
890{
891 let source = src.data_as::<T>()?;
892 let total = traversal.total();
893 if total == 0 {
894 unsafe { dest.write_at(dest.offset(), K::finish(K::identity())) };
896 return Ok(false);
897 }
898 let acc = if reduce_context_is_serial(ctx) {
899 unsafe { reduce_full_range::<T, K>(source.as_ptr(), src.offset(), traversal, 0..total)? }
905 } else {
906 ctx.run(|| reduce_full_policy::<T, K>(source, src.offset(), traversal))?
907 };
908
909 if K::needs_ordered_fallback(&acc) {
910 return Ok(true);
913 }
914 unsafe { dest.write_at(dest.offset(), K::finish(acc)) };
916 Ok(false)
917}
918
919fn reduce_full_policy<T, K>(
920 source: &[T],
921 source_base: isize,
922 traversal: &FullTraversal,
923) -> Result<K::Acc>
924where
925 T: ErasedReduceScalar,
926 K: ReduceKernel<T>,
927{
928 let total = traversal.total();
929 #[cfg(feature = "parallel")]
930 {
931 let nthreads = if K::reassociable() {
937 crate::threading::parallel_threads_for_len(total)
938 } else {
939 1
940 };
941 if nthreads > 1 {
942 let source_ptr = crate::threading::SendPtr(source.as_ptr() as *mut T);
943 return crate::threading::parallel_map_reduce(
944 0..total,
945 nthreads,
946 &|range| {
947 unsafe {
950 reduce_full_range::<T, K>(
951 source_ptr.as_const(),
952 source_base,
953 traversal,
954 range,
955 )
956 }
957 },
958 &|left, right| Ok(K::combine(left?, right?)),
959 );
960 }
961 }
962 unsafe { reduce_full_range::<T, K>(source.as_ptr(), source_base, traversal, 0..total) }
965}
966
967fn dispatch_reduce<T, W>(
968 op: ReduceOp,
969 layout: &ReduceLayout,
970 ctx: &ExecContext,
971 dest: &mut W,
972 src: &ErasedRawStridedRef<'_>,
973) -> Result<()>
974where
975 T: ErasedReduceScalar,
976 W: ReduceWriter<T>,
977{
978 match op {
981 ReduceOp::Sum => dispatch_reduce_kernel::<T, W, SumKernel>(layout, ctx, dest, src),
982 ReduceOp::Product => {
983 dispatch_reduce_kernel::<T, W, ReassociatedProductKernel>(layout, ctx, dest, src)
984 }
985 ReduceOp::StableProduct => {
986 dispatch_reduce_kernel::<T, W, StableProductKernel>(layout, ctx, dest, src)
987 }
988 ReduceOp::SumSquares => {
989 dispatch_reduce_kernel::<T, W, SumSquaresKernel>(layout, ctx, dest, src)
990 }
991 ReduceOp::Max => dispatch_reduce_kernel::<T, W, MaxKernel>(layout, ctx, dest, src),
992 ReduceOp::Min => dispatch_reduce_kernel::<T, W, MinKernel>(layout, ctx, dest, src),
993 }
994}
995
996fn dispatch_reduce_kernel<T, W, K>(
997 layout: &ReduceLayout,
998 ctx: &ExecContext,
999 dest: &mut W,
1000 src: &ErasedRawStridedRef<'_>,
1001) -> Result<()>
1002where
1003 T: ErasedReduceScalar,
1004 W: ReduceWriter<T>,
1005 K: ReduceKernel<T>,
1006{
1007 let fallback = match layout {
1008 ReduceLayout::Full { traversal, .. } => {
1009 execute_reduce_full::<T, W, K>(ctx, dest, src, traversal)?
1010 }
1011 ReduceLayout::Axes {
1012 outer_axes,
1013 inner_axes,
1014 dest_total,
1015 reduce_total,
1016 full,
1017 ..
1018 } => {
1019 let axes = AxesLayout {
1020 outer_axes,
1021 inner_axes,
1022 dest_total: *dest_total,
1023 reduce_total: *reduce_total,
1024 };
1025 match full {
1026 Some(traversal) => execute_reduce_full::<T, W, K>(ctx, dest, src, traversal)?,
1027 None => {
1028 execute_reduce_axes::<T, W, K>(ctx, dest, src, axes)?;
1032 false
1033 }
1034 }
1035 }
1036 };
1037 if fallback {
1038 return dispatch_reduce_kernel::<T, W, K::Fallback>(layout, ctx, dest, src);
1043 }
1044 Ok(())
1045}
1046
1047fn execute_reduce_axes<T, W, K>(
1048 ctx: &ExecContext,
1049 dest: &mut W,
1050 src: &ErasedRawStridedRef<'_>,
1051 layout: AxesLayout<'_>,
1052) -> Result<()>
1053where
1054 T: ErasedReduceScalar,
1055 W: ReduceWriter<T>,
1056 K: ReduceKernel<T>,
1057{
1058 if layout.dest_total == 0 {
1059 return Ok(());
1060 }
1061
1062 if layout.reduce_total == 0 {
1063 if ctx.is_serial() {
1064 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1065 } else {
1066 ctx.run(|| execute_reduce_axes_identity_policy::<T, W, K>(dest, layout))
1067 }
1068 } else if reduce_context_is_serial(ctx) {
1069 execute_reduce_axes_policy::<T, W, K>(dest, src, layout, false)
1070 } else {
1071 ctx.run(|| execute_reduce_axes_policy::<T, W, K>(dest, src, layout, true))
1072 }
1073}
1074
1075fn execute_reduce_axes_policy<T, W, K>(
1076 dest: &mut W,
1077 src: &ErasedRawStridedRef<'_>,
1078 layout: AxesLayout<'_>,
1079 allow_parallel: bool,
1080) -> Result<()>
1081where
1082 T: ErasedReduceScalar,
1083 W: ReduceWriter<T>,
1084 K: ReduceKernel<T>,
1085{
1086 let source_data = src.data_as::<T>()?;
1087 let source_base = src.offset();
1088 let dest_base = dest.offset();
1089 let dest_ptr = unsafe { dest.ptr() };
1091 #[cfg(feature = "parallel")]
1092 if allow_parallel {
1093 let work = layout.dest_total.saturating_mul(layout.reduce_total);
1099 let nthreads = crate::threading::parallel_threads_for_len(work).min(layout.dest_total);
1100 if nthreads > 1 {
1101 let dest_ptr = crate::threading::SendPtr(dest_ptr);
1102 let source_ptr = crate::threading::SendPtr(source_data.as_ptr() as *mut T);
1103 return crate::threading::parallel_map_reduce(
1104 0..layout.dest_total,
1105 nthreads,
1106 &|range| {
1107 let parts = AxesRange {
1108 source: source_ptr.as_const(),
1109 source_base,
1110 dest: dest_ptr.as_ptr(),
1111 dest_base,
1112 outer_axes: layout.outer_axes,
1113 inner_axes: layout.inner_axes,
1114 reduce_total: layout.reduce_total,
1115 };
1116 unsafe { reduce_axes_range::<T, K>(parts, range) }
1118 },
1119 &|left, right| left.and(right),
1120 );
1121 }
1122 }
1123 #[cfg(not(feature = "parallel"))]
1124 let _ = allow_parallel;
1125
1126 let parts = AxesRange {
1127 source: source_data.as_ptr(),
1128 source_base,
1129 dest: dest_ptr,
1130 dest_base,
1131 outer_axes: layout.outer_axes,
1132 inner_axes: layout.inner_axes,
1133 reduce_total: layout.reduce_total,
1134 };
1135 unsafe { reduce_axes_range::<T, K>(parts, 0..layout.dest_total) }
1142}
1143
1144fn execute_reduce_axes_identity_serial<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1145where
1146 T: ErasedReduceScalar,
1147 W: ReduceWriter<T>,
1148 K: ReduceKernel<T>,
1149{
1150 let mut outer = ReduceOuterCursor::decode(0, 0, dest.offset(), layout.outer_axes)?;
1151 for output in 0..layout.dest_total {
1152 unsafe { dest.write_at(outer.dest_offset, K::finish(K::identity())) };
1160 if output + 1 < layout.dest_total {
1161 outer.advance();
1162 }
1163 }
1164 Ok(())
1165}
1166fn execute_reduce_axes_identity_policy<T, W, K>(dest: &mut W, layout: AxesLayout<'_>) -> Result<()>
1167where
1168 T: ErasedReduceScalar,
1169 W: ReduceWriter<T>,
1170 K: ReduceKernel<T>,
1171{
1172 #[cfg(feature = "parallel")]
1173 {
1174 let nthreads = crate::threading::parallel_threads_for_len(layout.dest_total);
1175 if nthreads > 1 {
1176 return execute_reduce_axes_identity_parallel::<T, W, K>(dest, layout, nthreads);
1177 }
1178 }
1179 execute_reduce_axes_identity_serial::<T, W, K>(dest, layout)
1180}
1181#[cfg(feature = "parallel")]
1182fn execute_reduce_axes_identity_parallel<T, W, K>(
1183 dest: &mut W,
1184 layout: AxesLayout<'_>,
1185 nthreads: usize,
1186) -> Result<()>
1187where
1188 T: ErasedReduceScalar,
1189 W: ReduceWriter<T>,
1190 K: ReduceKernel<T>,
1191{
1192 let dest_ptr = crate::threading::SendPtr(unsafe { dest.ptr() });
1194 let dest_offset_base = dest.offset();
1195 crate::threading::parallel_map_reduce(
1196 0..layout.dest_total,
1197 nthreads,
1198 &|range| {
1199 let range_end = range.end;
1200 let mut outer =
1201 ReduceOuterCursor::decode(range.start, 0, dest_offset_base, layout.outer_axes)?;
1202 let dest_ptr = dest_ptr.as_ptr();
1203 for output in range {
1204 unsafe {
1212 dest_ptr
1213 .offset(outer.dest_offset)
1214 .write(K::finish(K::identity()))
1215 };
1216 if output + 1 < range_end {
1217 outer.advance();
1218 }
1219 }
1220 Ok(())
1221 },
1222 &|left, right| left.and(right),
1223 )
1224}
1225
1226trait ErasedReduceScalar:
1227 KernelStorageElement
1228 + Copy
1229 + One
1230 + Zero
1231 + crate::MaybeSendSync
1232 + crate::simd::MaybeSimdOps
1233 + crate::simd::MaybeSimdProduct
1234 + crate::simd::MaybeSimdSumSquares
1235 + crate::simd::MaybeSimdBlockFold
1236 + StableProductScalar
1237{
1238 fn reduce_sum(lhs: Self, rhs: Self) -> Self;
1239 fn reduce_product(lhs: Self, rhs: Self) -> Self;
1240 fn max_identity() -> Self;
1242 fn min_identity() -> Self;
1244 fn reduce_max(lhs: Self, rhs: Self) -> Self;
1245 fn reduce_min(lhs: Self, rhs: Self) -> Self;
1246}
1247
1248macro_rules! impl_float_erased_reduce_scalar {
1249 ($($ty:ty),* $(,)?) => {
1250 $(
1251 impl ErasedReduceScalar for $ty {
1252 #[inline(always)]
1253 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1254 lhs + rhs
1255 }
1256
1257 #[inline(always)]
1258 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1259 lhs * rhs
1260 }
1261
1262 #[inline(always)]
1263 fn max_identity() -> Self {
1264 <$ty>::NEG_INFINITY
1265 }
1266
1267 #[inline(always)]
1268 fn min_identity() -> Self {
1269 <$ty>::INFINITY
1270 }
1271
1272 #[inline(always)]
1273 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1274 if (lhs > rhs) | lhs.is_nan() {
1282 lhs
1283 } else {
1284 rhs
1285 }
1286 }
1287
1288 #[inline(always)]
1289 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1290 if (lhs < rhs) | lhs.is_nan() {
1298 lhs
1299 } else {
1300 rhs
1301 }
1302 }
1303 }
1304 )*
1305 };
1306}
1307
1308macro_rules! impl_complex_erased_reduce_scalar {
1309 ($($ty:ty),* $(,)?) => {
1310 $(
1311 impl ErasedReduceScalar for $ty {
1312 #[inline(always)]
1313 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1314 lhs + rhs
1315 }
1316
1317 #[inline(always)]
1318 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1319 lhs * rhs
1320 }
1321
1322 fn max_identity() -> Self {
1323 unreachable!("complex max reduction is rejected at plan compile")
1325 }
1326
1327 fn min_identity() -> Self {
1328 unreachable!("complex min reduction is rejected at plan compile")
1330 }
1331
1332 fn reduce_max(_lhs: Self, _rhs: Self) -> Self {
1333 unreachable!("complex max reduction is rejected at plan compile")
1335 }
1336
1337 fn reduce_min(_lhs: Self, _rhs: Self) -> Self {
1338 unreachable!("complex min reduction is rejected at plan compile")
1340 }
1341 }
1342 )*
1343 };
1344}
1345
1346macro_rules! impl_wrapping_erased_reduce_scalar {
1347 ($($ty:ty),* $(,)?) => {
1348 $(
1349 impl ErasedReduceScalar for $ty {
1350 #[inline(always)]
1351 fn reduce_sum(lhs: Self, rhs: Self) -> Self {
1352 lhs.wrapping_add(rhs)
1353 }
1354
1355 #[inline(always)]
1356 fn reduce_product(lhs: Self, rhs: Self) -> Self {
1357 lhs.wrapping_mul(rhs)
1358 }
1359
1360 #[inline(always)]
1361 fn max_identity() -> Self {
1362 <$ty>::MIN
1363 }
1364
1365 #[inline(always)]
1366 fn min_identity() -> Self {
1367 <$ty>::MAX
1368 }
1369
1370 #[inline(always)]
1371 fn reduce_max(lhs: Self, rhs: Self) -> Self {
1372 lhs.max(rhs)
1373 }
1374
1375 #[inline(always)]
1376 fn reduce_min(lhs: Self, rhs: Self) -> Self {
1377 lhs.min(rhs)
1378 }
1379 }
1380 )*
1381 };
1382}
1383
1384impl_float_erased_reduce_scalar!(f32, f64);
1385
1386impl_complex_erased_reduce_scalar!(Complex32, Complex64);
1387
1388impl_wrapping_erased_reduce_scalar!(i32, i64);
1389
1390fn validate_unique_axes(axes: &[usize], rank: usize) -> Result<()> {
1391 let mut seen = vec![false; rank];
1392 for &axis in axes {
1393 if axis >= rank {
1394 return Err(StridedError::InvalidAxis { axis, rank });
1395 }
1396 if seen[axis] {
1397 return Err(StridedError::InvalidAxis { axis, rank });
1398 }
1399 seen[axis] = true;
1400 }
1401 Ok(())
1402}
1403
1404struct ReduceOuterCursor<'a> {
1405 axes: &'a [ReduceOuterAxis],
1406 coords: CoordScratch,
1407 source_offset: isize,
1408 dest_offset: isize,
1409}
1410impl<'a> ReduceOuterCursor<'a> {
1411 fn decode(
1412 mut linear: usize,
1413 source_base: isize,
1414 dest_base: isize,
1415 axes: &'a [ReduceOuterAxis],
1416 ) -> Result<Self> {
1417 let mut coords = CoordScratch::new(axes.len());
1418 let mut source_offset = source_base;
1419 let mut dest_offset = dest_base;
1420 for (coord, axis) in coords.as_mut_slice().iter_mut().zip(axes) {
1421 debug_assert!(axis.extent != 0);
1425 *coord = linear % axis.extent;
1426 linear /= axis.extent;
1427 source_offset = checked_offset_add(source_offset, axis.source_step, *coord)?;
1428 dest_offset = checked_offset_add(dest_offset, axis.dest_step, *coord)?;
1429 }
1430 Ok(Self {
1431 axes,
1432 coords,
1433 source_offset,
1434 dest_offset,
1435 })
1436 }
1437
1438 #[inline]
1440 fn leading_coord(&self) -> usize {
1441 self.coords.first()
1442 }
1443
1444 #[inline]
1445 fn advance(&mut self) {
1446 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1450 let next = *coord + 1;
1451 if next < axis.extent {
1452 *coord = next;
1453 self.source_offset += axis.source_step;
1454 self.dest_offset += axis.dest_step;
1455 return;
1456 }
1457 *coord = 0;
1458 self.source_offset += axis.source_reset;
1459 self.dest_offset += axis.dest_reset;
1460 }
1461 }
1462}
1463struct ReduceInnerCursor<'a> {
1464 axes: &'a [ReduceInnerAxis],
1465 coords: CoordScratch,
1466 source_offset: isize,
1467}
1468impl<'a> ReduceInnerCursor<'a> {
1469 fn new(source_base: isize, axes: &'a [ReduceInnerAxis]) -> Self {
1470 Self {
1471 axes,
1472 coords: CoordScratch::new(axes.len()),
1473 source_offset: source_base,
1474 }
1475 }
1476
1477 #[inline]
1478 fn reset(&mut self, source_base: isize) {
1479 self.coords.as_mut_slice().fill(0);
1480 self.source_offset = source_base;
1481 }
1482
1483 #[inline]
1484 fn advance(&mut self) {
1485 for (coord, axis) in self.coords.as_mut_slice().iter_mut().zip(self.axes) {
1489 let next = *coord + 1;
1490 if next < axis.extent {
1491 *coord = next;
1492 self.source_offset += axis.source_step;
1493 return;
1494 }
1495 *coord = 0;
1496 self.source_offset += axis.source_reset;
1497 }
1498 }
1499}
1500fn check_reduce_layout_offset_arithmetic(dims: &[usize], strides: &[isize]) -> Result<()> {
1501 if dims.len() != strides.len() {
1502 return Err(StridedError::StrideLengthMismatch);
1503 }
1504 let mut min_offset = 0isize;
1505 let mut max_offset = 0isize;
1506 for (&dim, &stride) in dims.iter().zip(strides) {
1507 let last =
1508 isize::try_from(dim.saturating_sub(1)).map_err(|_| StridedError::OffsetOverflow)?;
1509 let extent = stride
1510 .checked_mul(last)
1511 .ok_or(StridedError::OffsetOverflow)?;
1512 if extent < 0 {
1513 min_offset = min_offset
1514 .checked_add(extent)
1515 .ok_or(StridedError::OffsetOverflow)?;
1516 } else {
1517 max_offset = max_offset
1518 .checked_add(extent)
1519 .ok_or(StridedError::OffsetOverflow)?;
1520 }
1521 }
1522 let _ = (min_offset, max_offset);
1523 Ok(())
1524}
1525fn compress_reduce_outer_axes(axes: Vec<ReduceOuterAxis>) -> Result<Vec<ReduceOuterAxis>> {
1526 let mut compressed: Vec<ReduceOuterAxis> = Vec::with_capacity(axes.len());
1527 for axis in axes {
1528 if let Some(previous) = compressed.last_mut() {
1529 let previous_extent =
1530 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1531 let expected_source = previous
1532 .source_step
1533 .checked_mul(previous_extent)
1534 .ok_or(StridedError::OffsetOverflow)?;
1535 let expected_dest = previous
1536 .dest_step
1537 .checked_mul(previous_extent)
1538 .ok_or(StridedError::OffsetOverflow)?;
1539 if axis.source_step == expected_source && axis.dest_step == expected_dest {
1540 let fused_extent = previous
1541 .extent
1542 .checked_mul(axis.extent)
1543 .ok_or(StridedError::OffsetOverflow)?;
1544 previous.extent = fused_extent;
1545 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1546 previous.dest_reset = checked_reduce_reset(fused_extent, previous.dest_step)?;
1547 continue;
1548 }
1549 }
1550 compressed.push(axis);
1551 }
1552 Ok(compressed)
1553}
1554fn compress_reduce_inner_axes(axes: Vec<ReduceInnerAxis>) -> Result<Vec<ReduceInnerAxis>> {
1555 let mut compressed: Vec<ReduceInnerAxis> = Vec::with_capacity(axes.len());
1556 for axis in axes {
1557 if let Some(previous) = compressed.last_mut() {
1558 let previous_extent =
1559 isize::try_from(previous.extent).map_err(|_| StridedError::OffsetOverflow)?;
1560 let expected_source = previous
1561 .source_step
1562 .checked_mul(previous_extent)
1563 .ok_or(StridedError::OffsetOverflow)?;
1564 if axis.source_step == expected_source {
1565 let fused_extent = previous
1566 .extent
1567 .checked_mul(axis.extent)
1568 .ok_or(StridedError::OffsetOverflow)?;
1569 previous.extent = fused_extent;
1570 previous.source_reset = checked_reduce_reset(fused_extent, previous.source_step)?;
1571 continue;
1572 }
1573 }
1574 compressed.push(axis);
1575 }
1576 Ok(compressed)
1577}
1578fn checked_reduce_reset(extent: usize, stride: isize) -> Result<isize> {
1579 if extent == 0 {
1580 return Ok(0);
1581 }
1582 let last = isize::try_from(extent - 1).map_err(|_| StridedError::OffsetOverflow)?;
1583 stride
1584 .checked_mul(last)
1585 .and_then(isize::checked_neg)
1586 .ok_or(StridedError::OffsetOverflow)
1587}
1588fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1589 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1590 let scaled = stride
1591 .checked_mul(coord)
1592 .ok_or(StridedError::OffsetOverflow)?;
1593 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1594}
1595
1596struct CoordScratch {
1597 inline: [usize; RAW_FUSED_RANK_LIMIT],
1598 heap: Option<Vec<usize>>,
1599 len: usize,
1600}
1601
1602impl CoordScratch {
1603 fn new(len: usize) -> Self {
1604 if len <= RAW_FUSED_RANK_LIMIT {
1605 Self {
1606 inline: [0; RAW_FUSED_RANK_LIMIT],
1607 heap: None,
1608 len,
1609 }
1610 } else {
1611 Self {
1612 inline: [0; RAW_FUSED_RANK_LIMIT],
1613 heap: Some(vec![0; len]),
1614 len,
1615 }
1616 }
1617 }
1618
1619 #[inline]
1620 fn first(&self) -> usize {
1621 match &self.heap {
1622 Some(heap) => heap.first().copied().unwrap_or(0),
1623 None => self.inline[0],
1624 }
1625 }
1626
1627 fn as_mut_slice(&mut self) -> &mut [usize] {
1628 match &mut self.heap {
1629 Some(heap) => heap,
1630 None => &mut self.inline[..self.len],
1631 }
1632 }
1633}