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