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