1use crate::kernel::{
4 build_plan_fused, ensure_same_shape, for_each_inner_block_preordered, same_contiguous_layout,
5 sequential_contiguous_layout, total_len,
6};
7use crate::simd;
8use crate::view::{col_major_strides, StridedArray, StridedView, StridedViewMut};
9use crate::{Exec, Result, StridedError};
10use core::num::NonZeroUsize;
11use std::ops::Range;
12use strided_view::ElementOp;
13
14use crate::cost::KernelClass;
15use crate::fuse::compute_costs;
16use crate::inline::InlineVec;
17use crate::threading::{
18 for_each_inner_block_with_offsets, is_parallel, mapreduce_threaded_fold, SendPtr,
19};
20
21const FOLD_LANES: usize = 16;
23
24const AXIS_SWEEP_MIN_LEN: usize = 16;
27
28const AXIS_SWEEP_BLOCK_BYTES: usize = 64 * 1024;
30
31#[inline(never)]
38fn fold_lanes<U, R>(lanes: [U; FOLD_LANES], init: U, reduce_fn: &R) -> U
39where
40 R: Fn(U, U) -> U,
41{
42 lanes.into_iter().fold(init, reduce_fn)
43}
44
45#[inline(always)]
55fn fold_contiguous<T, Op, M, R, U>(src: &[T], init: U, map_fn: &M, reduce_fn: &R) -> U
56where
57 T: Copy,
58 Op: ElementOp<T>,
59 M: Fn(T) -> U,
60 R: Fn(U, U) -> U,
61 U: Clone,
62{
63 if src.len() < 2 * FOLD_LANES {
64 let mut acc = init;
65 for &value in src {
66 acc = reduce_fn(acc, map_fn(Op::apply(value)));
67 }
68 return acc;
69 }
70 let (head, rest) = src.split_at(FOLD_LANES);
71 let mut lanes: [U; FOLD_LANES] = core::array::from_fn(|lane| map_fn(Op::apply(head[lane])));
72 let mut chunks = rest.chunks_exact(FOLD_LANES);
73 for chunk in chunks.by_ref() {
74 for (lane, &value) in lanes.iter_mut().zip(chunk) {
75 *lane = reduce_fn(lane.clone(), map_fn(Op::apply(value)));
76 }
77 }
78 for (lane, &value) in lanes.iter_mut().zip(chunks.remainder()) {
79 *lane = reduce_fn(lane.clone(), map_fn(Op::apply(value)));
80 }
81 fold_lanes(lanes, init, reduce_fn)
82}
83
84#[inline(always)]
89unsafe fn fold_run<T, Op, M, R, U>(
90 ptr: *const T,
91 stride: isize,
92 len: usize,
93 init: U,
94 map_fn: &M,
95 reduce_fn: &R,
96) -> U
97where
98 T: Copy,
99 Op: ElementOp<T>,
100 M: Fn(T) -> U,
101 R: Fn(U, U) -> U,
102 U: Clone,
103{
104 if stride == 1 {
105 let run = unsafe { std::slice::from_raw_parts(ptr, len) };
107 return fold_contiguous::<T, Op, M, R, U>(run, init, map_fn, reduce_fn);
108 }
109 let mut acc = init;
110 let mut cursor = ptr;
111 for index in 0..len {
112 acc = reduce_fn(acc, map_fn(Op::apply(unsafe { *cursor })));
114 if index + 1 < len {
115 cursor = cursor.wrapping_offset(stride);
116 }
117 }
118 acc
119}
120
121pub fn reduce<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
154 src: &StridedView<T, Op>,
155 map_fn: M,
156 reduce_fn: R,
157 init: U,
158 exec: Exec<'_>,
159) -> Result<U>
160where
161 M: Fn(T) -> U + Sync,
162 R: Fn(U, U) -> U + Sync,
163 U: Clone + Send + Sync,
164{
165 reduce_impl(src, map_fn, reduce_fn, init, exec)
166}
167
168fn reduce_impl<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
169 src: &StridedView<T, Op>,
170 map_fn: M,
171 reduce_fn: R,
172 init: U,
173 exec: Exec<'_>,
174) -> Result<U>
175where
176 M: Fn(T) -> U + Sync,
177 R: Fn(U, U) -> U + Sync,
178 U: Clone + Send + Sync,
179{
180 let src_ptr = src.ptr();
181 let src_dims = src.dims();
182 let src_strides = src.strides();
183
184 if total_len(src_dims)? == 0 {
188 return Ok(init);
189 }
190
191 let contiguous =
192 sequential_contiguous_layout(KernelClass::Reduce, src_dims, &[src_strides], &exec)?;
193 if contiguous.is_some() {
194 let len = total_len(src_dims)?;
195 let src = unsafe { std::slice::from_raw_parts(src_ptr, len) };
196 return Ok(simd::dispatch_if_large(len, || {
197 fold_contiguous::<T, Op, M, R, U>(src, init, &map_fn, &reduce_fn)
198 }));
199 }
200
201 let total = total_len(src_dims)?;
205 if is_parallel(KernelClass::Reduce, total, &exec)
206 && same_contiguous_layout(src_dims, &[src_strides]).is_some()
207 {
208 let src_slice = unsafe { std::slice::from_raw_parts(src_ptr, total) };
209 let result = crate::threading::parallel_map_reduce(
210 0..total,
211 crate::cost::for_len_tree(exec, KernelClass::Reduce, total),
212 &|range| {
213 simd::dispatch_if_large(range.len(), || {
214 fold_contiguous::<T, Op, M, R, U>(
215 &src_slice[range],
216 init.clone(),
217 &map_fn,
218 &reduce_fn,
219 )
220 })
221 },
222 &|a, b| reduce_fn(a, b),
223 );
224 return Ok(result);
225 }
226
227 let strides_list: [&[isize]; 1] = [src_strides];
228
229 let (fused_dims, ordered_strides, plan) =
230 build_plan_fused(src_dims, &strides_list, None, std::mem::size_of::<T>());
231
232 let total = total_len(&fused_dims)?;
233 if is_parallel(KernelClass::Reduce, total, &exec) {
234 let src_send = SendPtr(src_ptr as *mut T);
235 let costs = compute_costs(&ordered_strides);
236 let initial_offsets: crate::inline::OperandVec<isize> =
237 ordered_strides.iter().map(|_| 0isize).collect();
238 let map_fn_ref = &map_fn;
239 let reduce_fn_ref = &reduce_fn;
240 let init_ref = &init;
241 let result = mapreduce_threaded_fold(
244 &fused_dims,
245 &plan.block,
246 &ordered_strides,
247 &initial_offsets,
248 &costs,
249 KernelClass::Reduce,
250 exec,
251 &|dims, blocks, strides_list, offsets| {
252 let mut acc = init_ref.clone();
253 for_each_inner_block_with_offsets(
254 dims,
255 blocks,
256 strides_list,
257 offsets,
258 |offsets, len, strides| {
259 let mut ptr = unsafe { src_send.as_const().offset(offsets[0]) };
260 let stride = strides[0];
261 for _ in 0..len {
262 let val = Op::apply(unsafe { *ptr });
263 let mapped = map_fn_ref(val);
264 acc = reduce_fn_ref(acc.clone(), mapped);
265 unsafe {
266 ptr = ptr.offset(stride);
267 }
268 }
269 Ok(())
270 },
271 )?;
272 Ok(acc)
273 },
274 &|a, b| reduce_fn_ref(a, b),
275 )?;
276 return Ok(result);
277 }
278
279 let mut acc = init;
280 let initial_offsets: crate::inline::OperandVec<isize> =
281 ordered_strides.iter().map(|_| 0isize).collect();
282 for_each_inner_block_preordered(
283 &fused_dims,
284 &plan.block,
285 &ordered_strides,
286 &initial_offsets,
287 |offsets, len, strides| {
288 let mut ptr = unsafe { src_ptr.offset(offsets[0]) };
289 let stride = strides[0];
290 for _ in 0..len {
291 let val = Op::apply(unsafe { *ptr });
292 let mapped = map_fn(val);
293 acc = reduce_fn(acc.clone(), mapped);
294 unsafe {
295 ptr = ptr.offset(stride);
296 }
297 }
298 Ok(())
299 },
300 )?;
301
302 Ok(acc)
303}
304
305#[inline(always)]
321fn fold_contiguous2<A, B, OpA, OpB, M, R, U>(
322 a: &[A],
323 b: &[B],
324 init: U,
325 map_fn: &M,
326 reduce_fn: &R,
327) -> U
328where
329 A: Copy,
330 B: Copy,
331 OpA: ElementOp<A>,
332 OpB: ElementOp<B>,
333 M: Fn(A, B) -> U,
334 R: Fn(U, U) -> U,
335 U: Clone,
336{
337 debug_assert_eq!(a.len(), b.len());
338 if a.len() < 2 * FOLD_LANES {
339 let mut acc = init;
340 for (&value_a, &value_b) in a.iter().zip(b) {
341 acc = reduce_fn(acc, map_fn(OpA::apply(value_a), OpB::apply(value_b)));
342 }
343 return acc;
344 }
345 let (head_a, rest_a) = a.split_at(FOLD_LANES);
346 let (head_b, rest_b) = b.split_at(FOLD_LANES);
347 let mut lanes: [U; FOLD_LANES] =
348 core::array::from_fn(|lane| map_fn(OpA::apply(head_a[lane]), OpB::apply(head_b[lane])));
349 let mut chunks_a = rest_a.chunks_exact(FOLD_LANES);
350 let mut chunks_b = rest_b.chunks_exact(FOLD_LANES);
351 for (chunk_a, chunk_b) in chunks_a.by_ref().zip(chunks_b.by_ref()) {
352 for ((lane, &value_a), &value_b) in lanes.iter_mut().zip(chunk_a).zip(chunk_b) {
353 *lane = reduce_fn(
354 lane.clone(),
355 map_fn(OpA::apply(value_a), OpB::apply(value_b)),
356 );
357 }
358 }
359 for ((lane, &value_a), &value_b) in lanes
360 .iter_mut()
361 .zip(chunks_a.remainder())
362 .zip(chunks_b.remainder())
363 {
364 *lane = reduce_fn(
365 lane.clone(),
366 map_fn(OpA::apply(value_a), OpB::apply(value_b)),
367 );
368 }
369 lanes.into_iter().fold(init, reduce_fn)
370}
371
372#[inline(always)]
379#[allow(clippy::too_many_arguments)]
380unsafe fn fold_run2<A, B, OpA, OpB, M, R, U>(
381 a_ptr: *const A,
382 a_stride: isize,
383 b_ptr: *const B,
384 b_stride: isize,
385 len: usize,
386 init: &U,
387 map_fn: &M,
388 reduce_fn: &R,
389) -> U
390where
391 A: Copy,
392 B: Copy,
393 OpA: ElementOp<A>,
394 OpB: ElementOp<B>,
395 M: Fn(A, B) -> U,
396 R: Fn(U, U) -> U,
397 U: Clone,
398{
399 if a_stride == 1 && b_stride == 1 {
400 let a = unsafe { std::slice::from_raw_parts(a_ptr, len) };
403 let b = unsafe { std::slice::from_raw_parts(b_ptr, len) };
404 return fold_contiguous2::<A, B, OpA, OpB, M, R, U>(a, b, init.clone(), map_fn, reduce_fn);
405 }
406 let mut acc = init.clone();
407 let mut cursor_a = a_ptr;
408 let mut cursor_b = b_ptr;
409 for index in 0..len {
410 let mapped = map_fn(
412 OpA::apply(unsafe { *cursor_a }),
413 OpB::apply(unsafe { *cursor_b }),
414 );
415 acc = reduce_fn(acc, mapped);
416 if index + 1 < len {
417 cursor_a = cursor_a.wrapping_offset(a_stride);
418 cursor_b = cursor_b.wrapping_offset(b_stride);
419 }
420 }
421 acc
422}
423
424pub fn zip_reduce<A, B, OpA, OpB, M, R, U>(
486 a: &StridedView<A, OpA>,
487 b: &StridedView<B, OpB>,
488 map_fn: M,
489 reduce_fn: R,
490 init: U,
491 exec: Exec<'_>,
492) -> Result<U>
493where
494 A: Copy + Send + Sync,
495 B: Copy + Send + Sync,
496 OpA: ElementOp<A>,
497 OpB: ElementOp<B>,
498 M: Fn(A, B) -> U + Sync,
499 R: Fn(U, U) -> U + Sync,
500 U: Clone + Send + Sync,
501{
502 ensure_same_shape(a.dims(), b.dims())?;
503
504 let a_ptr = a.ptr();
505 let b_ptr = b.ptr();
506 let dims = a.dims();
507 let a_strides = a.strides();
508 let b_strides = b.strides();
509
510 let total = total_len(dims)?;
514 if total == 0 {
515 return Ok(init);
516 }
517
518 if sequential_contiguous_layout(KernelClass::Reduce, dims, &[a_strides, b_strides], &exec)?
519 .is_some()
520 {
521 return Ok(simd::dispatch_if_large(total, || {
522 let sa = unsafe { std::slice::from_raw_parts(a_ptr, total) };
524 let sb = unsafe { std::slice::from_raw_parts(b_ptr, total) };
525 fold_contiguous2::<A, B, OpA, OpB, M, R, U>(sa, sb, init, &map_fn, &reduce_fn)
526 }));
527 }
528
529 if is_parallel(KernelClass::Reduce, total, &exec)
532 && same_contiguous_layout(dims, &[a_strides, b_strides]).is_some()
533 {
534 let sa = unsafe { std::slice::from_raw_parts(a_ptr, total) };
536 let sb = unsafe { std::slice::from_raw_parts(b_ptr, total) };
537 let result = crate::threading::parallel_map_reduce(
538 0..total,
539 crate::cost::for_len_tree(exec, KernelClass::Reduce, total),
540 &|range| {
541 let len = range.len();
542 simd::dispatch_if_large(len, || {
543 fold_contiguous2::<A, B, OpA, OpB, M, R, U>(
544 &sa[range.clone()],
545 &sb[range],
546 init.clone(),
547 &map_fn,
548 &reduce_fn,
549 )
550 })
551 },
552 &|left, right| reduce_fn(left, right),
553 );
554 return Ok(result);
555 }
556
557 let strides_list: [&[isize]; 2] = [a_strides, b_strides];
558 let elem_size = std::mem::size_of::<A>().max(std::mem::size_of::<B>());
559 let (fused_dims, ordered_strides, plan) =
560 build_plan_fused(dims, &strides_list, None, elem_size);
561
562 let total = total_len(&fused_dims)?;
563 if is_parallel(KernelClass::Reduce, total, &exec) {
564 let a_send = SendPtr(a_ptr as *mut A);
565 let b_send = SendPtr(b_ptr as *mut B);
566 let costs = compute_costs(&ordered_strides);
567 let initial_offsets: crate::inline::OperandVec<isize> =
568 ordered_strides.iter().map(|_| 0isize).collect();
569 let map_fn_ref = &map_fn;
570 let reduce_fn_ref = &reduce_fn;
571 let init_ref = &init;
572 let result = mapreduce_threaded_fold(
575 &fused_dims,
576 &plan.block,
577 &ordered_strides,
578 &initial_offsets,
579 &costs,
580 KernelClass::Reduce,
581 exec,
582 &|dims, blocks, strides_list, offsets| {
583 let mut acc = init_ref.clone();
584 for_each_inner_block_with_offsets(
585 dims,
586 blocks,
587 strides_list,
588 offsets,
589 |offsets, len, strides| {
590 acc = unsafe {
593 fold_run2::<A, B, OpA, OpB, M, R, U>(
594 a_send.as_const().offset(offsets[0]),
595 strides[0],
596 b_send.as_const().offset(offsets[1]),
597 strides[1],
598 len,
599 &acc,
600 map_fn_ref,
601 reduce_fn_ref,
602 )
603 };
604 Ok(())
605 },
606 )?;
607 Ok(acc)
608 },
609 &|left, right| reduce_fn_ref(left, right),
610 )?;
611 return Ok(result);
612 }
613
614 let mut acc = init;
615 let initial_offsets: crate::inline::OperandVec<isize> =
616 ordered_strides.iter().map(|_| 0isize).collect();
617 for_each_inner_block_preordered(
618 &fused_dims,
619 &plan.block,
620 &ordered_strides,
621 &initial_offsets,
622 |offsets, len, strides| {
623 acc = unsafe {
626 fold_run2::<A, B, OpA, OpB, M, R, U>(
627 a_ptr.offset(offsets[0]),
628 strides[0],
629 b_ptr.offset(offsets[1]),
630 strides[1],
631 len,
632 &acc,
633 &map_fn,
634 &reduce_fn,
635 )
636 };
637 Ok(())
638 },
639 )?;
640
641 Ok(acc)
642}
643
644pub fn reduce_axis<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
659 src: &StridedView<T, Op>,
660 axis: usize,
661 map_fn: M,
662 reduce_fn: R,
663 init: U,
664 exec: Exec<'_>,
665) -> Result<StridedArray<U>>
666where
667 M: Fn(T) -> U + Sync,
668 R: Fn(U, U) -> U + Sync,
669 U: Clone + Send + Sync,
670{
671 let rank = src.ndim();
672 if axis >= rank {
673 return Err(StridedError::InvalidAxis { axis, rank });
674 }
675 total_len(src.dims())?;
677 let out_dims: InlineVec<usize> = src
678 .dims()
679 .iter()
680 .enumerate()
681 .filter(|(i, _)| *i != axis)
682 .map(|(_, &d)| d)
683 .collect();
684 let (out_dims, out_strides) = if out_dims.is_empty() {
687 let mut dims = InlineVec::new();
688 dims.push(1usize);
689 let strides = col_major_strides(&dims);
690 (dims, strides)
691 } else {
692 let strides = col_major_strides(&out_dims);
693 (out_dims, strides)
694 };
695 let total_out = total_len(&out_dims)?;
696 let mut out =
697 StridedArray::<U>::from_parts(vec![init.clone(); total_out], &out_dims, &out_strides, 0)?;
698 {
699 let mut dest = out.view_mut();
700 reduce_axis_into_impl(&mut dest, src, axis, map_fn, reduce_fn, init, exec)?;
702 }
703 Ok(out)
704}
705
706pub fn reduce_axis_into<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
735 dest: &mut StridedViewMut<U>,
736 src: &StridedView<T, Op>,
737 axis: usize,
738 map_fn: M,
739 reduce_fn: R,
740 init: U,
741 exec: Exec<'_>,
742) -> Result<()>
743where
744 M: Fn(T) -> U + Sync,
745 R: Fn(U, U) -> U + Sync,
746 U: Clone + Send + Sync,
747{
748 let rank = src.ndim();
749 if axis >= rank {
750 return Err(StridedError::InvalidAxis { axis, rank });
751 }
752 let src_dims = src.dims();
753 total_len(src_dims)?;
756 let kept_len = rank - 1;
757 let dest_len = total_len(dest.dims())?;
758 if kept_len == 0 {
759 if dest_len != 1 {
760 return Err(StridedError::ShapeMismatch(vec![dest_len], vec![1]));
761 }
762 } else {
763 if dest.ndim() != kept_len {
764 return Err(StridedError::RankMismatch(dest.ndim(), kept_len));
765 }
766 let dest_dims = dest.dims();
767 let mut d = 0usize;
768 for (i, &src_dim) in src_dims.iter().enumerate() {
769 if i == axis {
770 continue;
771 }
772 if src_dim != dest_dims[d] {
773 let expected: Vec<usize> = src_dims
774 .iter()
775 .enumerate()
776 .filter(|(i, _)| *i != axis)
777 .map(|(_, &d)| d)
778 .collect();
779 return Err(StridedError::ShapeMismatch(dest_dims.to_vec(), expected));
780 }
781 d += 1;
782 }
783 if !crate::layout_check::is_injective_layout(dest.dims(), dest.strides()) {
784 return Err(StridedError::NonInjectiveOutputLayout);
785 }
786 }
787 reduce_axis_into_impl(dest, src, axis, map_fn, reduce_fn, init, exec)
788}
789
790fn reduce_axis_into_impl<T: Copy + Send + Sync, Op: ElementOp<T>, M, R, U>(
797 dest: &mut StridedViewMut<U>,
798 src: &StridedView<T, Op>,
799 axis: usize,
800 map_fn: M,
801 reduce_fn: R,
802 init: U,
803 exec: Exec<'_>,
804) -> Result<()>
805where
806 M: Fn(T) -> U + Sync,
807 R: Fn(U, U) -> U + Sync,
808 U: Clone + Send + Sync,
809{
810 let rank = src.ndim();
811 let src_dims = src.dims();
812 let src_strides = src.strides();
813 let src_ptr = src.ptr();
814 let dest_len = total_len(dest.dims())?;
815 let axis_len = src_dims[axis];
816 let axis_stride = src_strides[axis];
817
818 if axis_len == 0 {
819 fill_outputs_with(dest, &init);
824 return Ok(());
825 }
826
827 if rank == 1 {
828 let acc = unsafe {
831 fold_run::<T, Op, M, R, U>(src_ptr, axis_stride, axis_len, init, &map_fn, &reduce_fn)
832 };
833 unsafe { *dest.as_mut_ptr() = acc };
834 return Ok(());
835 }
836 if dest_len == 0 {
837 return Ok(());
838 }
839
840 let dest_stride_view = dest.strides();
845 let mut kept: InlineVec<(usize, isize)> = InlineVec::new();
846 let mut dest_linear = true;
847 let mut prefix = 1usize;
848 for (i, (&dim, &src_stride)) in src_dims.iter().zip(src_strides).enumerate() {
849 if i == axis {
850 continue;
851 }
852 let d = kept.len();
853 kept.push((dim, src_stride));
854 if dest_linear && dest_stride_view[d] != prefix as isize {
855 dest_linear = false;
856 }
857 prefix = prefix.saturating_mul(dim);
858 }
859 let dest_strides: InlineVec<isize> = if dest_linear {
860 InlineVec::new()
861 } else {
862 dest_stride_view.iter().copied().collect()
863 };
864 let out_ptr = dest.as_mut_ptr();
865 let parts = AxisParts {
866 kept: &kept,
867 dest_strides: &dest_strides,
868 axis_len,
869 axis_stride,
870 map_fn: &map_fn,
871 reduce_fn: &reduce_fn,
872 init: &init,
873 };
874
875 let work = dest_len.saturating_mul(axis_len);
876 let exec = crate::cost::for_len(exec, KernelClass::Reduce, work)
877 .limit(NonZeroUsize::new(dest_len).unwrap_or(NonZeroUsize::MIN));
878 if !exec.is_serial() {
879 let src_send = SendPtr(src_ptr as *mut T);
880 let out_send = SendPtr(out_ptr);
881 let parts = &parts;
882 if dest_linear {
883 crate::threading::parallel_for_each(0..dest_len, exec, &|range| {
884 unsafe {
888 reduce_axis_range_linear::<T, Op, M, R, U>(
889 src_send.as_const(),
890 out_send.as_ptr(),
891 range,
892 parts,
893 )
894 }
895 });
896 } else {
897 crate::threading::parallel_for_each(0..dest_len, exec, &|range| {
898 unsafe {
900 reduce_axis_range_strided::<T, Op, M, R, U>(
901 src_send.as_const(),
902 out_send.as_ptr(),
903 range,
904 parts,
905 )
906 }
907 });
908 }
909 return Ok(());
910 }
911
912 if dest_linear {
914 unsafe {
915 reduce_axis_range_linear::<T, Op, M, R, U>(src_ptr, out_ptr, 0..dest_len, &parts)
916 };
917 } else {
918 unsafe {
919 reduce_axis_range_strided::<T, Op, M, R, U>(src_ptr, out_ptr, 0..dest_len, &parts)
920 };
921 }
922 Ok(())
923}
924
925fn fill_outputs_with<U: Clone>(dest: &mut StridedViewMut<U>, value: &U) {
932 let dims = dest.dims();
933 if dims.iter().any(|&dim| dim == 0) {
934 return;
935 }
936 let strides = dest.strides();
937 let rank = dims.len();
938 let total: usize = dims.iter().product();
939 let base = dest.as_mut_ptr();
940 if rank == 0 {
941 unsafe { *base = value.clone() };
943 return;
944 }
945 let mut coords_inline = [0usize; strided_view::INLINE_RANK];
946 let mut coords_heap: Vec<usize> = Vec::new();
947 let coords: &mut [usize] = if rank <= strided_view::INLINE_RANK {
948 &mut coords_inline[..rank]
949 } else {
950 coords_heap.resize(rank, 0);
951 &mut coords_heap[..]
952 };
953 let mut offset = 0isize;
954 for _ in 0..total {
955 unsafe { *base.offset(offset) = value.clone() };
959 for axis in 0..rank {
960 coords[axis] += 1;
961 offset += strides[axis];
962 if coords[axis] < dims[axis] {
963 break;
964 }
965 coords[axis] = 0;
966 offset -= dims[axis] as isize * strides[axis];
967 }
968 }
969}
970
971struct AxisParts<'a, M, R, U> {
974 kept: &'a [(usize, isize)],
976 dest_strides: &'a [isize],
978 axis_len: usize,
979 axis_stride: isize,
980 map_fn: &'a M,
981 reduce_fn: &'a R,
982 init: &'a U,
983}
984
985#[inline(never)]
998unsafe fn reduce_axis_range_linear<T, Op, M, R, U>(
999 src: *const T,
1000 out: *mut U,
1001 range: Range<usize>,
1002 parts: &AxisParts<'_, M, R, U>,
1003) where
1004 T: Copy,
1005 Op: ElementOp<T>,
1006 M: Fn(T) -> U,
1007 R: Fn(U, U) -> U,
1008 U: Clone,
1009{
1010 let kept = parts.kept;
1011 let (lead_extent, lead_stride) = kept[0];
1012 let sweep = lead_stride == 1 && lead_extent >= AXIS_SWEEP_MIN_LEN && parts.axis_len > 1;
1013 let block = (AXIS_SWEEP_BLOCK_BYTES / std::mem::size_of::<U>().max(1)).max(AXIS_SWEEP_MIN_LEN);
1014
1015 let coords_len = kept.len();
1020 let mut coords_inline = [0usize; strided_view::INLINE_RANK];
1021 let mut coords_heap: Vec<usize> = Vec::new();
1022 let coords: &mut [usize] = if coords_len <= strided_view::INLINE_RANK {
1023 &mut coords_inline[..coords_len]
1024 } else {
1025 coords_heap.resize(coords_len, 0);
1026 &mut coords_heap[..]
1027 };
1028 let mut rest = range.start;
1029 let mut src_off = 0isize;
1030 for (coord, &(extent, stride)) in coords.iter_mut().zip(kept) {
1031 *coord = rest % extent;
1032 rest /= extent;
1033 src_off += *coord as isize * stride;
1034 }
1035
1036 let mut output = range.start;
1037 while output < range.end {
1038 let len = if sweep {
1039 (lead_extent - coords[0]).min(range.end - output).min(block)
1040 } else {
1041 1
1042 };
1043 if sweep {
1044 unsafe { sweep_block::<T, Op, M, R, U>(src, src_off, out.add(output), len, parts) };
1048 } else {
1049 unsafe {
1051 let acc = fold_run::<T, Op, M, R, U>(
1052 src.offset(src_off),
1053 parts.axis_stride,
1054 parts.axis_len,
1055 parts.init.clone(),
1056 parts.map_fn,
1057 parts.reduce_fn,
1058 );
1059 *out.add(output) = acc;
1060 }
1061 }
1062 output += len;
1063 if output >= range.end {
1064 break;
1065 }
1066 coords[0] += len;
1069 src_off += len as isize * lead_stride;
1070 let mut axis = 0;
1071 while coords[axis] == kept[axis].0 {
1072 src_off -= kept[axis].0 as isize * kept[axis].1;
1073 coords[axis] = 0;
1074 axis += 1;
1075 coords[axis] += 1;
1076 src_off += kept[axis].1;
1077 }
1078 }
1079}
1080
1081#[inline(never)]
1091unsafe fn reduce_axis_range_strided<T, Op, M, R, U>(
1092 src: *const T,
1093 out: *mut U,
1094 range: Range<usize>,
1095 parts: &AxisParts<'_, M, R, U>,
1096) where
1097 T: Copy,
1098 Op: ElementOp<T>,
1099 M: Fn(T) -> U,
1100 R: Fn(U, U) -> U,
1101 U: Clone,
1102{
1103 let kept = parts.kept;
1104 let dest_strides = parts.dest_strides;
1105 let (lead_extent, lead_stride) = kept[0];
1106 let lead_dest_stride = dest_strides[0];
1107 let sweep = lead_stride == 1
1108 && lead_dest_stride == 1
1109 && lead_extent >= AXIS_SWEEP_MIN_LEN
1110 && parts.axis_len > 1;
1111 let block = (AXIS_SWEEP_BLOCK_BYTES / std::mem::size_of::<U>().max(1)).max(AXIS_SWEEP_MIN_LEN);
1112
1113 let coords_len = kept.len();
1117 let mut coords_inline = [0usize; strided_view::INLINE_RANK];
1118 let mut coords_heap: Vec<usize> = Vec::new();
1119 let coords: &mut [usize] = if coords_len <= strided_view::INLINE_RANK {
1120 &mut coords_inline[..coords_len]
1121 } else {
1122 coords_heap.resize(coords_len, 0);
1123 &mut coords_heap[..]
1124 };
1125 let mut rest = range.start;
1126 let mut src_off = 0isize;
1127 let mut dest_off = 0isize;
1128 for (coord, (&(extent, stride), &dest_stride)) in
1129 coords.iter_mut().zip(kept.iter().zip(dest_strides))
1130 {
1131 *coord = rest % extent;
1132 rest /= extent;
1133 src_off += *coord as isize * stride;
1134 dest_off += *coord as isize * dest_stride;
1135 }
1136
1137 let mut output = range.start;
1138 while output < range.end {
1139 let len = if sweep {
1140 (lead_extent - coords[0]).min(range.end - output).min(block)
1141 } else {
1142 1
1143 };
1144 if sweep {
1145 unsafe {
1148 sweep_block::<T, Op, M, R, U>(src, src_off, out.offset(dest_off), len, parts)
1149 };
1150 } else {
1151 unsafe {
1153 let acc = fold_run::<T, Op, M, R, U>(
1154 src.offset(src_off),
1155 parts.axis_stride,
1156 parts.axis_len,
1157 parts.init.clone(),
1158 parts.map_fn,
1159 parts.reduce_fn,
1160 );
1161 *out.offset(dest_off) = acc;
1162 }
1163 }
1164 output += len;
1165 if output >= range.end {
1166 break;
1167 }
1168 coords[0] += len;
1169 src_off += len as isize * lead_stride;
1170 dest_off += len as isize * lead_dest_stride;
1171 let mut axis = 0;
1172 while coords[axis] == kept[axis].0 {
1173 src_off -= kept[axis].0 as isize * kept[axis].1;
1174 dest_off -= kept[axis].0 as isize * dest_strides[axis];
1175 coords[axis] = 0;
1176 axis += 1;
1177 coords[axis] += 1;
1178 src_off += kept[axis].1;
1179 dest_off += dest_strides[axis];
1180 }
1181 }
1182}
1183
1184#[inline(always)]
1192unsafe fn sweep_block<T, Op, M, R, U>(
1193 src: *const T,
1194 src_off: isize,
1195 out: *mut U,
1196 len: usize,
1197 parts: &AxisParts<'_, M, R, U>,
1198) where
1199 T: Copy,
1200 Op: ElementOp<T>,
1201 M: Fn(T) -> U,
1202 R: Fn(U, U) -> U,
1203 U: Clone,
1204{
1205 let map_fn = parts.map_fn;
1206 let reduce_fn = parts.reduce_fn;
1207 let out = unsafe { std::slice::from_raw_parts_mut(out, len) };
1209 let column = |k: usize| {
1210 unsafe {
1212 std::slice::from_raw_parts(src.offset(src_off + k as isize * parts.axis_stride), len)
1213 }
1214 };
1215 let first = column(0);
1216 for (slot, &value) in out.iter_mut().zip(first) {
1217 *slot = reduce_fn(parts.init.clone(), map_fn(Op::apply(value)));
1218 }
1219 let mut k = 1;
1220 while k + 4 <= parts.axis_len {
1221 let (c0, c1, c2, c3) = (column(k), column(k + 1), column(k + 2), column(k + 3));
1222 for (index, slot) in out.iter_mut().enumerate() {
1223 let mut acc = reduce_fn(slot.clone(), map_fn(Op::apply(c0[index])));
1224 acc = reduce_fn(acc, map_fn(Op::apply(c1[index])));
1225 acc = reduce_fn(acc, map_fn(Op::apply(c2[index])));
1226 acc = reduce_fn(acc, map_fn(Op::apply(c3[index])));
1227 *slot = acc;
1228 }
1229 k += 4;
1230 }
1231 while k < parts.axis_len {
1232 let values = column(k);
1233 for (slot, &value) in out.iter_mut().zip(values) {
1234 *slot = reduce_fn(slot.clone(), map_fn(Op::apply(value)));
1235 }
1236 k += 1;
1237 }
1238}