Skip to main content

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