1use crate::kernel::{
10 build_plan_fused, build_plan_fused_small, ensure_same_shape, for_each_inner_block_preordered,
11 sequential_contiguous_layout, total_len, SMALL_TENSOR_THRESHOLD,
12};
13use crate::maybe_sync::{MaybeSendSync, MaybeSync};
14use crate::simd;
15use crate::view::{StridedView, StridedViewMut};
16use crate::{Result, StridedError};
17use core::mem::MaybeUninit;
18use std::ops::Mul;
19use strided_view::{ElementOp, Identity};
20
21#[cfg(feature = "parallel")]
22use crate::fuse::compute_costs;
23#[cfg(feature = "parallel")]
24use crate::threading::{for_each_inner_block_with_offsets, mapreduce_threaded, MINTHREADLENGTH};
25#[cfg(feature = "parallel")]
26use smallvec::SmallVec;
27
28#[cfg(feature = "parallel")]
29type AxisVec<T> = SmallVec<[T; 8]>;
30#[cfg(not(feature = "parallel"))]
31type AxisVec<T> = Vec<T>;
32
33const CONTIGUOUS_RANGE_MIN_LEN: usize = 1 << 15;
34
35#[derive(Clone, Copy, Debug)]
41pub struct ValidatedDestinationLayout(());
42
43#[inline]
44fn validate_destination_layout(
45 dims: &[usize],
46 strides: &[isize],
47) -> Result<ValidatedDestinationLayout> {
48 if crate::layout_check::is_injective_layout(dims, strides) {
49 Ok(ValidatedDestinationLayout(()))
50 } else {
51 Err(StridedError::NonInjectiveOutputLayout)
52 }
53}
54
55#[inline]
56pub fn validate_destination_layout_without_alloc(
70 dims: &[usize],
71 strides: &[isize],
72) -> Result<ValidatedDestinationLayout> {
73 if crate::layout_check::is_injective_layout_without_alloc(dims, strides) {
74 Ok(ValidatedDestinationLayout(()))
75 } else {
76 Err(StridedError::NonInjectiveOutputLayout)
77 }
78}
79
80fn reachable_byte_range(
81 ptr: usize,
82 elem_size: usize,
83 dims: &[usize],
84 strides: &[isize],
85) -> Result<Option<(usize, usize)>> {
86 if elem_size == 0 || dims.contains(&0) {
87 return Ok(None);
88 }
89
90 let mut min_offset = 0isize;
91 let mut max_offset = 0isize;
92 for (&dim, &stride) in dims.iter().zip(strides) {
93 if dim <= 1 {
94 continue;
95 }
96 let extent = isize::try_from(dim - 1).map_err(|_| StridedError::OffsetOverflow)?;
97 let span = stride
98 .checked_mul(extent)
99 .ok_or(StridedError::OffsetOverflow)?;
100 if span < 0 {
101 min_offset = min_offset
102 .checked_add(span)
103 .ok_or(StridedError::OffsetOverflow)?;
104 } else {
105 max_offset = max_offset
106 .checked_add(span)
107 .ok_or(StridedError::OffsetOverflow)?;
108 }
109 }
110
111 let ptr = ptr as i128;
112 let elem_size = elem_size as i128;
113 let start = ptr
114 .checked_add(
115 (min_offset as i128)
116 .checked_mul(elem_size)
117 .ok_or(StridedError::OffsetOverflow)?,
118 )
119 .ok_or(StridedError::OffsetOverflow)?;
120 let end = ptr
121 .checked_add(
122 (max_offset as i128)
123 .checked_mul(elem_size)
124 .and_then(|offset| offset.checked_add(elem_size))
125 .ok_or(StridedError::OffsetOverflow)?,
126 )
127 .ok_or(StridedError::OffsetOverflow)?;
128 if start < 0 || end < 0 || start > usize::MAX as i128 || end > usize::MAX as i128 {
129 return Err(StridedError::OffsetOverflow);
130 }
131 Ok(Some((start as usize, end as usize)))
132}
133
134fn validate_typed_no_overlap<D, A, Op: ElementOp<A>>(
135 dest: &StridedViewMut<MaybeUninit<D>>,
136 input: &StridedView<A, Op>,
137 input_index: usize,
138) -> Result<()> {
139 let dest_range = reachable_byte_range(
140 dest.ptr() as usize,
141 core::mem::size_of::<D>(),
142 dest.dims(),
143 dest.strides(),
144 )?;
145 let input_range = reachable_byte_range(
146 input.ptr() as usize,
147 core::mem::size_of::<A>(),
148 input.dims(),
149 input.strides(),
150 )?;
151 if let (Some((dest_start, dest_end)), Some((input_start, input_end))) =
152 (dest_range, input_range)
153 {
154 if dest_start < input_end && input_start < dest_end {
155 return Err(StridedError::OverlappingInputOutput { input: input_index });
156 }
157 }
158 Ok(())
159}
160
161trait MapKernel<A, D> {
179 fn apply(&self, src: &[A], dst: &mut [D]) -> bool;
181}
182
183struct NoKernel;
184
185impl<A, D> MapKernel<A, D> for NoKernel {
186 #[inline(always)]
187 fn apply(&self, _src: &[A], _dst: &mut [D]) -> bool {
188 false
189 }
190}
191
192struct SliceKernel<K>(K);
193
194impl<A, D, K: Fn(&[A], &mut [D]) -> bool> MapKernel<A, D> for SliceKernel<K> {
195 #[inline(always)]
196 fn apply(&self, src: &[A], dst: &mut [D]) -> bool {
197 (self.0)(src, dst)
198 }
199}
200
201unsafe fn inner_loop_map1<D: Copy, A: Copy, Op: ElementOp<A>, K: MapKernel<A, D>>(
202 dp: *mut D,
203 ds: isize,
204 sp: *const A,
205 ss: isize,
206 len: usize,
207 f: &impl Fn(A) -> D,
208 kernel: &K,
209) {
210 if ds == 1 && ss == 1 {
211 let src = std::slice::from_raw_parts(sp, len);
212 let dst = std::slice::from_raw_parts_mut(dp, len);
213 if kernel.apply(src, dst) {
214 return;
215 }
216 simd::dispatch_if_large(len, || {
217 for (d, s) in dst.iter_mut().zip(src.iter()) {
218 *d = f(Op::apply(*s));
219 }
220 });
221 } else {
222 let mut dp = dp;
223 let mut sp = sp;
224 for _ in 0..len {
225 *dp = f(Op::apply(*sp));
226 dp = dp.wrapping_offset(ds);
227 sp = sp.wrapping_offset(ss);
228 }
229 }
230}
231
232#[inline(always)]
234unsafe fn inner_loop_map2<D: Copy, A: Copy, B: Copy, OpA: ElementOp<A>, OpB: ElementOp<B>>(
235 dp: *mut D,
236 ds: isize,
237 ap: *const A,
238 a_s: isize,
239 bp: *const B,
240 b_s: isize,
241 len: usize,
242 f: &impl Fn(A, B) -> D,
243) {
244 if ds == 1 && a_s == 1 && b_s == 1 {
245 let src_a = std::slice::from_raw_parts(ap, len);
246 let src_b = std::slice::from_raw_parts(bp, len);
247 let dst = std::slice::from_raw_parts_mut(dp, len);
248 simd::dispatch_if_large(len, || {
249 for i in 0..len {
250 dst[i] = f(OpA::apply(src_a[i]), OpB::apply(src_b[i]));
251 }
252 });
253 } else if ds == 1 && a_s == 1 && b_s == 0 {
254 let src_a = std::slice::from_raw_parts(ap, len);
255 let b = OpB::apply(*bp);
256 let dst = std::slice::from_raw_parts_mut(dp, len);
257 simd::dispatch_if_large(len, || {
258 for i in 0..len {
259 dst[i] = f(OpA::apply(src_a[i]), b);
260 }
261 });
262 } else if ds == 1 && a_s == 0 && b_s == 1 {
263 let a = OpA::apply(*ap);
264 let src_b = std::slice::from_raw_parts(bp, len);
265 let dst = std::slice::from_raw_parts_mut(dp, len);
266 simd::dispatch_if_large(len, || {
267 for i in 0..len {
268 dst[i] = f(a, OpB::apply(src_b[i]));
269 }
270 });
271 } else if ds == 1 && a_s == 0 && b_s == 0 {
272 let a = OpA::apply(*ap);
273 let b = OpB::apply(*bp);
274 let dst = std::slice::from_raw_parts_mut(dp, len);
275 simd::dispatch_if_large(len, || {
276 for d in dst.iter_mut() {
277 *d = f(a, b);
278 }
279 });
280 } else if ds == 1 && b_s == 0 {
281 let b = OpB::apply(*bp);
282 let dst = std::slice::from_raw_parts_mut(dp, len);
283 let mut ap = ap;
284 simd::dispatch_if_large(len, || {
285 for d in dst.iter_mut() {
286 *d = f(OpA::apply(*ap), b);
287 ap = ap.wrapping_offset(a_s);
288 }
289 });
290 } else if ds == 1 && a_s == 0 {
291 let a = OpA::apply(*ap);
292 let dst = std::slice::from_raw_parts_mut(dp, len);
293 let mut bp = bp;
294 simd::dispatch_if_large(len, || {
295 for d in dst.iter_mut() {
296 *d = f(a, OpB::apply(*bp));
297 bp = bp.wrapping_offset(b_s);
298 }
299 });
300 } else {
301 let mut dp = dp;
302 let mut ap = ap;
303 let mut bp = bp;
304 for _ in 0..len {
305 *dp = f(OpA::apply(*ap), OpB::apply(*bp));
306 dp = dp.wrapping_offset(ds);
307 ap = ap.wrapping_offset(a_s);
308 bp = bp.wrapping_offset(b_s);
309 }
310 }
311}
312
313trait MulOutput<D: Copy + 'static>: Copy + MaybeSendSync + 'static {
315 type Slot: Copy + MaybeSendSync + 'static;
316
317 unsafe fn write(dst: *mut Self::Slot, value: D);
318
319 unsafe fn try_contiguous<A: 'static, B: 'static>(
320 dst: *mut Self::Slot,
321 len: usize,
322 a: &[A],
323 b: &[B],
324 ) -> bool;
325}
326
327#[derive(Clone, Copy)]
328struct InitializedOutput;
329
330impl<D: Copy + MaybeSendSync + 'static> MulOutput<D> for InitializedOutput {
331 type Slot = D;
332
333 #[inline(always)]
334 unsafe fn write(dst: *mut D, value: D) {
335 unsafe { dst.write(value) };
336 }
337
338 #[inline(always)]
339 unsafe fn try_contiguous<A: 'static, B: 'static>(
340 dst: *mut D,
341 len: usize,
342 a: &[A],
343 b: &[B],
344 ) -> bool {
345 unsafe { simd::try_mul_contiguous_ptr(dst, len, a, b) }
346 }
347}
348
349#[derive(Clone, Copy)]
350struct UninitializedOutput;
351
352impl<D: Copy + MaybeSendSync + 'static> MulOutput<D> for UninitializedOutput {
353 type Slot = MaybeUninit<D>;
354
355 #[inline(always)]
356 unsafe fn write(dst: *mut MaybeUninit<D>, value: D) {
357 unsafe { dst.write(MaybeUninit::new(value)) };
358 }
359
360 #[inline(always)]
361 unsafe fn try_contiguous<A: 'static, B: 'static>(
362 dst: *mut MaybeUninit<D>,
363 len: usize,
364 a: &[A],
365 b: &[B],
366 ) -> bool {
367 unsafe { simd::try_mul_contiguous_ptr(dst.cast::<D>(), len, a, b) }
370 }
371}
372
373#[inline(always)]
374fn multiply_value<A, B, D>(lhs: A, rhs: B) -> D
375where
376 A: Copy + Mul<B, Output = D> + 'static,
377 B: Copy + 'static,
378 D: Copy + 'static,
379{
380 use core::any::TypeId;
381
382 if TypeId::of::<A>() == TypeId::of::<i32>()
383 && TypeId::of::<B>() == TypeId::of::<i32>()
384 && TypeId::of::<D>() == TypeId::of::<i32>()
385 {
386 let lhs = unsafe { *(&lhs as *const A).cast::<i32>() };
388 let rhs = unsafe { *(&rhs as *const B).cast::<i32>() };
389 let value = lhs.wrapping_mul(rhs);
390 return unsafe { core::mem::transmute_copy(&value) };
391 }
392 if TypeId::of::<A>() == TypeId::of::<i64>()
393 && TypeId::of::<B>() == TypeId::of::<i64>()
394 && TypeId::of::<D>() == TypeId::of::<i64>()
395 {
396 let lhs = unsafe { *(&lhs as *const A).cast::<i64>() };
398 let rhs = unsafe { *(&rhs as *const B).cast::<i64>() };
399 let value = lhs.wrapping_mul(rhs);
400 return unsafe { core::mem::transmute_copy(&value) };
401 }
402 lhs * rhs
403}
404
405#[inline(always)]
406unsafe fn inner_loop_mul2<
407 O: MulOutput<D>,
408 D: Copy + 'static,
409 A: Copy + Mul<B, Output = D> + 'static,
410 B: Copy + 'static,
411>(
412 dp: *mut O::Slot,
413 ds: isize,
414 ap: *const A,
415 a_s: isize,
416 bp: *const B,
417 b_s: isize,
418 len: usize,
419) {
420 if ds == 1 && a_s == 1 && b_s == 1 {
421 let src_a = std::slice::from_raw_parts(ap, len);
422 let src_b = std::slice::from_raw_parts(bp, len);
423 if len >= 64 && O::try_contiguous(dp, len, src_a, src_b) {
424 return;
425 }
426 for i in 0..len {
427 O::write(dp.add(i), multiply_value(src_a[i], src_b[i]));
428 }
429 } else if ds == 1 && a_s == 1 && b_s == 0 {
430 let src_a = std::slice::from_raw_parts(ap, len);
431 let b = *bp;
432 for i in 0..len {
433 O::write(dp.add(i), multiply_value(src_a[i], b));
434 }
435 } else if ds == 1 && a_s == 0 && b_s == 1 {
436 let a = *ap;
437 let src_b = std::slice::from_raw_parts(bp, len);
438 for i in 0..len {
439 O::write(dp.add(i), multiply_value(a, src_b[i]));
440 }
441 } else if ds == 1 && a_s == 0 && b_s == 0 {
442 let a = *ap;
443 let b = *bp;
444 for i in 0..len {
445 O::write(dp.add(i), multiply_value(a, b));
446 }
447 } else if ds == 1 && b_s == 0 {
448 let b = *bp;
449 let mut ap = ap;
450 for i in 0..len {
451 O::write(dp.add(i), multiply_value(*ap, b));
452 ap = ap.wrapping_offset(a_s);
453 }
454 } else if ds == 1 && a_s == 0 {
455 let a = *ap;
456 let mut bp = bp;
457 for i in 0..len {
458 O::write(dp.add(i), multiply_value(a, *bp));
459 bp = bp.wrapping_offset(b_s);
460 }
461 } else {
462 let mut dp = dp;
463 let mut ap = ap;
464 let mut bp = bp;
465 for _ in 0..len {
466 O::write(dp, multiply_value(*ap, *bp));
467 dp = dp.wrapping_offset(ds);
468 ap = ap.wrapping_offset(a_s);
469 bp = bp.wrapping_offset(b_s);
470 }
471 }
472}
473
474#[derive(Clone, Debug, Eq, PartialEq)]
475struct ContiguousMulRangePlan {
476 axis_order: AxisVec<usize>,
477 inner_len: usize,
478 inner_axis_count: usize,
479 row_len: usize,
480 outer_axis_start: usize,
481 fast_axis: usize,
482 a_fast_stride: isize,
483 b_fast_stride: isize,
484 a_row_stride: isize,
485 b_row_stride: isize,
486}
487
488impl ContiguousMulRangePlan {
489 fn walks_inputs_without_blocking(&self) -> bool {
499 let streams = |stride: isize| stride == 0 || stride == 1;
500 if streams(self.a_fast_stride) && streams(self.b_fast_stride) {
501 return true;
502 }
503 #[cfg(feature = "parallel")]
504 if transposed_scalar_tile_kind(self).is_some() {
505 return true;
506 }
507 false
508 }
509}
510
511#[cfg(feature = "parallel")]
512#[derive(Clone, Copy, Debug, Eq, PartialEq)]
513enum TransposedScalarTileKind {
514 RhsScalar,
515 LhsScalar,
516}
517
518#[cfg(feature = "parallel")]
519fn transposed_scalar_tile_kind(plan: &ContiguousMulRangePlan) -> Option<TransposedScalarTileKind> {
520 let row_len = isize::try_from(plan.row_len).ok()?;
521 if plan.a_fast_stride == row_len
522 && plan.a_row_stride == 1
523 && plan.b_fast_stride == 0
524 && plan.b_row_stride == 0
525 {
526 return Some(TransposedScalarTileKind::RhsScalar);
527 }
528
529 if plan.b_fast_stride == row_len
530 && plan.b_row_stride == 1
531 && plan.a_fast_stride == 0
532 && plan.a_row_stride == 0
533 {
534 return Some(TransposedScalarTileKind::LhsScalar);
535 }
536
537 None
538}
539
540fn compact_axis_order(dims: &[usize], strides: &[isize]) -> Option<AxisVec<usize>> {
541 if dims.len() != strides.len() {
542 return None;
543 }
544
545 let mut active = AxisVec::<usize>::new();
546 let mut inactive = AxisVec::<usize>::new();
547 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
548 if stride < 0 {
549 return None;
550 }
551 if dim > 1 {
552 active.push(axis);
553 } else {
554 inactive.push(axis);
555 }
556 }
557
558 active.sort_by(|&lhs, &rhs| strides[lhs].cmp(&strides[rhs]).then_with(|| lhs.cmp(&rhs)));
559
560 let mut expected = 1isize;
561 for &axis in &active {
562 if strides[axis] != expected {
563 return None;
564 }
565 expected = expected.saturating_mul(dims[axis] as isize);
566 }
567
568 active.extend(inactive);
569 Some(active)
570}
571
572fn can_fuse_contiguous_range_axis(dim: usize, prev_stride: isize, next_stride: isize) -> bool {
573 dim <= 1 || (prev_stride == 0 && next_stride == 0) || next_stride == prev_stride * dim as isize
574}
575
576fn contiguous_mul_range_plan(
577 dims: &[usize],
578 dst_strides: &[isize],
579 a_strides: &[isize],
580 b_strides: &[isize],
581) -> Option<ContiguousMulRangePlan> {
582 let axis_order = compact_axis_order(dims, dst_strides)?;
583 if dims.is_empty() {
584 return Some(ContiguousMulRangePlan {
585 axis_order,
586 inner_len: 1,
587 inner_axis_count: 0,
588 row_len: 1,
589 outer_axis_start: 0,
590 fast_axis: 0,
591 a_fast_stride: 0,
592 b_fast_stride: 0,
593 a_row_stride: 0,
594 b_row_stride: 0,
595 });
596 }
597
598 let first_pos = axis_order
599 .iter()
600 .position(|&axis| dims[axis] > 1)
601 .unwrap_or(0);
602 let first_axis = axis_order[first_pos];
603 let mut inner_len = dims[first_axis].max(1);
604 let mut inner_axis_count = first_pos + 1;
605 let mut prev_axis = first_axis;
606
607 for &axis in axis_order.iter().skip(first_pos + 1) {
608 if can_fuse_contiguous_range_axis(
609 dims[prev_axis],
610 dst_strides[prev_axis],
611 dst_strides[axis],
612 ) && can_fuse_contiguous_range_axis(
613 dims[prev_axis],
614 a_strides[prev_axis],
615 a_strides[axis],
616 ) && can_fuse_contiguous_range_axis(
617 dims[prev_axis],
618 b_strides[prev_axis],
619 b_strides[axis],
620 ) {
621 inner_len = inner_len.checked_mul(dims[axis].max(1))?;
622 inner_axis_count += 1;
623 prev_axis = axis;
624 } else {
625 break;
626 }
627 }
628
629 let row = axis_order
630 .iter()
631 .enumerate()
632 .skip(inner_axis_count)
633 .find(|&(_, &axis)| dims[axis] > 1);
634 let (row_len, outer_axis_start, a_row_stride, b_row_stride) =
635 if let Some((row_pos, &row_axis)) = row {
636 (
637 dims[row_axis],
638 row_pos + 1,
639 a_strides[row_axis],
640 b_strides[row_axis],
641 )
642 } else {
643 (1, axis_order.len(), 0, 0)
644 };
645
646 Some(ContiguousMulRangePlan {
647 axis_order,
648 inner_len,
649 inner_axis_count,
650 row_len,
651 outer_axis_start,
652 fast_axis: first_axis,
653 a_fast_stride: a_strides[first_axis],
654 b_fast_stride: b_strides[first_axis],
655 a_row_stride,
656 b_row_stride,
657 })
658}
659
660struct ContiguousMulOuterCursor<'a> {
661 dims: &'a [usize],
662 a_strides: &'a [isize],
663 b_strides: &'a [isize],
664 axes: AxisVec<usize>,
665 coords: AxisVec<usize>,
666 a_offset: isize,
667 b_offset: isize,
668}
669
670impl<'a> ContiguousMulOuterCursor<'a> {
671 fn new(
672 dims: &'a [usize],
673 a_strides: &'a [isize],
674 b_strides: &'a [isize],
675 plan: &ContiguousMulRangePlan,
676 outer_group: usize,
677 ) -> Self {
678 let axes: AxisVec<usize> = plan
679 .axis_order
680 .iter()
681 .skip(plan.outer_axis_start)
682 .copied()
683 .collect();
684 let mut coords = AxisVec::<usize>::with_capacity(axes.len());
685 let mut rem = outer_group;
686 let mut a_offset = 0isize;
687 let mut b_offset = 0isize;
688
689 for &axis in &axes {
690 let dim = dims[axis].max(1);
691 let coord = rem % dim;
692 rem /= dim;
693 coords.push(coord);
694 a_offset += coord as isize * a_strides[axis];
695 b_offset += coord as isize * b_strides[axis];
696 }
697
698 Self {
699 dims,
700 a_strides,
701 b_strides,
702 axes,
703 coords,
704 a_offset,
705 b_offset,
706 }
707 }
708
709 fn advance(&mut self) {
710 for (i, &axis) in self.axes.iter().enumerate() {
711 let dim = self.dims[axis].max(1);
712 if dim <= 1 {
713 continue;
714 }
715
716 if self.coords[i] + 1 < dim {
719 self.coords[i] += 1;
720 self.a_offset += self.a_strides[axis];
721 self.b_offset += self.b_strides[axis];
722 break;
723 }
724
725 let last = (dim - 1) as isize;
726 self.coords[i] = 0;
727 self.a_offset -= last * self.a_strides[axis];
728 self.b_offset -= last * self.b_strides[axis];
729 }
730 }
731}
732
733#[inline(always)]
734unsafe fn run_contiguous_mul_row_block<
735 O: MulOutput<D>,
736 D: Copy + 'static,
737 A: Copy + Mul<B, Output = D> + 'static,
738 B: Copy + 'static,
739>(
740 dst_ptr: *mut O::Slot,
741 a_ptr: *const A,
742 b_ptr: *const B,
743 plan: &ContiguousMulRangePlan,
744 base_index: usize,
745 total: usize,
746 base_a_offset: isize,
747 base_b_offset: isize,
748) {
749 let inner_len = plan.inner_len.max(1);
750 let row_len = plan.row_len.max(1);
751 #[cfg(feature = "parallel")]
752 let block_len = inner_len.saturating_mul(row_len);
753
754 #[cfg(feature = "parallel")]
755 if total.saturating_sub(base_index) >= block_len {
756 match transposed_scalar_tile_kind(plan) {
757 Some(TransposedScalarTileKind::RhsScalar) => {
758 if simd::try_mul_transposed_scalar_rhs_2d::<D, A, B>(
759 dst_ptr.add(base_index).cast::<D>(),
760 a_ptr.offset(base_a_offset),
761 b_ptr.offset(base_b_offset),
762 inner_len,
763 row_len,
764 plan.a_fast_stride,
765 plan.a_row_stride,
766 ) {
767 return;
768 }
769 }
770 Some(TransposedScalarTileKind::LhsScalar) => {
771 if simd::try_mul_transposed_scalar_lhs_2d::<D, A, B>(
772 dst_ptr.add(base_index).cast::<D>(),
773 a_ptr.offset(base_a_offset),
774 b_ptr.offset(base_b_offset),
775 inner_len,
776 row_len,
777 plan.b_fast_stride,
778 plan.b_row_stride,
779 ) {
780 return;
781 }
782 }
783 None => {}
784 }
785 }
786
787 let mut index = base_index;
788 let mut a_offset = base_a_offset;
789 let mut b_offset = base_b_offset;
790
791 for _ in 0..row_len {
792 if index >= total {
793 break;
794 }
795 let len = inner_len.min(total - index);
796 inner_loop_mul2::<O, D, A, B>(
797 dst_ptr.add(index),
798 1,
799 a_ptr.offset(a_offset),
800 plan.a_fast_stride,
801 b_ptr.offset(b_offset),
802 plan.b_fast_stride,
803 len,
804 );
805 index += inner_len;
806 a_offset += plan.a_row_stride;
807 b_offset += plan.b_row_stride;
808 }
809}
810
811#[cfg(feature = "parallel")]
812fn strided_offset_for_contiguous_linear_index(
813 dims: &[usize],
814 strides: &[isize],
815 axis_order: &[usize],
816 mut index: usize,
817) -> isize {
818 let mut offset = 0isize;
819 for &axis in axis_order {
820 let dim = dims[axis];
821 if dim == 0 {
822 return 0;
823 }
824 let coord = index % dim;
825 index /= dim;
826 offset += coord as isize * strides[axis];
827 }
828 offset
829}
830
831fn try_contiguous_range_mul<
832 O: MulOutput<D>,
833 D: Copy + MaybeSendSync + 'static,
834 A: Copy + MaybeSendSync + Mul<B, Output = D> + 'static,
835 B: Copy + MaybeSendSync + 'static,
836>(
837 dst_ptr: *mut O::Slot,
838 dims: &[usize],
839 dst_strides: &[isize],
840 a_ptr: *const A,
841 a_strides: &[isize],
842 b_ptr: *const B,
843 b_strides: &[isize],
844) -> bool {
845 let Ok(total) = total_len(dims) else {
848 return false;
849 };
850 if total == 0 {
851 return true;
852 }
853 if total <= CONTIGUOUS_RANGE_MIN_LEN {
854 return false;
855 }
856
857 let Some(plan) = contiguous_mul_range_plan(dims, dst_strides, a_strides, b_strides) else {
858 return false;
859 };
860 if !plan.walks_inputs_without_blocking() {
861 return false;
862 }
863
864 let inner_len = plan.inner_len.max(1);
865 let row_len = plan.row_len.max(1);
866 let block_len = inner_len.saturating_mul(row_len).max(1);
867 let outer_groups = total.div_ceil(block_len);
868
869 #[cfg(feature = "parallel")]
870 {
871 let nthreads = crate::execution_policy::rayon_threads();
872 if nthreads > 1 {
873 use crate::threading::{parallel_for_each, SendPtr};
874
875 let dst = SendPtr(dst_ptr);
876 let a = SendPtr(a_ptr as *mut A);
877 let b = SendPtr(b_ptr as *mut B);
878
879 if outer_groups < nthreads {
880 let chunk_len = total.div_ceil(nthreads);
881 let nchunks = total.div_ceil(chunk_len);
882
883 parallel_for_each(0..nchunks, nthreads, &|chunks| {
884 for chunk in chunks {
885 let start = chunk * chunk_len;
886 let end = (start + chunk_len).min(total);
887 let mut index = start;
888
889 while index < end {
890 let in_inner = index % inner_len;
891 let len = (inner_len - in_inner).min(end - index);
892 let a_offset = strided_offset_for_contiguous_linear_index(
893 dims,
894 a_strides,
895 &plan.axis_order,
896 index,
897 );
898 let b_offset = strided_offset_for_contiguous_linear_index(
899 dims,
900 b_strides,
901 &plan.axis_order,
902 index,
903 );
904
905 unsafe {
906 inner_loop_mul2::<O, D, A, B>(
907 dst.as_ptr().add(index),
908 1,
909 a.as_const().offset(a_offset),
910 plan.a_fast_stride,
911 b.as_const().offset(b_offset),
912 plan.b_fast_stride,
913 len,
914 );
915 }
916 index += len;
917 }
918 }
919 });
920
921 return true;
922 }
923
924 let groups_per_chunk = outer_groups.div_ceil(nthreads);
925 let nchunks = outer_groups.div_ceil(groups_per_chunk);
926
927 parallel_for_each(0..nchunks, nthreads, &|chunks| {
928 for chunk in chunks {
929 let group_start = chunk * groups_per_chunk;
930 let group_end = (group_start + groups_per_chunk).min(outer_groups);
931 let mut cursor = ContiguousMulOuterCursor::new(
932 dims,
933 a_strides,
934 b_strides,
935 &plan,
936 group_start,
937 );
938
939 for group in group_start..group_end {
940 let index = group * block_len;
941 unsafe {
942 run_contiguous_mul_row_block::<O, D, A, B>(
943 dst.as_ptr(),
944 a.as_const(),
945 b.as_const(),
946 &plan,
947 index,
948 total,
949 cursor.a_offset,
950 cursor.b_offset,
951 );
952 }
953 cursor.advance();
954 }
955 }
956 });
957
958 true
959 } else {
960 run_contiguous_range_mul_single_thread::<O, D, A, B>(
961 dst_ptr,
962 dims,
963 a_ptr,
964 a_strides,
965 b_ptr,
966 b_strides,
967 &plan,
968 total,
969 block_len,
970 outer_groups,
971 )
972 }
973 }
974
975 #[cfg(not(feature = "parallel"))]
976 {
977 run_contiguous_range_mul_single_thread::<O, D, A, B>(
978 dst_ptr,
979 dims,
980 a_ptr,
981 a_strides,
982 b_ptr,
983 b_strides,
984 &plan,
985 total,
986 block_len,
987 outer_groups,
988 )
989 }
990}
991
992fn run_contiguous_range_mul_single_thread<
993 O: MulOutput<D>,
994 D: Copy + MaybeSendSync + 'static,
995 A: Copy + MaybeSendSync + Mul<B, Output = D> + 'static,
996 B: Copy + MaybeSendSync + 'static,
997>(
998 dst_ptr: *mut O::Slot,
999 dims: &[usize],
1000 a_ptr: *const A,
1001 a_strides: &[isize],
1002 b_ptr: *const B,
1003 b_strides: &[isize],
1004 plan: &ContiguousMulRangePlan,
1005 total: usize,
1006 block_len: usize,
1007 outer_groups: usize,
1008) -> bool {
1009 let mut cursor = ContiguousMulOuterCursor::new(dims, a_strides, b_strides, plan, 0);
1010 for group in 0..outer_groups {
1011 let index = group * block_len;
1012 unsafe {
1013 run_contiguous_mul_row_block::<O, D, A, B>(
1014 dst_ptr,
1015 a_ptr,
1016 b_ptr,
1017 plan,
1018 index,
1019 total,
1020 cursor.a_offset,
1021 cursor.b_offset,
1022 );
1023 }
1024 cursor.advance();
1025 }
1026 true
1027}
1028
1029#[inline(always)]
1031unsafe fn inner_loop_map3<
1032 D: Copy,
1033 A: Copy,
1034 B: Copy,
1035 C: Copy,
1036 OpA: ElementOp<A>,
1037 OpB: ElementOp<B>,
1038 OpC: ElementOp<C>,
1039>(
1040 dp: *mut D,
1041 ds: isize,
1042 ap: *const A,
1043 a_s: isize,
1044 bp: *const B,
1045 b_s: isize,
1046 cp: *const C,
1047 c_s: isize,
1048 len: usize,
1049 f: &impl Fn(A, B, C) -> D,
1050) {
1051 if ds == 1 && a_s == 1 && b_s == 1 && c_s == 1 {
1052 let src_a = std::slice::from_raw_parts(ap, len);
1053 let src_b = std::slice::from_raw_parts(bp, len);
1054 let src_c = std::slice::from_raw_parts(cp, len);
1055 let dst = std::slice::from_raw_parts_mut(dp, len);
1056 simd::dispatch_if_large(len, || {
1057 for (((d, &a), &b), &c) in dst.iter_mut().zip(src_a).zip(src_b).zip(src_c) {
1058 *d = f(OpA::apply(a), OpB::apply(b), OpC::apply(c));
1059 }
1060 });
1061 } else {
1062 let mut dp = dp;
1063 let mut ap = ap;
1064 let mut bp = bp;
1065 let mut cp = cp;
1066 for _ in 0..len {
1067 *dp = f(OpA::apply(*ap), OpB::apply(*bp), OpC::apply(*cp));
1068 dp = dp.wrapping_offset(ds);
1069 ap = ap.wrapping_offset(a_s);
1070 bp = bp.wrapping_offset(b_s);
1071 cp = cp.wrapping_offset(c_s);
1072 }
1073 }
1074}
1075
1076#[inline(always)]
1078unsafe fn inner_loop_map4<
1079 D: Copy,
1080 A: Copy,
1081 B: Copy,
1082 C: Copy,
1083 E: Copy,
1084 OpA: ElementOp<A>,
1085 OpB: ElementOp<B>,
1086 OpC: ElementOp<C>,
1087 OpE: ElementOp<E>,
1088>(
1089 dp: *mut D,
1090 ds: isize,
1091 ap: *const A,
1092 a_s: isize,
1093 bp: *const B,
1094 b_s: isize,
1095 cp: *const C,
1096 c_s: isize,
1097 ep: *const E,
1098 e_s: isize,
1099 len: usize,
1100 f: &impl Fn(A, B, C, E) -> D,
1101) {
1102 if ds == 1 && a_s == 1 && b_s == 1 && c_s == 1 && e_s == 1 {
1103 let src_a = std::slice::from_raw_parts(ap, len);
1104 let src_b = std::slice::from_raw_parts(bp, len);
1105 let src_c = std::slice::from_raw_parts(cp, len);
1106 let src_e = std::slice::from_raw_parts(ep, len);
1107 let dst = std::slice::from_raw_parts_mut(dp, len);
1108 simd::dispatch_if_large(len, || {
1109 for i in 0..len {
1110 dst[i] = f(
1111 OpA::apply(src_a[i]),
1112 OpB::apply(src_b[i]),
1113 OpC::apply(src_c[i]),
1114 OpE::apply(src_e[i]),
1115 );
1116 }
1117 });
1118 } else {
1119 let mut dp = dp;
1120 let mut ap = ap;
1121 let mut bp = bp;
1122 let mut cp = cp;
1123 let mut ep = ep;
1124 for _ in 0..len {
1125 *dp = f(
1126 OpA::apply(*ap),
1127 OpB::apply(*bp),
1128 OpC::apply(*cp),
1129 OpE::apply(*ep),
1130 );
1131 dp = dp.wrapping_offset(ds);
1132 ap = ap.wrapping_offset(a_s);
1133 bp = bp.wrapping_offset(b_s);
1134 cp = cp.wrapping_offset(c_s);
1135 ep = ep.wrapping_offset(e_s);
1136 }
1137 }
1138}
1139
1140pub fn map_into<D: Copy + MaybeSendSync, A: Copy + MaybeSendSync, Op: ElementOp<A>>(
1145 dest: &mut StridedViewMut<D>,
1146 src: &StridedView<A, Op>,
1147 f: impl Fn(A) -> D + MaybeSync,
1148) -> Result<()> {
1149 map_parts_into::<D, A, Op, NoKernel>(
1150 dest.as_mut_ptr(),
1151 dest.dims(),
1152 dest.strides(),
1153 src.ptr(),
1154 src.dims(),
1155 src.strides(),
1156 f,
1157 &NoKernel,
1158 )
1159}
1160
1161pub(crate) fn map_into_validated<
1162 D: Copy + MaybeSendSync,
1163 A: Copy + MaybeSendSync,
1164 Op: ElementOp<A>,
1165>(
1166 dest: &mut StridedViewMut<D>,
1167 src: &StridedView<A, Op>,
1168 f: impl Fn(A) -> D + MaybeSync,
1169 validated: ValidatedDestinationLayout,
1170) -> Result<()> {
1171 ensure_same_shape(dest.dims(), src.dims())?;
1172 map_parts_into_validated::<D, A, Op, NoKernel>(
1173 dest.as_mut_ptr(),
1174 dest.dims(),
1175 dest.strides(),
1176 src.ptr(),
1177 src.strides(),
1178 f,
1179 &NoKernel,
1180 validated,
1181 )
1182}
1183
1184pub fn map_into_uninit_with_kernel<T: Copy + MaybeSendSync>(
1225 dest: &mut StridedViewMut<MaybeUninit<T>>,
1226 src: &StridedView<T>,
1227 kernel: impl Fn(&[T], &mut [MaybeUninit<T>]) -> bool + Sync,
1228 scalar: impl Fn(T) -> T + MaybeSync,
1229) -> Result<()> {
1230 map_parts_into::<MaybeUninit<T>, T, Identity, _>(
1231 dest.as_mut_ptr(),
1232 dest.dims(),
1233 dest.strides(),
1234 src.ptr(),
1235 src.dims(),
1236 src.strides(),
1237 |value| MaybeUninit::new(scalar(value)),
1238 &SliceKernel(kernel),
1239 )
1240}
1241
1242#[cfg(test)]
1248pub(crate) fn map_raw_into<D: Copy + MaybeSendSync, A: Copy + MaybeSendSync, Op: ElementOp<A>>(
1249 dest: &mut crate::RawStridedMut<'_, D>,
1250 src: &crate::RawStridedRef<'_, A>,
1251 f: impl Fn(A) -> D + MaybeSync,
1252) -> Result<()> {
1253 map_parts_into::<D, A, Op, NoKernel>(
1254 dest.as_mut_ptr(),
1255 dest.dims(),
1256 dest.strides(),
1257 src.ptr(),
1258 src.dims(),
1259 src.strides(),
1260 f,
1261 &NoKernel,
1262 )
1263}
1264
1265pub(crate) fn map_raw_into_validated<
1266 D: Copy + MaybeSendSync,
1267 A: Copy + MaybeSendSync,
1268 Op: ElementOp<A>,
1269>(
1270 dest: &mut crate::RawStridedMut<'_, D>,
1271 src: &crate::RawStridedRef<'_, A>,
1272 f: impl Fn(A) -> D + MaybeSync,
1273 validated: ValidatedDestinationLayout,
1274) -> Result<()> {
1275 ensure_same_shape(dest.dims(), src.dims())?;
1276 map_parts_into_validated::<D, A, Op, NoKernel>(
1277 dest.as_mut_ptr(),
1278 dest.dims(),
1279 dest.strides(),
1280 src.ptr(),
1281 src.strides(),
1282 f,
1283 &NoKernel,
1284 validated,
1285 )
1286}
1287
1288#[allow(clippy::too_many_arguments)]
1289fn map_parts_into<
1290 D: Copy + MaybeSendSync,
1291 A: Copy + MaybeSendSync,
1292 Op: ElementOp<A>,
1293 K: MapKernel<A, D> + Sync,
1294>(
1295 dst_ptr: *mut D,
1296 dst_dims: &[usize],
1297 dst_strides: &[isize],
1298 src_ptr: *const A,
1299 src_dims: &[usize],
1300 src_strides: &[isize],
1301 f: impl Fn(A) -> D + MaybeSync,
1302 kernel: &K,
1303) -> Result<()> {
1304 ensure_same_shape(dst_dims, src_dims)?;
1305 let validated = validate_destination_layout(dst_dims, dst_strides)?;
1306 map_parts_into_validated::<D, A, Op, K>(
1307 dst_ptr,
1308 dst_dims,
1309 dst_strides,
1310 src_ptr,
1311 src_strides,
1312 f,
1313 kernel,
1314 validated,
1315 )
1316}
1317
1318#[allow(clippy::too_many_arguments)]
1319#[inline(never)]
1326fn map_contiguous<D: Copy, A: Copy, Op: ElementOp<A>>(
1327 dst: &mut [D],
1328 src: &[A],
1329 f: impl Fn(A) -> D,
1330) {
1331 simd::dispatch_if_large(dst.len(), move || {
1332 for i in 0..dst.len() {
1333 dst[i] = f(Op::apply(src[i]));
1334 }
1335 });
1336}
1337
1338fn map_parts_into_validated<
1339 D: Copy + MaybeSendSync,
1340 A: Copy + MaybeSendSync,
1341 Op: ElementOp<A>,
1342 K: MapKernel<A, D> + Sync,
1343>(
1344 dst_ptr: *mut D,
1345 dst_dims: &[usize],
1346 dst_strides: &[isize],
1347 src_ptr: *const A,
1348 src_strides: &[isize],
1349 f: impl Fn(A) -> D + MaybeSync,
1350 kernel: &K,
1351 _validated: ValidatedDestinationLayout,
1352) -> Result<()> {
1353 if sequential_contiguous_layout(dst_dims, &[dst_strides, src_strides])?.is_some() {
1354 let len = total_len(dst_dims)?;
1355 let dst = unsafe { std::slice::from_raw_parts_mut(dst_ptr, len) };
1359 let src = unsafe { std::slice::from_raw_parts(src_ptr, len) };
1360 if kernel.apply(src, dst) {
1361 return Ok(());
1362 }
1363 map_contiguous::<D, A, Op>(dst, src, f);
1364 return Ok(());
1365 }
1366
1367 let strides_list: [&[isize]; 2] = [dst_strides, src_strides];
1368 let elem_size = std::mem::size_of::<D>().max(std::mem::size_of::<A>());
1369 let total = total_len(dst_dims)?;
1370
1371 let (fused_dims, ordered_strides, plan) = if total <= SMALL_TENSOR_THRESHOLD {
1373 build_plan_fused_small(dst_dims, &strides_list)
1374 } else {
1375 build_plan_fused(dst_dims, &strides_list, Some(0), elem_size)
1376 };
1377
1378 #[cfg(feature = "parallel")]
1379 {
1380 let total = total_len(&fused_dims)?;
1381 let nthreads = crate::execution_policy::rayon_threads();
1382 if total > MINTHREADLENGTH && nthreads > 1 {
1383 use crate::threading::SendPtr;
1384 let dst_send = SendPtr(dst_ptr);
1385 let src_send = SendPtr(src_ptr as *mut A);
1386
1387 let costs = compute_costs(&ordered_strides);
1388 let initial_offsets = vec![0isize; strides_list.len()];
1389 return mapreduce_threaded(
1390 &fused_dims,
1391 &plan.block,
1392 &ordered_strides,
1393 &initial_offsets,
1394 &costs,
1395 nthreads,
1396 0,
1397 1,
1398 &|dims, blocks, strides_list, offsets| {
1399 for_each_inner_block_with_offsets(
1400 dims,
1401 blocks,
1402 strides_list,
1403 offsets,
1404 |offsets, len, strides| {
1405 let dp = unsafe { dst_send.as_ptr().offset(offsets[0]) };
1406 let sp = unsafe { src_send.as_const().offset(offsets[1]) };
1407 unsafe {
1408 inner_loop_map1::<D, A, Op, K>(
1409 dp, strides[0], sp, strides[1], len, &f, kernel,
1410 )
1411 };
1412 Ok(())
1413 },
1414 )
1415 },
1416 );
1417 }
1418 }
1419
1420 let initial_offsets = vec![0isize; ordered_strides.len()];
1421 for_each_inner_block_preordered(
1422 &fused_dims,
1423 &plan.block,
1424 &ordered_strides,
1425 &initial_offsets,
1426 |offsets, len, strides| {
1427 let dp = unsafe { dst_ptr.offset(offsets[0]) };
1428 let sp = unsafe { src_ptr.offset(offsets[1]) };
1429 unsafe {
1430 inner_loop_map1::<D, A, Op, K>(dp, strides[0], sp, strides[1], len, &f, kernel)
1431 };
1432 Ok(())
1433 },
1434 )
1435}
1436
1437pub fn zip_map2_into<
1442 D: Copy + MaybeSendSync,
1443 A: Copy + MaybeSendSync,
1444 B: Copy + MaybeSendSync,
1445 OpA: ElementOp<A>,
1446 OpB: ElementOp<B>,
1447>(
1448 dest: &mut StridedViewMut<D>,
1449 a: &StridedView<A, OpA>,
1450 b: &StridedView<B, OpB>,
1451 f: impl Fn(A, B) -> D + MaybeSync,
1452) -> Result<()> {
1453 zip_map2_parts_into::<D, A, B, OpA, OpB>(
1454 dest.as_mut_ptr(),
1455 dest.dims(),
1456 dest.strides(),
1457 a.ptr(),
1458 a.dims(),
1459 a.strides(),
1460 b.ptr(),
1461 b.dims(),
1462 b.strides(),
1463 f,
1464 )
1465}
1466
1467pub(crate) fn zip_map2_into_validated<
1468 D: Copy + MaybeSendSync,
1469 A: Copy + MaybeSendSync,
1470 B: Copy + MaybeSendSync,
1471 OpA: ElementOp<A>,
1472 OpB: ElementOp<B>,
1473>(
1474 dest: &mut StridedViewMut<D>,
1475 a: &StridedView<A, OpA>,
1476 b: &StridedView<B, OpB>,
1477 f: impl Fn(A, B) -> D + MaybeSync,
1478 validated: ValidatedDestinationLayout,
1479) -> Result<()> {
1480 zip_map2_parts_into_validated::<D, A, B, OpA, OpB>(
1481 dest.as_mut_ptr(),
1482 dest.dims(),
1483 dest.strides(),
1484 a.ptr(),
1485 a.strides(),
1486 b.ptr(),
1487 b.strides(),
1488 f,
1489 validated,
1490 )
1491}
1492
1493#[non_exhaustive]
1495#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1496pub enum CompareOp {
1497 Eq,
1498 Lt,
1499 Le,
1500 Gt,
1501 Ge,
1502}
1503
1504pub fn compare_into<T, OpA, OpB>(
1515 dest: &mut StridedViewMut<bool>,
1516 a: &StridedView<T, OpA>,
1517 b: &StridedView<T, OpB>,
1518 op: CompareOp,
1519) -> Result<()>
1520where
1521 T: Copy + MaybeSendSync + PartialOrd,
1522 OpA: ElementOp<T>,
1523 OpB: ElementOp<T>,
1524{
1525 match op {
1526 CompareOp::Eq => zip_map2_into(dest, a, b, |lhs, rhs| lhs == rhs),
1527 CompareOp::Lt => zip_map2_into(dest, a, b, |lhs, rhs| lhs < rhs),
1528 CompareOp::Le => zip_map2_into(dest, a, b, |lhs, rhs| lhs <= rhs),
1529 CompareOp::Gt => zip_map2_into(dest, a, b, |lhs, rhs| lhs > rhs),
1530 CompareOp::Ge => zip_map2_into(dest, a, b, |lhs, rhs| lhs >= rhs),
1531 }
1532}
1533
1534pub fn compare_into_uninit<T, OpA, OpB>(
1553 dest: &mut StridedViewMut<MaybeUninit<bool>>,
1554 a: &StridedView<T, OpA>,
1555 b: &StridedView<T, OpB>,
1556 op: CompareOp,
1557) -> Result<()>
1558where
1559 T: Copy + MaybeSendSync + PartialOrd,
1560 OpA: ElementOp<T>,
1561 OpB: ElementOp<T>,
1562{
1563 ensure_same_shape(dest.dims(), a.dims())?;
1564 ensure_same_shape(dest.dims(), b.dims())?;
1565 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
1566 validate_typed_no_overlap(dest, a, 0)?;
1567 validate_typed_no_overlap(dest, b, 1)?;
1568 match op {
1569 CompareOp::Eq => zip_map2_into_validated(
1570 dest,
1571 a,
1572 b,
1573 |lhs, rhs| MaybeUninit::new(lhs == rhs),
1574 validated,
1575 ),
1576 CompareOp::Lt => zip_map2_into_validated(
1577 dest,
1578 a,
1579 b,
1580 |lhs, rhs| MaybeUninit::new(lhs < rhs),
1581 validated,
1582 ),
1583 CompareOp::Le => zip_map2_into_validated(
1584 dest,
1585 a,
1586 b,
1587 |lhs, rhs| MaybeUninit::new(lhs <= rhs),
1588 validated,
1589 ),
1590 CompareOp::Gt => zip_map2_into_validated(
1591 dest,
1592 a,
1593 b,
1594 |lhs, rhs| MaybeUninit::new(lhs > rhs),
1595 validated,
1596 ),
1597 CompareOp::Ge => zip_map2_into_validated(
1598 dest,
1599 a,
1600 b,
1601 |lhs, rhs| MaybeUninit::new(lhs >= rhs),
1602 validated,
1603 ),
1604 }
1605}
1606
1607pub(crate) fn zip_map2_raw_into_validated<
1608 D: Copy + MaybeSendSync,
1609 A: Copy + MaybeSendSync,
1610 B: Copy + MaybeSendSync,
1611 OpA: ElementOp<A>,
1612 OpB: ElementOp<B>,
1613>(
1614 dest: &mut crate::RawStridedMut<'_, D>,
1615 a: &crate::RawStridedRef<'_, A>,
1616 b: &crate::RawStridedRef<'_, B>,
1617 f: impl Fn(A, B) -> D + MaybeSync,
1618 validated: ValidatedDestinationLayout,
1619) -> Result<()> {
1620 ensure_same_shape(dest.dims(), a.dims())?;
1621 ensure_same_shape(dest.dims(), b.dims())?;
1622 zip_map2_parts_into_validated::<D, A, B, OpA, OpB>(
1623 dest.as_mut_ptr(),
1624 dest.dims(),
1625 dest.strides(),
1626 a.ptr(),
1627 a.strides(),
1628 b.ptr(),
1629 b.strides(),
1630 f,
1631 validated,
1632 )
1633}
1634
1635#[allow(clippy::too_many_arguments)]
1636fn zip_map2_parts_into<
1637 D: Copy + MaybeSendSync,
1638 A: Copy + MaybeSendSync,
1639 B: Copy + MaybeSendSync,
1640 OpA: ElementOp<A>,
1641 OpB: ElementOp<B>,
1642>(
1643 dst_ptr: *mut D,
1644 dst_dims: &[usize],
1645 dst_strides: &[isize],
1646 a_ptr: *const A,
1647 a_dims: &[usize],
1648 a_strides: &[isize],
1649 b_ptr: *const B,
1650 b_dims: &[usize],
1651 b_strides: &[isize],
1652 f: impl Fn(A, B) -> D + MaybeSync,
1653) -> Result<()> {
1654 ensure_same_shape(dst_dims, a_dims)?;
1655 ensure_same_shape(dst_dims, b_dims)?;
1656 let validated = validate_destination_layout(dst_dims, dst_strides)?;
1657 zip_map2_parts_into_validated::<D, A, B, OpA, OpB>(
1658 dst_ptr,
1659 dst_dims,
1660 dst_strides,
1661 a_ptr,
1662 a_strides,
1663 b_ptr,
1664 b_strides,
1665 f,
1666 validated,
1667 )
1668}
1669
1670#[allow(clippy::too_many_arguments)]
1671fn zip_map2_parts_into_validated<
1672 D: Copy + MaybeSendSync,
1673 A: Copy + MaybeSendSync,
1674 B: Copy + MaybeSendSync,
1675 OpA: ElementOp<A>,
1676 OpB: ElementOp<B>,
1677>(
1678 dst_ptr: *mut D,
1679 dst_dims: &[usize],
1680 dst_strides: &[isize],
1681 a_ptr: *const A,
1682 a_strides: &[isize],
1683 b_ptr: *const B,
1684 b_strides: &[isize],
1685 f: impl Fn(A, B) -> D + MaybeSync,
1686 _validated: ValidatedDestinationLayout,
1687) -> Result<()> {
1688 if sequential_contiguous_layout(dst_dims, &[dst_strides, a_strides, b_strides])?.is_some() {
1689 let len = total_len(dst_dims)?;
1690 let dst = unsafe { std::slice::from_raw_parts_mut(dst_ptr, len) };
1691 let sa = unsafe { std::slice::from_raw_parts(a_ptr, len) };
1692 let sb = unsafe { std::slice::from_raw_parts(b_ptr, len) };
1693 simd::dispatch_if_large(len, || {
1694 for i in 0..len {
1695 dst[i] = f(OpA::apply(sa[i]), OpB::apply(sb[i]));
1696 }
1697 });
1698 return Ok(());
1699 }
1700
1701 let strides_list: [&[isize]; 3] = [dst_strides, a_strides, b_strides];
1702 let elem_size = std::mem::size_of::<D>()
1703 .max(std::mem::size_of::<A>())
1704 .max(std::mem::size_of::<B>());
1705 let total = total_len(dst_dims)?;
1706
1707 let (fused_dims, ordered_strides, plan) = if total <= SMALL_TENSOR_THRESHOLD {
1709 build_plan_fused_small(dst_dims, &strides_list)
1710 } else {
1711 build_plan_fused(dst_dims, &strides_list, Some(0), elem_size)
1712 };
1713
1714 #[cfg(feature = "parallel")]
1715 {
1716 let total = total_len(&fused_dims)?;
1717 let nthreads = crate::execution_policy::rayon_threads();
1718 if total > MINTHREADLENGTH && nthreads > 1 {
1719 use crate::threading::SendPtr;
1720 let dst_send = SendPtr(dst_ptr);
1721 let a_send = SendPtr(a_ptr as *mut A);
1722 let b_send = SendPtr(b_ptr as *mut B);
1723
1724 let costs = compute_costs(&ordered_strides);
1725 let initial_offsets = vec![0isize; strides_list.len()];
1726 return mapreduce_threaded(
1727 &fused_dims,
1728 &plan.block,
1729 &ordered_strides,
1730 &initial_offsets,
1731 &costs,
1732 nthreads,
1733 0,
1734 1,
1735 &|dims, blocks, strides_list, offsets| {
1736 for_each_inner_block_with_offsets(
1737 dims,
1738 blocks,
1739 strides_list,
1740 offsets,
1741 |offsets, len, strides| {
1742 let dp = unsafe { dst_send.as_ptr().offset(offsets[0]) };
1743 let ap = unsafe { a_send.as_const().offset(offsets[1]) };
1744 let bp = unsafe { b_send.as_const().offset(offsets[2]) };
1745 unsafe {
1746 inner_loop_map2::<D, A, B, OpA, OpB>(
1747 dp, strides[0], ap, strides[1], bp, strides[2], len, &f,
1748 )
1749 };
1750 Ok(())
1751 },
1752 )
1753 },
1754 );
1755 }
1756 }
1757
1758 let initial_offsets = vec![0isize; ordered_strides.len()];
1759 for_each_inner_block_preordered(
1760 &fused_dims,
1761 &plan.block,
1762 &ordered_strides,
1763 &initial_offsets,
1764 |offsets, len, strides| {
1765 let dp = unsafe { dst_ptr.offset(offsets[0]) };
1766 let ap = unsafe { a_ptr.offset(offsets[1]) };
1767 let bp = unsafe { b_ptr.offset(offsets[2]) };
1768 unsafe {
1769 inner_loop_map2::<D, A, B, OpA, OpB>(
1770 dp, strides[0], ap, strides[1], bp, strides[2], len, &f,
1771 )
1772 };
1773 Ok(())
1774 },
1775 )
1776}
1777
1778fn mul_identity_into_raw<
1779 O: MulOutput<D>,
1780 D: Copy + MaybeSendSync + 'static,
1781 A: Copy + MaybeSendSync + Mul<B, Output = D> + 'static,
1782 B: Copy + MaybeSendSync + 'static,
1783>(
1784 dst_ptr: *mut O::Slot,
1785 dst_dims: &[usize],
1786 dst_strides: &[isize],
1787 a_ptr: *const A,
1788 a_strides: &[isize],
1789 b_ptr: *const B,
1790 b_strides: &[isize],
1791 _validated: ValidatedDestinationLayout,
1792) -> Result<()> {
1793 debug_assert_eq!(dst_dims.len(), a_strides.len());
1794 debug_assert_eq!(dst_dims.len(), b_strides.len());
1795
1796 if sequential_contiguous_layout(dst_dims, &[dst_strides, a_strides, b_strides])?.is_some() {
1797 let len = total_len(dst_dims)?;
1798 let sa = unsafe { std::slice::from_raw_parts(a_ptr, len) };
1799 let sb = unsafe { std::slice::from_raw_parts(b_ptr, len) };
1800 if unsafe { O::try_contiguous(dst_ptr, len, sa, sb) } {
1801 return Ok(());
1802 }
1803 for i in 0..len {
1804 unsafe { O::write(dst_ptr.add(i), multiply_value(sa[i], sb[i])) };
1805 }
1806 return Ok(());
1807 }
1808
1809 let strides_list: [&[isize]; 3] = [dst_strides, a_strides, b_strides];
1810 let elem_size = std::mem::size_of::<D>()
1811 .max(std::mem::size_of::<A>())
1812 .max(std::mem::size_of::<B>());
1813 let total = total_len(dst_dims)?;
1814
1815 if try_contiguous_range_mul::<O, D, A, B>(
1816 dst_ptr,
1817 dst_dims,
1818 dst_strides,
1819 a_ptr,
1820 a_strides,
1821 b_ptr,
1822 b_strides,
1823 ) {
1824 return Ok(());
1825 }
1826
1827 let (fused_dims, ordered_strides, plan) = if total <= SMALL_TENSOR_THRESHOLD {
1828 build_plan_fused_small(dst_dims, &strides_list)
1829 } else {
1830 build_plan_fused(dst_dims, &strides_list, Some(0), elem_size)
1831 };
1832
1833 #[cfg(feature = "parallel")]
1834 {
1835 let total = total_len(&fused_dims)?;
1836 let nthreads = crate::execution_policy::rayon_threads();
1837 if total > MINTHREADLENGTH && nthreads > 1 {
1838 use crate::threading::SendPtr;
1839 let dst_send = SendPtr(dst_ptr);
1840 let a_send = SendPtr(a_ptr as *mut A);
1841 let b_send = SendPtr(b_ptr as *mut B);
1842
1843 let costs = compute_costs(&ordered_strides);
1844 let initial_offsets = vec![0isize; strides_list.len()];
1845 return mapreduce_threaded(
1846 &fused_dims,
1847 &plan.block,
1848 &ordered_strides,
1849 &initial_offsets,
1850 &costs,
1851 nthreads,
1852 0,
1853 1,
1854 &|dims, blocks, strides_list, offsets| {
1855 for_each_inner_block_with_offsets(
1856 dims,
1857 blocks,
1858 strides_list,
1859 offsets,
1860 |offsets, len, strides| {
1861 let dp = unsafe { dst_send.as_ptr().offset(offsets[0]) };
1862 let ap = unsafe { a_send.as_const().offset(offsets[1]) };
1863 let bp = unsafe { b_send.as_const().offset(offsets[2]) };
1864 unsafe {
1865 inner_loop_mul2::<O, D, A, B>(
1866 dp, strides[0], ap, strides[1], bp, strides[2], len,
1867 )
1868 };
1869 Ok(())
1870 },
1871 )
1872 },
1873 );
1874 }
1875 }
1876
1877 let initial_offsets = vec![0isize; ordered_strides.len()];
1878 for_each_inner_block_preordered(
1879 &fused_dims,
1880 &plan.block,
1881 &ordered_strides,
1882 &initial_offsets,
1883 |offsets, len, strides| {
1884 let dp = unsafe { dst_ptr.offset(offsets[0]) };
1885 let ap = unsafe { a_ptr.offset(offsets[1]) };
1886 let bp = unsafe { b_ptr.offset(offsets[2]) };
1887 unsafe {
1888 inner_loop_mul2::<O, D, A, B>(dp, strides[0], ap, strides[1], bp, strides[2], len)
1889 };
1890 Ok(())
1891 },
1892 )
1893}
1894
1895pub fn mul_into<
1900 D: Copy + MaybeSendSync + 'static,
1901 A: Copy + Mul<B, Output = D> + MaybeSendSync + 'static,
1902 B: Copy + MaybeSendSync + 'static,
1903 OpA: ElementOp<A>,
1904 OpB: ElementOp<B>,
1905>(
1906 dest: &mut StridedViewMut<D>,
1907 a: &StridedView<A, OpA>,
1908 b: &StridedView<B, OpB>,
1909) -> Result<()> {
1910 ensure_same_shape(dest.dims(), a.dims())?;
1911 ensure_same_shape(dest.dims(), b.dims())?;
1912 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
1913
1914 if OpA::IS_IDENTITY && OpB::IS_IDENTITY {
1915 return mul_identity_into_raw::<InitializedOutput, D, A, B>(
1916 dest.as_mut_ptr(),
1917 dest.dims(),
1918 dest.strides(),
1919 a.ptr(),
1920 a.strides(),
1921 b.ptr(),
1922 b.strides(),
1923 validated,
1924 );
1925 }
1926
1927 zip_map2_into_validated(dest, a, b, multiply_value, validated)
1928}
1929
1930pub fn mul_into_uninit<
1949 D: Copy + MaybeSendSync + 'static,
1950 A: Copy + Mul<B, Output = D> + MaybeSendSync + 'static,
1951 B: Copy + MaybeSendSync + 'static,
1952 OpA: ElementOp<A>,
1953 OpB: ElementOp<B>,
1954>(
1955 dest: &mut StridedViewMut<MaybeUninit<D>>,
1956 a: &StridedView<A, OpA>,
1957 b: &StridedView<B, OpB>,
1958) -> Result<()> {
1959 ensure_same_shape(dest.dims(), a.dims())?;
1960 ensure_same_shape(dest.dims(), b.dims())?;
1961 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
1962 validate_typed_no_overlap(dest, a, 0)?;
1963 validate_typed_no_overlap(dest, b, 1)?;
1964
1965 if OpA::IS_IDENTITY && OpB::IS_IDENTITY {
1966 return mul_identity_into_raw::<UninitializedOutput, D, A, B>(
1967 dest.as_mut_ptr(),
1968 dest.dims(),
1969 dest.strides(),
1970 a.ptr(),
1971 a.strides(),
1972 b.ptr(),
1973 b.strides(),
1974 validated,
1975 );
1976 }
1977
1978 zip_map2_into_validated(
1979 dest,
1980 a,
1981 b,
1982 |lhs, rhs| MaybeUninit::new(multiply_value(lhs, rhs)),
1983 validated,
1984 )
1985}
1986
1987fn broadcast_strides_for_axes(
1988 source_dims: &[usize],
1989 source_strides: &[isize],
1990 target_dims: &[usize],
1991 axes: &[usize],
1992) -> Result<AxisVec<isize>> {
1993 if source_dims.len() != axes.len() {
1994 return Err(StridedError::RankMismatch(source_dims.len(), axes.len()));
1995 }
1996 debug_assert_eq!(source_dims.len(), source_strides.len());
1997
1998 let mut seen = AxisVec::<bool>::new();
1999 seen.resize(target_dims.len(), false);
2000 let mut strides = AxisVec::<isize>::new();
2001 strides.resize(target_dims.len(), 0);
2002 for (src_axis, &dst_axis) in axes.iter().enumerate() {
2003 if dst_axis >= target_dims.len() {
2004 return Err(StridedError::InvalidAxis {
2005 axis: dst_axis,
2006 rank: target_dims.len(),
2007 });
2008 }
2009 if seen[dst_axis] {
2010 return Err(StridedError::InvalidAxis {
2011 axis: dst_axis,
2012 rank: target_dims.len(),
2013 });
2014 }
2015 seen[dst_axis] = true;
2016
2017 let source_dim = source_dims[src_axis];
2018 let target_dim = target_dims[dst_axis];
2019 if source_dim != target_dim && source_dim != 1 {
2020 return Err(StridedError::ShapeMismatch(
2021 source_dims.to_vec(),
2022 target_dims.to_vec(),
2023 ));
2024 }
2025 if source_dim == target_dim {
2026 strides[dst_axis] = source_strides[src_axis];
2027 }
2028 }
2029
2030 Ok(strides)
2031}
2032
2033fn broadcast_view_with_strides<'a, T, Op: ElementOp<T>>(
2038 view: &StridedView<'a, T, Op>,
2039 target_dims: &[usize],
2040 strides: &[isize],
2041) -> StridedView<'a, T, Op> {
2042 unsafe {
2043 StridedView::new_owned_unchecked(
2044 view.data(),
2045 target_dims.to_vec(),
2046 strides.to_vec(),
2047 view.offset(),
2048 )
2049 }
2050}
2051
2052pub fn broadcast_mul_into<
2057 D: Copy + MaybeSendSync + 'static,
2058 A: Copy + Mul<B, Output = D> + MaybeSendSync + 'static,
2059 B: Copy + MaybeSendSync + 'static,
2060 OpA: ElementOp<A>,
2061 OpB: ElementOp<B>,
2062>(
2063 dest: &mut StridedViewMut<D>,
2064 a: &StridedView<A, OpA>,
2065 a_axes: &[usize],
2066 b: &StridedView<B, OpB>,
2067 b_axes: &[usize],
2068) -> Result<()> {
2069 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
2070 let a_strides = broadcast_strides_for_axes(a.dims(), a.strides(), dest.dims(), a_axes)?;
2071 let b_strides = broadcast_strides_for_axes(b.dims(), b.strides(), dest.dims(), b_axes)?;
2072
2073 if OpA::IS_IDENTITY && OpB::IS_IDENTITY {
2074 return mul_identity_into_raw::<InitializedOutput, D, A, B>(
2075 dest.as_mut_ptr(),
2076 dest.dims(),
2077 dest.strides(),
2078 a.ptr(),
2079 &a_strides,
2080 b.ptr(),
2081 &b_strides,
2082 validated,
2083 );
2084 }
2085
2086 let a = broadcast_view_with_strides(a, dest.dims(), &a_strides);
2087 let b = broadcast_view_with_strides(b, dest.dims(), &b_strides);
2088 zip_map2_into_validated(dest, &a, &b, multiply_value, validated)
2089}
2090
2091pub fn broadcast_mul_into_uninit<
2110 D: Copy + MaybeSendSync + 'static,
2111 A: Copy + Mul<B, Output = D> + MaybeSendSync + 'static,
2112 B: Copy + MaybeSendSync + 'static,
2113 OpA: ElementOp<A>,
2114 OpB: ElementOp<B>,
2115>(
2116 dest: &mut StridedViewMut<MaybeUninit<D>>,
2117 a: &StridedView<A, OpA>,
2118 a_axes: &[usize],
2119 b: &StridedView<B, OpB>,
2120 b_axes: &[usize],
2121) -> Result<()> {
2122 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
2123 let a_strides = broadcast_strides_for_axes(a.dims(), a.strides(), dest.dims(), a_axes)?;
2124 let b_strides = broadcast_strides_for_axes(b.dims(), b.strides(), dest.dims(), b_axes)?;
2125 validate_typed_no_overlap(dest, a, 0)?;
2126 validate_typed_no_overlap(dest, b, 1)?;
2127
2128 if OpA::IS_IDENTITY && OpB::IS_IDENTITY {
2129 return mul_identity_into_raw::<UninitializedOutput, D, A, B>(
2130 dest.as_mut_ptr(),
2131 dest.dims(),
2132 dest.strides(),
2133 a.ptr(),
2134 &a_strides,
2135 b.ptr(),
2136 &b_strides,
2137 validated,
2138 );
2139 }
2140
2141 let a = broadcast_view_with_strides(a, dest.dims(), &a_strides);
2142 let b = broadcast_view_with_strides(b, dest.dims(), &b_strides);
2143 zip_map2_into_validated(
2144 dest,
2145 &a,
2146 &b,
2147 |lhs, rhs| MaybeUninit::new(multiply_value(lhs, rhs)),
2148 validated,
2149 )
2150}
2151
2152pub fn zip_map3_into<
2154 D: Copy + MaybeSendSync,
2155 A: Copy + MaybeSendSync,
2156 B: Copy + MaybeSendSync,
2157 C: Copy + MaybeSendSync,
2158 OpA: ElementOp<A>,
2159 OpB: ElementOp<B>,
2160 OpC: ElementOp<C>,
2161>(
2162 dest: &mut StridedViewMut<D>,
2163 a: &StridedView<A, OpA>,
2164 b: &StridedView<B, OpB>,
2165 c: &StridedView<C, OpC>,
2166 f: impl Fn(A, B, C) -> D + MaybeSync,
2167) -> Result<()> {
2168 ensure_same_shape(dest.dims(), a.dims())?;
2169 ensure_same_shape(dest.dims(), b.dims())?;
2170 ensure_same_shape(dest.dims(), c.dims())?;
2171 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
2172 zip_map3_into_validated(dest, a, b, c, f, validated)
2173}
2174
2175pub(crate) fn zip_map3_into_validated<
2176 D: Copy + MaybeSendSync,
2177 A: Copy + MaybeSendSync,
2178 B: Copy + MaybeSendSync,
2179 C: Copy + MaybeSendSync,
2180 OpA: ElementOp<A>,
2181 OpB: ElementOp<B>,
2182 OpC: ElementOp<C>,
2183>(
2184 dest: &mut StridedViewMut<D>,
2185 a: &StridedView<A, OpA>,
2186 b: &StridedView<B, OpB>,
2187 c: &StridedView<C, OpC>,
2188 f: impl Fn(A, B, C) -> D + MaybeSync,
2189 _validated: ValidatedDestinationLayout,
2190) -> Result<()> {
2191 ensure_same_shape(dest.dims(), a.dims())?;
2192 ensure_same_shape(dest.dims(), b.dims())?;
2193 ensure_same_shape(dest.dims(), c.dims())?;
2194 let dst_ptr = dest.as_mut_ptr();
2195 let a_ptr = a.ptr();
2196 let b_ptr = b.ptr();
2197 let c_ptr = c.ptr();
2198
2199 let dst_dims = dest.dims();
2200 let dst_strides = dest.strides();
2201
2202 if sequential_contiguous_layout(
2203 dst_dims,
2204 &[dst_strides, a.strides(), b.strides(), c.strides()],
2205 )?
2206 .is_some()
2207 {
2208 let len = total_len(dst_dims)?;
2209 let dst = unsafe { std::slice::from_raw_parts_mut(dst_ptr, len) };
2210 let sa = unsafe { std::slice::from_raw_parts(a_ptr, len) };
2211 let sb = unsafe { std::slice::from_raw_parts(b_ptr, len) };
2212 let sc = unsafe { std::slice::from_raw_parts(c_ptr, len) };
2213 simd::dispatch_if_large(len, || {
2214 for (((d, &a), &b), &c) in dst.iter_mut().zip(sa).zip(sb).zip(sc) {
2215 *d = f(OpA::apply(a), OpB::apply(b), OpC::apply(c));
2216 }
2217 });
2218 return Ok(());
2219 }
2220
2221 let strides_list: [&[isize]; 4] = [dst_strides, a.strides(), b.strides(), c.strides()];
2222 let elem_size = std::mem::size_of::<D>()
2223 .max(std::mem::size_of::<A>())
2224 .max(std::mem::size_of::<B>())
2225 .max(std::mem::size_of::<C>());
2226 let total = total_len(dst_dims)?;
2227
2228 let (fused_dims, ordered_strides, plan) = if total <= SMALL_TENSOR_THRESHOLD {
2230 build_plan_fused_small(dst_dims, &strides_list)
2231 } else {
2232 build_plan_fused(dst_dims, &strides_list, Some(0), elem_size)
2233 };
2234
2235 #[cfg(feature = "parallel")]
2236 {
2237 let total = total_len(&fused_dims)?;
2238 let nthreads = crate::execution_policy::rayon_threads();
2239 if total > MINTHREADLENGTH && nthreads > 1 {
2240 use crate::threading::SendPtr;
2241 let dst_send = SendPtr(dst_ptr);
2242 let a_send = SendPtr(a_ptr as *mut A);
2243 let b_send = SendPtr(b_ptr as *mut B);
2244 let c_send = SendPtr(c_ptr as *mut C);
2245
2246 let costs = compute_costs(&ordered_strides);
2247 let initial_offsets = vec![0isize; strides_list.len()];
2248 return mapreduce_threaded(
2249 &fused_dims,
2250 &plan.block,
2251 &ordered_strides,
2252 &initial_offsets,
2253 &costs,
2254 nthreads,
2255 0,
2256 1,
2257 &|dims, blocks, strides_list, offsets| {
2258 for_each_inner_block_with_offsets(
2259 dims,
2260 blocks,
2261 strides_list,
2262 offsets,
2263 |offsets, len, strides| {
2264 let dp = unsafe { dst_send.as_ptr().offset(offsets[0]) };
2265 let ap = unsafe { a_send.as_const().offset(offsets[1]) };
2266 let bp = unsafe { b_send.as_const().offset(offsets[2]) };
2267 let cp = unsafe { c_send.as_const().offset(offsets[3]) };
2268 unsafe {
2269 inner_loop_map3::<D, A, B, C, OpA, OpB, OpC>(
2270 dp, strides[0], ap, strides[1], bp, strides[2], cp, strides[3],
2271 len, &f,
2272 )
2273 };
2274 Ok(())
2275 },
2276 )
2277 },
2278 );
2279 }
2280 }
2281
2282 let initial_offsets = vec![0isize; ordered_strides.len()];
2283 for_each_inner_block_preordered(
2284 &fused_dims,
2285 &plan.block,
2286 &ordered_strides,
2287 &initial_offsets,
2288 |offsets, len, strides| {
2289 let dp = unsafe { dst_ptr.offset(offsets[0]) };
2290 let ap = unsafe { a_ptr.offset(offsets[1]) };
2291 let bp = unsafe { b_ptr.offset(offsets[2]) };
2292 let cp = unsafe { c_ptr.offset(offsets[3]) };
2293 unsafe {
2294 inner_loop_map3::<D, A, B, C, OpA, OpB, OpC>(
2295 dp, strides[0], ap, strides[1], bp, strides[2], cp, strides[3], len, &f,
2296 )
2297 };
2298 Ok(())
2299 },
2300 )
2301}
2302
2303pub fn zip_map4_into<
2305 D: Copy + MaybeSendSync,
2306 A: Copy + MaybeSendSync,
2307 B: Copy + MaybeSendSync,
2308 C: Copy + MaybeSendSync,
2309 E: Copy + MaybeSendSync,
2310 OpA: ElementOp<A>,
2311 OpB: ElementOp<B>,
2312 OpC: ElementOp<C>,
2313 OpE: ElementOp<E>,
2314>(
2315 dest: &mut StridedViewMut<D>,
2316 a: &StridedView<A, OpA>,
2317 b: &StridedView<B, OpB>,
2318 c: &StridedView<C, OpC>,
2319 e: &StridedView<E, OpE>,
2320 f: impl Fn(A, B, C, E) -> D + MaybeSync,
2321) -> Result<()> {
2322 ensure_same_shape(dest.dims(), a.dims())?;
2323 ensure_same_shape(dest.dims(), b.dims())?;
2324 ensure_same_shape(dest.dims(), c.dims())?;
2325 ensure_same_shape(dest.dims(), e.dims())?;
2326 let validated = validate_destination_layout(dest.dims(), dest.strides())?;
2327 zip_map4_into_validated(dest, a, b, c, e, f, validated)
2328}
2329
2330pub(crate) fn zip_map4_into_validated<
2331 D: Copy + MaybeSendSync,
2332 A: Copy + MaybeSendSync,
2333 B: Copy + MaybeSendSync,
2334 C: Copy + MaybeSendSync,
2335 E: Copy + MaybeSendSync,
2336 OpA: ElementOp<A>,
2337 OpB: ElementOp<B>,
2338 OpC: ElementOp<C>,
2339 OpE: ElementOp<E>,
2340>(
2341 dest: &mut StridedViewMut<D>,
2342 a: &StridedView<A, OpA>,
2343 b: &StridedView<B, OpB>,
2344 c: &StridedView<C, OpC>,
2345 e: &StridedView<E, OpE>,
2346 f: impl Fn(A, B, C, E) -> D + MaybeSync,
2347 _validated: ValidatedDestinationLayout,
2348) -> Result<()> {
2349 ensure_same_shape(dest.dims(), a.dims())?;
2350 ensure_same_shape(dest.dims(), b.dims())?;
2351 ensure_same_shape(dest.dims(), c.dims())?;
2352 ensure_same_shape(dest.dims(), e.dims())?;
2353 let dst_ptr = dest.as_mut_ptr();
2354 let a_ptr = a.ptr();
2355 let b_ptr = b.ptr();
2356 let c_ptr = c.ptr();
2357 let e_ptr = e.ptr();
2358
2359 let dst_dims = dest.dims();
2360 let dst_strides = dest.strides();
2361
2362 if sequential_contiguous_layout(
2363 dst_dims,
2364 &[
2365 dst_strides,
2366 a.strides(),
2367 b.strides(),
2368 c.strides(),
2369 e.strides(),
2370 ],
2371 )?
2372 .is_some()
2373 {
2374 let len = total_len(dst_dims)?;
2375 let dst = unsafe { std::slice::from_raw_parts_mut(dst_ptr, len) };
2376 let sa = unsafe { std::slice::from_raw_parts(a_ptr, len) };
2377 let sb = unsafe { std::slice::from_raw_parts(b_ptr, len) };
2378 let sc = unsafe { std::slice::from_raw_parts(c_ptr, len) };
2379 let se = unsafe { std::slice::from_raw_parts(e_ptr, len) };
2380 simd::dispatch_if_large(len, || {
2381 for i in 0..len {
2382 dst[i] = f(
2383 OpA::apply(sa[i]),
2384 OpB::apply(sb[i]),
2385 OpC::apply(sc[i]),
2386 OpE::apply(se[i]),
2387 );
2388 }
2389 });
2390 return Ok(());
2391 }
2392
2393 let strides_list: [&[isize]; 5] = [
2394 dst_strides,
2395 a.strides(),
2396 b.strides(),
2397 c.strides(),
2398 e.strides(),
2399 ];
2400 let elem_size = std::mem::size_of::<D>()
2401 .max(std::mem::size_of::<A>())
2402 .max(std::mem::size_of::<B>())
2403 .max(std::mem::size_of::<C>())
2404 .max(std::mem::size_of::<E>());
2405 let total = total_len(dst_dims)?;
2406
2407 let (fused_dims, ordered_strides, plan) = if total <= SMALL_TENSOR_THRESHOLD {
2409 build_plan_fused_small(dst_dims, &strides_list)
2410 } else {
2411 build_plan_fused(dst_dims, &strides_list, Some(0), elem_size)
2412 };
2413
2414 #[cfg(feature = "parallel")]
2415 {
2416 let total = total_len(&fused_dims)?;
2417 let nthreads = crate::execution_policy::rayon_threads();
2418 if total > MINTHREADLENGTH && nthreads > 1 {
2419 use crate::threading::SendPtr;
2420 let dst_send = SendPtr(dst_ptr);
2421 let a_send = SendPtr(a_ptr as *mut A);
2422 let b_send = SendPtr(b_ptr as *mut B);
2423 let c_send = SendPtr(c_ptr as *mut C);
2424 let e_send = SendPtr(e_ptr as *mut E);
2425
2426 let costs = compute_costs(&ordered_strides);
2427 let initial_offsets = vec![0isize; strides_list.len()];
2428 return mapreduce_threaded(
2429 &fused_dims,
2430 &plan.block,
2431 &ordered_strides,
2432 &initial_offsets,
2433 &costs,
2434 nthreads,
2435 0,
2436 1,
2437 &|dims, blocks, strides_list, offsets| {
2438 for_each_inner_block_with_offsets(
2439 dims,
2440 blocks,
2441 strides_list,
2442 offsets,
2443 |offsets, len, strides| {
2444 let dp = unsafe { dst_send.as_ptr().offset(offsets[0]) };
2445 let ap = unsafe { a_send.as_const().offset(offsets[1]) };
2446 let bp = unsafe { b_send.as_const().offset(offsets[2]) };
2447 let cp = unsafe { c_send.as_const().offset(offsets[3]) };
2448 let ep = unsafe { e_send.as_const().offset(offsets[4]) };
2449 unsafe {
2450 inner_loop_map4::<D, A, B, C, E, OpA, OpB, OpC, OpE>(
2451 dp, strides[0], ap, strides[1], bp, strides[2], cp, strides[3],
2452 ep, strides[4], len, &f,
2453 )
2454 };
2455 Ok(())
2456 },
2457 )
2458 },
2459 );
2460 }
2461 }
2462
2463 let initial_offsets = vec![0isize; ordered_strides.len()];
2464 for_each_inner_block_preordered(
2465 &fused_dims,
2466 &plan.block,
2467 &ordered_strides,
2468 &initial_offsets,
2469 |offsets, len, strides| {
2470 let dp = unsafe { dst_ptr.offset(offsets[0]) };
2471 let ap = unsafe { a_ptr.offset(offsets[1]) };
2472 let bp = unsafe { b_ptr.offset(offsets[2]) };
2473 let cp = unsafe { c_ptr.offset(offsets[3]) };
2474 let ep = unsafe { e_ptr.offset(offsets[4]) };
2475 unsafe {
2476 inner_loop_map4::<D, A, B, C, E, OpA, OpB, OpC, OpE>(
2477 dp, strides[0], ap, strides[1], bp, strides[2], cp, strides[3], ep, strides[4],
2478 len, &f,
2479 )
2480 };
2481 Ok(())
2482 },
2483 )
2484}
2485
2486#[cfg(test)]
2487#[path = "map_view/tests/scalar_branch_tests.rs"]
2488mod scalar_branch_tests;
2489
2490#[cfg(all(test, feature = "parallel"))]
2491#[path = "map_view/tests/tests.rs"]
2492mod tests;