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