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