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