Skip to main content

strided_kernel/
map_view.rs

1//! Map operations on dynamic-rank strided views.
2//!
3//! These are the canonical view-based map functions, equivalent to Julia's `Base.map!`.
4//! Mutable destinations must have an injective layout. Validation is
5//! conservative and bounded: layouts that are injective but cannot be proven
6//! by the bounded checker are rejected with
7//! [`StridedError::NonInjectiveOutputLayout`].
8
9use 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// ============================================================================
144// Stride-specialized inner loop helpers
145//
146// When all inner strides are 1 (contiguous in the innermost dimension),
147// we use slice-based iteration so LLVM can auto-vectorize effectively.
148// This is the Rust equivalent of Julia's @simd on the innermost loop.
149// ============================================================================
150
151/// Unary inner loop: `dest[i] = f(Op::apply(src[i]))` for `len` elements.
152#[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/// Binary inner loop: `dest[i] = f(OpA::apply(a[i]), OpB::apply(b[i]))`.
181#[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
261/// Binary multiplication inner loop for identity element ops.
262trait 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        // SIMD stores write complete values directly through raw pointers; no
316        // initialized reference is formed over the uninitialized destination.
317        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        // The exact TypeId checks prove identical layout and validity.
335        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        // The exact TypeId checks prove identical layout and validity.
345        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/// Ternary inner loop: `dest[i] = f(a[i], b[i], c[i])`.
946#[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/// Quaternary inner loop: `dest[i] = f(a[i], b[i], c[i], e[i])`.
997#[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
1060/// Apply a function element-wise from source to destination.
1061///
1062/// The element operation `Op` is applied lazily when reading from `src`.
1063/// Source and destination may have different element types.
1064pub 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    // Small tensor fast path: skip compute_order and compute_block_sizes
1190    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
1251/// Binary element-wise operation: `dest[i] = f(a[i], b[i])`.
1252///
1253/// Source operands `a` and `b` may have different element types from each other
1254/// and from `dest`. The closure `f` handles per-element type conversion.
1255pub 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/// Runtime comparison selected once before entering the element loop.
1308#[non_exhaustive]
1309#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1310pub enum CompareOp {
1311    Eq,
1312    Lt,
1313    Le,
1314    Gt,
1315    Ge,
1316}
1317
1318/// Compare two ordered views elementwise into a Boolean destination.
1319///
1320/// Unlike embedding a runtime operation match inside a [`zip_map2_into`]
1321/// closure, this entry point selects a fixed comparison before traversal.
1322///
1323/// # Errors
1324///
1325/// Returns [`StridedError::ShapeMismatch`] when the source and destination
1326/// shapes differ, or [`StridedError::NonInjectiveOutputLayout`] when distinct
1327/// logical destination elements may overlap.
1328pub 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
1348/// Compare two views into a fully overwritten uninitialized Boolean output.
1349///
1350/// Dtype-independent shape, destination-injectivity, and reachable-byte
1351/// overlap validation completes before the first write. Safe Rust borrows
1352/// already prevent input/output aliasing; the explicit overlap check preserves
1353/// the contract for views produced through unsafe constructors.
1354///
1355/// `Ok(())` means every logical destination element is initialized. An error
1356/// occurs before writes. A panic during replay may leave a partially initialized
1357/// destination, which remains safe to drop as `MaybeUninit<bool>`.
1358///
1359/// # Errors
1360///
1361/// Returns [`StridedError::ShapeMismatch`] for unequal shapes,
1362/// [`StridedError::NonInjectiveOutputLayout`] for an overlapping output layout,
1363/// [`StridedError::OverlappingInputOutput`] for aliased storage, or
1364/// [`StridedError::OffsetOverflow`] when a reachable byte range is not
1365/// representable.
1366pub 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    // Small tensor fast path: skip compute_order and compute_block_sizes
1522    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
1709/// Element-wise multiplication: `dest[i] = a[i] * b[i]`.
1710///
1711/// All views must have the same shape. Broadcast operands should be represented
1712/// as stride-0 views before calling this function.
1713pub 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
1744/// Multiply two views into a fully overwritten uninitialized output.
1745///
1746/// Shape, destination-injectivity, and reachable-byte overlap validation
1747/// completes before the first write. Safe Rust borrows already prevent
1748/// input/output aliasing; the explicit overlap check preserves the contract for
1749/// views produced through unsafe constructors.
1750///
1751/// `Ok(())` means every logical destination element is initialized. An error
1752/// occurs before writes. A panic during replay may leave a partially initialized
1753/// destination, which remains safe to drop as `MaybeUninit<D>`.
1754///
1755/// # Errors
1756///
1757/// Returns [`StridedError::ShapeMismatch`] for unequal shapes,
1758/// [`StridedError::NonInjectiveOutputLayout`] for an overlapping output layout,
1759/// [`StridedError::OverlappingInputOutput`] for aliased storage, or
1760/// [`StridedError::OffsetOverflow`] when a reachable byte range is not
1761/// representable.
1762pub 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
1855/// Broadcasted element-wise multiplication: `dest[i] = a[i] * b[i]`.
1856///
1857/// `a_axes` and `b_axes` map each source axis to an axis of `dest`. Output axes
1858/// not referenced by a source operand are treated as stride-0 broadcast axes.
1859pub 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
1894/// Broadcast and multiply into a fully overwritten uninitialized output.
1895///
1896/// Axis mappings, shapes, destination injectivity, and reachable-byte overlap
1897/// are validated before the first write. Safe Rust borrows already prevent
1898/// input/output aliasing; the explicit overlap check preserves the contract for
1899/// views produced through unsafe constructors.
1900///
1901/// `Ok(())` means every logical destination element is initialized. An error
1902/// occurs before writes. A panic during replay may leave a partially initialized
1903/// destination, which remains safe to drop as `MaybeUninit<D>`.
1904///
1905/// # Errors
1906///
1907/// Returns a typed rank, axis, or shape error for an invalid broadcast mapping,
1908/// [`StridedError::NonInjectiveOutputLayout`] for an overlapping output layout,
1909/// [`StridedError::OverlappingInputOutput`] for aliased storage, or
1910/// [`StridedError::OffsetOverflow`] when a reachable byte range is not
1911/// representable.
1912pub 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
1955/// Ternary element-wise operation: `dest[i] = f(a[i], b[i], c[i])`.
1956pub 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    // Small tensor fast path: skip compute_order and compute_block_sizes
2032    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
2106/// Quaternary element-wise operation: `dest[i] = f(a[i], b[i], c[i], e[i])`.
2107pub 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    // Small tensor fast path: skip compute_order and compute_block_sizes
2211    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}